9 Commits

Author SHA1 Message Date
37800d441c Merge branch 'master' into unify-modules-and-classes
# Conflicts:
#	src/module/io.wren
#	src/module/io.wren.inc
#	test/api/call.wren
#	test/api/returns.wren
2016-01-01 11:49:00 -08:00
d22c5ec737 Merge branch 'master' into unify-modules-and-classes 2015-12-22 16:04:09 -08:00
c472a61bff Inherit methods from enclosing classes.
Like NewtonScript, the inner classes superclasses take precedence, but
if not found there then the enclosing classes are searched.

This code is still a bit hacky in some corners, but it's a step in the
right direction.
2015-12-21 17:05:46 -08:00
8ff11a3c2c Nested classes.
A class definition can appear directly within the body of a class. It
works similar to a class variable where the class object is stored in a
field and a getter is defined on the enclosing class to return it.

Unlike class variables, nested classes do not expose setters.
2015-12-21 15:29:08 -08:00
8ed0cde91c Allow "var" in a class body for defining "properties".
A property is a field with an implicit getter, setter, and optional
class body initializer.

It's handy for defining publicly visible state in a class. When modules
are classes, this is needed for "top level" variables.

Right now, a class var gets both a getter and setter. It would be nice
to also have something like "val" for properties that are publicly
visible but not settable.

Also, still need to support "static var" for metaclass properties.
2015-12-21 08:04:39 -08:00
969ee0adc4 Allow code inside class bodies.
Every statement that isn't a method definition gets compiled into a
special "<body>" method in the class. Every constructor in the class
implicitly calls that before executing.

Since it's an instance method, it executes in the context of the class:
it can write fields, access "this", etc.
2015-12-20 08:08:33 -08:00
eff4485a56 Clean up definition syntax:
- Don't use "def" on constructors.
- Put "foreign" and "static" before "def".
2015-12-18 06:59:49 -08:00
859a51e22d Require "def" before method definitions. 2015-12-18 06:48:54 -08:00
7f4c5f021d Make "def" a reserved word. 2015-12-17 20:40:22 -08:00
156 changed files with 2131 additions and 1671 deletions

View File

@ -1,4 +1,4 @@
class Cthulu {
construct new() {}
message { "Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn" }
def message { "Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn" }
}

View File

@ -2,7 +2,7 @@ import "cthulu" for Cthulu
class Lovecraft {
construct new() {}
say() { Cthulu.new().message }
def say() { Cthulu.new().message }
}
System.print(Lovecraft.new().say())

View File

@ -32,7 +32,7 @@ class SyntaxExample {
}
// Method without arguments
variables {
def variables {
// Valid local variable names.
var hi
var camelCase
@ -42,54 +42,54 @@ class SyntaxExample {
}
// Method with empty argument list
fields() {
def fields() {
// Fields
_under_score = 1
_field = 2
}
// Static method with single argument
static fields(a) {
static def fields(a) {
// Static field
__a = a
}
// Setter
field=(value) { _field = value }
def field=(value) { _field = value }
// Method with arguments
print(a, b) { System.print(a + b) }
def print(a, b) { System.print(a + b) }
// Operators
+(other) { "infix + %(other)" }
-(other) { "infix - %(other)" }
*(other) { "infix * %(other)" }
/(other) { "infix / %(other)" }
%(other) { "infix \% %(other)" }
<(other) { "infix < %(other)" }
>(other) { "infix > %(other)" }
<=(other) { "infix <= %(other)" }
>=(other) { "infix >= %(other)" }
==(other) { "infix == %(other)" }
!=(other) { "infix != %(other)" }
&(other) { "infix & %(other)" }
|(other) { "infix | %(other)" }
def +(other) { "infix + %(other)" }
def -(other) { "infix - %(other)" }
def *(other) { "infix * %(other)" }
def /(other) { "infix / %(other)" }
def %(other) { "infix \% %(other)" }
def <(other) { "infix < %(other)" }
def >(other) { "infix > %(other)" }
def <=(other) { "infix <= %(other)" }
def >=(other) { "infix >= %(other)" }
def ==(other) { "infix == %(other)" }
def !=(other) { "infix != %(other)" }
def &(other) { "infix & %(other)" }
def |(other) { "infix | %(other)" }
! { "prefix !" }
~ { "prefix ~" }
- { "prefix -" }
def ! { "prefix !" }
def ~ { "prefix ~" }
def - { "prefix -" }
}
// `class`, `is`
class ReservedWords is SyntaxExample {
reserved {
def reserved {
// `super`, `true`, `false`
super(true, false)
// `this`
this.foo
}
foo {
def foo {
// `var`
var n = 27
// `while`, `if`, `else`
@ -106,7 +106,7 @@ class ReservedWords is SyntaxExample {
return null
}
imports {
def imports {
// `import`
import "hello"
// `import`, `for`
@ -120,9 +120,9 @@ class ReservedWords is SyntaxExample {
}
class Literals is SyntaxExample {
booleans { true || false }
def booleans { true || false }
numbers {
def numbers {
0
1234
-5678
@ -133,7 +133,7 @@ class Literals is SyntaxExample {
0x1234567890ABCDEF
}
strings {
def strings {
"hi there"
// Escapes:
"\0" // The NUL byte: 0.
@ -152,20 +152,20 @@ class Literals is SyntaxExample {
System.print("\x48\x69\x2e") // "Hi."
}
ranges {
def ranges {
3..8 // inclusive
4...6 // half-inclusive
}
nothing { null }
def nothing { null }
lists {
def lists {
var list = [1, "banana", true]
list[0] = 5
list[1..2]
}
maps {
def maps {
var stringMap = {
"George": "Harrison",
"John": "Lennon",

View File

@ -1,18 +1,18 @@
import "scheduler" for Scheduler
class Directory {
static list(path) {
static def list(path) {
if (!(path is String)) Fiber.abort("Path must be a string.")
list_(path, Fiber.current)
return Scheduler.runNextScheduled_()
}
foreign static list_(path, fiber)
foreign static def list_(path, fiber)
}
foreign class File {
static open(path) {
static def open(path) {
if (!(path is String)) Fiber.abort("Path must be a string.")
open_(path, Fiber.current)
@ -20,7 +20,7 @@ foreign class File {
return new_(fd)
}
static open(path, fn) {
static def open(path, fn) {
var file = open(path)
var fiber = Fiber.new { fn.call(file) }
@ -33,18 +33,18 @@ foreign class File {
return result
}
static read(path) {
static def read(path) {
return File.open(path) {|file| file.readBytes(file.size) }
}
static size(path) {
static def size(path) {
if (!(path is String)) Fiber.abort("Path must be a string.")
sizePath_(path, Fiber.current)
return Scheduler.runNextScheduled_()
}
static stat(path) {
static def stat(path) {
if (!(path is String)) Fiber.abort("Path must be a string.")
statPath_(path, Fiber.current)
@ -53,25 +53,25 @@ foreign class File {
construct new_(fd) {}
close() {
def close() {
if (close_(Fiber.current)) return
Scheduler.runNextScheduled_()
}
foreign descriptor
foreign def descriptor
isOpen { descriptor != -1 }
def isOpen { descriptor != -1 }
size {
def size {
if (!isOpen) Fiber.abort("File is not open.")
size_(Fiber.current)
return Scheduler.runNextScheduled_()
}
readBytes(count) { readBytes(count, 0) }
def readBytes(count) { readBytes(count, 0) }
readBytes(count, offset) {
def readBytes(count, offset) {
if (!isOpen) Fiber.abort("File is not open.")
if (!(count is Num)) Fiber.abort("Count must be an integer.")
if (!count.isInteger) Fiber.abort("Count must be an integer.")
@ -85,13 +85,13 @@ foreign class File {
return Scheduler.runNextScheduled_()
}
foreign static open_(path, fiber)
foreign static sizePath_(path, fiber)
foreign static statPath_(path, fiber)
foreign static def open_(path, fiber)
foreign static def sizePath_(path, fiber)
foreign static def statPath_(path, fiber)
foreign close_(fiber)
foreign readBytes_(count, start, fiber)
foreign size_(fiber)
foreign def close_(fiber)
foreign def readBytes_(count, start, fiber)
foreign def size_(fiber)
}
class Stat {
@ -99,20 +99,20 @@ class Stat {
_fields = fields
}
device { _fields[0] }
inode { _fields[1] }
mode { _fields[2] }
linkCount { _fields[3] }
user { _fields[4] }
group { _fields[5] }
specialDevice { _fields[6] }
size { _fields[7] }
blockSize { _fields[8] }
blockCount { _fields[9] }
def device { _fields[0] }
def inode { _fields[1] }
def mode { _fields[2] }
def linkCount { _fields[3] }
def user { _fields[4] }
def group { _fields[5] }
def specialDevice { _fields[6] }
def size { _fields[7] }
def blockSize { _fields[8] }
def blockCount { _fields[9] }
}
class Stdin {
static readLine() {
static def readLine() {
if (__isClosed == true) {
Fiber.abort("Stdin was closed.")
}
@ -127,7 +127,7 @@ class Stdin {
return line
}
static onData_(data) {
static def onData_(data) {
if (data == null) {
__isClosed = true
readStop_()
@ -163,6 +163,6 @@ class Stdin {
}
}
foreign static readStart_()
foreign static readStop_()
foreign static def readStart_()
foreign static def readStop_()
}

View File

@ -3,18 +3,18 @@ static const char* ioModuleSource =
"import \"scheduler\" for Scheduler\n"
"\n"
"class Directory {\n"
" static list(path) {\n"
" static def list(path) {\n"
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
"\n"
" list_(path, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" foreign static list_(path, fiber)\n"
" foreign static def list_(path, fiber)\n"
"}\n"
"\n"
"foreign class File {\n"
" static open(path) {\n"
" static def open(path) {\n"
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
"\n"
" open_(path, Fiber.current)\n"
@ -22,7 +22,7 @@ static const char* ioModuleSource =
" return new_(fd)\n"
" }\n"
"\n"
" static open(path, fn) {\n"
" static def open(path, fn) {\n"
" var file = open(path)\n"
" var fiber = Fiber.new { fn.call(file) }\n"
"\n"
@ -35,18 +35,18 @@ static const char* ioModuleSource =
" return result\n"
" }\n"
"\n"
" static read(path) {\n"
" static def read(path) {\n"
" return File.open(path) {|file| file.readBytes(file.size) }\n"
" }\n"
"\n"
" static size(path) {\n"
" static def size(path) {\n"
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
"\n"
" sizePath_(path, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" static stat(path) {\n"
" static def stat(path) {\n"
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
"\n"
" statPath_(path, Fiber.current)\n"
@ -55,25 +55,25 @@ static const char* ioModuleSource =
"\n"
" construct new_(fd) {}\n"
"\n"
" close() {\n"
" def close() {\n"
" if (close_(Fiber.current)) return\n"
" Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" foreign descriptor\n"
" foreign def descriptor\n"
"\n"
" isOpen { descriptor != -1 }\n"
" def isOpen { descriptor != -1 }\n"
"\n"
" size {\n"
" def size {\n"
" if (!isOpen) Fiber.abort(\"File is not open.\")\n"
"\n"
" size_(Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" readBytes(count) { readBytes(count, 0) }\n"
" def readBytes(count) { readBytes(count, 0) }\n"
"\n"
" readBytes(count, offset) {\n"
" def readBytes(count, offset) {\n"
" if (!isOpen) Fiber.abort(\"File is not open.\")\n"
" if (!(count is Num)) Fiber.abort(\"Count must be an integer.\")\n"
" if (!count.isInteger) Fiber.abort(\"Count must be an integer.\")\n"
@ -87,13 +87,13 @@ static const char* ioModuleSource =
" return Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" foreign static open_(path, fiber)\n"
" foreign static sizePath_(path, fiber)\n"
" foreign static statPath_(path, fiber)\n"
" foreign static def open_(path, fiber)\n"
" foreign static def sizePath_(path, fiber)\n"
" foreign static def statPath_(path, fiber)\n"
"\n"
" foreign close_(fiber)\n"
" foreign readBytes_(count, start, fiber)\n"
" foreign size_(fiber)\n"
" foreign def close_(fiber)\n"
" foreign def readBytes_(count, start, fiber)\n"
" foreign def size_(fiber)\n"
"}\n"
"\n"
"class Stat {\n"
@ -101,20 +101,20 @@ static const char* ioModuleSource =
" _fields = fields\n"
" }\n"
"\n"
" device { _fields[0] }\n"
" inode { _fields[1] }\n"
" mode { _fields[2] }\n"
" linkCount { _fields[3] }\n"
" user { _fields[4] }\n"
" group { _fields[5] }\n"
" specialDevice { _fields[6] }\n"
" size { _fields[7] }\n"
" blockSize { _fields[8] }\n"
" blockCount { _fields[9] }\n"
" def device { _fields[0] }\n"
" def inode { _fields[1] }\n"
" def mode { _fields[2] }\n"
" def linkCount { _fields[3] }\n"
" def user { _fields[4] }\n"
" def group { _fields[5] }\n"
" def specialDevice { _fields[6] }\n"
" def size { _fields[7] }\n"
" def blockSize { _fields[8] }\n"
" def blockCount { _fields[9] }\n"
"}\n"
"\n"
"class Stdin {\n"
" static readLine() {\n"
" static def readLine() {\n"
" if (__isClosed == true) {\n"
" Fiber.abort(\"Stdin was closed.\")\n"
" }\n"
@ -129,7 +129,7 @@ static const char* ioModuleSource =
" return line\n"
" }\n"
"\n"
" static onData_(data) {\n"
" static def onData_(data) {\n"
" if (data == null) {\n"
" __isClosed = true\n"
" readStop_()\n"
@ -165,6 +165,6 @@ static const char* ioModuleSource =
" }\n"
" }\n"
"\n"
" foreign static readStart_()\n"
" foreign static readStop_()\n"
" foreign static def readStart_()\n"
" foreign static def readStop_()\n"
"}\n";

View File

@ -1,5 +1,5 @@
class Scheduler {
static add(callable) {
static def add(callable) {
if (__scheduled == null) __scheduled = []
__scheduled.add(Fiber.new {
@ -9,11 +9,11 @@ class Scheduler {
}
// Called by native code.
static resume_(fiber) { fiber.transfer() }
static resume_(fiber, arg) { fiber.transfer(arg) }
static resumeError_(fiber, error) { fiber.transferError(error) }
static def resume_(fiber) { fiber.transfer() }
static def resume_(fiber, arg) { fiber.transfer(arg) }
static def resumeError_(fiber, error) { fiber.transferError(error) }
static runNextScheduled_() {
static def runNextScheduled_() {
if (__scheduled == null || __scheduled.isEmpty) {
return Fiber.suspend()
} else {
@ -21,7 +21,7 @@ class Scheduler {
}
}
foreign static captureMethods_()
foreign static def captureMethods_()
}
Scheduler.captureMethods_()

View File

@ -1,7 +1,7 @@
// Generated automatically from src/module/scheduler.wren. Do not edit.
static const char* schedulerModuleSource =
"class Scheduler {\n"
" static add(callable) {\n"
" static def add(callable) {\n"
" if (__scheduled == null) __scheduled = []\n"
"\n"
" __scheduled.add(Fiber.new {\n"
@ -11,11 +11,11 @@ static const char* schedulerModuleSource =
" }\n"
"\n"
" // Called by native code.\n"
" static resume_(fiber) { fiber.transfer() }\n"
" static resume_(fiber, arg) { fiber.transfer(arg) }\n"
" static resumeError_(fiber, error) { fiber.transferError(error) }\n"
" static def resume_(fiber) { fiber.transfer() }\n"
" static def resume_(fiber, arg) { fiber.transfer(arg) }\n"
" static def resumeError_(fiber, error) { fiber.transferError(error) }\n"
"\n"
" static runNextScheduled_() {\n"
" static def runNextScheduled_() {\n"
" if (__scheduled == null || __scheduled.isEmpty) {\n"
" return Fiber.suspend()\n"
" } else {\n"
@ -23,7 +23,7 @@ static const char* schedulerModuleSource =
" }\n"
" }\n"
"\n"
" foreign static captureMethods_()\n"
" foreign static def captureMethods_()\n"
"}\n"
"\n"
"Scheduler.captureMethods_()\n";

View File

@ -1,7 +1,7 @@
import "scheduler" for Scheduler
class Timer {
static sleep(milliseconds) {
static def sleep(milliseconds) {
if (!(milliseconds is Num)) Fiber.abort("Milliseconds must be a number.")
if (milliseconds < 0) Fiber.abort("Milliseconds cannot be negative.")
@ -9,5 +9,5 @@ class Timer {
Scheduler.runNextScheduled_()
}
foreign static startTimer_(milliseconds, fiber)
foreign static def startTimer_(milliseconds, fiber)
}

View File

@ -3,7 +3,7 @@ static const char* timerModuleSource =
"import \"scheduler\" for Scheduler\n"
"\n"
"class Timer {\n"
" static sleep(milliseconds) {\n"
" static def sleep(milliseconds) {\n"
" if (!(milliseconds is Num)) Fiber.abort(\"Milliseconds must be a number.\")\n"
" if (milliseconds < 0) Fiber.abort(\"Milliseconds cannot be negative.\")\n"
"\n"
@ -11,5 +11,5 @@ static const char* timerModuleSource =
" Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" foreign static startTimer_(milliseconds, fiber)\n"
" foreign static def startTimer_(milliseconds, fiber)\n"
"}\n";

View File

@ -1,5 +1,5 @@
class Meta {
static eval(source) {
static def eval(source) {
if (!(source is String)) Fiber.abort("Source code must be a string.")
var fn = compile_(source)
@ -9,5 +9,5 @@ class Meta {
Fiber.new(fn).call()
}
foreign static compile_(source)
foreign static def compile_(source)
}

View File

@ -1,7 +1,7 @@
// Generated automatically from src/optional/wren_opt_meta.wren. Do not edit.
static const char* metaModuleSource =
"class Meta {\n"
" static eval(source) {\n"
" static def eval(source) {\n"
" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n"
"\n"
" var fn = compile_(source)\n"
@ -11,5 +11,5 @@ static const char* metaModuleSource =
" Fiber.new(fn).call()\n"
" }\n"
"\n"
" foreign static compile_(source)\n"
" foreign static def compile_(source)\n"
"}\n";

View File

@ -35,15 +35,15 @@ foreign class Random {
}
}
foreign seed_()
foreign seed_(seed)
foreign seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16)
foreign def seed_()
foreign def seed_(seed)
foreign def seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16)
foreign float()
float(end) { float() * end }
float(start, end) { float() * (end - start) + start }
foreign def float()
def float(end) { float() * end }
def float(start, end) { float() * (end - start) + start }
foreign int()
int(end) { (float() * end).floor }
int(start, end) { (float() * (end - start)).floor + start }
foreign def int()
def int(end) { (float() * end).floor }
def int(start, end) { (float() * (end - start)).floor + start }
}

View File

@ -37,15 +37,15 @@ static const char* randomModuleSource =
" }\n"
" }\n"
"\n"
" foreign seed_()\n"
" foreign seed_(seed)\n"
" foreign seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16)\n"
" foreign def seed_()\n"
" foreign def seed_(seed)\n"
" foreign def seed_(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16)\n"
"\n"
" foreign float()\n"
" float(end) { float() * end }\n"
" float(start, end) { float() * (end - start) + start }\n"
" foreign def float()\n"
" def float(end) { float() * end }\n"
" def float(start, end) { float() * (end - start) + start }\n"
"\n"
" foreign int()\n"
" int(end) { (float() * end).floor }\n"
" int(start, end) { (float() * (end - start)).floor + start }\n"
" foreign def int()\n"
" def int(end) { (float() * end).floor }\n"
" def int(start, end) { (float() * (end - start)).floor + start }\n"
"}\n";

View File

@ -80,6 +80,7 @@ typedef enum
TOKEN_BREAK,
TOKEN_CLASS,
TOKEN_CONSTRUCT,
TOKEN_DEF,
TOKEN_ELSE,
TOKEN_FALSE,
TOKEN_FOR,
@ -276,21 +277,7 @@ typedef struct
int arity;
} Signature;
// Bookkeeping information for compiling a class definition.
typedef struct
{
// Symbol table for the fields of the class.
SymbolTable fields;
// True if the class being compiled is a foreign class.
bool isForeign;
// True if the current method being compiled is static.
bool inStatic;
// The signature of the method being compiled.
Signature* signature;
} ClassCompiler;
typedef struct sClassCompiler ClassCompiler;
struct sCompiler
{
@ -352,6 +339,28 @@ struct sCompiler
IntBuffer debugSourceLines;
};
// Bookkeeping information for compiling a class definition.
struct sClassCompiler
{
// Symbol table for the fields of the class.
SymbolTable fields;
// True if the class being compiled is a foreign class.
bool isForeign;
// True if the current method being compiled is static.
bool inStatic;
// The signature of the method being compiled.
Signature* signature;
// The compiler used for body code.
//
// Any code inside a class body that is not a method definition will get
// compiled into this function which is then called by each constructor.
Compiler body;
};
// The stack effect of each opcode. The index in the array is the opcode, and
// the value is the stack effect of that instruction.
static const int stackEffects[] = {
@ -516,6 +525,7 @@ static Keyword keywords[] =
{"break", 5, TOKEN_BREAK},
{"class", 5, TOKEN_CLASS},
{"construct", 9, TOKEN_CONSTRUCT},
{"def", 3, TOKEN_DEF},
{"else", 4, TOKEN_ELSE},
{"false", 5, TOKEN_FALSE},
{"for", 3, TOKEN_FOR},
@ -1434,6 +1444,22 @@ static void loadLocal(Compiler* compiler, int slot)
emitByteArg(compiler, CODE_LOAD_LOCAL, slot);
}
// Loads the receiver of the currently enclosing method. Correctly handles
// functions defined inside methods.
static void loadThis(Compiler* compiler)
{
Code loadInstruction;
int index = resolveNonmodule(compiler, "this", 4, &loadInstruction);
if (loadInstruction == CODE_LOAD_LOCAL)
{
loadLocal(compiler, index);
}
else
{
emitByteArg(compiler, loadInstruction, index);
}
}
// Discards memory owned by [compiler].
static void freeCompiler(Compiler* compiler)
{
@ -1614,31 +1640,6 @@ static bool finishBlock(Compiler* compiler)
return false;
}
// Parses a method or function body, after the initial "{" has been consumed.
//
// It [isInitializer] is `true`, this is the body of a constructor initializer.
// In that case, this adds the code to ensure it returns `this`.
static void finishBody(Compiler* compiler, bool isInitializer)
{
bool isExpressionBody = finishBlock(compiler);
if (isInitializer)
{
// If the initializer body evaluates to a value, discard it.
if (isExpressionBody) emitOp(compiler, CODE_POP);
// The receiver is always stored in the first local slot.
emitOp(compiler, CODE_LOAD_LOCAL_0);
}
else if (!isExpressionBody)
{
// Implicitly return null in statement bodies.
emitOp(compiler, CODE_NULL);
}
emitOp(compiler, CODE_RETURN);
}
// The VM can only handle a certain number of parameters, so check that we
// haven't exceeded that and give a usable error.
static void validateNumParameters(Compiler* compiler, int numArgs)
@ -1813,6 +1814,43 @@ static void callMethod(Compiler* compiler, int numArgs, const char* name,
emitShortArg(compiler, (Code)(CODE_CALL_0 + numArgs), symbol);
}
// Parses a method or function body, after the initial "{" has been consumed.
//
// It [isInitializer] is `true`, this is the body of a constructor initializer.
// In that case, this adds the code to execute the code in the class body and
// make the method return `this`.
static void finishBody(Compiler* compiler, bool isInitializer)
{
if (isInitializer)
{
// Execute the class body code before the initializer code.
loadThis(compiler);
callMethod(compiler, 0, "<body>", 6);
emitOp(compiler, CODE_POP);
// TODO: This regresses performance on classes that don't have any code in
// their body and where calling this is a no-op. See if there's a way we
// can elide the call when not needed.
}
bool isExpressionBody = finishBlock(compiler);
if (isInitializer)
{
// If the initializer body evaluates to a value, discard it.
if (isExpressionBody) emitOp(compiler, CODE_POP);
// The receiver is always stored in the first local slot.
emitOp(compiler, CODE_LOAD_LOCAL_0);
}
else if (!isExpressionBody)
{
// Implicitly return null in statement bodies.
emitOp(compiler, CODE_NULL);
}
emitOp(compiler, CODE_RETURN);
}
// Compiles an (optional) argument list for a method call with [methodSignature]
// and then calls it.
static void methodCall(Compiler* compiler, Code instruction,
@ -1913,22 +1951,6 @@ static void namedCall(Compiler* compiler, bool allowAssignment,
}
}
// Loads the receiver of the currently enclosing method. Correctly handles
// functions defined inside methods.
static void loadThis(Compiler* compiler)
{
Code loadInstruction;
int index = resolveNonmodule(compiler, "this", 4, &loadInstruction);
if (loadInstruction == CODE_LOAD_LOCAL)
{
loadLocal(compiler, index);
}
else
{
emitByteArg(compiler, loadInstruction, index);
}
}
// Pushes the value for a module-level variable implicitly imported from core.
static void loadCoreVariable(Compiler* compiler, const char* name)
{
@ -2042,14 +2064,19 @@ static ClassCompiler* getEnclosingClass(Compiler* compiler)
return compiler == NULL ? NULL : compiler->enclosingClass;
}
static void field(Compiler* compiler, bool allowAssignment)
// Determines the field index in the current enclosing class for a field with
// [name].
//
// Implicitly defines the field if this is the first time it's used. Reports an
// error and returns `255` if the field cannot be defined.
static int findField(Compiler* compiler, const char* name, int length)
{
// Initialize it with a fake value so we can keep parsing and minimize the
// number of cascaded errors.
int field = 255;
ClassCompiler* enclosingClass = getEnclosingClass(compiler);
if (enclosingClass == NULL)
{
error(compiler, "Cannot reference a field outside of a class definition.");
@ -2066,37 +2093,67 @@ static void field(Compiler* compiler, bool allowAssignment)
{
// Look up the field, or implicitly define it.
field = wrenSymbolTableEnsure(compiler->parser->vm, &enclosingClass->fields,
compiler->parser->previous.start,
compiler->parser->previous.length);
name, length);
if (field >= MAX_FIELDS)
{
error(compiler, "A class can only have %d fields.", MAX_FIELDS);
}
}
return field;
}
static void loadField(Compiler* compiler, int field)
{
// TODO: Is != NULL check right here?
// If we're directly inside a method, use a more optimal instruction.
if (compiler->parent != NULL &&
compiler->parent->enclosingClass != NULL)
{
emitByteArg(compiler, CODE_LOAD_FIELD_THIS, field);
}
else
{
loadThis(compiler);
emitByteArg(compiler, CODE_LOAD_FIELD, field);
}
}
static void storeField(Compiler* compiler, int field)
{
// TODO: Is != NULL check right here?
// If we're directly inside a method, use a more optimal instruction.
if (compiler->parent != NULL &&
compiler->parent->enclosingClass != NULL)
{
emitByteArg(compiler, CODE_STORE_FIELD_THIS, field);
}
else
{
loadThis(compiler);
emitByteArg(compiler, CODE_STORE_FIELD, field);
}
}
static void field(Compiler* compiler, bool allowAssignment)
{
int field = findField(compiler,
compiler->parser->previous.start,
compiler->parser->previous.length);
// If there's an "=" after a field name, it's an assignment.
bool isLoad = true;
if (match(compiler, TOKEN_EQ))
{
if (!allowAssignment) error(compiler, "Invalid assignment.");
// Compile the right-hand side.
expression(compiler);
isLoad = false;
}
// If we're directly inside a method, use a more optimal instruction.
if (compiler->parent != NULL &&
compiler->parent->enclosingClass == enclosingClass)
{
emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD_THIS : CODE_STORE_FIELD_THIS,
field);
storeField(compiler, field);
}
else
{
loadThis(compiler);
emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD : CODE_STORE_FIELD, field);
loadField(compiler, field);
}
}
@ -2612,7 +2669,8 @@ GrammarRule rules[] =
/* TOKEN_BANGEQ */ INFIX_OPERATOR(PREC_EQUALITY, "!="),
/* TOKEN_BREAK */ UNUSED,
/* TOKEN_CLASS */ UNUSED,
/* TOKEN_CONSTRUCT */ { NULL, NULL, constructorSignature, PREC_NONE, NULL },
/* TOKEN_CONSTRUCT */ UNUSED,
/* TOKEN_DEF */ UNUSED,
/* TOKEN_ELSE */ UNUSED,
/* TOKEN_FALSE */ PREFIX(boolean),
/* TOKEN_FOR */ UNUSED,
@ -3125,14 +3183,22 @@ static void defineMethod(Compiler* compiler, int classSlot, bool isStatic,
//
// Returns `true` if it compiled successfully, or `false` if the method couldn't
// be parsed.
static bool method(Compiler* compiler, int classSlot)
static bool method(Compiler* compiler, int classSlot, bool isForeign,
bool isConstructor)
{
// TODO: What about foreign constructors?
bool isForeign = match(compiler, TOKEN_FOREIGN);
compiler->enclosingClass->inStatic = match(compiler, TOKEN_STATIC);
SignatureFn signatureFn = rules[compiler->parser->current.type].method;
nextToken(compiler->parser);
SignatureFn signatureFn;
// Methods are declared using "construct" for constructors or "def" for
// everything else.
if (isConstructor)
{
signatureFn = constructorSignature;
}
else
{
signatureFn = rules[compiler->parser->current.type].method;
nextToken(compiler->parser);
}
if (signatureFn == NULL)
{
@ -3196,9 +3262,102 @@ static bool method(Compiler* compiler, int classSlot)
return true;
}
// Defines a synthetic getter method with [getterName] that returns the value
// of the field with [fieldName].
static void defineGetter(Compiler* compiler, int classSlot,
const char* getterName, int getterLength,
const char* fieldName, int fieldLength)
{
Compiler getterCompiler;
initCompiler(&getterCompiler, compiler->parser, compiler, false);
int field = findField(compiler, fieldName, fieldLength);
// It loads and returns the corresponding field.
loadField(&getterCompiler, field);
emitOp(&getterCompiler, CODE_RETURN);
endCompiler(&getterCompiler, getterName, getterLength);
// Define the method for it.
int symbol = methodSymbol(compiler, getterName, getterLength);
defineMethod(compiler, classSlot, compiler->enclosingClass->inStatic,
symbol);
}
static void property(Compiler* compiler, int classSlot)
{
consume(compiler, TOKEN_NAME, "Expect property name after 'var'.");
const char* propertyName = compiler->parser->previous.start;
int propertyLength = compiler->parser->previous.length;
// Prepend "_" to make a field corresponding to the property.
// TODO: Put limit on field length.
char fieldName[256];
fieldName[0] = '_';
memcpy(&fieldName[1], propertyName, propertyLength);
defineGetter(compiler, classSlot, propertyName, propertyLength,
fieldName, propertyLength + 1);
// Synthesize the setter.
Compiler setterCompiler;
initCompiler(&setterCompiler, compiler->parser, compiler, false);
// It stores and returns the corresponding field.
int field = findField(compiler, fieldName, propertyLength + 1);
storeField(&setterCompiler, field);
emitOp(&setterCompiler, CODE_RETURN);
endCompiler(&setterCompiler, propertyName, propertyLength);
// Define the method for it.
Signature setterSignature = { propertyName, propertyLength, SIG_SETTER, 1 };
int setterSymbol = signatureSymbol(compiler, &setterSignature);
defineMethod(compiler, classSlot, compiler->enclosingClass->inStatic,
setterSymbol);
// Compile the initializer into the class body if there is one.
if (match(compiler, TOKEN_EQ))
{
ignoreNewlines(compiler);
expression(&compiler->enclosingClass->body);
storeField(&compiler->enclosingClass->body, field);
emitOp(&compiler->enclosingClass->body, CODE_POP);
}
}
static int classDefinition(Compiler* compiler, bool isForeign);
static void nestedClass(Compiler* compiler, int enclosingClassSlot)
{
// Create the class inside the enclosing class's body code.
Compiler* bodyCompiler = &compiler->enclosingClass->body;
int slot = classDefinition(bodyCompiler, false);
// TODO: Once modules are classes and class definitions can only occur inside
// other classes (and not inside blocks), then a class will always be stored
// in a field, and we can skip storing it in a variable.
loadLocal(bodyCompiler, slot);
// Store the class in a field.
// TODO: Should the field name have "_" before it like class variables do?
// Do we want to allow methods in the class to assign to it?
Local* local = &bodyCompiler->locals[slot];
int field = findField(compiler, local->name, local->length);
storeField(bodyCompiler, field);
emitOp(bodyCompiler, CODE_POP);
// Create a getter that returns the class.
defineGetter(compiler, enclosingClassSlot, local->name, local->length,
local->name, local->length);
}
// Compiles a class definition. Assumes the "class" token has already been
// consumed (along with a possibly preceding "foreign" token).
static void classDefinition(Compiler* compiler, bool isForeign)
//
// Returns the slot for the variable that the class is stored in.
static int classDefinition(Compiler* compiler, bool isForeign)
{
// Create a variable to store the class in.
int slot = declareNamedVariable(compiler);
@ -3249,6 +3408,8 @@ static void classDefinition(Compiler* compiler, bool isForeign)
wrenSymbolTableInit(&classCompiler.fields);
compiler->enclosingClass = &classCompiler;
initCompiler(&classCompiler.body, compiler->parser, compiler, false);
// Compile the method definitions.
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after class declaration.");
@ -3256,14 +3417,61 @@ static void classDefinition(Compiler* compiler, bool isForeign)
while (!match(compiler, TOKEN_RIGHT_BRACE))
{
if (!method(compiler, slot)) break;
// Determine if we're defining a method or executing code in the class body.
// Methods can have a couple of different preambles using some combinations
// of "foreign", "static", "construct", and "def".
compiler->enclosingClass->inStatic = false;
if (match(compiler, TOKEN_FOREIGN))
{
compiler->enclosingClass->inStatic = match(compiler, TOKEN_STATIC);
consume(compiler, TOKEN_DEF,
"Expect 'def' before foreign method declaration.");
method(compiler, slot, true, false);
}
else if (match(compiler, TOKEN_STATIC))
{
compiler->enclosingClass->inStatic = true;
consume(compiler, TOKEN_DEF, "Expect 'def' after 'static'.");
method(compiler, slot, false, false);
}
else if (match(compiler, TOKEN_DEF))
{
method(compiler, slot, false, false);
}
else if (match(compiler, TOKEN_CONSTRUCT))
{
method(compiler, slot, false, true);
}
else if (match(compiler, TOKEN_CLASS))
{
nestedClass(compiler, slot);
}
else if (match(compiler, TOKEN_VAR))
{
// TODO: Static properties.
property(compiler, slot);
}
else
{
// Any other code gets compiled into the body method.
statement(&classCompiler.body);
}
// Don't require a newline after the last definition.
// Don't require a newline after the last definition or statement.
if (match(compiler, TOKEN_RIGHT_BRACE)) break;
consumeLine(compiler, "Expect newline after definition in class.");
}
// Define a method to contain all the code in the class body.
// TODO: Better debug name including class name.
emitOp(&classCompiler.body, CODE_NULL);
emitOp(&classCompiler.body, CODE_RETURN);
endCompiler(&classCompiler.body, "<body>", 6);
int bodySymbol = methodSymbol(compiler, "<body>", 6);
defineMethod(compiler, slot, false, bodySymbol);
// Update the class with the number of fields.
if (!isForeign)
{
@ -3276,6 +3484,7 @@ static void classDefinition(Compiler* compiler, bool isForeign)
compiler->enclosingClass = NULL;
popScope(compiler);
return slot;
}
// Compiles an "import" statement.

View File

@ -5,7 +5,7 @@ class Null {}
class Num {}
class Sequence {
all(f) {
def all(f) {
var result = true
for (element in this) {
result = f.call(element)
@ -14,7 +14,7 @@ class Sequence {
return result
}
any(f) {
def any(f) {
var result = false
for (element in this) {
result = f.call(element)
@ -23,14 +23,14 @@ class Sequence {
return result
}
contains(element) {
def contains(element) {
for (item in this) {
if (element == item) return true
}
return false
}
count {
def count {
var result = 0
for (element in this) {
result = result + 1
@ -38,7 +38,7 @@ class Sequence {
return result
}
count(f) {
def count(f) {
var result = 0
for (element in this) {
if (f.call(element)) result = result + 1
@ -46,26 +46,26 @@ class Sequence {
return result
}
each(f) {
def each(f) {
for (element in this) {
f.call(element)
}
}
isEmpty { iterate(null) ? false : true }
def isEmpty { iterate(null) ? false : true }
map(transformation) { MapSequence.new(this, transformation) }
def map(transformation) { MapSequence.new(this, transformation) }
where(predicate) { WhereSequence.new(this, predicate) }
def where(predicate) { WhereSequence.new(this, predicate) }
reduce(acc, f) {
def reduce(acc, f) {
for (element in this) {
acc = f.call(acc, element)
}
return acc
}
reduce(f) {
def reduce(f) {
var iter = iterate(null)
if (!iter) Fiber.abort("Can't reduce an empty sequence.")
@ -78,9 +78,9 @@ class Sequence {
return result
}
join() { join("") }
def join() { join("") }
join(sep) {
def join(sep) {
var first = true
var result = ""
@ -93,7 +93,7 @@ class Sequence {
return result
}
toList {
def toList {
var result = List.new()
for (element in this) {
result.add(element)
@ -108,8 +108,8 @@ class MapSequence is Sequence {
_fn = fn
}
iterate(iterator) { _sequence.iterate(iterator) }
iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }
def iterate(iterator) { _sequence.iterate(iterator) }
def iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }
}
class WhereSequence is Sequence {
@ -118,19 +118,19 @@ class WhereSequence is Sequence {
_fn = fn
}
iterate(iterator) {
def iterate(iterator) {
while (iterator = _sequence.iterate(iterator)) {
if (_fn.call(_sequence.iteratorValue(iterator))) break
}
return iterator
}
iteratorValue(iterator) { _sequence.iteratorValue(iterator) }
def iteratorValue(iterator) { _sequence.iteratorValue(iterator) }
}
class String is Sequence {
bytes { StringByteSequence.new(this) }
codePoints { StringCodePointSequence.new(this) }
def bytes { StringByteSequence.new(this) }
def codePoints { StringCodePointSequence.new(this) }
}
class StringByteSequence is Sequence {
@ -138,11 +138,11 @@ class StringByteSequence is Sequence {
_string = string
}
[index] { _string.byteAt_(index) }
iterate(iterator) { _string.iterateByte_(iterator) }
iteratorValue(iterator) { _string.byteAt_(iterator) }
def [index] { _string.byteAt_(index) }
def iterate(iterator) { _string.iterateByte_(iterator) }
def iteratorValue(iterator) { _string.byteAt_(iterator) }
count { _string.byteCount_ }
def count { _string.byteCount_ }
}
class StringCodePointSequence is Sequence {
@ -150,24 +150,24 @@ class StringCodePointSequence is Sequence {
_string = string
}
[index] { _string.codePointAt_(index) }
iterate(iterator) { _string.iterate(iterator) }
iteratorValue(iterator) { _string.codePointAt_(iterator) }
def [index] { _string.codePointAt_(index) }
def iterate(iterator) { _string.iterate(iterator) }
def iteratorValue(iterator) { _string.codePointAt_(iterator) }
count { _string.count }
def count { _string.count }
}
class List is Sequence {
addAll(other) {
def addAll(other) {
for (element in other) {
add(element)
}
return other
}
toString { "[%(join(", "))]" }
def toString { "[%(join(", "))]" }
+(other) {
def +(other) {
var result = this[0..-1]
for (element in other) {
result.add(element)
@ -177,10 +177,10 @@ class List is Sequence {
}
class Map {
keys { MapKeySequence.new(this) }
values { MapValueSequence.new(this) }
def keys { MapKeySequence.new(this) }
def values { MapValueSequence.new(this) }
toString {
def toString {
var first = true
var result = "{"
@ -199,8 +199,8 @@ class MapKeySequence is Sequence {
_map = map
}
iterate(n) { _map.iterate_(n) }
iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }
def iterate(n) { _map.iterate_(n) }
def iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }
}
class MapValueSequence is Sequence {
@ -208,38 +208,38 @@ class MapValueSequence is Sequence {
_map = map
}
iterate(n) { _map.iterate_(n) }
iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }
def iterate(n) { _map.iterate_(n) }
def iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }
}
class Range is Sequence {}
class System {
static print() {
static def print() {
writeString_("\n")
}
static print(obj) {
static def print(obj) {
writeObject_(obj)
writeString_("\n")
return obj
}
static printAll(sequence) {
static def printAll(sequence) {
for (object in sequence) writeObject_(object)
writeString_("\n")
}
static write(obj) {
static def write(obj) {
writeObject_(obj)
return obj
}
static writeAll(sequence) {
static def writeAll(sequence) {
for (object in sequence) writeObject_(object)
}
static writeObject_(obj) {
static def writeObject_(obj) {
var string = obj.toString
if (string is String) {
writeString_(string)

View File

@ -7,7 +7,7 @@ static const char* coreModuleSource =
"class Num {}\n"
"\n"
"class Sequence {\n"
" all(f) {\n"
" def all(f) {\n"
" var result = true\n"
" for (element in this) {\n"
" result = f.call(element)\n"
@ -16,7 +16,7 @@ static const char* coreModuleSource =
" return result\n"
" }\n"
"\n"
" any(f) {\n"
" def any(f) {\n"
" var result = false\n"
" for (element in this) {\n"
" result = f.call(element)\n"
@ -25,14 +25,14 @@ static const char* coreModuleSource =
" return result\n"
" }\n"
"\n"
" contains(element) {\n"
" def contains(element) {\n"
" for (item in this) {\n"
" if (element == item) return true\n"
" }\n"
" return false\n"
" }\n"
"\n"
" count {\n"
" def count {\n"
" var result = 0\n"
" for (element in this) {\n"
" result = result + 1\n"
@ -40,7 +40,7 @@ static const char* coreModuleSource =
" return result\n"
" }\n"
"\n"
" count(f) {\n"
" def count(f) {\n"
" var result = 0\n"
" for (element in this) {\n"
" if (f.call(element)) result = result + 1\n"
@ -48,26 +48,26 @@ static const char* coreModuleSource =
" return result\n"
" }\n"
"\n"
" each(f) {\n"
" def each(f) {\n"
" for (element in this) {\n"
" f.call(element)\n"
" }\n"
" }\n"
"\n"
" isEmpty { iterate(null) ? false : true }\n"
" def isEmpty { iterate(null) ? false : true }\n"
"\n"
" map(transformation) { MapSequence.new(this, transformation) }\n"
" def map(transformation) { MapSequence.new(this, transformation) }\n"
"\n"
" where(predicate) { WhereSequence.new(this, predicate) }\n"
" def where(predicate) { WhereSequence.new(this, predicate) }\n"
"\n"
" reduce(acc, f) {\n"
" def reduce(acc, f) {\n"
" for (element in this) {\n"
" acc = f.call(acc, element)\n"
" }\n"
" return acc\n"
" }\n"
"\n"
" reduce(f) {\n"
" def reduce(f) {\n"
" var iter = iterate(null)\n"
" if (!iter) Fiber.abort(\"Can't reduce an empty sequence.\")\n"
"\n"
@ -80,9 +80,9 @@ static const char* coreModuleSource =
" return result\n"
" }\n"
"\n"
" join() { join(\"\") }\n"
" def join() { join(\"\") }\n"
"\n"
" join(sep) {\n"
" def join(sep) {\n"
" var first = true\n"
" var result = \"\"\n"
"\n"
@ -95,7 +95,7 @@ static const char* coreModuleSource =
" return result\n"
" }\n"
"\n"
" toList {\n"
" def toList {\n"
" var result = List.new()\n"
" for (element in this) {\n"
" result.add(element)\n"
@ -110,8 +110,8 @@ static const char* coreModuleSource =
" _fn = fn\n"
" }\n"
"\n"
" iterate(iterator) { _sequence.iterate(iterator) }\n"
" iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }\n"
" def iterate(iterator) { _sequence.iterate(iterator) }\n"
" def iteratorValue(iterator) { _fn.call(_sequence.iteratorValue(iterator)) }\n"
"}\n"
"\n"
"class WhereSequence is Sequence {\n"
@ -120,19 +120,19 @@ static const char* coreModuleSource =
" _fn = fn\n"
" }\n"
"\n"
" iterate(iterator) {\n"
" def iterate(iterator) {\n"
" while (iterator = _sequence.iterate(iterator)) {\n"
" if (_fn.call(_sequence.iteratorValue(iterator))) break\n"
" }\n"
" return iterator\n"
" }\n"
"\n"
" iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
" def iteratorValue(iterator) { _sequence.iteratorValue(iterator) }\n"
"}\n"
"\n"
"class String is Sequence {\n"
" bytes { StringByteSequence.new(this) }\n"
" codePoints { StringCodePointSequence.new(this) }\n"
" def bytes { StringByteSequence.new(this) }\n"
" def codePoints { StringCodePointSequence.new(this) }\n"
"}\n"
"\n"
"class StringByteSequence is Sequence {\n"
@ -140,11 +140,11 @@ static const char* coreModuleSource =
" _string = string\n"
" }\n"
"\n"
" [index] { _string.byteAt_(index) }\n"
" iterate(iterator) { _string.iterateByte_(iterator) }\n"
" iteratorValue(iterator) { _string.byteAt_(iterator) }\n"
" def [index] { _string.byteAt_(index) }\n"
" def iterate(iterator) { _string.iterateByte_(iterator) }\n"
" def iteratorValue(iterator) { _string.byteAt_(iterator) }\n"
"\n"
" count { _string.byteCount_ }\n"
" def count { _string.byteCount_ }\n"
"}\n"
"\n"
"class StringCodePointSequence is Sequence {\n"
@ -152,24 +152,24 @@ static const char* coreModuleSource =
" _string = string\n"
" }\n"
"\n"
" [index] { _string.codePointAt_(index) }\n"
" iterate(iterator) { _string.iterate(iterator) }\n"
" iteratorValue(iterator) { _string.codePointAt_(iterator) }\n"
" def [index] { _string.codePointAt_(index) }\n"
" def iterate(iterator) { _string.iterate(iterator) }\n"
" def iteratorValue(iterator) { _string.codePointAt_(iterator) }\n"
"\n"
" count { _string.count }\n"
" def count { _string.count }\n"
"}\n"
"\n"
"class List is Sequence {\n"
" addAll(other) {\n"
" def addAll(other) {\n"
" for (element in other) {\n"
" add(element)\n"
" }\n"
" return other\n"
" }\n"
"\n"
" toString { \"[%(join(\", \"))]\" }\n"
" def toString { \"[%(join(\", \"))]\" }\n"
"\n"
" +(other) {\n"
" def +(other) {\n"
" var result = this[0..-1]\n"
" for (element in other) {\n"
" result.add(element)\n"
@ -179,10 +179,10 @@ static const char* coreModuleSource =
"}\n"
"\n"
"class Map {\n"
" keys { MapKeySequence.new(this) }\n"
" values { MapValueSequence.new(this) }\n"
" def keys { MapKeySequence.new(this) }\n"
" def values { MapValueSequence.new(this) }\n"
"\n"
" toString {\n"
" def toString {\n"
" var first = true\n"
" var result = \"{\"\n"
"\n"
@ -201,8 +201,8 @@ static const char* coreModuleSource =
" _map = map\n"
" }\n"
"\n"
" iterate(n) { _map.iterate_(n) }\n"
" iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }\n"
" def iterate(n) { _map.iterate_(n) }\n"
" def iteratorValue(iterator) { _map.keyIteratorValue_(iterator) }\n"
"}\n"
"\n"
"class MapValueSequence is Sequence {\n"
@ -210,38 +210,38 @@ static const char* coreModuleSource =
" _map = map\n"
" }\n"
"\n"
" iterate(n) { _map.iterate_(n) }\n"
" iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }\n"
" def iterate(n) { _map.iterate_(n) }\n"
" def iteratorValue(iterator) { _map.valueIteratorValue_(iterator) }\n"
"}\n"
"\n"
"class Range is Sequence {}\n"
"\n"
"class System {\n"
" static print() {\n"
" static def print() {\n"
" writeString_(\"\n\")\n"
" }\n"
"\n"
" static print(obj) {\n"
" static def print(obj) {\n"
" writeObject_(obj)\n"
" writeString_(\"\n\")\n"
" return obj\n"
" }\n"
"\n"
" static printAll(sequence) {\n"
" static def printAll(sequence) {\n"
" for (object in sequence) writeObject_(object)\n"
" writeString_(\"\n\")\n"
" }\n"
"\n"
" static write(obj) {\n"
" static def write(obj) {\n"
" writeObject_(obj)\n"
" return obj\n"
" }\n"
"\n"
" static writeAll(sequence) {\n"
" static def writeAll(sequence) {\n"
" for (object in sequence) writeObject_(object)\n"
" }\n"
"\n"
" static writeObject_(obj) {\n"
" static def writeObject_(obj) {\n"
" var string = obj.toString\n"
" if (string is String) {\n"
" writeString_(string)\n"

View File

@ -48,6 +48,7 @@ ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name)
ObjClass* classObj = ALLOCATE(vm, ObjClass);
initObj(vm, &classObj->obj, OBJ_CLASS, NULL);
classObj->superclass = NULL;
classObj->enclosingClass = NULL;
classObj->numFields = numFields;
classObj->name = name;
@ -82,7 +83,8 @@ void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass)
}
}
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields,
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass,
ObjClass* enclosingClass, int numFields,
ObjString* name)
{
// Create the metaclass.
@ -92,6 +94,9 @@ ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields,
ObjClass* metaclass = wrenNewSingleClass(vm, 0, AS_STRING(metaclassName));
metaclass->obj.classObj = vm->classClass;
// TODO: Is this what we want? Test.
metaclass->enclosingClass = enclosingClass;
wrenPopRoot(vm);
// Make sure the metaclass isn't collected when we allocate the class.
@ -102,6 +107,8 @@ ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields,
wrenBindSuperclass(vm, metaclass, vm->classClass);
ObjClass* classObj = wrenNewSingleClass(vm, numFields, name);
classObj->enclosingClass = enclosingClass;
// Make sure the class isn't collected while the inherited methods are being
// bound.

View File

@ -376,6 +376,8 @@ struct sObjClass
{
Obj obj;
ObjClass* superclass;
ObjClass* enclosingClass;
// The number of fields needed for an instance of this class, including all
// of its superclass fields.
@ -616,7 +618,8 @@ ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name);
void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass);
// Creates a new class object as well as its associated metaclass.
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields,
ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass,
ObjClass* enclosingClass, int numFields,
ObjString* name);
void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method);

View File

@ -386,10 +386,36 @@ static void runtimeError(WrenVM* vm)
// Aborts the current fiber with an appropriate method not found error for a
// method with [symbol] on [classObj].
static void methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol)
static Method* methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol)
{
ObjClass* originalClass = classObj;
classObj = originalClass->enclosingClass;
while (classObj != NULL)
{
if (symbol < classObj->methods.count)
{
Method* method = &classObj->methods.data[symbol];
if (method->type != METHOD_NONE)
{
// TODO: Doing this lazily is a drag. The problem is we don't know all
// of the enclosing class's methods at the point in time when the inner
// class is created. If we change the grammar to only allow inner
// classes to appear after all method definitions, we could do this
// eagerly. Worth doing?
// Copy it down now so we don't have to walk the enclosing classes
// every time.
originalClass->methods.data[symbol] = *method;
return method;
}
}
classObj = classObj->enclosingClass;
}
vm->fiber->error = wrenStringFormat(vm, "@ does not implement '$'.",
OBJ_VAL(classObj->name), vm->methodNames.data[symbol].buffer);
OBJ_VAL(originalClass->name), vm->methodNames.data[symbol].buffer);
return NULL;
}
// Checks that [value], which must be a function or closure, does not require
@ -643,15 +669,22 @@ static void createClass(WrenVM* vm, int numFields, ObjModule* module)
Value name = vm->fiber->stackTop[-2];
Value superclass = vm->fiber->stackTop[-1];
// TODO: This does the wrong thing when there is no enclosing method (i.e.
// top level code). If we make the top level a class definition then this
// should work provided we make sure that class is in the first slot of the
// first call frame.
ObjClass* enclosingClass = wrenGetClass(vm,
vm->fiber->frames[vm->fiber->numFrames - 1].stackStart[0]);
// We have two values on the stack and we are going to leave one, so discard
// the other slot.
vm->fiber->stackTop--;
vm->fiber->error = validateSuperclass(vm, name, superclass, numFields);
if (!IS_NULL(vm->fiber->error)) return;
ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), numFields,
AS_STRING(name));
ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), enclosingClass,
numFields, AS_STRING(name));
vm->fiber->stackTop[-1] = OBJ_VAL(classObj);
if (numFields == -1) bindForeignClass(vm, classObj, module);
@ -919,12 +952,13 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
goto completeCall;
completeCall:
// If the class's method table doesn't include the symbol, bail.
// If the class's method table doesn't include the symbol, look in the
// enclosing classes, or fail if not found there.
if (symbol >= classObj->methods.count ||
(method = &classObj->methods.data[symbol])->type == METHOD_NONE)
{
methodNotFound(vm, classObj, symbol);
RUNTIME_ERROR();
method = methodNotFound(vm, classObj, symbol);
if (method == NULL) RUNTIME_ERROR();
}
switch (method->type)

View File

@ -1,17 +1,17 @@
class Call {
static noParams {
static def noParams {
System.print("noParams")
}
static zero() {
static def zero() {
System.print("zero")
}
static one(one) {
static def one(one) {
System.print("one %(one)")
}
static two(one, two) {
static def two(one, two) {
// Don't print null bytes.
if (two is String && two.bytes.contains(0)) {
two = two.bytes.toList
@ -20,7 +20,7 @@ class Call {
System.print("two %(one) %(two)")
}
static getValue() { ["a", "b"] }
static def getValue() { ["a", "b"] }
}
// expect: noParams

View File

@ -1,12 +1,12 @@
class ForeignClass {
foreign static finalized
foreign static def finalized
}
// Class with a default constructor.
foreign class Counter {
construct new() {}
foreign increment(amount)
foreign value
foreign def increment(amount)
foreign def value
}
var counter = Counter.new()
@ -18,7 +18,7 @@ System.print(counter.value) // expect: 4.3
// Foreign classes can inherit a class as long as it has no fields.
class PointBase {
inherited() {
def inherited() {
System.print("inherited method")
}
}
@ -33,8 +33,8 @@ foreign class Point is PointBase {
System.print("%(x), %(y), %(z)")
}
foreign translate(x, y, z)
foreign toString
foreign def translate(x, y, z)
foreign def toString
}
var p = Point.new(1, 2, 3) // expect: 1, 2, 3

View File

@ -1,11 +1,11 @@
import "get_variable_module"
class GetVariable {
foreign static beforeDefined()
foreign static afterDefined()
foreign static afterAssigned()
foreign static otherSlot()
foreign static otherModule()
foreign static def beforeDefined()
foreign static def afterDefined()
foreign static def afterAssigned()
foreign static def otherSlot()
foreign static def otherModule()
}
System.print(GetVariable.beforeDefined()) // expect: null

View File

@ -1,6 +1,6 @@
class Lists {
foreign static newList()
foreign static insert()
foreign static def newList()
foreign static def insert()
}
var list = Lists.newList()

View File

@ -1,9 +1,9 @@
class Slots {
foreign static noSet
foreign static getSlots(bool, num, string, bytes, value)
foreign static setSlots(a, b, c, d)
foreign static ensure()
foreign static ensureOutsideForeign()
foreign static def noSet
foreign static def getSlots(bool, num, string, bytes, value)
foreign static def setSlots(a, b, c, d)
foreign static def ensure()
foreign static def ensureOutsideForeign()
}
// If nothing is set in the return slot, it retains its previous value, the

View File

@ -1,6 +1,6 @@
class Value {
foreign static value=(value)
foreign static value
foreign static def value=(value)
foreign static def value
}
Value.value = ["list", "of", "strings"]

View File

@ -1,5 +1,5 @@
class Benchmark {
foreign static arguments(a, b, c, d)
foreign static def arguments(a, b, c, d)
}
var start = System.clock

View File

@ -11,7 +11,7 @@ class Tree {
}
}
check {
def check {
if (_left == null) {
return _item
}

View File

@ -11,7 +11,7 @@ class Tree {
}
}
check {
def check {
if (_left == null) {
return _item
}

View File

@ -44,15 +44,15 @@ class Strength {
_name = name
}
value { _value }
name { _name }
def value { _value }
def name { _name }
nextWeaker { ORDERED[_value] }
def nextWeaker { ORDERED[_value] }
static stronger(s1, s2) { s1.value < s2.value }
static weaker(s1, s2) { s1.value > s2.value }
static weakest(s1, s2) { Strength.weaker(s1, s2) ? s1 : s2 }
static strongest(s1, s2) { Strength.stronger(s1, s2) ? s1 : s2 }
static def stronger(s1, s2) { s1.value < s2.value }
static def weaker(s1, s2) { s1.value > s2.value }
static def weakest(s1, s2) { Strength.weaker(s1, s2) ? s1 : s2 }
static def strongest(s1, s2) { Strength.stronger(s1, s2) ? s1 : s2 }
}
// Compile time computed constants.
@ -75,10 +75,10 @@ class Constraint {
_strength = strength
}
strength { _strength }
def strength { _strength }
// Activate this constraint and attempt to satisfy it.
addConstraint() {
def addConstraint() {
addToGraph()
ThePlanner.incrementalAdd(this)
}
@ -88,7 +88,7 @@ class Constraint {
// graph. Answer the constraint that this constraint overrides, if
// there is one, or nil, if there isn't.
// Assume: I am not already satisfied.
satisfy(mark) {
def satisfy(mark) {
chooseMethod(mark)
if (!isSatisfied) {
if (_strength == REQUIRED) {
@ -107,7 +107,7 @@ class Constraint {
return overridden
}
destroyConstraint() {
def destroyConstraint() {
if (isSatisfied) ThePlanner.incrementalRemove(this)
removeFromGraph()
}
@ -115,7 +115,7 @@ class Constraint {
// Normal constraints are not input constraints. An input constraint
// is one that depends on external state, such as the mouse, the
// keybord, a clock, or some arbitraty piece of imperative code.
isInput { false }
def isInput { false }
}
// Abstract superclass for constraints having a single possible output variable.
@ -128,44 +128,44 @@ class UnaryConstraint is Constraint {
}
// Adds this constraint to the constraint graph.
addToGraph() {
def addToGraph() {
_myOutput.addConstraint(this)
_satisfied = false
}
// Decides if this constraint can be satisfied and records that decision.
chooseMethod(mark) {
def chooseMethod(mark) {
_satisfied = (_myOutput.mark != mark) &&
Strength.stronger(strength, _myOutput.walkStrength)
}
// Returns true if this constraint is satisfied in the current solution.
isSatisfied { _satisfied }
def isSatisfied { _satisfied }
markInputs(mark) {
def markInputs(mark) {
// has no inputs.
}
// Returns the current output variable.
output { _myOutput }
def output { _myOutput }
// Calculate the walkabout strength, the stay flag, and, if it is
// 'stay', the value for the current output of this constraint. Assume
// this constraint is satisfied.
recalculate() {
def recalculate() {
_myOutput.walkStrength = strength
_myOutput.stay = !isInput
if (_myOutput.stay) execute() // Stay optimization.
}
// Records that this constraint is unsatisfied.
markUnsatisfied() {
def markUnsatisfied() {
_satisfied = false
}
inputsKnown(mark) { true }
def inputsKnown(mark) { true }
removeFromGraph() {
def removeFromGraph() {
if (_myOutput != null) _myOutput.removeConstraint(this)
_satisfied = false
}
@ -180,7 +180,7 @@ class StayConstraint is UnaryConstraint {
super(variable, strength)
}
execute() {
def execute() {
// Stay constraints do nothing.
}
}
@ -193,9 +193,9 @@ class EditConstraint is UnaryConstraint {
}
// Edits indicate that a variable is to be changed by imperative code.
isInput { true }
def isInput { true }
execute() {
def execute() {
// Edit constraints do nothing.
}
}
@ -216,14 +216,14 @@ class BinaryConstraint is Constraint {
addConstraint()
}
direction { _direction }
v1 { _v1 }
v2 { _v2 }
def direction { _direction }
def v1 { _v1 }
def v2 { _v2 }
// Decides if this constraint can be satisfied and which way it
// should flow based on the relative strength of the variables related,
// and record that decision.
chooseMethod(mark) {
def chooseMethod(mark) {
if (_v1.mark == mark) {
if (_v2.mark != mark &&
Strength.stronger(strength, _v2.walkStrength)) {
@ -258,30 +258,30 @@ class BinaryConstraint is Constraint {
}
// Add this constraint to the constraint graph.
addToGraph() {
def addToGraph() {
_v1.addConstraint(this)
_v2.addConstraint(this)
_direction = NONE
}
// Answer true if this constraint is satisfied in the current solution.
isSatisfied { _direction != NONE }
def isSatisfied { _direction != NONE }
// Mark the input variable with the given mark.
markInputs(mark) {
def markInputs(mark) {
input.mark = mark
}
// Returns the current input variable
input { _direction == FORWARD ? _v1 : _v2 }
def input { _direction == FORWARD ? _v1 : _v2 }
// Returns the current output variable.
output { _direction == FORWARD ? _v2 : _v1 }
def output { _direction == FORWARD ? _v2 : _v1 }
// Calculate the walkabout strength, the stay flag, and, if it is
// 'stay', the value for the current output of this
// constraint. Assume this constraint is satisfied.
recalculate() {
def recalculate() {
var ihn = input
var out = output
out.walkStrength = Strength.weakest(strength, ihn.walkStrength)
@ -290,16 +290,16 @@ class BinaryConstraint is Constraint {
}
// Record the fact that this constraint is unsatisfied.
markUnsatisfied() {
def markUnsatisfied() {
_direction = NONE
}
inputsKnown(mark) {
def inputsKnown(mark) {
var i = input
return i.mark == mark || i.stay || i.determinedBy == null
}
removeFromGraph() {
def removeFromGraph() {
if (_v1 != null) _v1.removeConstraint(this)
if (_v2 != null) _v2.removeConstraint(this)
_direction = NONE
@ -318,25 +318,25 @@ class ScaleConstraint is BinaryConstraint {
}
// Adds this constraint to the constraint graph.
addToGraph() {
def addToGraph() {
super()
_scale.addConstraint(this)
_offset.addConstraint(this)
}
removeFromGraph() {
def removeFromGraph() {
super()
if (_scale != null) _scale.removeConstraint(this)
if (_offset != null) _offset.removeConstraint(this)
}
markInputs(mark) {
def markInputs(mark) {
super.markInputs(mark)
_scale.mark = _offset.mark = mark
}
// Enforce this constraint. Assume that it is satisfied.
execute() {
def execute() {
if (direction == FORWARD) {
v2.value = v1.value * _scale.value + _offset.value
} else {
@ -348,7 +348,7 @@ class ScaleConstraint is BinaryConstraint {
// Calculate the walkabout strength, the stay flag, and, if it is
// 'stay', the value for the current output of this constraint. Assume
// this constraint is satisfied.
recalculate() {
def recalculate() {
var ihn = input
var out = output
out.walkStrength = Strength.weakest(strength, ihn.walkStrength)
@ -364,7 +364,7 @@ class EqualityConstraint is BinaryConstraint {
}
// Enforce this constraint. Assume that it is satisfied.
execute() {
def execute() {
output.value = input.value
}
}
@ -376,34 +376,26 @@ class EqualityConstraint is BinaryConstraint {
class Variable {
construct new(name, value) {
_constraints = []
_determinedBy = null
_mark = 0
_walkStrength = WEAKEST
_stay = true
_name = name
_value = value
}
constraints { _constraints }
determinedBy { _determinedBy }
determinedBy=(value) { _determinedBy = value }
mark { _mark }
mark=(value) { _mark = value }
walkStrength { _walkStrength }
walkStrength=(value) { _walkStrength = value }
stay { _stay }
stay=(value) { _stay = value }
value { _value }
value=(newValue) { _value = newValue }
def constraints { _constraints }
var determinedBy
var mark = 0
var walkStrength = WEAKEST
var stay = true
var value
// Add the given constraint to the set of all constraints that refer
// this variable.
addConstraint(constraint) {
def addConstraint(constraint) {
_constraints.add(constraint)
}
// Removes all traces of c from this variable.
removeConstraint(constraint) {
def removeConstraint(constraint) {
_constraints = _constraints.where { |c| c != constraint }
if (_determinedBy == constraint) _determinedBy = null
}
@ -417,13 +409,13 @@ class Plan {
_list = []
}
addConstraint(constraint) {
def addConstraint(constraint) {
_list.add(constraint)
}
size { _list.count }
def size { _list.count }
execute() {
def execute() {
for (constraint in _list) {
constraint.execute()
}
@ -447,7 +439,7 @@ class Planner {
// a unique mark value so that we know where we've been. This allows
// the algorithm to avoid getting into an infinite loop even if the
// constraint graph has an inadvertent cycle.
incrementalAdd(constraint) {
def incrementalAdd(constraint) {
var mark = newMark()
var overridden = constraint.satisfy(mark)
while (overridden != null) {
@ -464,7 +456,7 @@ class Planner {
// strength, strongest first, as a heuristic for avoiding
// unnecessarily adding and then overriding weak constraints.
// Assume: [c] is satisfied.
incrementalRemove(constraint) {
def incrementalRemove(constraint) {
var out = constraint.output
constraint.markUnsatisfied()
constraint.removeFromGraph()
@ -480,7 +472,7 @@ class Planner {
}
// Select a previously unused mark value.
newMark() { _currentMark = _currentMark + 1 }
def newMark() { _currentMark = _currentMark + 1 }
// Extract a plan for resatisfaction starting from the given source
// constraints, usually a set of input constraints. This method
@ -499,7 +491,7 @@ class Planner {
// variables, which are not stay but which are also not computed by
// any constraint.
// Assume: [sources] are all satisfied.
makePlan(sources) {
def makePlan(sources) {
var mark = newMark()
var plan = Plan.new()
var todo = sources
@ -516,7 +508,7 @@ class Planner {
// Extract a plan for resatisfying starting from the output of the
// given [constraints], usually a set of input constraints.
extractPlanFromConstraints(constraints) {
def extractPlanFromConstraints(constraints) {
var sources = []
for (constraint in constraints) {
// if not in plan already and eligible for inclusion.
@ -536,7 +528,7 @@ class Planner {
// the given mark. Thus, encountering a marked node downstream of
// the output constraint means that there is a path from the
// constraint's output to one of its inputs.
addPropagate(constraint, mark) {
def addPropagate(constraint, mark) {
var todo = [constraint]
while (todo.count > 0) {
var d = todo.removeAt(-1)
@ -555,7 +547,7 @@ class Planner {
// Update the walkabout strengths and stay flags of all variables
// downstream of the given constraint. Answer a collection of
// unsatisfied constraints sorted in order of decreasing strength.
removePropagateFrom(out) {
def removePropagateFrom(out) {
out.determinedBy = null
out.walkStrength = WEAKEST
out.stay = true
@ -579,7 +571,7 @@ class Planner {
return unsatisfied
}
addConstraintsConsumingTo(v, coll) {
def addConstraintsConsumingTo(v, coll) {
var determining = v.determinedBy
for (constraint in v.constraints) {
if (constraint != determining && constraint.isSatisfied) {

View File

@ -1,5 +1,5 @@
class Fib {
static get(n) {
static def get(n) {
if (n < 2) return n
return get(n - 1) + get(n - 2)
}

View File

@ -3,8 +3,8 @@ class Toggle {
_state = startState
}
value { _state }
activate {
def value { _state }
def activate {
_state = !_state
return this
}
@ -17,7 +17,7 @@ class NthToggle is Toggle {
_count = 0
}
activate {
def activate {
_count = _count + 1
if (_count >= _countMax) {
super.activate

View File

@ -16,7 +16,7 @@ System.print([1, [2, [3], 4], 5].join(",")) // expect: 1,[2, [3], 4],5
// Calls toString on elements.
class Foo {
construct new() {}
toString { "Foo.toString" }
def toString { "Foo.toString" }
}
System.print([1, Foo.new(), 2].join(", ")) // expect: 1, Foo.toString, 2

View File

@ -10,7 +10,7 @@ System.print([1, [2, [3], 4], 5]) // expect: [1, [2, [3], 4], 5]
// Calls toString on elements.
class Foo {
construct new() {}
toString { "Foo.toString" }
def toString { "Foo.toString" }
}
System.print([1, Foo.new(), 2]) // expect: [1, Foo.toString, 2]

View File

@ -10,7 +10,7 @@ System.print({1: {2: {}}}) // expect: {1: {2: {}}}
// Calls toString on elements.
class Foo {
construct new() {}
toString { "Foo.toString" }
def toString { "Foo.toString" }
}
System.print({1: Foo.new()}) // expect: {1: Foo.toString}

View File

@ -35,7 +35,7 @@ System.print(Object.same(foo, Foo.new())) // expect: false
// Ignores == operators.
class Bar {
construct new() {}
==(other) { true }
def ==(other) { true }
}
var bar = Bar.new()

View File

@ -1,13 +1,13 @@
class TestSequence is Sequence {
construct new() {}
iterate(iterator) {
def iterate(iterator) {
if (iterator == null) return 1
if (iterator == 10) return false
return iterator + 1
}
iteratorValue(iterator) { iterator }
def iteratorValue(iterator) { iterator }
}
System.print(TestSequence.new().count) // expect: 10

View File

@ -3,8 +3,8 @@ System.print([1].isEmpty) // expect: false
class InfiniteSequence is Sequence {
construct new() {}
iterate(iterator) { true }
iteratorValue(iterator) { iterator }
def iterate(iterator) { true }
def iteratorValue(iterator) { iterator }
}
// Should not try to iterate the whole sequence.

View File

@ -5,25 +5,25 @@ class FibIterator {
_next = 1
}
iterate {
def iterate {
var sum = _current + _next
_current = _next
_next = sum
}
value { _current }
def value { _current }
}
class Fib is Sequence {
construct new() {}
iterate(iterator) {
def iterate(iterator) {
if (iterator == null) return FibIterator.new()
iterator.iterate
return iterator
}
iteratorValue(iterator) { iterator.value }
def iteratorValue(iterator) { iterator.value }
}
var squareFib = Fib.new().map {|fib| fib * fib }

View File

@ -1,13 +1,13 @@
class TestSequence is Sequence {
construct new() {}
iterate(iterator) {
def iterate(iterator) {
if (iterator == null) return 1
if (iterator == 3) return false
return iterator + 1
}
iteratorValue(iterator) { iterator }
def iteratorValue(iterator) { iterator }
}
System.print(TestSequence.new().toList) // expect: [1, 2, 3]

View File

@ -5,25 +5,25 @@ class FibIterator {
_next = 1
}
iterate {
def iterate {
var sum = _current + _next
_current = _next
_next = sum
}
value { _current }
def value { _current }
}
class Fib is Sequence {
construct new() {}
iterate(iterator) {
def iterate(iterator) {
if (iterator == null) return FibIterator.new()
iterator.iterate
return iterator
}
iteratorValue(iterator) { iterator.value }
def iteratorValue(iterator) { iterator.value }
}
var largeFibs = Fib.new().where {|fib| fib > 100 }

View File

@ -1,7 +1,7 @@
class Foo {
construct new() {}
toString { "Foo.toString" }
def toString { "Foo.toString" }
}
// Calls toString on argument.

View File

@ -1,7 +1,7 @@
class Foo {
construct new() {}
toString { "Foo" }
def toString { "Foo" }
}
System.printAll([]) // expect:

View File

@ -1,6 +1,6 @@
class BadToString {
construct new() {}
toString { 3 }
def toString { 3 }
}
System.print(BadToString.new()) // expect: [invalid toString]

View File

@ -1,7 +1,7 @@
class Foo {
construct new() {}
toString { "Foo" }
def toString { "Foo" }
}
System.writeAll([]) // expect:

View File

@ -1,6 +1,6 @@
class BadToString {
construct new() {}
toString { 3 }
def toString { 3 }
}
System.write(BadToString.new())

View File

@ -1,7 +1,7 @@
var done = false
while (!done) {
class Foo {
method {
def method {
break // expect error
}
}

View File

@ -0,0 +1,28 @@
class Foo {
System.print("one")
construct new() {
System.print("constructor")
}
System.print("two")
construct other() {
System.print("other constructor")
}
}
// Does not execute body until object is constructed.
System.print("before") // expect: before
// Executes body before constructor body.
Foo.new() // expect: one
// expect: two
// expect: constructor
// Executes body for each constructor.
Foo.other() // expect: one
// expect: two
// expect: other constructor
// TODO: Calling (and not calling) superclass constructor in subclass.

View File

@ -1,16 +1,16 @@
class foo {
construct new() {}
static callFoo {
static def callFoo {
System.print(foo)
}
callFoo {
def callFoo {
System.print(foo)
}
foo { "instance foo method" }
static foo { "static foo method" }
def foo { "instance foo method" }
static def foo { "static foo method" }
}
foo.callFoo // expect: static foo method

View File

@ -0,0 +1,19 @@
class A {
construct new() {}
class B {
construct new() {}
class C {
construct new() {}
class D {
construct new() {}
def test() { System.print("ok") }
}
}
}
}
A.new().B.new().C.new().D.new().test() // expect: ok

View File

@ -1,15 +1,15 @@
class Foo {
construct new() {}
static sayName {
static def sayName {
System.print(Foo)
}
sayName {
def sayName {
System.print(Foo)
}
static toString { "Foo!" }
static def toString { "Foo!" }
}
Foo.sayName // expect: Foo!

View File

@ -0,0 +1,18 @@
class Outer {
construct new() {
// TODO: Remove "this." when capitalized names are no longer top-level.
System.print(this.Inner) // expect: Inner
System.print(this.Inner.new().foo) // expect: Inner foo
System.print(foo) // expect: Outer foo
}
class Inner {
construct new() {}
def foo { "Inner foo" }
}
def foo { "Outer foo" }
}
Outer.new()

View File

@ -0,0 +1,6 @@
class Foo {
def // expect error
method {} // expect error
}
// The second error is cascaded.

View File

@ -1,6 +1,4 @@
class Foo {
static // expect error
method {} // expect error
def method {}
}
// The second error is cascaded.

View File

@ -0,0 +1,12 @@
class Outer {
// TODO: Remove "this." when capitalized names are no longer top-level.
this.Inner.new() // expect runtime error: Null does not implement 'new()'.
class Inner {
construct new() {}
}
construct new() {}
}
Outer.new()

View File

@ -8,4 +8,4 @@ class B {
// No newline after last method.
class C {
method {} }
def method {} }

View File

@ -0,0 +1,9 @@
class Foo {
construct new() {}
System.print(this)
def toString { "foo" }
}
Foo.new() // expect: foo

View File

@ -0,0 +1,23 @@
class Foo {
System.print("one")
construct new() {
System.print("six")
}
var bar = System.print("two")
System.print("three")
var baz = System.print("four")
System.print("five")
}
var foo = Foo.new()
// expect: one
// expect: two
// expect: three
// expect: four
// expect: five
// expect: six

View File

@ -0,0 +1,7 @@
class Foo {
construct new() {}
var bar =
"value"
}
System.print(Foo.new().bar) // expect: value

View File

@ -0,0 +1,5 @@
class Foo {
construct new() {}
var // expect error
bar
}

View File

@ -0,0 +1,9 @@
class Foo {
construct new() {}
var bar = "init"
}
var foo = Foo.new()
System.print(foo.bar) // expect: init
System.print(foo.bar = "value") // expect: value
System.print(foo.bar) // expect: value

View File

@ -0,0 +1,12 @@
class Foo {
construct new() {}
var bar
}
var foo = Foo.new()
System.print(foo.bar) // expect: null
System.print(foo.bar = "value") // expect: value
System.print(foo.bar) // expect: value
// TODO: Duplicate.
// TODO: static var.

View File

@ -3,7 +3,7 @@ var F = null
class Foo {
construct new() {}
method(param) {
def method(param) {
F = Fn.new {
System.print(param)
}

View File

@ -1,7 +1,7 @@
class Foo {
construct new() {}
static bar { true }
static baz { 1 }
static def bar { true }
static def baz { 1 }
}
// Condition precedence.

View File

@ -1,5 +1,5 @@
class Foo {
this +(value) { // expect error
construct +(value) { // expect error
System.print("ok")
}
}

View File

@ -1,5 +1,5 @@
class Foo {
this -(value) { // expect error
construct -(value) { // expect error
System.print("ok")
}
}

View File

@ -1,5 +1,5 @@
class Foo {
this name=(value) { // expect error
construct name=(value) { // expect error
System.print("ok")
}
}

View File

@ -1,5 +1,5 @@
class Foo {
this [value] { // expect error
construct [value] { // expect error
System.print("ok")
}
}

View File

@ -1,5 +1,5 @@
class Foo {
this ! { // expect error
construct ! { // expect error
System.print("ok")
}
}

View File

@ -2,7 +2,7 @@ class Foo {
construct named() { _field = "named" }
construct other() { _field = "other" }
toString { _field }
def toString { _field }
}
System.print(Foo.named()) // expect: named

View File

@ -1,5 +1,5 @@
class Foo {
this new { // expect error
construct new { // expect error
System.print("ok")
}
}

View File

@ -4,7 +4,7 @@ class A {
_field = arg
}
aField { _field }
def aField { _field }
}
class B is A {
@ -14,7 +14,7 @@ class B is A {
_field = arg1
}
bField { _field }
def bField { _field }
}
class C is B {
@ -24,7 +24,7 @@ class C is B {
_field = "c"
}
cField { _field }
def cField { _field }
}
var c = C.new()

View File

@ -1,11 +1,11 @@
class Foo {
construct new() { _field = "Foo field" }
closeOverGet {
def closeOverGet {
return Fn.new { _field }
}
closeOverSet {
def closeOverSet {
return Fn.new { _field = "new value" }
}
}

View File

@ -1,6 +1,6 @@
class Foo {
construct new() {}
write { System.print(_field) }
def write { System.print(_field) }
}
Foo.new().write // expect: null

View File

@ -0,0 +1,16 @@
class Foo {
construct new() {
System.print("constructor %(_field)")
}
System.print("before %(_field)")
_field = "initialized"
System.print("after %(_field)")
}
Foo.new()
// expect: before null
// expect: after initialized
// expect: constructor initialized

View File

@ -1,5 +1,5 @@
foreign class Foo {
bar {
def bar {
// Can't read a field.
System.print(_bar) // expect error

View File

@ -1,5 +1,5 @@
class Foo {
static bar {
static def bar {
Fn.new { _field = "wat" } // expect error
}
}

View File

@ -1,5 +1,5 @@
class Foo {
static bar {
static def bar {
_field = "wat" // expect error
}
}

View File

@ -4,9 +4,9 @@
// the enclosing instance is closed over.
class Outer {
foo {
def foo {
class Inner {
static bar {
static def bar {
_field = "nope" // expect error
}
}

View File

@ -1,7 +1,7 @@
class Foo {
construct new() {}
set(a, b, c, d, e) {
def set(a, b, c, d, e) {
_a = a
_b = b
_c = c
@ -9,7 +9,7 @@ class Foo {
_e = e
}
write {
def write {
System.print(_a)
System.print(_b)
System.print(_c)

View File

@ -1,14 +1,14 @@
class Outer {
construct new() {}
method {
def method {
_field = "outer"
System.print(_field) // expect: outer
class Inner {
construct new() {}
method {
def method {
_field = "inner"
System.print(_field) // expect: inner
}

View File

@ -6,7 +6,7 @@ class Node {
_right = right
}
write() {
def write() {
if (_left is Node) {
_left.write()
}

View File

@ -1,7 +1,7 @@
class Foo {
construct new() {}
write { System.print(_field) } // Compile a use of the field...
init { _field = "value" } // ...before an assignment to it.
def write { System.print(_field) } // Compile a use of the field...
def init { _field = "value" } // ...before an assignment to it.
}
var foo = Foo.new()

View File

@ -1,7 +1,7 @@
class Iter {
construct new(value) { _value = value }
iterate(iterator) { _value }
iteratorValue(iterator) { "value" }
def iterate(iterator) { _value }
def iteratorValue(iterator) { "value" }
}
// False and null are false.

View File

@ -1,3 +1,3 @@
class Foo {
static foreign method // expect error
static def foreign method // expect error
}

View File

@ -1,5 +1,5 @@
class Foo {
method() {
def method() {
_field = "value"
}
}

View File

@ -1,3 +1,5 @@
class Foo {
foreign method { "body" } // expect error
}
foreign def method { "body" } // expect error
} // expect error
// The second error is cascaded.

View File

@ -1,3 +1,3 @@
class Foo {
foreign someUnknownMethod // expect runtime error: Could not find foreign method 'someUnknownMethod' for class Foo in module 'main'.
foreign def someUnknownMethod // expect runtime error: Could not find foreign method 'someUnknownMethod' for class Foo in module 'main'.
}

View File

@ -1,15 +1,15 @@
class Foo {
construct new() {}
getter {
def getter {
System.print("getter")
}
setter=(value) {
def setter=(value) {
System.print("setter")
}
method(a) {
def method(a) {
System.print("method")
}
}
@ -17,7 +17,7 @@ class Foo {
class Bar is Foo {
construct new() {}
test {
def test {
getter // expect: getter
setter = "value" // expect: setter
method("arg") // expect: method

View File

@ -1,19 +1,19 @@
class Foo {
construct new() {}
getter {
def getter {
System.print("getter")
}
setter=(value) {
def setter=(value) {
System.print("setter")
}
method(a) {
def method(a) {
System.print("method")
}
test {
def test {
getter // expect: getter
setter = "value" // expect: setter
method("arg") // expect: method

View File

@ -1,9 +1,9 @@
class Foo {
construct new() {}
bar { "getter" }
def bar { "getter" }
test {
def test {
System.print(bar) // expect: getter
{

View File

@ -1,12 +1,12 @@
class Foo {
construct new() {}
bar=(value) {
def bar=(value) {
System.print("setter")
return value
}
test {
def test {
bar = "value" // expect: setter
{

View File

@ -1,19 +1,19 @@
class Outer {
construct new() {}
getter {
def getter {
System.print("outer getter")
}
setter=(value) {
def setter=(value) {
System.print("outer setter")
}
method(a) {
def method(a) {
System.print("outer method")
}
test {
def test {
getter // expect: outer getter
setter = "value" // expect: outer setter
method("arg") // expect: outer method
@ -21,19 +21,19 @@ class Outer {
class Inner {
construct new() {}
getter {
def getter {
System.print("inner getter")
}
setter=(value) {
def setter=(value) {
System.print("inner setter")
}
method(a) {
def method(a) {
System.print("inner method")
}
test {
def test {
getter // expect: inner getter
setter = "value" // expect: inner setter
method("arg") // expect: inner method

View File

@ -1,17 +1,17 @@
class Foo {
static getter {
static def getter {
System.print("getter")
}
static setter=(value) {
static def setter=(value) {
System.print("setter")
}
static method(a) {
static def method(a) {
System.print("method")
}
static test {
static def test {
getter // expect: getter
setter = "value" // expect: setter
method("arg") // expect: method

View File

@ -1,5 +1,5 @@
class Foo {
static methodOnFoo { System.print("foo") }
static def methodOnFoo { System.print("foo") }
}
class Bar is Foo {}

View File

@ -1,12 +1,12 @@
class Foo {
construct new() {}
foo(a, b) {
def foo(a, b) {
_field1 = a
_field2 = b
}
fooPrint {
def fooPrint {
System.print(_field1)
System.print(_field2)
}
@ -15,12 +15,12 @@ class Foo {
class Bar is Foo {
construct new() {}
bar(a, b) {
def bar(a, b) {
_field1 = a
_field2 = b
}
barPrint {
def barPrint {
System.print(_field1)
System.print(_field2)
}

View File

@ -0,0 +1,34 @@
class Outer {
construct new() {}
def outerBefore() { System.print("Outer before") }
def shadowOuter() { System.print("bad") }
class Inner {
construct new() {}
def innerBefore() { System.print("Inner before") }
def shadowInner() { System.print("bad") }
class MoreInner {
construct new() {}
def shadowOuter() { System.print("ok") }
def shadowInner() { System.print("ok") }
}
def innerAfter() { System.print("Inner after") }
}
def outerAfter() { System.print("Outer after") }
}
var moreInner = Outer.new().Inner.new().MoreInner.new()
moreInner.outerBefore() // expect: Outer before
moreInner.outerAfter() // expect: Outer after
moreInner.innerBefore() // expect: Inner before
moreInner.innerAfter() // expect: Inner after
moreInner.shadowOuter() // expect: ok
moreInner.shadowInner() // expect: ok
// TODO: Test how super interacts with enclosing classes.

View File

@ -1,16 +1,16 @@
class Foo {
methodOnFoo { System.print("foo") }
method(a) { System.print("foo") }
method(a, b, c) { System.print("foo") }
override { System.print("foo") }
def methodOnFoo { System.print("foo") }
def method(a) { System.print("foo") }
def method(a, b, c) { System.print("foo") }
def override { System.print("foo") }
}
class Bar is Foo {
construct new() {}
methodOnBar { System.print("bar") }
method(a, b) { System.print("bar") }
method(a, b, c, d) { System.print("bar") }
override { System.print("bar") }
def methodOnBar { System.print("bar") }
def method(a, b) { System.print("bar") }
def method(a, b, c, d) { System.print("bar") }
def override { System.print("bar") }
}
var bar = Bar.new()

View File

@ -1,11 +1,11 @@
class Foo {
construct new() { _field = "Foo field" }
closeOverFooGet {
def closeOverFooGet {
return Fn.new { Fn.new { _field } }
}
closeOverFooSet {
def closeOverFooSet {
return Fn.new { Fn.new { _field = "new foo value" } }
}
}
@ -16,11 +16,11 @@ class Bar is Foo {
_field = "Bar field"
}
closeOverBarGet {
def closeOverBarGet {
return Fn.new { Fn.new { _field } }
}
closeOverBarSet {
def closeOverBarSet {
return Fn.new { Fn.new { _field = "new bar value" } }
}
}

View File

@ -0,0 +1,15 @@
class Base {
def method() { System.print("Base") }
}
class Outer {
construct new() {}
def method() { System.print("Outer") }
class Inner is Base {
construct new() {}
}
}
Outer.new().Inner.new().method() // expect: Base

View File

@ -1,23 +1,23 @@
class Foo {
construct new() {}
method { "getter" }
method() { "no args" }
method(a) { a }
method(a, b) { a + b }
method(a, b, c) { a + b + c }
method(a, b, c, d) { a + b + c + d }
method(a, b, c, d, e) { a + b + c + d + e }
method(a, b, c, d, e, f) { a + b + c + d + e + f }
method(a, b, c, d, e, f, g) { a + b + c + d + e + f + g }
method(a, b, c, d, e, f, g, h) { a + b + c + d + e + f + g + h }
method(a, b, c, d, e, f, g, h, i) { a + b + c + d + e + f + g + h + i }
method(a, b, c, d, e, f, g, h, i, j) { a + b + c + d + e + f + g + h + i + j }
method(a, b, c, d, e, f, g, h, i, j, k) { a + b + c + d + e + f + g + h + i + j + k}
method(a, b, c, d, e, f, g, h, i, j, k, l) { a + b + c + d + e + f + g + h + i + j + k + l}
method(a, b, c, d, e, f, g, h, i, j, k, l, m) { a + b + c + d + e + f + g + h + i + j + k + l + m}
method(a, b, c, d, e, f, g, h, i, j, k, l, m, n) { a + b + c + d + e + f + g + h + i + j + k + l + m + n}
method(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) { a + b + c + d + e + f + g + h + i + j + k + l + m + n + o}
method(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) { a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p}
def method { "getter" }
def method() { "no args" }
def method(a) { a }
def method(a, b) { a + b }
def method(a, b, c) { a + b + c }
def method(a, b, c, d) { a + b + c + d }
def method(a, b, c, d, e) { a + b + c + d + e }
def method(a, b, c, d, e, f) { a + b + c + d + e + f }
def method(a, b, c, d, e, f, g) { a + b + c + d + e + f + g }
def method(a, b, c, d, e, f, g, h) { a + b + c + d + e + f + g + h }
def method(a, b, c, d, e, f, g, h, i) { a + b + c + d + e + f + g + h + i }
def method(a, b, c, d, e, f, g, h, i, j) { a + b + c + d + e + f + g + h + i + j }
def method(a, b, c, d, e, f, g, h, i, j, k) { a + b + c + d + e + f + g + h + i + j + k}
def method(a, b, c, d, e, f, g, h, i, j, k, l) { a + b + c + d + e + f + g + h + i + j + k + l}
def method(a, b, c, d, e, f, g, h, i, j, k, l, m) { a + b + c + d + e + f + g + h + i + j + k + l + m}
def method(a, b, c, d, e, f, g, h, i, j, k, l, m, n) { a + b + c + d + e + f + g + h + i + j + k + l + m + n}
def method(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) { a + b + c + d + e + f + g + h + i + j + k + l + m + n + o}
def method(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) { a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p}
}
var foo = Foo.new()

Some files were not shown because too many files have changed in this diff Show More