diff --git a/README.md b/README.md
index 8346801..e124f11 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
**Pocketlang** is a small (~3000 semicolons) and [fast](https://github.com/ThakeeNathees/pocketlang#performance)
functional language written in C. It's syntactically similar to Ruby and it can
-be learned [within 15 minutes](https://thakeenathees.github.io/pocketlang/getting-started-learn-in-15-minutes.html).
+be learned [within 15 minutes](https://thakeenathees.github.io/pocketlang/docs/v0.1.0/Reference/Cheat-Sheet.html).
Including the compiler, bytecode VM and runtime, it's a standalone executable
with zero external dependencies just as it's self descriptive name. The pocketlang
VM can be embedded in another hosting program very easily.
@@ -33,7 +33,7 @@ end
## Try It Now
-You can [try pocketlang on your browser](https://thakeenathees.github.io/pocketlang/getting-started-try-it-now.html).
+You can [try pocketlang on your browser](https://thakeenathees.github.io/pocketlang/try-online.html).
It's a [WebAssembly](https://webassembly.org/) build of the VM compiled using [emscripten](https://emscripten.org/).
Note that in the webassembly version of the language, some features (input, file handling, relative import, etc.)
have disabled, has limited memory allocations, and the stdout calls might be slower.
diff --git a/docs/generate.py b/docs/generate.py
index 98ccb4c..8617e86 100644
--- a/docs/generate.py
+++ b/docs/generate.py
@@ -254,8 +254,8 @@ def generate_navtree(context):
def generate_toc_entries(topics):
gen = ""
for topic in topics:
- gen += f'
\m'
- gen += f' {topic.replace("-", " ")}\n'
+ gen += f''
+ gen += f'{topic.replace("-", " ")}'
gen += '\n'
return gen
diff --git a/docs/static/css/try_online.css b/docs/static/css/try_online.css
index e5a4bb6..363f514 100644
--- a/docs/static/css/try_online.css
+++ b/docs/static/css/try_online.css
@@ -139,7 +139,20 @@ body {
@media screen and (max-width: 995px) {
:root {
- --fs-editor: 14px;
--fs-code-example-title: 10px;
}
-}
\ No newline at end of file
+
+ #code-area {
+ flex-direction: column;
+ }
+
+ #code-editor::-webkit-scrollbar {
+ width: 0;
+ height: 0;
+ }
+
+ #code-output {
+ height: auto;
+ min-height: 150px;
+ }
+}
diff --git a/docs/templates/index.html b/docs/templates/index.html
index 47e9b9f..bad2629 100644
--- a/docs/templates/index.html
+++ b/docs/templates/index.html
@@ -40,7 +40,7 @@
diff --git a/docs/templates/try-online.html b/docs/templates/try-online.html
index 649e411..004e335 100644
--- a/docs/templates/try-online.html
+++ b/docs/templates/try-online.html
@@ -19,7 +19,7 @@
-
pocketlang
+
pocketlang
- Docs
diff --git a/docs/wasm/compile.py b/docs/wasm/compile.py
index 4629d02..e7f7bc9 100644
--- a/docs/wasm/compile.py
+++ b/docs/wasm/compile.py
@@ -8,6 +8,7 @@ THIS_PATH = abspath(dirname(__file__))
POCKET_SOURCE_DIR = join(THIS_PATH, "../../../pocketlang/src/")
JS_API_PATH = join(THIS_PATH, "io_api.js")
+MAIN_C = join(THIS_PATH, "main.c")
TARGET_DIR = join(THIS_PATH, "../static/wasm/")
TARGET_NAME = "pocketlang.html"
@@ -31,7 +32,7 @@ def main():
exports = "\"EXPORTED_RUNTIME_METHODS=['ccall','cwrap']\""
js_api = JS_API_PATH
- cmd = f"emcc {include} main.c {sources} -o {output} " +\
+ cmd = f"emcc {include} {MAIN_C} {sources} -o {output} " +\
f"-s {exports} --js-library {js_api}"
print(cmd)
os.system(cmd)
diff --git a/src/pk_compiler.c b/src/pk_compiler.c
index ca266c3..f3da90a 100644
--- a/src/pk_compiler.c
+++ b/src/pk_compiler.c
@@ -1435,10 +1435,17 @@ static void exprInterpolation(Compiler* compiler) {
// The last string is not TK_STRING_INTERP but it would be
// TK_STRING. Apped it.
+ // Optimize case last string could be empty. Skip it.
consume(compiler, TK_STRING, "Non terminated interpolated string.");
- exprLiteral(compiler);
- emitOpcode(compiler, OP_LIST_APPEND);
- size++;
+ if (compiler->previous.type == TK_STRING /* != if syntax error. */) {
+ ASSERT(IS_OBJ_TYPE(compiler->previous.value, OBJ_STRING), OOPS);
+ String* str = (String*)AS_OBJ(compiler->previous.value);
+ if (str->length != 0) {
+ exprLiteral(compiler);
+ emitOpcode(compiler, OP_LIST_APPEND);
+ size++;
+ }
+ }
patchListSize(compiler, size_index, size);
@@ -1446,6 +1453,11 @@ static void exprInterpolation(Compiler* compiler) {
emitOpcode(compiler, OP_CALL);
emitByte(compiler, 1);
+ // After the above call, the lits and the "list_join" function will be popped
+ // from the stack and a string will be pushed. The so the result stack effect
+ // is -1.
+ compilerChangeStack(compiler, -1);
+
}
static void exprFunc(Compiler* compiler) {
@@ -2139,12 +2151,10 @@ static int compileClass(Compiler* compiler) {
uint32_t f_index = scriptAddName(compiler->script, compiler->vm,
f_name, f_len);
- // TODO: Add a string compare macro.
String* new_name = compiler->script->names.data[f_index];
for (uint32_t i = 0; i < cls->field_names.count; i++) {
String* prev = compiler->script->names.data[cls->field_names.data[i]];
- if (new_name->hash == prev->hash && new_name->length == prev->length &&
- memcmp(new_name->data, prev->data, prev->length) == 0) {
+ if (IS_STR_EQ(new_name, prev)) {
parseError(compiler, "Class field with name '%s' already exists.",
new_name->data);
}
@@ -2339,7 +2349,7 @@ static Script* importFile(Compiler* compiler, const char* path) {
// Check if the script already exists.
Var entry = mapGet(vm->scripts, VAR_OBJ(path_name));
if (!IS_UNDEF(entry)) {
- ASSERT(AS_OBJ(entry)->type == OBJ_SCRIPT, OOPS);
+ ASSERT(IS_OBJ_TYPE(entry, OBJ_SCRIPT), OOPS);
// Push the script on the stack.
emitOpcode(compiler, OP_IMPORT);
@@ -2411,7 +2421,7 @@ static Script* importCoreLib(Compiler* compiler, const char* name_start,
emitOpcode(compiler, OP_IMPORT);
emitShort(compiler, index);
- ASSERT(AS_OBJ(entry)->type == OBJ_SCRIPT, OOPS);
+ ASSERT(IS_OBJ_TYPE(entry, OBJ_SCRIPT), OOPS);
return (Script*)AS_OBJ(entry);
}
@@ -2882,6 +2892,10 @@ static void compileStatement(Compiler* compiler) {
// level expression's evaluated value will be printed.
static void compileTopLevelStatement(Compiler* compiler) {
+ // At the top level the stack size should be 0, before and after compiling
+ // a top level statement, since there aren't any locals at the top level.
+ ASSERT(compiler->stack_size == 0, OOPS);
+
if (match(compiler, TK_CLASS)) {
compileClass(compiler);
@@ -2904,6 +2918,11 @@ static void compileTopLevelStatement(Compiler* compiler) {
} else {
compileStatement(compiler);
}
+
+ // At the top level the stack size should be 0, before and after compiling
+ // a top level statement, since there aren't any locals at the top level.
+ ASSERT(compiler->stack_size == 0, OOPS);
+
}
PkResult compile(PKVM* vm, Script* script, const char* source,
diff --git a/src/pk_core.c b/src/pk_core.c
index c2aac2d..d98b74c 100644
--- a/src/pk_core.c
+++ b/src/pk_core.c
@@ -359,7 +359,7 @@ static inline bool isInteger(Var var, int64_t* value) {
return false;
}
-// Check if [var] is bool/number. If not set error and return false.
+// Check if [var] is bool/number. If not, it'll set error and return false.
static inline bool validateNumeric(PKVM* vm, Var var, double* value,
const char* name) {
if (isNumeric(var, value)) return true;
@@ -367,7 +367,7 @@ static inline bool validateNumeric(PKVM* vm, Var var, double* value,
return false;
}
-// Check if [var] is 32 bit integer. If not set error and return false.
+// Check if [var] is 32 bit integer. If not, it'll set error and return false.
static inline bool validateInteger(PKVM* vm, Var var, int64_t* value,
const char* name) {
if (isInteger(var, value)) return true;
@@ -386,6 +386,16 @@ static inline bool validateIndex(PKVM* vm, int64_t index, uint32_t size,
return true;
}
+// Check if the [condition] is true. If not, it'll set an error and return
+// false.
+static inline bool validateCond(PKVM* vm, bool condition, const char* err) {
+ if (!condition) {
+ VM_SET_ERROR(vm, newString(vm, err));
+ return false;
+ }
+ return true;
+}
+
// Check if [var] is string for argument at [arg]. If not set error and
// return false.
#define VALIDATE_ARG_OBJ(m_class, m_type, m_name) \
@@ -483,7 +493,6 @@ DEF(coreHelp,
vm->config.write_fn(vm, fn->docstring);
vm->config.write_fn(vm, "\n\n");
} else {
- // TODO: A better message.
vm->config.write_fn(vm, "function '");
vm->config.write_fn(vm, fn->name);
vm->config.write_fn(vm, "()' doesn't have a docstring.\n");
@@ -742,11 +751,11 @@ DEF(coreListJoin,
pkByteBuffer buff;
pkByteBufferInit(&buff);
- for (int i = 0; i < list->elements.count; i++) {
+ for (uint32_t i = 0; i < list->elements.count; i++) {
String* elem = toString(vm, list->elements.data[i]);
vmPushTempRef(vm, &elem->_super); // elem
pkByteBufferAddString(&buff, vm, elem->data, elem->length);
- vmPopTempRef(vm);
+ vmPopTempRef(vm); // elem
}
String* str = newStringLength(vm, (const char*)buff.data, buff.count);
@@ -870,6 +879,9 @@ DEF(stdLangDisas,
Function* fn;
if (!validateArgFunction(vm, 1, &fn)) return;
+ if (!validateCond(vm, !fn->is_native,
+ "Cannot disassemble native functions.")) return;
+
pkByteBuffer buff;
pkByteBufferInit(&buff);
dumpFunctionCode(vm, fn, &buff);
@@ -1261,7 +1273,7 @@ void initializeCore(PKVM* vm) {
// Initialize builtin functions.
INITIALIZE_BUILTIN_FN("type_name", coreTypeName, 1);
- // TODO: Add is keyword with modules for builtin types.
+ // TODO: Add 'is' keyword with modules for builtin types.
// ex: val is Num; val is null; val is List; val is Range
// List.append(l, e) # List is implicitly imported core module.
// String.lower(s)
@@ -1664,10 +1676,9 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
case OBJ_MAP:
{
- // Not sure should I allow string values could be accessed with
- // this way. ex:
// map = { "foo" : 42, "can't access" : 32 }
- // val = map.foo ## 42
+ // val = map.foo ## <-- This should be error
+ // Only the map's attributes are accessed here.
TODO;
UNREACHABLE();
}
diff --git a/src/pk_var.h b/src/pk_var.h
index 61dab54..0556cdf 100644
--- a/src/pk_var.h
+++ b/src/pk_var.h
@@ -140,7 +140,8 @@
#define IS_OBJ(value) ((value & _MASK_OBJECT) == _MASK_OBJECT)
// Evaluate to true if the var is an object and type of [obj_type].
-#define IS_OBJ_TYPE(var, obj_type) IS_OBJ(var) && AS_OBJ(var)->type == obj_type
+#define IS_OBJ_TYPE(var, obj_type) \
+ (IS_OBJ(var) && (AS_OBJ(var)->type == (obj_type)))
// Check if the 2 pocket strings are equal.
#define IS_STR_EQ(s1, s2) \
@@ -432,7 +433,7 @@ String* newStringLength(PKVM* vm, const char* text, uint32_t length);
#else // Macro implementation.
// Allocate new string using the cstring [text].
#define newString(vm, text) \
- newStringLength(vm, text, (!text) ? 0 : (uint32_t)strlen(text))
+ (newStringLength(vm, text, (!(text)) ? 0 : (uint32_t)strlen(text)))
#endif
// Allocate new List and return List*.
diff --git a/src/pk_vm.c b/src/pk_vm.c
index 3b5726c..afa1197 100644
--- a/src/pk_vm.c
+++ b/src/pk_vm.c
@@ -46,9 +46,6 @@ PkConfiguration pkNewConfiguration(void) {
PkCompileOptions pkNewCompilerOptions(void) {
PkCompileOptions options;
options.debug = false;
- // TODO:
- //options.dump_opcodes = false;
- //options.dump_stream = stdout;
options.repl_mode = false;
return options;
}
diff --git a/tests/random/string_algo.pk b/tests/random/string_algo.pk
new file mode 100644
index 0000000..c233739
--- /dev/null
+++ b/tests/random/string_algo.pk
@@ -0,0 +1,51 @@
+
+def reverse_string(s)
+ ret = ''
+ for i in s.length-1 .. -1 ## -1 so ends at 0.
+ ret += s[i]
+ end
+ return ret
+end
+
+def is_palindrome(s)
+ return reverse_string(s) == s
+end
+
+assert(reverse_string('abc') == 'cba')
+assert(reverse_string('123456') == '654321')
+assert(is_palindrome('racecar'))
+assert(!is_palindrome('foobar'))
+
+
+## An un-efficient function to check substrings.
+def string_find(s, sub)
+
+ ## Edge case.
+ if sub.length == 0 then return 0 end
+ if sub.length > s.length then return -1 end
+ if s.length == 0 then
+ if sub.length == 0 then return 0 end
+ return -1
+ end
+
+ for i in 0..s.length
+ if s[i] == sub[0]
+ found = true
+ for j in 1..sub.length
+ if s[i+j] != sub[j]
+ found = false
+ end
+ end
+ if found then return i end
+ end
+ end
+
+ return -1 ## Not found
+end
+
+assert(string_find("something", "thing") == 4)
+assert(string_find("a long string", "string") == 7)
+
+
+
+