mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-03-04 13:15:55 +08:00
Merge pull request #239 from ThakeeNathees/invalid-assignment-bug
Invalid definition inside expression fixed
This commit is contained in:
commit
052f1e263e
@ -33,6 +33,7 @@ TEST_SUITE = {
|
||||
"Modules Test" : (
|
||||
"modules/dummy.pk",
|
||||
"modules/math.pk",
|
||||
"modules/io.File.pk",
|
||||
),
|
||||
|
||||
"Random Scripts" : (
|
||||
|
@ -479,6 +479,18 @@ struct Compiler {
|
||||
// "r-value".
|
||||
bool l_value;
|
||||
|
||||
// We can do a new assignment inside an expression however we shouldn't
|
||||
// define a new one, since in pocketlang both assignment and definition
|
||||
// are syntactically the same, we use [can_define] "context" to prevent
|
||||
// such assignments.
|
||||
bool can_define;
|
||||
|
||||
// This value will set to true for parsing a temproary expression
|
||||
// (ie. if <expr> ...) and the value of the expression will be popped out of
|
||||
// the stack onece we're done, if it's a defenition, that value should be
|
||||
// duplicated on the stack to prevent it.
|
||||
bool expr_temp;
|
||||
|
||||
// This value will be true after parsing a call expression, for every other
|
||||
// Expressions it'll be false. This is **ONLY** to be used when compiling a
|
||||
// return statement to check if the last parsed expression is a call to
|
||||
@ -551,6 +563,8 @@ static void parserInit(Parser* parser, PKVM* vm, Compiler* compiler,
|
||||
static void compilerInit(Compiler* compiler, PKVM* vm, const char* source,
|
||||
Module* module, const CompileOptions* options) {
|
||||
|
||||
memset(compiler, 0, sizeof(Compiler));
|
||||
|
||||
compiler->next_compiler = NULL;
|
||||
|
||||
compiler->module = module;
|
||||
@ -561,6 +575,7 @@ static void compilerInit(Compiler* compiler, PKVM* vm, const char* source,
|
||||
compiler->loop = NULL;
|
||||
compiler->func = NULL;
|
||||
|
||||
compiler->can_define = true;
|
||||
compiler->new_local = false;
|
||||
compiler->is_last_call = false;
|
||||
|
||||
@ -1880,7 +1895,14 @@ static void exprInterpolation(Compiler* compiler) {
|
||||
}
|
||||
|
||||
static void exprFunction(Compiler* compiler) {
|
||||
bool can_define = compiler->can_define;
|
||||
bool expr_temp = compiler->expr_temp;
|
||||
|
||||
compiler->can_define = true;
|
||||
compiler->expr_temp = false;
|
||||
compileFunction(compiler, FUNC_LITERAL);
|
||||
compiler->can_define = can_define;
|
||||
compiler->expr_temp = expr_temp;
|
||||
}
|
||||
|
||||
static void exprName(Compiler* compiler) {
|
||||
@ -1926,10 +1948,18 @@ static void exprName(Compiler* compiler) {
|
||||
if (name_type == NAME_LOCAL_VAR) {
|
||||
new_local = true;
|
||||
}
|
||||
|
||||
if (!compiler->can_define) {
|
||||
semanticError(compiler, tkname,
|
||||
"Variable definition isn't allowed here.");
|
||||
}
|
||||
}
|
||||
|
||||
// Compile the assigned value.
|
||||
bool can_define = compiler->can_define;
|
||||
compiler->can_define = false;
|
||||
compileExpression(compiler);
|
||||
compiler->can_define = can_define;
|
||||
|
||||
} else { // name += / -= / *= ... = (expr);
|
||||
|
||||
@ -1963,6 +1993,11 @@ static void exprName(Compiler* compiler) {
|
||||
// If the compiler has errors, we cannot and don't have to assert.
|
||||
ASSERT(compiler->parser.has_errors ||
|
||||
(compiler->func->stack_size - 1) == index, OOPS);
|
||||
|
||||
if (compiler->expr_temp) {
|
||||
emitOpcode(compiler, OP_DUP);
|
||||
}
|
||||
|
||||
} else {
|
||||
// The assigned value or the result of the operator will be at the top of
|
||||
// the stack by now. Store it.
|
||||
@ -2281,9 +2316,18 @@ static void parsePrecedence(Compiler* compiler, Precedence precedence) {
|
||||
// reset once it done.
|
||||
bool l_value = compiler->l_value;
|
||||
|
||||
// Inside an expression no new difinition is allowed. We make a "backup"
|
||||
// here to prevent such and reset it once we're done.
|
||||
bool can_define = compiler->can_define;
|
||||
if (prefix != exprName) compiler->can_define = false;
|
||||
|
||||
compiler->l_value = precedence <= PREC_LOWEST;
|
||||
prefix(compiler);
|
||||
|
||||
// Prefix expression can be either allow or not allow a definition however
|
||||
// an infix expression can never be a definition.
|
||||
compiler->can_define = false;
|
||||
|
||||
// The above expression cannot be a call '(', since call is an infix
|
||||
// operator. But could be true (ex: x = f()). we set is_last_call to false
|
||||
// here and if the next infix operator is call this will be set to true
|
||||
@ -2304,6 +2348,7 @@ static void parsePrecedence(Compiler* compiler, Precedence precedence) {
|
||||
}
|
||||
|
||||
compiler->l_value = l_value;
|
||||
compiler->can_define = can_define;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
@ -3041,7 +3086,12 @@ static void compileExpression(Compiler* compiler) {
|
||||
static void compileIfStatement(Compiler* compiler, bool elif) {
|
||||
|
||||
skipNewLines(compiler);
|
||||
|
||||
bool expr_temp = compiler->expr_temp;
|
||||
compiler->expr_temp = true;
|
||||
compileExpression(compiler); //< Condition.
|
||||
compiler->expr_temp = expr_temp;
|
||||
|
||||
emitOpcode(compiler, OP_JUMP_IF_NOT);
|
||||
int ifpatch = emitShort(compiler, 0xffff); //< Will be patched.
|
||||
|
||||
@ -3090,7 +3140,11 @@ static void compileWhileStatement(Compiler* compiler) {
|
||||
loop.depth = compiler->scope_depth;
|
||||
compiler->loop = &loop;
|
||||
|
||||
bool expr_temp = compiler->expr_temp;
|
||||
compiler->expr_temp = true;
|
||||
compileExpression(compiler); //< Condition.
|
||||
compiler->expr_temp = expr_temp;
|
||||
|
||||
emitOpcode(compiler, OP_JUMP_IF_NOT);
|
||||
int whilepatch = emitShort(compiler, 0xffff); //< Will be patched.
|
||||
|
||||
@ -3122,7 +3176,10 @@ static void compileForStatement(Compiler* compiler) {
|
||||
|
||||
// Compile and store sequence.
|
||||
compilerAddVariable(compiler, "@Sequence", 9, iter_line); // Sequence
|
||||
bool expr_temp = compiler->expr_temp;
|
||||
compiler->expr_temp = true;
|
||||
compileExpression(compiler);
|
||||
compiler->expr_temp = expr_temp;
|
||||
|
||||
// Add iterator to locals. It's an increasing integer indicating that the
|
||||
// current loop is nth starting from 0.
|
||||
@ -3233,7 +3290,10 @@ static void compileStatement(Compiler* compiler) {
|
||||
"Cannor 'return' a value from constructor.");
|
||||
}
|
||||
|
||||
bool can_define = compiler->can_define;
|
||||
compiler->can_define = false;
|
||||
compileExpression(compiler); //< Return value is at stack top.
|
||||
compiler->can_define = can_define;
|
||||
|
||||
// If the last expression parsed with compileExpression() is a call
|
||||
// is_last_call would be true by now.
|
||||
|
@ -737,9 +737,9 @@ DEF(_objTypeName,
|
||||
}
|
||||
|
||||
DEF(_objRepr,
|
||||
"Object.repr() -> String\n"
|
||||
"Object._repr() -> String\n"
|
||||
"Returns the repr string of the object.") {
|
||||
RET(VAR_OBJ(varToString(vm, SELF, true)));
|
||||
RET(VAR_OBJ(toRepr(vm, SELF)));
|
||||
}
|
||||
|
||||
DEF(_numberTimes,
|
||||
@ -1145,7 +1145,7 @@ static void initializePrimitiveClasses(PKVM* vm) {
|
||||
|
||||
// TODO: write docs.
|
||||
ADD_METHOD(PK_OBJECT, "typename", _objTypeName, 0);
|
||||
ADD_METHOD(PK_OBJECT, "repr", _objRepr, 0);
|
||||
ADD_METHOD(PK_OBJECT, "_repr", _objRepr, 0);
|
||||
|
||||
ADD_METHOD(PK_NUMBER, "times", _numberTimes, 1);
|
||||
ADD_METHOD(PK_NUMBER, "isint", _numberIsint, 0);
|
||||
|
@ -374,6 +374,7 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
case OP_PUSH_TRUE:
|
||||
case OP_PUSH_FALSE:
|
||||
case OP_SWAP:
|
||||
case OP_DUP:
|
||||
NO_ARGS();
|
||||
break;
|
||||
|
||||
|
@ -33,6 +33,9 @@ OPCODE(PUSH_FALSE, 0, 1)
|
||||
// Swap the top 2 stack values.
|
||||
OPCODE(SWAP, 0, 0)
|
||||
|
||||
// Duplicate the stack top value.
|
||||
OPCODE(DUP, 0, 1)
|
||||
|
||||
// Push a new list to construct from literal.
|
||||
// param: 2 bytes list size (default is 0).
|
||||
OPCODE(PUSH_LIST, 2, 1)
|
||||
|
@ -828,6 +828,12 @@ L_vm_main_loop:
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(DUP):
|
||||
{
|
||||
PUSH(*(fiber->sp - 1));
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(PUSH_LIST):
|
||||
{
|
||||
List* list = newList(vm, (uint32_t)READ_SHORT());
|
||||
|
@ -407,6 +407,7 @@ DEF(_fileTell, "") {
|
||||
// from io import File
|
||||
// return File().open(path, mode)
|
||||
DEF(_open, NULL /* == _fileOpen */) {
|
||||
pkReserveSlots(vm, 3);
|
||||
|
||||
// slots[1] = path
|
||||
// slots[2] = mode
|
||||
|
@ -71,5 +71,11 @@ end
|
||||
assert(val == 'bar')
|
||||
|
||||
|
||||
def test_in_local()
|
||||
if x = fn y = false; return not y end ()
|
||||
assert(x == true)
|
||||
end
|
||||
end
|
||||
|
||||
# If we got here, that means all test were passed.
|
||||
print('All TESTS PASSED')
|
||||
|
26
tests/modules/io.File.pk
Normal file
26
tests/modules/io.File.pk
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
from io import File
|
||||
from path import join, dirname
|
||||
|
||||
FILE_PATH = join(dirname(__file__), 'test_file.txt')
|
||||
|
||||
def read_file()
|
||||
f = open(FILE_PATH)
|
||||
|
||||
assert(f is File)
|
||||
|
||||
LINES = [
|
||||
'line1 : foo\n',
|
||||
'line2 : bar\n',
|
||||
'line3 : baz\n',
|
||||
'line4 : qux\n',
|
||||
]
|
||||
|
||||
while line = f.getline()
|
||||
assert(line in LINES)
|
||||
end
|
||||
|
||||
f.close()
|
||||
end
|
||||
|
||||
read_file()
|
4
tests/modules/test_file.txt
Normal file
4
tests/modules/test_file.txt
Normal file
@ -0,0 +1,4 @@
|
||||
line1 : foo
|
||||
line2 : bar
|
||||
line3 : baz
|
||||
line4 : qux
|
Loading…
Reference in New Issue
Block a user