forked from Mirror/wren
Start hacking on "if" expressions.
This commit is contained in:
@ -35,7 +35,9 @@ typedef enum
|
||||
TOKEN_BANGEQ,
|
||||
|
||||
TOKEN_CLASS,
|
||||
TOKEN_ELSE,
|
||||
TOKEN_FALSE,
|
||||
TOKEN_IF,
|
||||
TOKEN_META,
|
||||
TOKEN_TRUE,
|
||||
TOKEN_VAR,
|
||||
@ -149,7 +151,7 @@ static void storeVariable(Compiler* compiler, int symbol);
|
||||
static int internSymbol(Compiler* compiler);
|
||||
|
||||
// Emits one bytecode instruction or argument.
|
||||
static void emit(Compiler* compiler, Code code);
|
||||
static int emit(Compiler* compiler, Code code);
|
||||
|
||||
// Outputs a compile or syntax error.
|
||||
static void error(Compiler* compiler, const char* format, ...);
|
||||
@ -253,7 +255,9 @@ ParseRule rules[] =
|
||||
/* TOKEN_EQEQ */ INFIX_OPERATOR(PREC_EQUALITY, "== "),
|
||||
/* TOKEN_BANGEQ */ INFIX_OPERATOR(PREC_EQUALITY, "!= "),
|
||||
/* TOKEN_CLASS */ UNUSED,
|
||||
/* TOKEN_ELSE */ UNUSED,
|
||||
/* TOKEN_FALSE */ PREFIX(boolean),
|
||||
/* TOKEN_IF */ UNUSED,
|
||||
/* TOKEN_META */ UNUSED,
|
||||
/* TOKEN_TRUE */ PREFIX(boolean),
|
||||
/* TOKEN_VAR */ UNUSED,
|
||||
@ -385,9 +389,10 @@ int internSymbol(Compiler* compiler)
|
||||
compiler->parser->previous.end - compiler->parser->previous.start);
|
||||
}
|
||||
|
||||
void emit(Compiler* compiler, Code code)
|
||||
int emit(Compiler* compiler, Code code)
|
||||
{
|
||||
compiler->block->bytecode[compiler->numCodes++] = code;
|
||||
return compiler->numCodes - 1;
|
||||
}
|
||||
|
||||
void error(Compiler* compiler, const char* format, ...)
|
||||
@ -469,6 +474,48 @@ void statement(Compiler* compiler)
|
||||
|
||||
void expression(Compiler* compiler)
|
||||
{
|
||||
if (match(compiler, TOKEN_IF))
|
||||
{
|
||||
// Compile the condition.
|
||||
consume(compiler, TOKEN_LEFT_PAREN);
|
||||
expression(compiler);
|
||||
consume(compiler, TOKEN_RIGHT_PAREN);
|
||||
|
||||
// TODO(bob): Block bodies.
|
||||
// Compile the then branch.
|
||||
emit(compiler, CODE_JUMP_IF);
|
||||
|
||||
// Emit a placeholder. We'll patch it when we know what to jump to.
|
||||
int ifJump = emit(compiler, 255);
|
||||
|
||||
expression(compiler);
|
||||
|
||||
// Jump over the else branch when the if branch is taken.
|
||||
emit(compiler, CODE_JUMP);
|
||||
|
||||
// Emit a placeholder. We'll patch it when we know what to jump to.
|
||||
int elseJump = emit(compiler, 255);
|
||||
|
||||
// Patch the jump.
|
||||
compiler->block->bytecode[ifJump] = compiler->numCodes - ifJump - 1;
|
||||
|
||||
// Compile the else branch if there is one.
|
||||
if (match(compiler, TOKEN_ELSE))
|
||||
{
|
||||
// TODO(bob): Block bodies.
|
||||
expression(compiler);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just default to null.
|
||||
emit(compiler, CODE_NULL);
|
||||
}
|
||||
|
||||
// Patch the jump over the else.
|
||||
compiler->block->bytecode[elseJump] = compiler->numCodes - elseJump - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
return parsePrecedence(compiler, PREC_LOWEST);
|
||||
}
|
||||
|
||||
@ -751,6 +798,8 @@ void nextToken(Parser* parser)
|
||||
case TOKEN_EQEQ:
|
||||
case TOKEN_BANGEQ:
|
||||
case TOKEN_CLASS:
|
||||
case TOKEN_ELSE:
|
||||
case TOKEN_IF:
|
||||
case TOKEN_META:
|
||||
case TOKEN_VAR:
|
||||
parser->skipNewlines = 1;
|
||||
@ -899,7 +948,9 @@ void readName(Parser* parser)
|
||||
TokenType type = TOKEN_NAME;
|
||||
|
||||
if (isKeyword(parser, "class")) type = TOKEN_CLASS;
|
||||
if (isKeyword(parser, "if")) type = TOKEN_IF;
|
||||
if (isKeyword(parser, "false")) type = TOKEN_FALSE;
|
||||
if (isKeyword(parser, "else")) type = TOKEN_ELSE;
|
||||
if (isKeyword(parser, "meta")) type = TOKEN_META;
|
||||
if (isKeyword(parser, "true")) type = TOKEN_TRUE;
|
||||
if (isKeyword(parser, "var")) type = TOKEN_VAR;
|
||||
|
||||
@ -227,6 +227,8 @@ void registerPrimitives(VM* vm)
|
||||
|
||||
vm->classClass = makeClass();
|
||||
|
||||
vm->nullClass = makeClass();
|
||||
|
||||
vm->numClass = makeClass();
|
||||
PRIMITIVE(vm->numClass, "abs", num_abs);
|
||||
PRIMITIVE(vm->numClass, "toString", num_toString)
|
||||
@ -257,4 +259,4 @@ void registerPrimitives(VM* vm)
|
||||
|
||||
// TODO(bob): Make this a distinct object type.
|
||||
vm->unsupported = (Value)makeInstance(unsupportedClass);
|
||||
}
|
||||
}
|
||||
|
||||
189
src/vm.c
189
src/vm.c
@ -81,6 +81,14 @@ ObjInstance* makeInstance(ObjClass* classObj)
|
||||
return instance;
|
||||
}
|
||||
|
||||
Value makeNull()
|
||||
{
|
||||
Obj* obj = malloc(sizeof(Obj));
|
||||
obj->type = OBJ_NULL;
|
||||
obj->flags = 0;
|
||||
return obj;
|
||||
}
|
||||
|
||||
ObjNum* makeNum(double number)
|
||||
{
|
||||
ObjNum* num = malloc(sizeof(ObjNum));
|
||||
@ -157,6 +165,147 @@ const char* getSymbolName(SymbolTable* symbols, int symbol)
|
||||
return symbols->names[symbol];
|
||||
}
|
||||
|
||||
// TODO(bob): For debugging. Move to separate module.
|
||||
/*
|
||||
void dumpCode(ObjBlock* block)
|
||||
{
|
||||
unsigned char* bytecode = block->bytecode;
|
||||
int done = 0;
|
||||
int i = 0;
|
||||
while (!done)
|
||||
{
|
||||
printf("%04d ", i);
|
||||
unsigned char code = bytecode[i++];
|
||||
|
||||
switch (code)
|
||||
{
|
||||
case CODE_CONSTANT:
|
||||
{
|
||||
int constant = bytecode[i++];
|
||||
printf("CONSTANT %d (", constant);
|
||||
printValue(block->constants[constant]);
|
||||
printf(")\n");
|
||||
printf("%04d (constant %d)\n", i - 1, constant);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_NULL:
|
||||
printf("NULL\n");
|
||||
break;
|
||||
|
||||
case CODE_FALSE:
|
||||
printf("FALSE\n");
|
||||
break;
|
||||
|
||||
case CODE_TRUE:
|
||||
printf("TRUE\n");
|
||||
break;
|
||||
|
||||
case CODE_CLASS:
|
||||
printf("CLASS\n");
|
||||
break;
|
||||
|
||||
case CODE_METHOD:
|
||||
{
|
||||
int symbol = bytecode[i++];
|
||||
int constant = bytecode[i++];
|
||||
printf("METHOD symbol %d constant %d\n", symbol, constant);
|
||||
printf("%04d (symbol %d)\n", i - 2, symbol);
|
||||
printf("%04d (constant %d)\n", i - 1, constant);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_LOAD_LOCAL:
|
||||
{
|
||||
int local = bytecode[i++];
|
||||
printf("LOAD_LOCAL %d\n", local);
|
||||
printf("%04d (local %d)\n", i - 1, local);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_STORE_LOCAL:
|
||||
{
|
||||
int local = bytecode[i++];
|
||||
printf("STORE_LOCAL %d\n", local);
|
||||
printf("%04d (local %d)\n", i - 1, local);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_LOAD_GLOBAL:
|
||||
{
|
||||
int global = bytecode[i++];
|
||||
printf("LOAD_GLOBAL %d\n", global);
|
||||
printf("%04d (global %d)\n", i - 1, global);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_STORE_GLOBAL:
|
||||
{
|
||||
int global = bytecode[i++];
|
||||
printf("STORE_GLOBAL %d\n", global);
|
||||
printf("%04d (global %d)\n", i - 1, global);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_DUP:
|
||||
printf("DUP\n");
|
||||
break;
|
||||
|
||||
case CODE_POP:
|
||||
printf("POP\n");
|
||||
break;
|
||||
|
||||
case CODE_CALL_0:
|
||||
case CODE_CALL_1:
|
||||
case CODE_CALL_2:
|
||||
case CODE_CALL_3:
|
||||
case CODE_CALL_4:
|
||||
case CODE_CALL_5:
|
||||
case CODE_CALL_6:
|
||||
case CODE_CALL_7:
|
||||
case CODE_CALL_8:
|
||||
case CODE_CALL_9:
|
||||
case CODE_CALL_10:
|
||||
{
|
||||
// Add one for the implicit receiver argument.
|
||||
int numArgs = bytecode[i - 1] - CODE_CALL_0;
|
||||
int symbol = bytecode[i++];
|
||||
printf("CALL_%d %d\n", numArgs, symbol);
|
||||
printf("%04d (symbol %d)\n", i - 1, symbol);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_JUMP:
|
||||
{
|
||||
int offset = bytecode[i++];
|
||||
printf("JUMP %d\n", offset);
|
||||
printf("%04d (offset %d)\n", i - 1, offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_JUMP_IF:
|
||||
{
|
||||
int offset = bytecode[i++];
|
||||
printf("JUMP_IF %d\n", offset);
|
||||
printf("%04d (offset %d)\n", i - 1, offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_END:
|
||||
{
|
||||
printf("CODE_END\n");
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
printf("[%d]\n", bytecode[i - 1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
Value interpret(VM* vm, ObjBlock* block)
|
||||
{
|
||||
Fiber fiber;
|
||||
@ -179,6 +328,10 @@ Value interpret(VM* vm, ObjBlock* block)
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_NULL:
|
||||
push(&fiber, makeNull());
|
||||
break;
|
||||
|
||||
case CODE_FALSE:
|
||||
push(&fiber, makeBool(0));
|
||||
break;
|
||||
@ -284,6 +437,10 @@ Value interpret(VM* vm, ObjBlock* block)
|
||||
classObj = vm->boolClass;
|
||||
break;
|
||||
|
||||
case OBJ_NULL:
|
||||
classObj = vm->nullClass;
|
||||
break;
|
||||
|
||||
case OBJ_NUM:
|
||||
classObj = vm->numClass;
|
||||
break;
|
||||
@ -333,6 +490,26 @@ Value interpret(VM* vm, ObjBlock* block)
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_JUMP:
|
||||
{
|
||||
int offset = frame->block->bytecode[frame->ip++];
|
||||
frame->ip += offset;
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_JUMP_IF:
|
||||
{
|
||||
int offset = frame->block->bytecode[frame->ip++];
|
||||
Value condition = pop(&fiber);
|
||||
|
||||
// False is the only falsey value.
|
||||
if (condition->type == OBJ_FALSE)
|
||||
{
|
||||
frame->ip += offset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_END:
|
||||
{
|
||||
Value result = pop(&fiber);
|
||||
@ -379,18 +556,22 @@ void printValue(Value value)
|
||||
printf("[block]");
|
||||
break;
|
||||
|
||||
case OBJ_FALSE:
|
||||
printf("false");
|
||||
break;
|
||||
|
||||
case OBJ_CLASS:
|
||||
printf("[class]");
|
||||
break;
|
||||
|
||||
case OBJ_FALSE:
|
||||
printf("false");
|
||||
break;
|
||||
|
||||
case OBJ_INSTANCE:
|
||||
printf("[instance]");
|
||||
break;
|
||||
|
||||
case OBJ_NULL:
|
||||
printf("null");
|
||||
break;
|
||||
|
||||
case OBJ_NUM:
|
||||
printf("%g", ((ObjNum*)value)->value);
|
||||
break;
|
||||
|
||||
16
src/vm.h
16
src/vm.h
@ -23,6 +23,7 @@ typedef enum {
|
||||
OBJ_CLASS,
|
||||
OBJ_FALSE,
|
||||
OBJ_INSTANCE,
|
||||
OBJ_NULL,
|
||||
OBJ_NUM,
|
||||
OBJ_STRING,
|
||||
OBJ_TRUE
|
||||
@ -105,6 +106,9 @@ typedef enum
|
||||
// Load the constant at index [arg].
|
||||
CODE_CONSTANT,
|
||||
|
||||
// Push null onto the stack.
|
||||
CODE_NULL,
|
||||
|
||||
// Push false onto the stack.
|
||||
CODE_FALSE,
|
||||
|
||||
@ -149,7 +153,13 @@ typedef enum
|
||||
CODE_CALL_8,
|
||||
CODE_CALL_9,
|
||||
CODE_CALL_10,
|
||||
|
||||
|
||||
// Jump the instruction pointer [arg1] forward.
|
||||
CODE_JUMP,
|
||||
|
||||
// Pop and if not truthy then jump the instruction pointer [arg1] forward.
|
||||
CODE_JUMP_IF,
|
||||
|
||||
// The current block is done and should be exited.
|
||||
CODE_END
|
||||
} Code;
|
||||
@ -168,6 +178,7 @@ struct sVM
|
||||
ObjClass* blockClass;
|
||||
ObjClass* boolClass;
|
||||
ObjClass* classClass;
|
||||
ObjClass* nullClass;
|
||||
ObjClass* numClass;
|
||||
ObjClass* stringClass;
|
||||
|
||||
@ -218,6 +229,9 @@ ObjClass* makeClass();
|
||||
// Creates a new instance of the given [classObj].
|
||||
ObjInstance* makeInstance(ObjClass* classObj);
|
||||
|
||||
// Creates a new null object.
|
||||
Value makeNull();
|
||||
|
||||
// Creates a new number object.
|
||||
ObjNum* makeNum(double number);
|
||||
|
||||
|
||||
31
test/block_syntax.wren
Normal file
31
test/block_syntax.wren
Normal file
@ -0,0 +1,31 @@
|
||||
// Single line.
|
||||
{ io.write("ok") }.call // expect: ok
|
||||
|
||||
// No trailing newline.
|
||||
{
|
||||
io.write("ok") }.call // expect: ok
|
||||
|
||||
// Trailing newline.
|
||||
{
|
||||
io.write("ok") // expect: ok
|
||||
}.call
|
||||
|
||||
// Multiple expressions.
|
||||
{
|
||||
io.write("1") // expect: 1
|
||||
io.write("2") // expect: 2
|
||||
}.call
|
||||
|
||||
// Extra newlines.
|
||||
{
|
||||
|
||||
|
||||
io.write("1") // expect: 1
|
||||
|
||||
|
||||
io.write("2") // expect: 2
|
||||
|
||||
|
||||
}.call
|
||||
|
||||
// TODO(bob): Arguments.
|
||||
16
test/if_syntax.wren
Normal file
16
test/if_syntax.wren
Normal file
@ -0,0 +1,16 @@
|
||||
// Evaluate the 'then' expression if the condition is true.
|
||||
if (true) io.write("good") // expect: good
|
||||
if (false) io.write("bad")
|
||||
|
||||
// Evaluate the 'else' expression if the condition is false.
|
||||
if (true) io.write("good") else io.write("bad") // expect: good
|
||||
if (false) io.write("bad") else io.write("good") // expect: good
|
||||
|
||||
// Return the 'then' expression if the condition is true.
|
||||
io.write(if (true) "good") // expect: good
|
||||
|
||||
// Return null if the condition is false and there is no else.
|
||||
io.write(if (false) "bad") // expect: null
|
||||
|
||||
// Return the 'else' expression if the condition is false.
|
||||
io.write(if (false) "bad" else "good") // expect: good
|
||||
Reference in New Issue
Block a user