forked from Mirror/wren
Add IO class.
With io.write(), can start writing tests now.
This commit is contained in:
@ -6,3 +6,4 @@ class Foo { // line comment
|
||||
var a = Foo.new
|
||||
a.bar
|
||||
"something".contains("meth")
|
||||
io.write("hey there")
|
||||
|
||||
187
runtests
Executable file
187
runtests
Executable file
@ -0,0 +1,187 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
from collections import defaultdict
|
||||
from os import listdir
|
||||
from os.path import abspath, dirname, isdir, isfile, join, realpath, relpath, splitext
|
||||
import re
|
||||
from subprocess import Popen, PIPE
|
||||
import sys
|
||||
|
||||
# Runs the tests.
|
||||
WREN_DIR = dirname(realpath(__file__))
|
||||
TEST_DIR = join(WREN_DIR, 'test')
|
||||
|
||||
if sys.platform == 'win32':
|
||||
WREN_APP = join(WREN_DIR, 'Debug', 'wren.exe')
|
||||
if not isfile(WREN_DIR):
|
||||
WREN_APP = join(WREN_DIR, 'Release', 'wren.exe')
|
||||
if not isfile(WREN_APP):
|
||||
sys.exit('Cannot find wren.exe!')
|
||||
elif sys.platform.startswith('linux'):
|
||||
WREN_APP = join(WREN_DIR, '1', 'out', 'Debug', 'wren')
|
||||
if not isfile(WREN_APP):
|
||||
WREN_APP = join(WREN_DIR, '1', 'out', 'Release', 'wren')
|
||||
if not isfile(WREN_APP):
|
||||
sys.exit('Cannot find wren!')
|
||||
elif sys.platform.startswith('darwin'):
|
||||
WREN_APP = join(WREN_DIR, 'build', 'Debug', 'wren')
|
||||
if not isfile(WREN_APP):
|
||||
WREN_APP = join(WREN_DIR, 'build', 'Release', 'wren')
|
||||
if not isfile(WREN_APP):
|
||||
sys.exit('Cannot find wren!')
|
||||
else:
|
||||
sys.exit('System not supported!')
|
||||
|
||||
EXPECT_PATTERN = re.compile(r'// expect: (.*)')
|
||||
SKIP_PATTERN = re.compile(r'// skip: (.*)')
|
||||
NONTEST_PATTERN = re.compile(r'// nontest')
|
||||
|
||||
if sys.platform == 'win32':
|
||||
class color:
|
||||
GREEN = ''
|
||||
RED = ''
|
||||
DEFAULT = ''
|
||||
PINK = ''
|
||||
YELLOW = ''
|
||||
else:
|
||||
class color:
|
||||
GREEN = '\033[32m'
|
||||
RED = '\033[31m'
|
||||
DEFAULT = '\033[0m'
|
||||
PINK = '\033[91m'
|
||||
YELLOW = '\033[33m'
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
skipped = defaultdict(int)
|
||||
num_skipped = 0;
|
||||
|
||||
|
||||
def walk(dir, callback):
|
||||
""" Walks [dir], and executes [callback] on each file. """
|
||||
dir = abspath(dir)
|
||||
for file in [file for file in listdir(dir) if not file in [".",".."]]:
|
||||
nfile = join(dir, file)
|
||||
if isdir(nfile):
|
||||
walk(nfile, callback)
|
||||
else:
|
||||
callback(nfile)
|
||||
|
||||
|
||||
def print_line(line=None):
|
||||
# Erase the line.
|
||||
print '\033[2K',
|
||||
# Move the cursor to the beginning.
|
||||
print '\r',
|
||||
if line:
|
||||
print line,
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def run_test(path):
|
||||
global passed
|
||||
global failed
|
||||
global skipped
|
||||
global num_skipped
|
||||
|
||||
if (splitext(path)[1] != '.wren'):
|
||||
return
|
||||
|
||||
# Check if we are just running a subset of the tests.
|
||||
if len(sys.argv) == 2:
|
||||
this_test = relpath(path, join(WREN_DIR, 'test'))
|
||||
if not this_test.startswith(sys.argv[1]):
|
||||
return
|
||||
|
||||
# Make a nice short path relative to the working directory.
|
||||
path = relpath(path)
|
||||
|
||||
# Read the test and parse out the expectations.
|
||||
expect_output = []
|
||||
expect_error = []
|
||||
expect_return = 0
|
||||
|
||||
print_line('Passed: ' + color.GREEN + str(passed) + color.DEFAULT +
|
||||
' Failed: ' + color.RED + str(failed) + color.DEFAULT +
|
||||
' Skipped: ' + color.YELLOW + str(num_skipped) + color.DEFAULT)
|
||||
|
||||
line_num = 1
|
||||
with open(path, 'r') as file:
|
||||
for line in file:
|
||||
match = EXPECT_PATTERN.search(line)
|
||||
if match:
|
||||
expect_output.append((match.group(1), line_num))
|
||||
|
||||
match = SKIP_PATTERN.search(line)
|
||||
if match:
|
||||
num_skipped += 1
|
||||
skipped[match.group(1)] += 1
|
||||
return
|
||||
|
||||
match = NONTEST_PATTERN.search(line)
|
||||
if match:
|
||||
# Not a test file at all, so ignore it.
|
||||
return
|
||||
|
||||
line_num += 1
|
||||
|
||||
# Invoke wren and run the test.
|
||||
proc = Popen([WREN_APP, path], stdout=PIPE, stderr=PIPE)
|
||||
(out, err) = proc.communicate()
|
||||
(out, err) = out.replace('\r\n', '\n'), err.replace('\r\n', '\n')
|
||||
|
||||
fails = []
|
||||
|
||||
# Validate the exit code.
|
||||
if proc.returncode != 0:
|
||||
fails.append('Expected return code 0 and got {0}.'.
|
||||
format(proc.returncode))
|
||||
else:
|
||||
# Validate the output.
|
||||
expect_index = 0
|
||||
|
||||
# Remove the trailing last empty line.
|
||||
out_lines = out.split('\n')
|
||||
if out_lines[-1] == '':
|
||||
del out_lines[-1]
|
||||
|
||||
for line in out_lines:
|
||||
if expect_index >= len(expect_output):
|
||||
fails.append('Got output "{0}" when none was expected.'.
|
||||
format(line))
|
||||
elif expect_output[expect_index][0] != line:
|
||||
fails.append('Expected output "{0}" on line {1} and got "{2}".'.
|
||||
format(expect_output[expect_index][0],
|
||||
expect_output[expect_index][1], line))
|
||||
expect_index += 1
|
||||
|
||||
while expect_index < len(expect_output):
|
||||
fails.append('Missing expected output "{0}" on line {1}.'.
|
||||
format(expect_output[expect_index][0],
|
||||
expect_output[expect_index][1]))
|
||||
expect_index += 1
|
||||
|
||||
# Display the results.
|
||||
if len(fails) == 0:
|
||||
passed += 1
|
||||
print color.GREEN + 'PASS' + color.DEFAULT + ': ' + path
|
||||
else:
|
||||
failed += 1
|
||||
print_line(color.RED + 'FAIL' + color.DEFAULT + ': ' + path)
|
||||
print
|
||||
for fail in fails:
|
||||
print ' ', color.PINK + fail + color.DEFAULT
|
||||
print
|
||||
|
||||
walk(TEST_DIR, run_test)
|
||||
|
||||
print_line()
|
||||
if failed == 0:
|
||||
print 'All ' + color.GREEN + str(passed) + color.DEFAULT + ' tests passed.'
|
||||
else:
|
||||
print (color.GREEN + str(passed) + color.DEFAULT + ' tests passed. ' +
|
||||
color.RED + str(failed) + color.DEFAULT + ' tests failed.')
|
||||
|
||||
for key in sorted(skipped.keys()):
|
||||
print ('Skipped ' + color.YELLOW + str(skipped[key]) + color.DEFAULT +
|
||||
' tests: ' + key)
|
||||
@ -268,8 +268,8 @@ int defineName(Compiler* compiler)
|
||||
}
|
||||
|
||||
int symbol = addSymbol(symbols,
|
||||
compiler->parser->source + compiler->parser->previous.start,
|
||||
compiler->parser->previous.end - compiler->parser->previous.start);
|
||||
compiler->parser->source + compiler->parser->previous.start,
|
||||
compiler->parser->previous.end - compiler->parser->previous.start);
|
||||
|
||||
if (symbol == -1)
|
||||
{
|
||||
|
||||
@ -30,9 +30,7 @@ int main(int argc, const char * argv[])
|
||||
|
||||
if (block)
|
||||
{
|
||||
Value value = interpret(vm, block);
|
||||
printValue(value);
|
||||
printf("\n");
|
||||
interpret(vm, block);
|
||||
}
|
||||
|
||||
freeVM(vm);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@ -6,14 +7,21 @@
|
||||
#define PRIMITIVE(cls, name, prim) \
|
||||
{ \
|
||||
int symbol = ensureSymbol(&vm->symbols, name, strlen(name)); \
|
||||
vm->cls##Class->methods[symbol].type = METHOD_PRIMITIVE; \
|
||||
vm->cls##Class->methods[symbol].primitive = primitive_##cls##_##prim; \
|
||||
cls->methods[symbol].type = METHOD_PRIMITIVE; \
|
||||
cls->methods[symbol].primitive = primitive_##prim; \
|
||||
}
|
||||
|
||||
#define DEF_PRIMITIVE(cls, prim) \
|
||||
static Value primitive_##cls##_##prim(Value* args, int numArgs)
|
||||
#define DEF_PRIMITIVE(prim) \
|
||||
static Value primitive_##prim(Value* args, int numArgs)
|
||||
|
||||
DEF_PRIMITIVE(num, abs)
|
||||
#define GLOBAL(cls, name) \
|
||||
{ \
|
||||
ObjInstance* obj = makeInstance(cls); \
|
||||
int symbol = addSymbol(&vm->globalSymbols, name, strlen(name)); \
|
||||
vm->globals[symbol] = (Value)obj; \
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(num_abs)
|
||||
{
|
||||
double value = ((ObjNum*)args[0])->value;
|
||||
if (value < 0) value = -value;
|
||||
@ -21,7 +29,7 @@ DEF_PRIMITIVE(num, abs)
|
||||
return (Value)makeNum(value);
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(string, contains)
|
||||
DEF_PRIMITIVE(string_contains)
|
||||
{
|
||||
const char* string = ((ObjString*)args[0])->value;
|
||||
// TODO(bob): Check type of arg first!
|
||||
@ -31,16 +39,27 @@ DEF_PRIMITIVE(string, contains)
|
||||
return (Value)makeNum(strstr(string, search) != NULL);
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(string, count)
|
||||
DEF_PRIMITIVE(string_count)
|
||||
{
|
||||
double count = strlen(((ObjString*)args[0])->value);
|
||||
|
||||
return (Value)makeNum(count);
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(io_write)
|
||||
{
|
||||
printValue(args[1]);
|
||||
printf("\n");
|
||||
return args[1];
|
||||
}
|
||||
|
||||
void registerPrimitives(VM* vm)
|
||||
{
|
||||
PRIMITIVE(num, "abs", abs);
|
||||
PRIMITIVE(string, "contains ", contains);
|
||||
PRIMITIVE(string, "count", count);
|
||||
PRIMITIVE(vm->numClass, "abs", num_abs);
|
||||
PRIMITIVE(vm->stringClass, "contains ", string_contains);
|
||||
PRIMITIVE(vm->stringClass, "count", string_count);
|
||||
|
||||
ObjClass* ioClass = makeClass();
|
||||
PRIMITIVE(ioClass, "write ", io_write);
|
||||
GLOBAL(ioClass, "io");
|
||||
}
|
||||
32
src/vm.c
32
src/vm.c
@ -200,9 +200,6 @@ Value interpret(VM* vm, ObjBlock* block)
|
||||
int constant = frame->block->bytecode[frame->ip++];
|
||||
Value value = frame->block->constants[constant];
|
||||
fiber.stack[fiber.stackSize++] = value;
|
||||
printf("load constant ");
|
||||
printValue(value);
|
||||
printf(" to %d\n", fiber.stackSize - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -213,13 +210,11 @@ Value interpret(VM* vm, ObjBlock* block)
|
||||
// Define a "new" method on the metaclass.
|
||||
// TODO(bob): Can this be inherited?
|
||||
int newSymbol = ensureSymbol(&vm->symbols, "new", strlen("new"));
|
||||
printf("define new %d\n", newSymbol);
|
||||
classObj->metaclass->methods[newSymbol].type = METHOD_PRIMITIVE;
|
||||
classObj->metaclass->methods[newSymbol].primitive =
|
||||
primitive_metaclass_new;
|
||||
|
||||
push(&fiber, (Value)classObj);
|
||||
printf("push class at %d\n", fiber.stackSize - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -232,10 +227,6 @@ Value interpret(VM* vm, ObjBlock* block)
|
||||
ObjBlock* body = (ObjBlock*)frame->block->constants[constant];
|
||||
classObj->methods[symbol].type = METHOD_BLOCK;
|
||||
classObj->methods[symbol].block = body;
|
||||
|
||||
printf("define method %d using constant %d on ", symbol, constant);
|
||||
printValue((Value)classObj);
|
||||
printf("\n");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -243,14 +234,12 @@ Value interpret(VM* vm, ObjBlock* block)
|
||||
{
|
||||
int local = frame->block->bytecode[frame->ip++];
|
||||
push(&fiber, fiber.stack[frame->locals + local]);
|
||||
printf("load local %d to %d\n", local, fiber.stackSize - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_STORE_LOCAL:
|
||||
{
|
||||
int local = frame->block->bytecode[frame->ip++];
|
||||
printf("store local %d from %d\n", local, fiber.stackSize - 1);
|
||||
fiber.stack[frame->locals + local] = fiber.stack[fiber.stackSize - 1];
|
||||
break;
|
||||
}
|
||||
@ -259,25 +248,21 @@ Value interpret(VM* vm, ObjBlock* block)
|
||||
{
|
||||
int global = frame->block->bytecode[frame->ip++];
|
||||
push(&fiber, vm->globals[global]);
|
||||
printf("load global %d to %d\n", global, fiber.stackSize - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_STORE_GLOBAL:
|
||||
{
|
||||
int global = frame->block->bytecode[frame->ip++];
|
||||
printf("store global %d from %d\n", global, fiber.stackSize - 1);
|
||||
vm->globals[global] = fiber.stack[fiber.stackSize - 1];
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_DUP:
|
||||
push(&fiber, fiber.stack[fiber.stackSize - 1]);
|
||||
printf("dup %d\n", fiber.stackSize - 1);
|
||||
break;
|
||||
|
||||
case CODE_POP:
|
||||
printf("pop %d\n", fiber.stackSize - 1);
|
||||
pop(&fiber);
|
||||
break;
|
||||
|
||||
@ -323,10 +308,6 @@ Value interpret(VM* vm, ObjBlock* block)
|
||||
break;
|
||||
}
|
||||
|
||||
printf("call %d on ", symbol);
|
||||
printValue(receiver);
|
||||
printf("\n");
|
||||
|
||||
Method* method = &classObj->methods[symbol];
|
||||
switch (method->type)
|
||||
{
|
||||
@ -363,19 +344,10 @@ Value interpret(VM* vm, ObjBlock* block)
|
||||
fiber.numFrames--;
|
||||
|
||||
// If we are returning from the top-level block, just return the value.
|
||||
if (fiber.numFrames == 0)
|
||||
{
|
||||
printf("done with result ");
|
||||
printValue(result);
|
||||
printf("\n");
|
||||
return result;
|
||||
}
|
||||
if (fiber.numFrames == 0) return result;
|
||||
|
||||
// Store the result of the block in the first slot, which is where the
|
||||
// caller expects it.
|
||||
printf("return and store result ");
|
||||
printValue(result);
|
||||
printf(" in %d\n", frame->locals);
|
||||
fiber.stack[frame->locals] = result;
|
||||
|
||||
// Discard the stack slots for the locals.
|
||||
@ -391,7 +363,7 @@ void printValue(Value value)
|
||||
switch (value->type)
|
||||
{
|
||||
case OBJ_NUM:
|
||||
printf("%f", ((ObjNum*)value)->value);
|
||||
printf("%g", ((ObjNum*)value)->value);
|
||||
break;
|
||||
|
||||
case OBJ_STRING:
|
||||
|
||||
6
test/number_literals.wren
Normal file
6
test/number_literals.wren
Normal file
@ -0,0 +1,6 @@
|
||||
io.write(123) // expect: 123
|
||||
io.write(987654) // expect: 987654
|
||||
io.write(0) // expect: 0
|
||||
io.write(-0) // expect: 0
|
||||
|
||||
// TODO(bob): Floating point numbers.
|
||||
Reference in New Issue
Block a user