mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-11 06:08:41 +01:00
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
|
var a = Foo.new
|
||||||
a.bar
|
a.bar
|
||||||
"something".contains("meth")
|
"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,
|
int symbol = addSymbol(symbols,
|
||||||
compiler->parser->source + compiler->parser->previous.start,
|
compiler->parser->source + compiler->parser->previous.start,
|
||||||
compiler->parser->previous.end - compiler->parser->previous.start);
|
compiler->parser->previous.end - compiler->parser->previous.start);
|
||||||
|
|
||||||
if (symbol == -1)
|
if (symbol == -1)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -30,9 +30,7 @@ int main(int argc, const char * argv[])
|
|||||||
|
|
||||||
if (block)
|
if (block)
|
||||||
{
|
{
|
||||||
Value value = interpret(vm, block);
|
interpret(vm, block);
|
||||||
printValue(value);
|
|
||||||
printf("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
freeVM(vm);
|
freeVM(vm);
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@ -6,14 +7,21 @@
|
|||||||
#define PRIMITIVE(cls, name, prim) \
|
#define PRIMITIVE(cls, name, prim) \
|
||||||
{ \
|
{ \
|
||||||
int symbol = ensureSymbol(&vm->symbols, name, strlen(name)); \
|
int symbol = ensureSymbol(&vm->symbols, name, strlen(name)); \
|
||||||
vm->cls##Class->methods[symbol].type = METHOD_PRIMITIVE; \
|
cls->methods[symbol].type = METHOD_PRIMITIVE; \
|
||||||
vm->cls##Class->methods[symbol].primitive = primitive_##cls##_##prim; \
|
cls->methods[symbol].primitive = primitive_##prim; \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define DEF_PRIMITIVE(cls, prim) \
|
#define DEF_PRIMITIVE(prim) \
|
||||||
static Value primitive_##cls##_##prim(Value* args, int numArgs)
|
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;
|
double value = ((ObjNum*)args[0])->value;
|
||||||
if (value < 0) value = -value;
|
if (value < 0) value = -value;
|
||||||
@ -21,7 +29,7 @@ DEF_PRIMITIVE(num, abs)
|
|||||||
return (Value)makeNum(value);
|
return (Value)makeNum(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEF_PRIMITIVE(string, contains)
|
DEF_PRIMITIVE(string_contains)
|
||||||
{
|
{
|
||||||
const char* string = ((ObjString*)args[0])->value;
|
const char* string = ((ObjString*)args[0])->value;
|
||||||
// TODO(bob): Check type of arg first!
|
// TODO(bob): Check type of arg first!
|
||||||
@ -31,16 +39,27 @@ DEF_PRIMITIVE(string, contains)
|
|||||||
return (Value)makeNum(strstr(string, search) != NULL);
|
return (Value)makeNum(strstr(string, search) != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEF_PRIMITIVE(string, count)
|
DEF_PRIMITIVE(string_count)
|
||||||
{
|
{
|
||||||
double count = strlen(((ObjString*)args[0])->value);
|
double count = strlen(((ObjString*)args[0])->value);
|
||||||
|
|
||||||
return (Value)makeNum(count);
|
return (Value)makeNum(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEF_PRIMITIVE(io_write)
|
||||||
|
{
|
||||||
|
printValue(args[1]);
|
||||||
|
printf("\n");
|
||||||
|
return args[1];
|
||||||
|
}
|
||||||
|
|
||||||
void registerPrimitives(VM* vm)
|
void registerPrimitives(VM* vm)
|
||||||
{
|
{
|
||||||
PRIMITIVE(num, "abs", abs);
|
PRIMITIVE(vm->numClass, "abs", num_abs);
|
||||||
PRIMITIVE(string, "contains ", contains);
|
PRIMITIVE(vm->stringClass, "contains ", string_contains);
|
||||||
PRIMITIVE(string, "count", count);
|
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++];
|
int constant = frame->block->bytecode[frame->ip++];
|
||||||
Value value = frame->block->constants[constant];
|
Value value = frame->block->constants[constant];
|
||||||
fiber.stack[fiber.stackSize++] = value;
|
fiber.stack[fiber.stackSize++] = value;
|
||||||
printf("load constant ");
|
|
||||||
printValue(value);
|
|
||||||
printf(" to %d\n", fiber.stackSize - 1);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,13 +210,11 @@ Value interpret(VM* vm, ObjBlock* block)
|
|||||||
// Define a "new" method on the metaclass.
|
// Define a "new" method on the metaclass.
|
||||||
// TODO(bob): Can this be inherited?
|
// TODO(bob): Can this be inherited?
|
||||||
int newSymbol = ensureSymbol(&vm->symbols, "new", strlen("new"));
|
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].type = METHOD_PRIMITIVE;
|
||||||
classObj->metaclass->methods[newSymbol].primitive =
|
classObj->metaclass->methods[newSymbol].primitive =
|
||||||
primitive_metaclass_new;
|
primitive_metaclass_new;
|
||||||
|
|
||||||
push(&fiber, (Value)classObj);
|
push(&fiber, (Value)classObj);
|
||||||
printf("push class at %d\n", fiber.stackSize - 1);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,10 +227,6 @@ Value interpret(VM* vm, ObjBlock* block)
|
|||||||
ObjBlock* body = (ObjBlock*)frame->block->constants[constant];
|
ObjBlock* body = (ObjBlock*)frame->block->constants[constant];
|
||||||
classObj->methods[symbol].type = METHOD_BLOCK;
|
classObj->methods[symbol].type = METHOD_BLOCK;
|
||||||
classObj->methods[symbol].block = body;
|
classObj->methods[symbol].block = body;
|
||||||
|
|
||||||
printf("define method %d using constant %d on ", symbol, constant);
|
|
||||||
printValue((Value)classObj);
|
|
||||||
printf("\n");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,14 +234,12 @@ Value interpret(VM* vm, ObjBlock* block)
|
|||||||
{
|
{
|
||||||
int local = frame->block->bytecode[frame->ip++];
|
int local = frame->block->bytecode[frame->ip++];
|
||||||
push(&fiber, fiber.stack[frame->locals + local]);
|
push(&fiber, fiber.stack[frame->locals + local]);
|
||||||
printf("load local %d to %d\n", local, fiber.stackSize - 1);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case CODE_STORE_LOCAL:
|
case CODE_STORE_LOCAL:
|
||||||
{
|
{
|
||||||
int local = frame->block->bytecode[frame->ip++];
|
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];
|
fiber.stack[frame->locals + local] = fiber.stack[fiber.stackSize - 1];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -259,25 +248,21 @@ Value interpret(VM* vm, ObjBlock* block)
|
|||||||
{
|
{
|
||||||
int global = frame->block->bytecode[frame->ip++];
|
int global = frame->block->bytecode[frame->ip++];
|
||||||
push(&fiber, vm->globals[global]);
|
push(&fiber, vm->globals[global]);
|
||||||
printf("load global %d to %d\n", global, fiber.stackSize - 1);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case CODE_STORE_GLOBAL:
|
case CODE_STORE_GLOBAL:
|
||||||
{
|
{
|
||||||
int global = frame->block->bytecode[frame->ip++];
|
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];
|
vm->globals[global] = fiber.stack[fiber.stackSize - 1];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case CODE_DUP:
|
case CODE_DUP:
|
||||||
push(&fiber, fiber.stack[fiber.stackSize - 1]);
|
push(&fiber, fiber.stack[fiber.stackSize - 1]);
|
||||||
printf("dup %d\n", fiber.stackSize - 1);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CODE_POP:
|
case CODE_POP:
|
||||||
printf("pop %d\n", fiber.stackSize - 1);
|
|
||||||
pop(&fiber);
|
pop(&fiber);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -323,10 +308,6 @@ Value interpret(VM* vm, ObjBlock* block)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("call %d on ", symbol);
|
|
||||||
printValue(receiver);
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
Method* method = &classObj->methods[symbol];
|
Method* method = &classObj->methods[symbol];
|
||||||
switch (method->type)
|
switch (method->type)
|
||||||
{
|
{
|
||||||
@ -363,19 +344,10 @@ Value interpret(VM* vm, ObjBlock* block)
|
|||||||
fiber.numFrames--;
|
fiber.numFrames--;
|
||||||
|
|
||||||
// If we are returning from the top-level block, just return the value.
|
// If we are returning from the top-level block, just return the value.
|
||||||
if (fiber.numFrames == 0)
|
if (fiber.numFrames == 0) return result;
|
||||||
{
|
|
||||||
printf("done with result ");
|
|
||||||
printValue(result);
|
|
||||||
printf("\n");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the result of the block in the first slot, which is where the
|
// Store the result of the block in the first slot, which is where the
|
||||||
// caller expects it.
|
// caller expects it.
|
||||||
printf("return and store result ");
|
|
||||||
printValue(result);
|
|
||||||
printf(" in %d\n", frame->locals);
|
|
||||||
fiber.stack[frame->locals] = result;
|
fiber.stack[frame->locals] = result;
|
||||||
|
|
||||||
// Discard the stack slots for the locals.
|
// Discard the stack slots for the locals.
|
||||||
@ -391,7 +363,7 @@ void printValue(Value value)
|
|||||||
switch (value->type)
|
switch (value->type)
|
||||||
{
|
{
|
||||||
case OBJ_NUM:
|
case OBJ_NUM:
|
||||||
printf("%f", ((ObjNum*)value)->value);
|
printf("%g", ((ObjNum*)value)->value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OBJ_STRING:
|
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