mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-05 20:26:53 +08:00
Merge pull request #180 from ThakeeNathees/minor-changes
Stack miss calculation bug fix & minor changes
This commit is contained in:
commit
adcd7dce45
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
17
docs/static/css/try_online.css
vendored
17
docs/static/css/try_online.css
vendored
@ -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;
|
||||
}
|
||||
}
|
||||
|
2
docs/templates/index.html
vendored
2
docs/templates/index.html
vendored
@ -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">
|
||||
|
2
docs/templates/try-online.html
vendored
2
docs/templates/try-online.html
vendored
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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*.
|
||||
|
@ -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;
|
||||
}
|
||||
|
51
tests/random/string_algo.pk
Normal file
51
tests/random/string_algo.pk
Normal 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)
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user