Merge pull request #180 from ThakeeNathees/minor-changes

Stack miss calculation bug fix & minor changes
This commit is contained in:
Thakee Nathees 2022-04-06 09:03:16 +05:30
commit adcd7dce45
11 changed files with 124 additions and 31 deletions

View File

@ -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.

View File

@ -254,8 +254,8 @@ def generate_navtree(context):
def generate_toc_entries(topics):
gen = ""
for topic in topics:
gen += f'<li><a class="link" href="#{topic}">\m'
gen += f' {topic.replace("-", " ")}\n'
gen += f'<li><a class="link" href="#{topic}">'
gen += f'{topic.replace("-", " ")}'
gen += '</a></li>\n'
return gen

View File

@ -139,7 +139,20 @@ body {
@media screen and (max-width: 995px) {
:root {
--fs-editor: 14px;
--fs-code-example-title: 10px;
}
}
#code-area {
flex-direction: column;
}
#code-editor::-webkit-scrollbar {
width: 0;
height: 0;
}
#code-output {
height: auto;
min-height: 150px;
}
}

View File

@ -40,7 +40,7 @@
</p>
<div id="intro-buttons">
<a class="button" href="{{ DOCS_URL }}Reference/Getting-Started.html">Get Started</a>
<a class="button" target="_blank" href="try-online.html">Try Online</a>
<a class="button" href="try-online.html">Try Online</a>
</div>
</div>
<img src="{{ STATIC_DIR }}img/pocketlang.svg">

View File

@ -19,7 +19,7 @@
<!-- Navigation bar at the top. -->
<div id="navbar">
<a href="#"><h1 id="nav-title">pocketlang</h1></a>
<a href="index.html"><h1 id="nav-title">pocketlang</h1></a>
<div>
<ul>
<li><a href="{{ DOCS_URL }}Reference/Getting-Started.html">Docs</a></li>

View File

@ -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)

View File

@ -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,

View File

@ -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();
}

View File

@ -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*.

View File

@ -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;
}

View File

@ -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)