else if change to elif again.

The primary purpose of this change is to disambiguate between
`else if` and `else \n if`.

Even though it's a breaking change, since it's at the very early
state we're allowed to make such breaking syntax change.

lang.write function moved to io and io.stdin, io.stdout, io.stderr
added for future streamed io operations

io.File read, write, open, close, getline, seek, tell implemented

str * int multiplication implemented
This commit is contained in:
Thakee Nathees 2022-05-24 01:29:41 +05:30
parent e16debcff0
commit 6e91b66e69
13 changed files with 453 additions and 140 deletions

View File

@ -136,6 +136,7 @@ typedef enum {
TK_WHILE, // while TK_WHILE, // while
TK_FOR, // for TK_FOR, // for
TK_IF, // if TK_IF, // if
TK_ELIF, // elif
TK_ELSE, // else TK_ELSE, // else
TK_BREAK, // break TK_BREAK, // break
TK_CONTINUE, // continue TK_CONTINUE, // continue
@ -200,6 +201,7 @@ static _Keyword _keywords[] = {
{ "while", 5, TK_WHILE }, { "while", 5, TK_WHILE },
{ "for", 3, TK_FOR }, { "for", 3, TK_FOR },
{ "if", 2, TK_IF }, { "if", 2, TK_IF },
{ "elif", 4, TK_ELIF },
{ "else", 4, TK_ELSE }, { "else", 4, TK_ELSE },
{ "break", 5, TK_BREAK }, { "break", 5, TK_BREAK },
{ "continue", 8, TK_CONTINUE }, { "continue", 8, TK_CONTINUE },
@ -1267,7 +1269,8 @@ static void skipNewLines(Compiler* compiler) {
matchLine(compiler); matchLine(compiler);
} }
// Match semi collon, multiple new lines or peek 'end', 'else' keywords. // Match semi collon, multiple new lines or peek 'end', 'else', 'elif'
// keywords.
static bool matchEndStatement(Compiler* compiler) { static bool matchEndStatement(Compiler* compiler) {
if (match(compiler, TK_SEMICOLLON)) { if (match(compiler, TK_SEMICOLLON)) {
skipNewLines(compiler); skipNewLines(compiler);
@ -1278,7 +1281,9 @@ static bool matchEndStatement(Compiler* compiler) {
// In the below statement we don't require any new lines or semicolons. // In the below statement we don't require any new lines or semicolons.
// 'if cond then stmnt1 else if cond2 then stmnt2 else stmnt3 end' // 'if cond then stmnt1 else if cond2 then stmnt2 else stmnt3 end'
if (peek(compiler) == TK_END || peek(compiler) == TK_ELSE) if (peek(compiler) == TK_END
|| peek(compiler) == TK_ELSE
|| peek(compiler) == TK_ELIF)
return true; return true;
return false; return false;
@ -1639,6 +1644,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence
/* TK_WHILE */ NO_RULE, /* TK_WHILE */ NO_RULE,
/* TK_FOR */ NO_RULE, /* TK_FOR */ NO_RULE,
/* TK_IF */ NO_RULE, /* TK_IF */ NO_RULE,
/* TK_ELIF */ NO_RULE,
/* TK_ELSE */ NO_RULE, /* TK_ELSE */ NO_RULE,
/* TK_BREAK */ NO_RULE, /* TK_BREAK */ NO_RULE,
/* TK_CONTINUE */ NO_RULE, /* TK_CONTINUE */ NO_RULE,
@ -2864,7 +2870,7 @@ static void compileBlockBody(Compiler* compiler, BlockType type) {
_TokenType next = peek(compiler); _TokenType next = peek(compiler);
while (!(next == TK_END || next == TK_EOF || while (!(next == TK_END || next == TK_EOF ||
((type == BLOCK_IF) && (next == TK_ELSE)))) { ((type == BLOCK_IF) && (next == TK_ELSE || next == TK_ELIF)))) {
compileStatement(compiler); compileStatement(compiler);
skipNewLines(compiler); skipNewLines(compiler);
@ -3024,7 +3030,7 @@ static void compileExpression(Compiler* compiler) {
parsePrecedence(compiler, PREC_LOWEST); parsePrecedence(compiler, PREC_LOWEST);
} }
static void compileIfStatement(Compiler* compiler, bool else_if) { static void compileIfStatement(Compiler* compiler, bool elif) {
skipNewLines(compiler); skipNewLines(compiler);
compileExpression(compiler); //< Condition. compileExpression(compiler); //< Condition.
@ -3033,9 +3039,7 @@ static void compileIfStatement(Compiler* compiler, bool else_if) {
compileBlockBody(compiler, BLOCK_IF); compileBlockBody(compiler, BLOCK_IF);
if (match(compiler, TK_ELSE)) { if (match(compiler, TK_ELIF)) {
if (match(compiler, TK_IF)) { //< Compile 'else if'.
// Jump pass else. // Jump pass else.
emitOpcode(compiler, OP_JUMP); emitOpcode(compiler, OP_JUMP);
int exit_jump = emitShort(compiler, 0xffff); //< Will be patched. int exit_jump = emitShort(compiler, 0xffff); //< Will be patched.
@ -3049,8 +3053,7 @@ static void compileIfStatement(Compiler* compiler, bool else_if) {
patchJump(compiler, exit_jump); patchJump(compiler, exit_jump);
} else { //< Compile 'else'. } else if (match(compiler, TK_ELSE)) {
// Jump pass else. // Jump pass else.
emitOpcode(compiler, OP_JUMP); emitOpcode(compiler, OP_JUMP);
int exit_jump = emitShort(compiler, 0xffff); //< Will be patched. int exit_jump = emitShort(compiler, 0xffff); //< Will be patched.
@ -3058,15 +3061,14 @@ static void compileIfStatement(Compiler* compiler, bool else_if) {
patchJump(compiler, ifpatch); patchJump(compiler, ifpatch);
compileBlockBody(compiler, BLOCK_ELSE); compileBlockBody(compiler, BLOCK_ELSE);
patchJump(compiler, exit_jump); patchJump(compiler, exit_jump);
}
} else { } else {
patchJump(compiler, ifpatch); patchJump(compiler, ifpatch);
} }
// 'else if' will not consume the 'end' keyword as it'll be leaved to be // elif will not consume the 'end' keyword as it'll be leaved to be consumed
// consumed by it's 'if'. // by it's 'if'.
if (!else_if) { if (!elif) {
skipNewLines(compiler); skipNewLines(compiler);
consume(compiler, TK_END, "Expected 'end' after statement end."); consume(compiler, TK_END, "Expected 'end' after statement end.");
} }

View File

@ -237,13 +237,6 @@ static inline bool _callBinaryOpMethod(PKVM* vm, Var self, Var other,
/* CORE BUILTIN FUNCTIONS */ /* CORE BUILTIN FUNCTIONS */
/*****************************************************************************/ /*****************************************************************************/
DEF(coreTypeName,
"type_name(value:var) -> string\n"
"Returns the type name of the of the value.") {
RET(VAR_OBJ(newString(vm, varTypeName(ARG(1)))));
}
DEF(coreHelp, DEF(coreHelp,
"help([fn:Closure]) -> null\n" "help([fn:Closure]) -> null\n"
"It'll print the docstring the object and return.") { "It'll print the docstring the object and return.") {
@ -545,7 +538,6 @@ static void initializeBuiltinFunctions(PKVM* vm) {
initializeBuiltinFN(vm, &vm->builtins_funcs[vm->builtins_count++], name, \ initializeBuiltinFN(vm, &vm->builtins_funcs[vm->builtins_count++], name, \
(int)strlen(name), argc, fn, DOCSTRING(fn)); (int)strlen(name), argc, fn, DOCSTRING(fn));
// General functions. // General functions.
INITIALIZE_BUILTIN_FN("type_name", coreTypeName, 1);
INITIALIZE_BUILTIN_FN("help", coreHelp, -1); INITIALIZE_BUILTIN_FN("help", coreHelp, -1);
INITIALIZE_BUILTIN_FN("assert", coreAssert, -1); INITIALIZE_BUILTIN_FN("assert", coreAssert, -1);
INITIALIZE_BUILTIN_FN("bin", coreBin, 1); INITIALIZE_BUILTIN_FN("bin", coreBin, 1);
@ -642,31 +634,6 @@ DEF(stdLangDebugBreak,
} }
#endif #endif
DEF(stdLangWrite,
"write(...) -> null\n"
"Write function, just like print function but it wont put space between"
"args and write a new line at the end.") {
// If the host application doesn't provide any write function, discard the
// output.
if (vm->config.stdout_write == NULL) return;
String* str; //< Will be cleaned by garbage collector;
for (int i = 1; i <= ARGC; i++) {
Var arg = ARG(i);
// If it's already a string don't allocate a new string instead use it.
if (IS_OBJ_TYPE(arg, OBJ_STRING)) {
str = (String*)AS_OBJ(arg);
} else {
str = varToString(vm, ARG(1), false);
if (str == NULL) RET(VAR_NULL);
}
vm->config.stdout_write(vm, str->data);
}
}
static void initializeCoreModules(PKVM* vm) { static void initializeCoreModules(PKVM* vm) {
#define MODULE_ADD_FN(module, name, fn, argc) \ #define MODULE_ADD_FN(module, name, fn, argc) \
moduleAddFunctionInternal(vm, module, name, fn, argc, DOCSTRING(fn)) moduleAddFunctionInternal(vm, module, name, fn, argc, DOCSTRING(fn))
@ -680,7 +647,6 @@ static void initializeCoreModules(PKVM* vm) {
NEW_MODULE(lang, "lang"); NEW_MODULE(lang, "lang");
MODULE_ADD_FN(lang, "gc", stdLangGC, 0); MODULE_ADD_FN(lang, "gc", stdLangGC, 0);
MODULE_ADD_FN(lang, "disas", stdLangDisas, 1); MODULE_ADD_FN(lang, "disas", stdLangDisas, 1);
MODULE_ADD_FN(lang, "write", stdLangWrite, -1);
#ifdef DEBUG #ifdef DEBUG
MODULE_ADD_FN(lang, "debug_break", stdLangDebugBreak, 0); MODULE_ADD_FN(lang, "debug_break", stdLangDebugBreak, 0);
#endif #endif
@ -764,6 +730,18 @@ static void _ctorFiber(PKVM* vm) {
#define SELF (vm->fiber->self) #define SELF (vm->fiber->self)
DEF(_objTypeName,
"Object.typename() -> String\n"
"Returns the type name of the object.") {
RET(VAR_OBJ(newString(vm, varTypeName(SELF))));
}
DEF(_objRepr,
"Object.repr() -> String\n"
"Returns the repr string of the object.") {
RET(VAR_OBJ(varToString(vm, SELF, true)));
}
DEF(_numberTimes, DEF(_numberTimes,
"Number.times(f:fn)\n" "Number.times(f:fn)\n"
"Iterate the function [f] n times. Here n is the integral value of the " "Iterate the function [f] n times. Here n is the integral value of the "
@ -1166,6 +1144,9 @@ static void initializePrimitiveClasses(PKVM* vm) {
} while (false) } while (false)
// TODO: write docs. // TODO: write docs.
ADD_METHOD(PK_OBJECT, "typename", _objTypeName, 0);
ADD_METHOD(PK_OBJECT, "repr", _objRepr, 0);
ADD_METHOD(PK_NUMBER, "times", _numberTimes, 1); ADD_METHOD(PK_NUMBER, "times", _numberTimes, 1);
ADD_METHOD(PK_NUMBER, "isint", _numberIsint, 0); ADD_METHOD(PK_NUMBER, "isint", _numberIsint, 0);
ADD_METHOD(PK_NUMBER, "isbyte", _numberIsbyte, 0); ADD_METHOD(PK_NUMBER, "isbyte", _numberIsbyte, 0);
@ -1465,6 +1446,26 @@ Var varSubtract(PKVM* vm, Var v1, Var v2, bool inplace) {
Var varMultiply(PKVM* vm, Var v1, Var v2, bool inplace) { Var varMultiply(PKVM* vm, Var v1, Var v2, bool inplace) {
CHECK_NUMERIC_OP(*); CHECK_NUMERIC_OP(*);
CHECK_INST_BINARY_OP("*"); CHECK_INST_BINARY_OP("*");
if (IS_OBJ_TYPE(v1, OBJ_STRING)) {
String* left = (String*) AS_OBJ(v1);
int64_t right;
if (isInteger(v2, &right)) {
if (left->length == 0) return VAR_OBJ(left);
if (right == 0) return VAR_OBJ(newString(vm, ""));
String* str = newStringLength(vm, "", left->length * (uint32_t) right);
char* buff = str->data;
for (int i = 0; i < (int) right; i++) {
memcpy(buff, left->data, left->length);
buff += left->length;
}
ASSERT(buff == str->data + str->length, OOPS);
str->hash = utilHashString(str->data);
return VAR_OBJ(str);
}
}
UNSUPPORTED_BINARY_OP("*"); UNSUPPORTED_BINARY_OP("*");
return VAR_NULL; return VAR_NULL;
} }

View File

@ -6,6 +6,8 @@
// This file contains all the pocketlang public function implementations. // This file contains all the pocketlang public function implementations.
#include <math.h>
#ifndef PK_AMALGAMATED #ifndef PK_AMALGAMATED
#include <pocketlang.h> #include <pocketlang.h>
#include "core.h" #include "core.h"
@ -78,7 +80,7 @@ static void stdoutWrite(PKVM* vm, const char* text);
static char* stdinRead(PKVM* vm); static char* stdinRead(PKVM* vm);
static char* loadScript(PKVM* vm, const char* path); static char* loadScript(PKVM* vm, const char* path);
PK_PUBLIC void* pkRealloc(PKVM* vm, void* ptr, size_t size) { void* pkRealloc(PKVM* vm, void* ptr, size_t size) {
ASSERT(vm->config.realloc_fn != NULL, "PKVM's allocator was NULL."); ASSERT(vm->config.realloc_fn != NULL, "PKVM's allocator was NULL.");
#if TRACE_MEMORY #if TRACE_MEMORY
void* newptr = vm->config.realloc_fn(ptr, size, vm->config.user_data); void* newptr = vm->config.realloc_fn(ptr, size, vm->config.user_data);
@ -553,7 +555,7 @@ bool pkCheckArgcRange(PKVM* vm, int argc, int min, int max) {
// FIXME: If the user needs just the boolean value of the object, they should // FIXME: If the user needs just the boolean value of the object, they should
// use pkGetSlotBool(). // use pkGetSlotBool().
PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int slot, bool* value) { bool pkValidateSlotBool(PKVM* vm, int slot, bool* value) {
CHECK_FIBER_EXISTS(vm); CHECK_FIBER_EXISTS(vm);
VALIDATE_SLOT_INDEX(slot); VALIDATE_SLOT_INDEX(slot);
@ -567,7 +569,7 @@ PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int slot, bool* value) {
return true; return true;
} }
PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int slot, double* value) { bool pkValidateSlotNumber(PKVM* vm, int slot, double* value) {
CHECK_FIBER_EXISTS(vm); CHECK_FIBER_EXISTS(vm);
VALIDATE_SLOT_INDEX(slot); VALIDATE_SLOT_INDEX(slot);
@ -581,7 +583,23 @@ PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int slot, double* value) {
return true; return true;
} }
PK_PUBLIC bool pkValidateSlotString(PKVM* vm, int slot, const char** value, bool pkValidateSlotInteger(PKVM* vm, int slot, int32_t* value) {
CHECK_FIBER_EXISTS(vm);
VALIDATE_SLOT_INDEX(slot);
double n;
if (!pkValidateSlotNumber(vm, slot, &n)) return false;
if (floor(n) != n) {
VM_SET_ERROR(vm, newString(vm, "Expected an integer got float."));
return false;
}
if (value) *value = (int32_t) n;
return true;
}
bool pkValidateSlotString(PKVM* vm, int slot, const char** value,
uint32_t* length) { uint32_t* length) {
CHECK_FIBER_EXISTS(vm); CHECK_FIBER_EXISTS(vm);
VALIDATE_SLOT_INDEX(slot); VALIDATE_SLOT_INDEX(slot);
@ -608,7 +626,7 @@ bool pkValidateSlotType(PKVM* vm, int slot, PkVarType type) {
return true; return true;
} }
PK_PUBLIC bool pkValidateSlotInstanceOf(PKVM* vm, int slot, int cls) { bool pkValidateSlotInstanceOf(PKVM* vm, int slot, int cls) {
CHECK_FIBER_EXISTS(vm); CHECK_FIBER_EXISTS(vm);
VALIDATE_SLOT_INDEX(slot); VALIDATE_SLOT_INDEX(slot);
VALIDATE_SLOT_INDEX(cls); VALIDATE_SLOT_INDEX(cls);
@ -719,7 +737,7 @@ void pkSetSlotString(PKVM* vm, int index, const char* value) {
SET_SLOT(index, VAR_OBJ(newString(vm, value))); SET_SLOT(index, VAR_OBJ(newString(vm, value)));
} }
PK_PUBLIC void pkSetSlotStringLength(PKVM* vm, int index, void pkSetSlotStringLength(PKVM* vm, int index,
const char* value, uint32_t length) { const char* value, uint32_t length) {
CHECK_FIBER_EXISTS(vm); CHECK_FIBER_EXISTS(vm);
VALIDATE_SLOT_INDEX(index); VALIDATE_SLOT_INDEX(index);
@ -751,7 +769,7 @@ bool pkSetAttribute(PKVM* vm, int instance, const char* name, int value) {
return !VM_HAS_ERROR(vm); return !VM_HAS_ERROR(vm);
} }
PK_PUBLIC bool pkGetAttribute(PKVM* vm, int instance, const char* name, bool pkGetAttribute(PKVM* vm, int instance, const char* name,
int index) { int index) {
CHECK_FIBER_EXISTS(vm); CHECK_FIBER_EXISTS(vm);
CHECK_ARG_NULL(name); CHECK_ARG_NULL(name);
@ -830,7 +848,7 @@ bool pkCallFunction(PKVM* vm, int fn, int argc, int argv, int ret) {
return false; return false;
} }
PK_PUBLIC bool pkCallMethod(PKVM* vm, int instance, const char* method, bool pkCallMethod(PKVM* vm, int instance, const char* method,
int argc, int argv, int ret) { int argc, int argv, int ret) {
CHECK_FIBER_EXISTS(vm); CHECK_FIBER_EXISTS(vm);
CHECK_ARG_NULL(method); CHECK_ARG_NULL(method);
@ -936,6 +954,7 @@ static char* stdinRead(PKVM* vm) {
char* str = pkRealloc(vm, NULL, buff.count); char* str = pkRealloc(vm, NULL, buff.count);
memcpy(str, buff.data, buff.count); memcpy(str, buff.data, buff.count);
pkByteBufferClear(&buff, vm);
return str; return str;
} }

View File

@ -98,6 +98,11 @@ typedef void (*pkWriteFn) (PKVM* vm, const char* text);
// string. // string.
typedef char* (*pkReadFn) (PKVM* vm); typedef char* (*pkReadFn) (PKVM* vm);
// A generic function thiat could be used by the PKVM to signal something to
// the host application. The first argument is depend on the callback it's
// registered.
typedef void (*pkSignalFn) (void*);
// Load and return the script. Called by the compiler to fetch initial source // Load and return the script. Called by the compiler to fetch initial source
// code and source for import statements. Return NULL to indicate failure to // code and source for import statements. Return NULL to indicate failure to
// load. Otherwise the string **must** be allocated with pkRealloc() and // load. Otherwise the string **must** be allocated with pkRealloc() and
@ -170,10 +175,12 @@ struct PkConfiguration {
// pointer is NULL it defaults to the VM's realloc(), free() wrappers. // pointer is NULL it defaults to the VM's realloc(), free() wrappers.
pkReallocFn realloc_fn; pkReallocFn realloc_fn;
// I/O callbacks.
pkWriteFn stderr_write; pkWriteFn stderr_write;
pkWriteFn stdout_write; pkWriteFn stdout_write;
pkReadFn stdin_read; pkReadFn stdin_read;
// Import system callbacks.
pkResolvePathFn resolve_path_fn; pkResolvePathFn resolve_path_fn;
pkLoadScriptFn load_script_fn; pkLoadScriptFn load_script_fn;
@ -291,6 +298,10 @@ PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int slot, bool* value);
// if not set a runtime error. // if not set a runtime error.
PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int slot, double* value); PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int slot, double* value);
// Helper function to check if the argument at the [slot] is an a whold number
// and if not set a runtime error.
PK_PUBLIC bool pkValidateSlotInteger(PKVM* vm, int slot, int32_t* value);
// Helper function to check if the argument at the [slot] slot is String and // Helper function to check if the argument at the [slot] slot is String and
// if not set a runtime error. // if not set a runtime error.
PK_PUBLIC bool pkValidateSlotString(PKVM* vm, int slot, PK_PUBLIC bool pkValidateSlotString(PKVM* vm, int slot,

View File

@ -4,10 +4,56 @@
* Distributed Under The MIT License * Distributed Under The MIT License
*/ */
#include <math.h>
#ifndef PK_AMALGAMATED #ifndef PK_AMALGAMATED
#include "libs.h" #include "libs.h"
#include "../core/value.h"
#endif #endif
DEF(_ioWrite,
"io.write(stream:var, bytes:String) -> null\n"
"Warning: the function is subjected to be changed anytime soon.\n"
"Write [bytes] string to the stream. stream should be any of io.stdin, "
"io.stdout, io.stderr.") {
double stream;
if (!pkValidateSlotNumber(vm, 1, &stream)) return;
if (stream != 0 && stream != 1 && stream != 2) {
pkSetRuntimeErrorFmt(vm, "Invalid stream (%g). Only use any of io.stdin, "
"io.stdout, io.stderr.", stream);
return;
}
const char* bytes;
uint32_t length;
if (!pkValidateSlotString(vm, 2, &bytes, &length)) return;
switch ((int) stream) {
case 0:
pkSetRuntimeError(vm, "Cannot write to stdin.");
return;
// TODO: If the string contain null bytes it won't print anything after
// that, not sure if that needs to be fixed.
case 1:
fprintf(stdout, "%s", bytes);
return;
case 2:
fprintf(stderr, "%s", bytes);
return;
}
}
DEF(_ioFlush,
"io.flush() -> null\n"
"Warning: the function is subjected to be changed anytime soon.\n"
"Flush stdout buffer.\n") {
fflush(stdout);
}
/*****************************************************************************/ /*****************************************************************************/
/* FILE CLASS */ /* FILE CLASS */
/*****************************************************************************/ /*****************************************************************************/
@ -22,13 +68,26 @@
// 'a+' | write to end | create new | // 'a+' | write to end | create new |
typedef enum { typedef enum {
FMODE_NONE = 0, FMODE_NONE = 0,
FMODE_READ = (1 << 0), FMODE_READ = (1 << 0),
FMODE_WRITE = (1 << 1), FMODE_WRITE = (1 << 1),
FMODE_APPEND = (1 << 2), FMODE_APPEND = (1 << 2),
_FMODE_EXT = (1 << 3), _FMODE_EXT = (1 << 3),
_FMODE_BIN = (1 << 4),
FMODE_READ_EXT = (_FMODE_EXT | FMODE_READ), FMODE_READ_EXT = (_FMODE_EXT | FMODE_READ),
FMODE_WRITE_EXT = (_FMODE_EXT | FMODE_WRITE), FMODE_WRITE_EXT = (_FMODE_EXT | FMODE_WRITE),
FMODE_APPEND_EXT = (_FMODE_EXT | FMODE_APPEND), FMODE_APPEND_EXT = (_FMODE_EXT | FMODE_APPEND),
FMODE_READ_BIN = (_FMODE_BIN | FMODE_READ),
FMODE_WRITE_BIN = (_FMODE_BIN | FMODE_READ),
FMODE_APPEND_BIN = (_FMODE_BIN | FMODE_READ),
FMODE_READ_BIN_EXT = (_FMODE_BIN | FMODE_READ_EXT),
FMODE_WRITE_BIN_EXT = (_FMODE_BIN | FMODE_WRITE_EXT),
FMODE_APPEND_BIN_EXT = (_FMODE_BIN | FMODE_APPEND_EXT),
} FileAccessMode; } FileAccessMode;
typedef struct { typedef struct {
@ -49,9 +108,15 @@ void* _newFile(PKVM* vm) {
void _deleteFile(PKVM* vm, void* ptr) { void _deleteFile(PKVM* vm, void* ptr) {
File* file = (File*)ptr; File* file = (File*)ptr;
if (!file->closed) { if (!file->closed) {
ASSERT(file->fp != NULL, OOPS);
if (fclose(file->fp) != 0) { /* TODO: error! */ } if (fclose(file->fp) != 0) { /* TODO: error! */ }
file->closed = true; file->closed = true;
file->fp = NULL;
} else {
ASSERT(file->fp == NULL, OOPS);
} }
pkRealloc(vm, file, 0); pkRealloc(vm, file, 0);
} }
@ -73,15 +138,24 @@ DEF(_fileOpen, "") {
if (argc == 2) { if (argc == 2) {
if (!pkValidateSlotString(vm, 2, &mode_str, NULL)) return; if (!pkValidateSlotString(vm, 2, &mode_str, NULL)) return;
// Check if the mode string is valid, and update the mode value. // TODO: I should properly parser this.
do { do {
if (strcmp(mode_str, "r") == 0) { mode = FMODE_READ; break; } if (strcmp(mode_str, "r") == 0) { mode = FMODE_READ; break; }
if (strcmp(mode_str, "w") == 0) { mode = FMODE_WRITE; break; } if (strcmp(mode_str, "w") == 0) { mode = FMODE_WRITE; break; }
if (strcmp(mode_str, "a") == 0) { mode = FMODE_APPEND; break; } if (strcmp(mode_str, "a") == 0) { mode = FMODE_APPEND; break; }
if (strcmp(mode_str, "r+") == 0) { mode = FMODE_READ_EXT; break; } if (strcmp(mode_str, "r+") == 0) { mode = FMODE_READ_EXT; break; }
if (strcmp(mode_str, "w+") == 0) { mode = FMODE_WRITE_EXT; break; } if (strcmp(mode_str, "w+") == 0) { mode = FMODE_WRITE_EXT; break; }
if (strcmp(mode_str, "a+") == 0) { mode = FMODE_APPEND_EXT; break; } if (strcmp(mode_str, "a+") == 0) { mode = FMODE_APPEND_EXT; break; }
if (strcmp(mode_str, "rb") == 0) { mode = FMODE_READ_BIN; break; }
if (strcmp(mode_str, "wb") == 0) { mode = FMODE_WRITE_BIN; break; }
if (strcmp(mode_str, "ab") == 0) { mode = FMODE_APPEND_BIN; break; }
if (strcmp(mode_str, "rb+") == 0) { mode = FMODE_READ_BIN_EXT; break; }
if (strcmp(mode_str, "wb+") == 0) { mode = FMODE_WRITE_BIN_EXT; break; }
if (strcmp(mode_str, "ab+") == 0) { mode = FMODE_APPEND_BIN_EXT; break; }
// TODO: (fmt, ...) va_arg for runtime error public api. // TODO: (fmt, ...) va_arg for runtime error public api.
// If we reached here, that means it's an invalid mode string. // If we reached here, that means it's an invalid mode string.
pkSetRuntimeError(vm, "Invalid mode string."); pkSetRuntimeError(vm, "Invalid mode string.");
@ -89,10 +163,6 @@ DEF(_fileOpen, "") {
} while (false); } while (false);
} }
// This TODO is just a blockade from running the bellow code, complete the
// native interface and test before removing it.
TODO;
FILE* fp = fopen(path, mode_str); FILE* fp = fopen(path, mode_str);
if (fp != NULL) { if (fp != NULL) {
@ -107,9 +177,28 @@ DEF(_fileOpen, "") {
} }
DEF(_fileRead, "") { DEF(_fileRead, "") {
// This TODO is just a blockade from running the bellow code, complete the
// native interface and test before removing it. int argc = pkGetArgc(vm);
TODO; if (!pkCheckArgcRange(vm, argc, 0, 1)) return;
// If count == -1, that means read all bytes.
long count = -1;
if (argc == 1) {
// Not using pkValidateInteger since it's supports 32 bit int range.
// but we need a long.
double count_;
if (!pkValidateSlotNumber(vm, 1, &count_)) return;
if (floor(count_) != count_) {
pkSetRuntimeError(vm, "Expected an integer.");
return;
}
if (count_ < 0 && count_ != -1) {
pkSetRuntimeError(vm, "Read bytes count should be either > 0 or == -1.");
return;
}
count = (long) count_;
}
File* file = (File*) pkGetSelf(vm); File* file = (File*) pkGetSelf(vm);
@ -118,23 +207,113 @@ DEF(_fileRead, "") {
return; return;
} }
if ((file->mode != FMODE_READ) && ((_FMODE_EXT & file->mode) == 0)) { if (!(file->mode & FMODE_READ) && !(_FMODE_EXT & file->mode)) {
pkSetRuntimeError(vm, "File is not readable."); pkSetRuntimeError(vm, "File is not readable.");
return; return;
} }
// TODO: this is temporary. if (count == -1) {
// Get the source length. In windows the ftell will includes the cariage
// return when using ftell with fseek. But that's not an issue since
// we'll be allocating more memory than needed for fread().
long current = ftell(file->fp);
fseek(file->fp, 0, SEEK_END);
count = ftell(file->fp);
fseek(file->fp, current, SEEK_SET);
}
char buff[2048]; // Allocate string + 1 for the NULL terminator.
size_t read = fread((void*)buff, sizeof(char), sizeof(buff), file->fp); char* buff = pkRealloc(vm, NULL, (size_t) count + 1);
(void) read; ASSERT(buff != NULL, "pkRealloc failed.");
pkSetSlotString(vm, 0, (const char*)buff);
clearerr(file->fp);
size_t read = fread(buff, sizeof(char), count, file->fp);
if (ferror(file->fp)) {
pkSetRuntimeErrorFmt(vm, "An error occured at C.fread() - '%s'.",
strerror(errno));
goto L_done;
}
bool is_read_failed = read > count;
if (!is_read_failed) buff[read] = '\0';
// If EOF is already reached it won't read anymore bytes.
if (read == 0) {
pkSetSlotStringLength(vm, 0, "", 0);
goto L_done;
}
if (is_read_failed) {
pkSetRuntimeError(vm, "C.fread() failed.");
goto L_done;
}
// TODO: maybe I should check if the [read] length is larger for uint32_t.
pkSetSlotStringLength(vm, 0, buff, (uint32_t) read);
goto L_done;
L_done:
pkRealloc(vm, buff, 0);
return;
}
// Note that fgetline is not standard in older version of C. so we're defining
// something similler.
DEF(_fileGetLine, "") {
File* file = (File*) pkGetSelf(vm);
if (file->closed) {
pkSetRuntimeError(vm, "Cannot read from a closed file.");
return;
}
if (!(file->mode & FMODE_READ) && !(_FMODE_EXT & file->mode)) {
pkSetRuntimeError(vm, "File is not readable.");
return;
}
if (file->mode & _FMODE_BIN) {
pkSetRuntimeError(vm, "Cannot getline binary files.");
return;
}
// FIXME:
// I'm not sure this is an efficient method to read a line using fgetc
// and a dynamic buffer. Consider fgets() or maybe find '\n' in file
// and use pkRealloc() with size using ftell() or something.
pkByteBuffer buff;
pkByteBufferInit(&buff);
char c;
do {
c = (char) fgetc(file->fp);
// End of file or error.
if (c == EOF) {
if (ferror(file->fp)) {
pkSetRuntimeErrorFmt(vm, "Error while reading line - '%s'.",
strerror(errno));
goto L_done;
}
break; // EOF is reached.
}
pkByteBufferWrite(&buff, vm, (uint8_t) c);
if (c == '\n') break;
} while (true);
// A null byte '\0' will be added by pocketlang.
pkSetSlotStringLength(vm, 0, buff.data, buff.count);
L_done:
pkByteBufferClear(&buff, vm);
return;
} }
DEF(_fileWrite, "") { DEF(_fileWrite, "") {
// This TODO is just a blockade from running the bellow code, complete the
// native interface and test before removing it.
TODO;
File* file = (File*) pkGetSelf(vm); File* file = (File*) pkGetSelf(vm);
const char* text; uint32_t length; const char* text; uint32_t length;
@ -145,22 +324,28 @@ DEF(_fileWrite, "") {
return; return;
} }
if ((file->mode != FMODE_WRITE) && ((_FMODE_EXT & file->mode) == 0)) { if (!(file->mode & FMODE_WRITE)
&& !(file->mode & FMODE_APPEND)
&& !(file->mode & _FMODE_EXT)) {
pkSetRuntimeError(vm, "File is not writable."); pkSetRuntimeError(vm, "File is not writable.");
return; return;
} }
clearerr(file->fp);
fwrite(text, sizeof(char), (size_t) length, file->fp); fwrite(text, sizeof(char), (size_t) length, file->fp);
if (ferror(file->fp)) {
pkSetRuntimeErrorFmt(vm, "An error occureed at C.fwrite() - '%s'.",
strerror(errno));
return;
}
} }
DEF(_fileClose, "") { DEF(_fileClose, "") {
// This TODO is just a blockade from running the bellow code, complete the
// native interface and test before removing it.
TODO;
File* file = (File*) pkGetSelf(vm); File* file = (File*) pkGetSelf(vm);
if (file->closed) { if (file->closed) {
ASSERT(file->fp == NULL, OOPS);
pkSetRuntimeError(vm, "File already closed."); pkSetRuntimeError(vm, "File already closed.");
return; return;
} }
@ -168,17 +353,79 @@ DEF(_fileClose, "") {
if (fclose(file->fp) != 0) { if (fclose(file->fp) != 0) {
pkSetRuntimeError(vm, "fclose() failed!."); pkSetRuntimeError(vm, "fclose() failed!.");
} }
file->fp = NULL;
file->closed = true; file->closed = true;
} }
DEF(_fileSeek,
"io.File.seek(offset:int, whence:int) -> null\n"
"") {
int argc = pkGetArgc(vm);
if (!pkCheckArgcRange(vm, argc, 1, 2)) return;
int32_t offset = 0, whence = 0;
if (!pkValidateSlotInteger(vm, 1, &offset)) return;
if (argc == 2) {
if (!pkValidateSlotInteger(vm, 2, &whence)) return;
if (whence < 0 || whence > 2) {
pkSetRuntimeErrorFmt(vm, "Invalid whence value (%i).", whence);
return;
}
}
File* file = (File*) pkGetSelf(vm);
if (file->closed) {
pkSetRuntimeError(vm, "Cannot seek from a closed file.");
return;
}
if (fseek(file->fp, offset, whence) != 0) {
pkSetRuntimeErrorFmt(vm, "An error occureed at C.fseek() - '%s'.",
strerror(errno));
return;
}
}
DEF(_fileTell, "") {
File* file = (File*) pkGetSelf(vm);
if (file->closed) {
pkSetRuntimeError(vm, "Cannot tell from a closed file.");
return;
}
// C.ftell() doesn't "throw" any error right?
pkSetSlotNumber(vm, 0, (double) ftell(file->fp));
}
void registerModuleIO(PKVM* vm) { void registerModuleIO(PKVM* vm) {
PkHandle* io = pkNewModule(vm, "io"); PkHandle* io = pkNewModule(vm, "io");
pkReserveSlots(vm, 2);
pkSetSlotHandle(vm, 0, io); // slot[0] = io
pkSetSlotNumber(vm, 1, 0); // slot[1] = 0
pkSetAttribute(vm, 0, "stdin", 1); // slot[0].stdin = slot[1]
pkSetSlotNumber(vm, 1, 1); // slot[1] = 1
pkSetAttribute(vm, 0, "stdout", 1); // slot[0].stdout = slot[1]
pkSetSlotNumber(vm, 1, 2); // slot[1] = 2
pkSetAttribute(vm, 0, "stderr", 1); // slot[0].stderr = slot[1]
pkModuleAddFunction(vm, io, "write", _ioWrite, 2);
pkModuleAddFunction(vm, io, "flush", _ioFlush, 0);
PkHandle* cls_file = pkNewClass(vm, "File", NULL, io, _newFile, _deleteFile); PkHandle* cls_file = pkNewClass(vm, "File", NULL, io, _newFile, _deleteFile);
pkClassAddMethod(vm, cls_file, "open", _fileOpen, -1); pkClassAddMethod(vm, cls_file, "open", _fileOpen, -1);
pkClassAddMethod(vm, cls_file, "read", _fileRead, 0); pkClassAddMethod(vm, cls_file, "read", _fileRead, -1);
pkClassAddMethod(vm, cls_file, "write", _fileWrite, 1); pkClassAddMethod(vm, cls_file, "write", _fileWrite, 1);
pkClassAddMethod(vm, cls_file, "getline", _fileGetLine, 0);
pkClassAddMethod(vm, cls_file, "close", _fileClose, 0); pkClassAddMethod(vm, cls_file, "close", _fileClose, 0);
pkClassAddMethod(vm, cls_file, "seek", _fileSeek, -1);
pkClassAddMethod(vm, cls_file, "tell", _fileTell, 0);
pkReleaseHandle(vm, cls_file); pkReleaseHandle(vm, cls_file);
pkRegisterModule(vm, io); pkRegisterModule(vm, io);

View File

@ -36,6 +36,23 @@ void _bytebuffReserve(PKVM* vm) {
pkByteBufferReserve(self, vm, (size_t) size); pkByteBufferReserve(self, vm, (size_t) size);
} }
// buff.fill(data, count)
void _bytebuffFill(PKVM* vm) {
uint32_t n;
if (!pkValidateSlotInteger(vm, 1, &n)) return;
if (n < 0x00 || n > 0xff) {
pkSetRuntimeErrorFmt(vm, "Expected integer in range "
"0x00 to 0xff, got %i.", n);
return;
}
double count;
if (!pkValidateSlotNumber(vm, 1, &count)) return;
pkByteBuffer* self = pkGetSelf(vm);
pkByteBufferFill(self, vm, (uint8_t) n, (int) count);
}
void _bytebuffClear(PKVM* vm) { void _bytebuffClear(PKVM* vm) {
// TODO: Should I also zero or reduce the capacity? // TODO: Should I also zero or reduce the capacity?
pkByteBuffer* self = pkGetSelf(vm); pkByteBuffer* self = pkGetSelf(vm);
@ -55,12 +72,8 @@ void _bytebuffWrite(PKVM* vm) {
return; return;
case PK_NUMBER: { case PK_NUMBER: {
double n = pkGetSlotNumber(vm, 1); uint32_t i;
if (floor(n) != n) { if (!pkValidateSlotInteger(vm, 1, &i)) return;
pkSetRuntimeErrorFmt(vm, "Expected an integer, got float %g.", n);
return;
}
int64_t i = (int64_t) n;
if (i < 0x00 || i > 0xff) { if (i < 0x00 || i > 0xff) {
pkSetRuntimeErrorFmt(vm, "Expected integer in range " pkSetRuntimeErrorFmt(vm, "Expected integer in range "
"0x00 to 0xff, got %i.", i); "0x00 to 0xff, got %i.", i);
@ -80,6 +93,10 @@ void _bytebuffWrite(PKVM* vm) {
return; return;
} }
// TODO:
case PK_LIST: {
}
default: default:
break; break;
} }
@ -144,6 +161,11 @@ void _bytebuffString(PKVM* vm) {
pkSetSlotStringLength(vm, 0, self->data, self->count); pkSetSlotStringLength(vm, 0, self->data, self->count);
} }
void _bytebuffCount(PKVM* vm) {
pkByteBuffer* self = pkGetSelf(vm);
pkSetSlotNumber(vm, 0, self->count);
}
/*****************************************************************************/ /*****************************************************************************/
/* VECTOR */ /* VECTOR */
/*****************************************************************************/ /*****************************************************************************/
@ -247,9 +269,11 @@ void registerModuleTypes(PKVM* vm) {
pkClassAddMethod(vm, cls_byte_buffer, "[]", _bytebuffSubscriptGet, 1); pkClassAddMethod(vm, cls_byte_buffer, "[]", _bytebuffSubscriptGet, 1);
pkClassAddMethod(vm, cls_byte_buffer, "[]=", _bytebuffSubscriptSet, 2); pkClassAddMethod(vm, cls_byte_buffer, "[]=", _bytebuffSubscriptSet, 2);
pkClassAddMethod(vm, cls_byte_buffer, "reserve", _bytebuffReserve, 1); pkClassAddMethod(vm, cls_byte_buffer, "reserve", _bytebuffReserve, 1);
pkClassAddMethod(vm, cls_byte_buffer, "fill", _bytebuffFill, 2);
pkClassAddMethod(vm, cls_byte_buffer, "clear", _bytebuffClear, 0); pkClassAddMethod(vm, cls_byte_buffer, "clear", _bytebuffClear, 0);
pkClassAddMethod(vm, cls_byte_buffer, "write", _bytebuffWrite, 1); pkClassAddMethod(vm, cls_byte_buffer, "write", _bytebuffWrite, 1);
pkClassAddMethod(vm, cls_byte_buffer, "string", _bytebuffString, 0); pkClassAddMethod(vm, cls_byte_buffer, "string", _bytebuffString, 0);
pkClassAddMethod(vm, cls_byte_buffer, "count", _bytebuffCount, 0);
pkReleaseHandle(vm, cls_byte_buffer); pkReleaseHandle(vm, cls_byte_buffer);
// TODO: add move mthods. // TODO: add move mthods.

View File

@ -1,4 +1,4 @@
from lang import write import io
############################################################################### ###############################################################################
## BRAINFUCK IMPLEMENTATION IN POCKETLANG ## ## BRAINFUCK IMPLEMENTATION IN POCKETLANG ##
@ -31,6 +31,11 @@ def main()
execute(hello_world) execute(hello_world)
execute(triangle) execute(triangle)
##execute(fibonacci) This will run endlessly (cannot run test). ##execute(fibonacci) This will run endlessly (cannot run test).
io.flush()
end
def write(msg)
io.write(io.stdout, msg)
end end
############################################################################### ###############################################################################
@ -51,52 +56,52 @@ def execute(expr)
if ptr >= mem.length then list_append(mem, 0) end if ptr >= mem.length then list_append(mem, 0) end
## Decrement the data pointer (to point to the next cell to the left). ## Decrement the data pointer (to point to the next cell to the left).
else if c == '<' elif c == '<'
ptr -= 1 ptr -= 1
if ptr < 0 then assert(false, "ip < 0") end if ptr < 0 then assert(false, "ip < 0") end
## Increment (increase by one) the byte at the data pointer. ## Increment (increase by one) the byte at the data pointer.
else if c == '+' elif c == '+'
if mem[ptr] == 255 then mem[ptr] = 0 if mem[ptr] == 255 then mem[ptr] = 0
else mem[ptr] += 1 end else mem[ptr] += 1 end
## Decrement (decrease by one) the byte at the data pointer. ## Decrement (decrease by one) the byte at the data pointer.
else if c == '-' elif c == '-'
if mem[ptr] == 0 then mem[ptr] = 255 if mem[ptr] == 0 then mem[ptr] = 255
else mem[ptr] -= 1 end else mem[ptr] -= 1 end
## output the byte at the data pointer. ## output the byte at the data pointer.
else if c == '.' elif c == '.'
write(chr(mem[ptr])) write(chr(mem[ptr]))
else if c == ',' elif c == ','
assert(false, "Currently input isn't supported in pocketlang.") assert(false, "Currently input isn't supported in pocketlang.")
## if the byte at the data pointer is zero, then instead of moving the ## if the byte at the data pointer is zero, then instead of moving the
## instruction pointer forward to the next command, jump it forward to ## instruction pointer forward to the next command, jump it forward to
## the command after the matching ] command. ## the command after the matching ] command.
else if c == '[' and mem[ptr] == 0 elif c == '[' and mem[ptr] == 0
open = 0 open = 0
while true while true
i += 1 i += 1
if expr[i] == ']' and open == 0 then break end if expr[i] == ']' and open == 0 then break end
if expr[i] == '[' then open += 1 if expr[i] == '[' then open += 1
else if expr[i] == ']' then open -= 1 elif expr[i] == ']' then open -= 1
end assert(open >= 0) end assert(open >= 0)
end end
## if the byte at the data pointer is nonzero, then instead of moving the ## if the byte at the data pointer is nonzero, then instead of moving the
## instruction pointer forward to the next command, jump it back to the ## instruction pointer forward to the next command, jump it back to the
## command after the matching [ command ## command after the matching [ command
else if c == ']' and mem[ptr] != 0 elif c == ']' and mem[ptr] != 0
open = 0 open = 0
while true while true
i -= 1 i -= 1
if expr[i] == '[' and open == 0 then break end if expr[i] == '[' and open == 0 then break end
if expr[i] == ']' then open -= 1 if expr[i] == ']' then open -= 1
else if expr[i] == '[' then open += 1 elif expr[i] == '[' then open += 1
end assert(open <= 0) end assert(open <= 0)
end end

View File

@ -2,8 +2,8 @@
for i in 1..100 for i in 1..100
if i%3 == 0 and i%5 == 0 then print('fizzbuzz') if i%3 == 0 and i%5 == 0 then print('fizzbuzz')
else if i%3 == 0 then print('fizz') elif i%3 == 0 then print('fizz')
else if i%5 == 0 then print('buzz') elif i%5 == 0 then print('buzz')
else print(i) else print(i)
end end
end end

View File

@ -10,6 +10,10 @@ assert("testing" == "test" + "ing")
assert("foo \ assert("foo \
bar" == "foo bar") bar" == "foo bar")
assert('abc ' * 3 == 'abc abc abc ')
assert('' * 1000 == '')
assert('foo' * 0 == '')
assert(-0b10110010 == -178 and 0b11001010 == 202) assert(-0b10110010 == -178 and 0b11001010 == 202)
assert(0b1111111111111111 == 65535) assert(0b1111111111111111 == 65535)
assert( assert(

View File

@ -119,7 +119,7 @@ class Path
def /(other) def /(other)
if other is String if other is String
return Path(self.path + "/" + other) return Path(self.path + "/" + other)
else if other is Path elif other is Path
return Path(self.path + "/" + other.path) return Path(self.path + "/" + other.path)
else else
assert(false, "Invalid type") assert(false, "Invalid type")

View File

@ -7,21 +7,21 @@ if true then variable = 42 else unreachable() end
assert(variable == 42, 'If statement failed.') assert(variable == 42, 'If statement failed.')
if false then unreachable() if false then unreachable()
else if true then variable = null elif true then variable = null
else unreachable() end else unreachable() end
assert(variable == null) assert(variable == null)
if false then unreachable() if false then unreachable()
else if false then unreachable() elif false then unreachable()
else if false then unreachable() elif false then unreachable()
else variable = "changed" end else variable = "changed" end
assert(variable == "changed") assert(variable == "changed")
if false then unreachable() if false then unreachable()
else if true elif true
if false if false
unreachable() unreachable()
else if true elif true
variable = 123 variable = 123
else else
unreachable() unreachable()
@ -39,9 +39,9 @@ assert(variable == 1212)
while true while true
variable = 22 variable = 22
if true then else if false then end if true then elif false then end
if false if false
else if true elif true
variable += 2300 variable += 2300
end end
variable += 1 variable += 1
@ -57,7 +57,7 @@ assert(sum == 54)
val = 2 val = 2
if val == 1 then val = null if val == 1 then val = null
else if val == 2 then val = 'foo' elif val == 2 then val = 'foo'
else val = null else val = null
end end
assert(val == 'foo') assert(val == 'foo')
@ -66,7 +66,7 @@ val = 2
if val == 1 then val = null if val == 1 then val = null
else else
if val == 2 then val = 'bar' if val == 2 then val = 'bar'
end ##< Need an extra end since 'else if' != 'else \n if'. end ##< Need an extra end since 'elif' != 'else \n if'.
end end
assert(val == 'bar') assert(val == 'bar')

View File

@ -3,7 +3,7 @@
import lang import lang
import lang, path import lang, path
import lang as o, path as p import lang as o, path as p
from lang import write from io import write
from time import sleep as s from time import sleep as s
import basics import basics

View File

@ -44,27 +44,27 @@ def eval(expr, ind)
end end
return [val, ind] return [val, ind]
else if c == '+' elif c == '+'
r = binary_op(expr, ind) r = binary_op(expr, ind)
if not r[0] then return [null, -1] end if not r[0] then return [null, -1] end
return [r[1] + r[2], r[3]] return [r[1] + r[2], r[3]]
else if c == '-' elif c == '-'
r = binary_op(expr, ind) r = binary_op(expr, ind)
if not r[0] then return [null, -1] end if not r[0] then return [null, -1] end
return [r[1] - r[2], r[3]] return [r[1] - r[2], r[3]]
else if c == '*' elif c == '*'
r = binary_op(expr, ind) r = binary_op(expr, ind)
if not r[0] then return [null, -1] end if not r[0] then return [null, -1] end
return [r[1] * r[2], r[3]] return [r[1] * r[2], r[3]]
else if c == '/' elif c == '/'
r = binary_op(expr, ind) r = binary_op(expr, ind)
if not r[0] then return [null, -1] end if not r[0] then return [null, -1] end
return [r[1] / r[2], r[3]] return [r[1] / r[2], r[3]]
else if isnum(c) elif isnum(c)
val = ord(c) - ord('0') val = ord(c) - ord('0')
assert(0 <= val and val < 10) assert(0 <= val and val < 10)
return [val, ind] return [val, ind]