Make constructors just methods.

* Eliminate "new" reserved word.
* Allow "this" before a method definition to define a constructor.
* Only create a default constructor for classes that don't define one.
This commit is contained in:
Bob Nystrom
2015-07-10 09:18:22 -07:00
parent 0ddaa2517c
commit 5fb6186d7d
221 changed files with 864 additions and 654 deletions

View File

@ -54,9 +54,9 @@ class Sequence {
isEmpty { iterate(null) ? false : true }
map(transformation) { new MapSequence(this, transformation) }
map(transformation) { MapSequence.new(this, transformation) }
where(predicate) { new WhereSequence(this, predicate) }
where(predicate) { WhereSequence.new(this, predicate) }
reduce(acc, f) {
for (element in this) {
@ -94,7 +94,7 @@ class Sequence {
}
toList {
var result = new List
var result = List.new()
for (element in this) {
result.add(element)
}
@ -103,7 +103,7 @@ class Sequence {
}
class MapSequence is Sequence {
new(sequence, fn) {
this new(sequence, fn) {
_sequence = sequence
_fn = fn
}
@ -113,7 +113,7 @@ class MapSequence is Sequence {
}
class WhereSequence is Sequence {
new(sequence, fn) {
this new(sequence, fn) {
_sequence = sequence
_fn = fn
}
@ -129,11 +129,11 @@ class WhereSequence is Sequence {
}
class String is Sequence {
bytes { new StringByteSequence(this) }
bytes { StringByteSequence.new(this) }
}
class StringByteSequence is Sequence {
new(string) {
this new(string) {
_string = string
}
@ -162,8 +162,8 @@ class List is Sequence {
}
class Map {
keys { new MapKeySequence(this) }
values { new MapValueSequence(this) }
keys { MapKeySequence.new(this) }
values { MapValueSequence.new(this) }
toString {
var first = true
@ -180,7 +180,7 @@ class Map {
}
class MapKeySequence is Sequence {
new(map) {
this new(map) {
_map = map
}
@ -189,7 +189,7 @@ class MapKeySequence is Sequence {
}
class MapValueSequence is Sequence {
new(map) {
this new(map) {
_map = map
}

View File

@ -64,7 +64,7 @@ class is fine:
And you can call each of the methods like so:
:::dart
var unicorn = new Unicorn
var unicorn = Unicorn.new()
unicorn.prance
unicorn.prance("Antwerp")
unicorn.prance("Brussels", "high noon")
@ -86,7 +86,7 @@ method that takes an *empty* argument list (`()`) and no argument list at all:
method() { "empty argument list" }
}
var confusing = new Confusing
var confusing = Confusing.new()
confusing.method // "no argument list".
confusing.method() // "empty argument list".
@ -159,39 +159,50 @@ overloading by arity, it's no problem for a class to define both.
## Constructors
To create a new instance of a class, you use the `new` keyword. We can make a
unicorn like so:
To create a new instance of a class, call a *constructor method* on its class.
By default, if you don't define any constructors yourself, you get a free one
named `new()`:
:::dart
new Unicorn
Unicorn.new()
You almost always want to define some state or do some other initialization on
a new object. For that, you'll want to define a constructor, like so:
However, you almost always want to define some state or do some other
initialization on a new object. For that, you'll want to define your own
constructor, like so:
:::dart
class Unicorn {
new {
IO.print("I am a constructor!")
}
}
When you create an instance with `new`, its constructor will be invoked. It's
just a method with a special name. Like methods, you can pass arguments to the
constructor by adding a parenthesized parameter list after `new`:
:::dart
class Unicorn {
new(name, color) {
this new(name, color) {
IO.print("My name is " + name + " and I am " + color + ".")
}
}
Values are passed to the constructor like so:
The `this` before the method name makes it a constructor. The `new` isn't
special. Constructors can have any name you like, which lets you clarify how it
creates the instance:
:::dart
new Unicorn("Flicker", "purple")
class Unicorn {
this brown(name) {
IO.print("My name is " + name + " and I am brown.")
}
}
Like other methods, you can overload constructors by [arity](#signature).
Constructors can obviously have arguments, and can be overloaded by
[arity](#signature). A constructor *must* be a named method with a (possibly
empty) argument list. Operators, getters, and setters cannot be constructors.
A constructor is actually a pair of methods. You get a method on the class:
:::dart
Unicorn.brown("Fred")
That creates the new instance, then it invokes the *initializer* on that
instance. This is where the constructor body you defined gets run.
This distinction is important because it means inside the body of the
constructor, you can access `this`, assign [fields](#fields), call superclass
constructors, etc.
## Fields
@ -292,8 +303,8 @@ And also instance methods. When you do so, there is still only one static field
shared among all instances of the class:
:::dart
var foo1 = new Foo
var foo2 = new Foo
var foo1 = Foo.new()
var foo2 = Foo.new()
foo1.setFromInstance("updated")
IO.print(foo2.baz) // updated.
@ -330,17 +341,36 @@ are not inherited.
Pegasus.canFly // ERROR: Static methods are not inherited.
Constructors, however, initialize the instance *after* it has been created.
They are defined as instance methods on the class and not on the metaclass.
That means that constructors *are* inherited.
This also means constructors are not inherited:
:::dart
class Unicorn {
new(name) {
this new(name) {
IO.print("My name is " + name + ".")
}
}
class Pegasus is Unicorn {}
new Pegasus("Fred") // Prints "My name is Fred.".
Pegasus.new("Fred") // Error!
Each class gets to control how it may be constructed independently of its base
classes. However, constructor *initializers* are inherited since those are
instance methods on the new object.
This means you can do `super` calls inside a constructor:
:::dart
class Unicorn {
this new(name) {
IO.print("My name is " + name + ".")
}
}
class Pegasus is Unicorn {
this new(name) {
super(name)
}
}
Pegasus.new("Fred") // Prints "My name is Fred.".

View File

@ -3,13 +3,13 @@
A lightweight coroutine. [Here](../fibers.html) is a gentle introduction.
### new **Fiber**(function)
### Fiber.**new**(function)
Creates a new fiber that executes `function` in a separate coroutine when the
fiber is run. Does not immediately start running the fiber.
:::dart
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("I won't get printed")
}
@ -25,7 +25,7 @@ Pauses the current fiber and transfers control to the parent fiber. "Parent"
here means the last fiber that was started using `call` and not `run`.
:::dart
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("Before yield")
Fiber.yield()
IO.print("After yield")
@ -41,7 +41,7 @@ If a yielded fiber is resumed by calling `call()` or `run()` with an argument,
`yield()` returns that value.
:::dart
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print(Fiber.yield()) // "value"
}
@ -65,7 +65,7 @@ Similar to `Fiber.yield` but provides a value to return to the parent fiber's
`call`.
:::dart
var fiber = new Fiber {
var fiber = Fiber.new {
Fiber.yield("value")
}
@ -78,7 +78,7 @@ Similar to `Fiber.yield` but provides a value to return to the parent fiber's
Starts or resumes the fiber if it is in a paused state.
:::dart
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("Fiber called")
Fiber.yield()
IO.print("Fiber called again")
@ -94,7 +94,7 @@ If the called fiber is resuming from a yield, the `yield()` method returns
`null` in the called fiber.
:::dart
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print(Fiber.yield())
}
@ -107,7 +107,7 @@ Invokes the fiber or resumes the fiber if it is in a paused state and sets
`value` as the returned value of the fiber's call to `yield`.
:::dart
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print(Fiber.yield())
}

View File

@ -4,7 +4,7 @@
A first class function—an object that wraps an executable chunk of code.
[Here](../functions.html) is a friendly introduction.
### new **Fn**(function)
### Fn.**new**(function)
Creates a new function from... `function`. Of course, `function` is already a
function, so this really just returns the argument. It exists mainly to let you
@ -12,7 +12,7 @@ create a "bare" function when you don't want to immediately pass it as a [block
argument](../functions.html#block-arguments) to some other method.
:::dart
var fn = new Fn {
var fn = Fn.new {
IO.print("The body")
}
@ -25,15 +25,15 @@ It is a runtime error if `function` is not a function.
The number of arguments the function requires.
:::dart
IO.print(new Fn {}.arity) // 0.
IO.print(new Fn {|a, b, c| a }.arity) // 3.
IO.print(Fn.new {}.arity) // 0.
IO.print(Fn.new {|a, b, c| a }.arity) // 3.
### **call**(args...)
Invokes the function with the given arguments.
:::dart
var fn = new Fn { |arg|
var fn = Fn.new { |arg|
IO.print(arg)
}

View File

@ -109,11 +109,11 @@ To force eager evaluation, just call `.toList` on the result.
### **reduce**(function)
Reduces the sequence down to a single value. `function` is a function that takes
two arguments, the accumulator and sequence item and returns the new accumulator
value. The accumulator is initialized from the first item in the sequence. Then,
the function is invoked on each remaining item in the sequence, iteratively
updating the accumulator.
Reduces the sequence down to a single value. `function` is a function that
takes two arguments, the accumulator and sequence item and returns the new
accumulator value. The accumulator is initialized from the first item in the
sequence. Then, the function is invoked on each remaining item in the sequence,
iteratively updating the accumulator.
It is a runtime error to call this on an empty sequence.

View File

@ -52,7 +52,7 @@ superclasses) don't define that method, there's nothing Wren can do:
:::dart
class Foo {}
var foo = new Foo
var foo = Foo.new()
foo.someRandomMethod
If you run this, Wren will print:
@ -103,7 +103,7 @@ error message as a string.
For example, if you run this program:
:::dart
var fiber = new Fiber {
var fiber = Fiber.new {
123.badMethod
}

View File

@ -113,13 +113,13 @@ base class constructor:
:::dart
class Base {
new(arg) {
this new(arg) {
IO.print("base constructor got ", arg)
}
}
class Derived is Base {
new {
this new() {
super("value") // Prints "base constructor got value".
}
}

View File

@ -27,7 +27,7 @@ script, a main fiber is created for you automatically. You can spawn new fibers
using the `Fiber` class's constructor:
:::dart
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("This runs in a separate fiber.")
}
@ -48,7 +48,7 @@ until it passes control to another fiber. If it reaches the end of its body,
it's considered *done*:
:::dart
var fiber = new Fiber { IO.print("Hi") }
var fiber = Fiber.new { IO.print("Hi") }
fiber.isDone // false
fiber.call()
fiber.isDone // true
@ -70,7 +70,7 @@ fiber is called, it picks up right where it left off and keeps going.
You can make a fiber yield by calling the static `yield()` method on `Fiber`:
:::dart
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("fiber 1")
Fiber.yield()
IO.print("fiber 2")
@ -103,7 +103,7 @@ fiber has yielded and is waiting to resume, the value becomes the return value
of the `yield()` call:
:::dart
var fiber = new Fiber {
var fiber = Fiber.new {
var result = Fiber.yield()
IO.print(result)
}
@ -120,7 +120,7 @@ Fibers can also pass values *back* when they yield. If you pass an argument to
invoke the fiber:
:::dart
var fiber = new Fiber {
var fiber = Fiber.new {
Fiber.yield("sent")
}
@ -140,7 +140,7 @@ Wren's fibers can do that, but they can do much more. Like Lua, they are full
example:
:::dart
var fiber = new Fiber {
var fiber = Fiber.new {
(1..10).map {|i|
Fiber.yield(i)
}

View File

@ -61,7 +61,7 @@ but *don't* need to pass it to a method. For that, you can call the `Fn`
class's constructor:
:::dart
var someFn = new Fn {
var someFn = Fn.new {
IO.print("Hi!")
}
@ -91,7 +91,7 @@ method is dynamically-dispatched like any other, so you can define your own
}
}
blondie.callMe(new FakeFn)
blondie.callMe(FakeFn.new())
## Function parameters
@ -130,9 +130,9 @@ value using a `return` statement. In other words, these two functions do the
same thing:
:::dart
new Fn { "return value" }
Fn.new { "return value" }
new Fn {
Fn.new {
return "return value"
}
@ -146,7 +146,7 @@ leaving the scope where the function is defined:
class Counter {
static create {
var i = 0
return new Fn { i = i + 1 }
return Fn.new { i = i + 1 }
}
}

View File

@ -14,7 +14,7 @@ a familiar, modern [syntax][].
}
}
var adjectives = new Fiber {
var adjectives = Fiber.new {
["small", "clean", "fast"].each {|word| Fiber.yield(word) }
}

View File

@ -56,12 +56,12 @@ Here's the same example in Wren:
:::dart
class Account {
new(balance) { _balance = balance }
this new(balance) { _balance = balance }
withdraw(amount) { _balance = _balance - amount }
}
// create and use an Account
var acc = new Account(1000)
var acc = Account.new(1000)
acc.withdraw(100)
Classes have a reputation for complexity because most of the widely used

View File

@ -28,8 +28,8 @@ Some people like to see all of the reserved words in a programming language in
one lump. If you're one of those folks, here you go:
:::dart
break class else false for foreign if import in is
new null return static super this true var while
break class else false for foreign if import in
is null return static super this true var while
## Identifiers
@ -102,7 +102,7 @@ compact notation:
:::dart
{ "single expression" }
If there is no newline after the `{` (or after the parameter list in a of
If there is no newline after the `{` (or after the parameter list in a
[function](functions.html)), then the block may only contain a single
expression, and it automatically returns the result of it. It's exactly the
same as doing:

View File

@ -1,7 +1,7 @@
import "cthulu" for Cthulu
class Lovecraft {
say { (new Cthulu).message }
say() { Cthulu.new().message }
}
IO.print((new Lovecraft).say)
IO.print(Lovecraft.new().say())

View File

@ -7,7 +7,7 @@
// Class definition with a toplevel name.
class SyntaxExample {
// Constructor
new {
this new() {
// Top-level name `IO`
IO.print("I am a constructor!")
@ -26,7 +26,7 @@ class SyntaxExample {
}
// Constructor with arguments
new(a, b) {
this constructor(a, b) {
print(a, b)
field = a
}
@ -82,14 +82,11 @@ class SyntaxExample {
// `class`, `is`
class ReservedWords is SyntaxExample {
// `new`
new {
reserved {
// `super`, `true`, `false`
super(true, false)
// `this`
this.foo
// `new`
new SyntaxExample
}
foo {

View File

@ -148,14 +148,15 @@ class Test:
def validate_runtime_error(self, error_lines):
if not error_lines:
if len(error_lines) < 2:
self.fail('Expected runtime error "{0} and got none.',
runtime_error_message)
self.runtime_error_message)
return
# Make sure we got the right error.
if error_lines[0] != self.runtime_error_message:
self.fail('Expected runtime error "{0}" and got:', runtime_error_message)
self.fail('Expected runtime error "{0}" and got:',
self.runtime_error_message)
self.fail(error_lines[0])
# Make sure the stack trace has the right line.

View File

@ -99,10 +99,11 @@
// foo() // No-argument method.
// foo(_) // One-argument method.
// foo(_,_) // Two-argument method.
// this foo() // Constructor initializer.
//
// The maximum signature length takes into account the longest method name, the
// maximum number of parameters with separators between them, and "()".
#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + (MAX_PARAMETERS * 2) + 1)
// maximum number of parameters with separators between them, "this ", and "()".
#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + (MAX_PARAMETERS * 2) + 6)
// The maximum length of an identifier. The only real reason for this limitation
// is so that error messages mentioning variables can be stack allocated.

View File

@ -81,7 +81,6 @@ typedef enum
TOKEN_IMPORT,
TOKEN_IN,
TOKEN_IS,
TOKEN_NEW,
TOKEN_NULL,
TOKEN_RETURN,
TOKEN_STATIC,
@ -213,6 +212,39 @@ typedef struct sLoop
struct sLoop* enclosing;
} Loop;
// The different signature syntaxes for different kinds of methods.
typedef enum
{
// A name followed by a (possibly empty) parenthesized parameter list. Also
// used for binary operators.
SIG_METHOD,
// Just a name. Also used for unary operators.
SIG_GETTER,
// A name followed by "=".
SIG_SETTER,
// A square bracketed parameter list.
SIG_SUBSCRIPT,
// A square bracketed parameter list followed by "=".
SIG_SUBSCRIPT_SETTER,
// A constructor initializer function. This has a distinct signature to
// prevent it from being invoked directly outside of the constructor on the
// metaclass.
SIG_INITIALIZER
} SignatureType;
typedef struct
{
const char* name;
int length;
SignatureType type;
int arity;
} Signature;
// Bookkeeping information for compiling a class definition.
typedef struct
{
@ -220,14 +252,10 @@ typedef struct
SymbolTable* fields;
// True if the current method being compiled is static.
bool isStaticMethod;
bool inStatic;
// The name of the method being compiled. Note that this is just the bare
// method name, and not its full signature.
const char* methodName;
// The length of the method name being compiled.
int methodLength;
// The signature of the method being compiled.
Signature* signature;
} ClassCompiler;
struct sCompiler
@ -608,7 +636,6 @@ static void readName(Parser* parser, TokenType type)
else if (isKeyword(parser, "import")) type = TOKEN_IMPORT;
else if (isKeyword(parser, "in")) type = TOKEN_IN;
else if (isKeyword(parser, "is")) type = TOKEN_IS;
else if (isKeyword(parser, "new")) type = TOKEN_NEW;
else if (isKeyword(parser, "null")) type = TOKEN_NULL;
else if (isKeyword(parser, "return")) type = TOKEN_RETURN;
else if (isKeyword(parser, "static")) type = TOKEN_STATIC;
@ -1386,34 +1413,6 @@ typedef enum
typedef void (*GrammarFn)(Compiler*, bool allowAssignment);
// The different signature syntaxes for different kinds of methods.
typedef enum
{
// A name followed by a (possibly empty) parenthesized parameter list. Also
// used for binary operators.
SIG_METHOD,
// Just a name. Also used for unary operators.
SIG_GETTER,
// A name followed by "=".
SIG_SETTER,
// A square bracketed parameter list.
SIG_SUBSCRIPT,
// A square bracketed parameter list followed by "=".
SIG_SUBSCRIPT_SETTER
} SignatureType;
typedef struct
{
const char* name;
int length;
SignatureType type;
int arity;
} Signature;
typedef void (*SignatureFn)(Compiler* compiler, Signature* signature);
typedef struct
@ -1484,13 +1483,16 @@ static bool finishBlock(Compiler* compiler)
}
// Parses a method or function body, after the initial "{" has been consumed.
static void finishBody(Compiler* compiler, bool isConstructor)
//
// 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 (isConstructor)
if (isInitializer)
{
// If the constructor body evaluates to a value, discard it.
// If the initializer body evaluates to a value, discard it.
if (isExpressionBody) emit(compiler, CODE_POP);
// The receiver is always stored in the first local slot.
@ -1559,9 +1561,11 @@ static void signatureParameterList(char name[MAX_METHOD_SIGNATURE], int* length,
static void signatureToString(Signature* signature,
char name[MAX_METHOD_SIGNATURE], int* length)
{
*length = 0;
// Build the full name from the signature.
*length = signature->length;
memcpy(name, signature->name, *length);
memcpy(name + *length, signature->name, signature->length);
*length += signature->length;
switch (signature->type)
{
@ -1587,6 +1591,13 @@ static void signatureToString(Signature* signature,
name[(*length)++] = '=';
signatureParameterList(name, length, 1, '(', ')');
break;
case SIG_INITIALIZER:
memcpy(name, "this ", 5);
memcpy(name + 5, signature->name, signature->length);
*length = 5 + signature->length;
signatureParameterList(name, length, signature->arity, '(', ')');
break;
}
name[*length] = '\0';
@ -1608,7 +1619,6 @@ static void signatureFromToken(Compiler* compiler, Signature* signature)
{
// Get the token for the method name.
Token* token = &compiler->parser->previous;
signature->type = SIG_GETTER;
signature->arity = 0;
signature->name = token->start;
signature->length = token->length;
@ -1667,15 +1677,18 @@ static void callMethod(Compiler* compiler, int numArgs, const char* name,
emitShortArg(compiler, (Code)(CODE_CALL_0 + numArgs), symbol);
}
// Compiles an (optional) argument list and then calls it.
// Compiles an (optional) argument list for a method call with [methodSignature]
// and then calls it.
static void methodCall(Compiler* compiler, Code instruction,
const char* name, int length)
Signature* methodSignature)
{
// Make a new signature that contains the updated arity and type based on
// the arguments we find.
Signature signature;
signature.type = SIG_GETTER;
signature.arity = 0;
signature.name = name;
signature.length = length;
signature.name = methodSignature->name;
signature.length = methodSignature->length;
// Parse the argument list, if any.
if (match(compiler, TOKEN_LEFT_PAREN))
@ -1726,6 +1739,18 @@ static void methodCall(Compiler* compiler, Code instruction,
// TODO: Allow Grace-style mixfix methods?
// If this is a super() call for an initializer, make sure we got an actual
// argument list.
if (methodSignature->type == SIG_INITIALIZER)
{
if (signature.type != SIG_METHOD)
{
error(compiler, "A superclass constructor must have an argument list.");
}
signature.type = SIG_INITIALIZER;
}
callSignature(compiler, instruction, &signature);
}
@ -1754,7 +1779,7 @@ static void namedCall(Compiler* compiler, bool allowAssignment,
}
else
{
methodCall(compiler, instruction, signature.name, signature.length);
methodCall(compiler, instruction, &signature);
}
}
@ -1791,7 +1816,7 @@ static void list(Compiler* compiler, bool allowAssignment)
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, listClassSymbol);
// Instantiate a new list.
callMethod(compiler, 0, "<instantiate>", 13);
callMethod(compiler, 0, "new()", 5);
// Compile the list elements. Each one compiles to a ".add()" call.
if (peek(compiler) != TOKEN_RIGHT_BRACKET)
@ -1827,7 +1852,7 @@ static void map(Compiler* compiler, bool allowAssignment)
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, mapClassSymbol);
// Instantiate a new map.
callMethod(compiler, 0, "<instantiate>", 13);
callMethod(compiler, 0, "new()", 5);
// Compile the map elements. Each one is compiled to just invoke the
// subscript setter on the map.
@ -1912,7 +1937,7 @@ static void field(Compiler* compiler, bool allowAssignment)
{
error(compiler, "Cannot reference a field outside of a class definition.");
}
else if (enclosingClass->isStaticMethod)
else if (enclosingClass->inStatic)
{
error(compiler, "Cannot use an instance field in a static method.");
}
@ -2138,14 +2163,17 @@ static void super_(Compiler* compiler, bool allowAssignment)
{
error(compiler, "Cannot use 'super' outside of a method.");
}
else if (enclosingClass->isStaticMethod)
else if (enclosingClass->inStatic)
{
// TODO: Why not?
error(compiler, "Cannot use 'super' in a static method.");
}
loadThis(compiler);
// TODO: Super operator calls.
// TODO: There's no syntax for invoking a superclass constructor with a
// different name from the enclosing one. Figure that out.
// See if it's a named super call, or an unnamed one.
if (match(compiler, TOKEN_DOT))
@ -2159,8 +2187,7 @@ static void super_(Compiler* compiler, bool allowAssignment)
// No explicit name, so use the name of the enclosing method. Make sure we
// check that enclosingClass isn't NULL first. We've already reported the
// error, but we don't want to crash here.
methodCall(compiler, CODE_SUPER_0, enclosingClass->methodName,
enclosingClass->methodLength);
methodCall(compiler, CODE_SUPER_0, enclosingClass->signature);
}
}
@ -2209,23 +2236,6 @@ static void call(Compiler* compiler, bool allowAssignment)
namedCall(compiler, allowAssignment, CODE_CALL_0);
}
static void new_(Compiler* compiler, bool allowAssignment)
{
// Allow a dotted name after 'new'.
consume(compiler, TOKEN_NAME, "Expect name after 'new'.");
name(compiler, false);
while (match(compiler, TOKEN_DOT))
{
call(compiler, false);
}
// The angle brackets in the name are to ensure users can't call it directly.
callMethod(compiler, 0, "<instantiate>", 13);
// Invoke the constructor on the new instance.
methodCall(compiler, CODE_CALL_0, "new", 3);
}
static void and_(Compiler* compiler, bool allowAssignment)
{
ignoreNewlines(compiler);
@ -2309,7 +2319,6 @@ void infixSignature(Compiler* compiler, Signature* signature)
void unarySignature(Compiler* compiler, Signature* signature)
{
// Do nothing. The name is already complete.
signature->type = SIG_GETTER;
}
// Compiles a method signature for an operator that can either be unary or
@ -2406,10 +2415,29 @@ void namedSignature(Compiler* compiler, Signature* signature)
// Compiles a method signature for a constructor.
void constructorSignature(Compiler* compiler, Signature* signature)
{
signature->type = SIG_GETTER;
signature->type = SIG_INITIALIZER;
// Add the parameters, if there are any.
parameterList(compiler, signature);
consume(compiler, TOKEN_NAME, "Expect constructor name after 'this'.");
// Capture the name.
signatureFromToken(compiler, signature);
if (match(compiler, TOKEN_EQ))
{
error(compiler, "A constructor cannot be a setter.");
}
if (!match(compiler, TOKEN_LEFT_PAREN))
{
error(compiler, "A constructor cannot be a getter.");
return;
}
// Allow an empty parameter list.
if (match(compiler, TOKEN_RIGHT_PAREN)) return;
finishParameterList(compiler, signature);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters.");
}
// This table defines all of the parsing rules for the prefix and infix
@ -2468,12 +2496,11 @@ GrammarRule rules[] =
/* TOKEN_IMPORT */ UNUSED,
/* TOKEN_IN */ UNUSED,
/* TOKEN_IS */ INFIX_OPERATOR(PREC_IS, "is"),
/* TOKEN_NEW */ { new_, NULL, constructorSignature, PREC_NONE, NULL },
/* TOKEN_NULL */ PREFIX(null),
/* TOKEN_RETURN */ UNUSED,
/* TOKEN_STATIC */ UNUSED,
/* TOKEN_SUPER */ PREFIX(super_),
/* TOKEN_THIS */ PREFIX(this_),
/* TOKEN_THIS */ { this_, NULL, constructorSignature, PREC_NONE, NULL },
/* TOKEN_TRUE */ PREFIX(boolean),
/* TOKEN_VAR */ UNUSED,
/* TOKEN_WHILE */ UNUSED,
@ -2570,6 +2597,7 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants,
case CODE_LOAD_LOCAL_6:
case CODE_LOAD_LOCAL_7:
case CODE_LOAD_LOCAL_8:
case CODE_CONSTRUCT:
return 0;
case CODE_LOAD_LOCAL:
@ -2904,17 +2932,91 @@ void statement(Compiler* compiler)
emit(compiler, CODE_POP);
}
// Compiles a method definition inside a class body. Returns the symbol in the
// method table for the new method.
static int method(Compiler* compiler, ClassCompiler* classCompiler,
bool isConstructor, bool isForeign, SignatureFn signatureFn)
// Creates a matching constructor method for an initializer with [signature]
// and [initializerSymbol].
//
// Construction is a two-stage process in Wren that involves two separate
// methods. There is a static method that allocates a new instance of the class.
// It then invokes an initializer method on the new instance, forwarding all of
// the constructor arguments to it.
//
// The allocator method always has a fixed implementation:
//
// CODE_CONSTRUCT - Replace the class in slot 0 with a new instance of it.
// CODE_CALL - Invoke the initializer on the new instance.
//
// This creates that method and calls the initializer with [initializerSymbol].
static void createConstructor(Compiler* compiler, Signature* signature,
int initializerSymbol)
{
// Build the method signature.
Signature signature;
signatureFromToken(compiler, &signature);
Compiler methodCompiler;
initCompiler(&methodCompiler, compiler->parser, compiler, false);
classCompiler->methodName = signature.name;
classCompiler->methodLength = signature.length;
// Allocate the instance.
emit(&methodCompiler, CODE_CONSTRUCT);
// Run its initializer.
emitShortArg(&methodCompiler, CODE_CALL_0 + signature->arity,
initializerSymbol);
// Return the instance.
emit(&methodCompiler, CODE_RETURN);
endCompiler(&methodCompiler, "", 0);
}
// Loads the enclosing class onto the stack and then binds the function already
// on the stack as a method on that class.
static void defineMethod(Compiler* compiler, int classSlot, bool isStatic,
int methodSymbol)
{
// Load the class. We have to do this for each method because we can't
// keep the class on top of the stack. If there are static fields, they
// will be locals above the initial variable slot for the class on the
// stack. To skip past those, we just load the class each time right before
// defining a method.
if (compiler->scopeDepth == 0)
{
// The class is at the top level (scope depth is 0, not -1 to account for
// the static variable scope surrounding the class itself), so load it from
// there.
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, classSlot);
}
else
{
loadLocal(compiler, classSlot);
}
// Define the method.
Code instruction = isStatic ? CODE_METHOD_STATIC : CODE_METHOD_INSTANCE;
emitShortArg(compiler, instruction, methodSymbol);
}
// Compiles a method definition inside a class body.
//
// Returns `true` if it compiled successfully, or `false` if the method couldn't
// be parsed.
static bool method(Compiler* compiler, ClassCompiler* classCompiler,
int classSlot, bool* hasConstructor)
{
Signature signature;
classCompiler->signature = &signature;
// TODO: What about foreign constructors?
bool isForeign = match(compiler, TOKEN_FOREIGN);
classCompiler->inStatic = match(compiler, TOKEN_STATIC);
SignatureFn signatureFn = rules[compiler->parser->current.type].method;
nextToken(compiler->parser);
if (signatureFn == NULL)
{
error(compiler, "Expect method definition.");
return false;
}
// Build the method signature.
signatureFromToken(compiler, &signature);
Compiler methodCompiler;
initCompiler(&methodCompiler, compiler->parser, compiler, false);
@ -2922,6 +3024,11 @@ static int method(Compiler* compiler, ClassCompiler* classCompiler,
// Compile the method signature.
signatureFn(&methodCompiler, &signature);
if (classCompiler->inStatic && signature.type == SIG_INITIALIZER)
{
error(compiler, "A constructor cannot be static.");
}
// Include the full signature in debug messages in stack traces.
char fullSignature[MAX_METHOD_SIGNATURE];
int length;
@ -2941,12 +3048,52 @@ static int method(Compiler* compiler, ClassCompiler* classCompiler,
else
{
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body.");
finishBody(&methodCompiler, isConstructor);
finishBody(&methodCompiler, signature.type == SIG_INITIALIZER);
endCompiler(&methodCompiler, fullSignature, length);
}
return signatureSymbol(compiler, &signature);
// Define the method. For a constructor, this defines the instance
// initializer method.
int methodSymbol = signatureSymbol(compiler, &signature);
defineMethod(compiler, classSlot, classCompiler->inStatic, methodSymbol);
if (signature.type == SIG_INITIALIZER)
{
// Also define a matching constructor method on the metaclass.
signature.type = SIG_METHOD;
int constructorSymbol = signatureSymbol(compiler, &signature);
createConstructor(compiler, &signature, methodSymbol);
defineMethod(compiler, classSlot, true, constructorSymbol);
// We don't need a default constructor anymore.
*hasConstructor = true;
}
return true;
}
// Defines a default "new()" constructor on the current class.
//
// It just invokes "this new()" on the instance. If a base class defines that,
// it will get invoked. Otherwise, it falls to the default one in Object which
// does nothing.
static void createDefaultConstructor(Compiler* compiler, int classSlot)
{
Signature signature;
signature.name = "new";
signature.length = 3;
signature.type = SIG_INITIALIZER;
signature.arity = 0;
int initializerSymbol = signatureSymbol(compiler, &signature);
signature.type = SIG_METHOD;
int constructorSymbol = signatureSymbol(compiler, &signature);
createConstructor(compiler, &signature, initializerSymbol);
defineMethod(compiler, classSlot, true, constructorSymbol);
}
// Compiles a class definition. Assumes the "class" token has already been
@ -2955,7 +3102,6 @@ static void classDefinition(Compiler* compiler)
{
// Create a variable to store the class in.
int slot = declareNamedVariable(compiler);
bool isModule = compiler->scopeDepth == -1;
// Make a string constant for the name.
int nameConstant = addConstant(compiler, wrenNewString(compiler->parser->vm,
@ -3004,54 +3150,11 @@ static void classDefinition(Compiler* compiler)
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after class declaration.");
matchLine(compiler);
bool hasConstructor = false;
while (!match(compiler, TOKEN_RIGHT_BRACE))
{
Code instruction = CODE_METHOD_INSTANCE;
bool isConstructor = false;
// TODO: What about foreign constructors?
bool isForeign = match(compiler, TOKEN_FOREIGN);
classCompiler.isStaticMethod = false;
if (match(compiler, TOKEN_STATIC))
{
instruction = CODE_METHOD_STATIC;
classCompiler.isStaticMethod = true;
}
else if (peek(compiler) == TOKEN_NEW)
{
// If the method name is "new", it's a constructor.
isConstructor = true;
}
SignatureFn signature = rules[compiler->parser->current.type].method;
nextToken(compiler->parser);
if (signature == NULL)
{
error(compiler, "Expect method definition.");
break;
}
int methodSymbol = method(compiler, &classCompiler, isConstructor,
isForeign, signature);
// Load the class. We have to do this for each method because we can't
// keep the class on top of the stack. If there are static fields, they
// will be locals above the initial variable slot for the class on the
// stack. To skip past those, we just load the class each time right before
// defining a method.
if (isModule)
{
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, slot);
}
else
{
loadLocal(compiler, slot);
}
// Define the method.
emitShortArg(compiler, instruction, methodSymbol);
if (!method(compiler, &classCompiler, slot, &hasConstructor)) break;
// Don't require a newline after the last definition.
if (match(compiler, TOKEN_RIGHT_BRACE)) break;
@ -3059,6 +3162,12 @@ static void classDefinition(Compiler* compiler)
consumeLine(compiler, "Expect newline after definition in class.");
}
// If no constructor was defined, create a default new() one.
if (!hasConstructor)
{
createDefaultConstructor(compiler, slot);
}
// Update the class with the number of fields.
compiler->bytecode.data[numFieldsInstruction] = (uint8_t)fields.count;
wrenSymbolTableClear(compiler->parser->vm, &fields);

View File

@ -68,9 +68,9 @@ static const char* coreLibSource =
"\n"
" isEmpty { iterate(null) ? false : true }\n"
"\n"
" map(transformation) { new MapSequence(this, transformation) }\n"
" map(transformation) { MapSequence.new(this, transformation) }\n"
"\n"
" where(predicate) { new WhereSequence(this, predicate) }\n"
" where(predicate) { WhereSequence.new(this, predicate) }\n"
"\n"
" reduce(acc, f) {\n"
" for (element in this) {\n"
@ -108,7 +108,7 @@ static const char* coreLibSource =
" }\n"
"\n"
" toList {\n"
" var result = new List\n"
" var result = List.new()\n"
" for (element in this) {\n"
" result.add(element)\n"
" }\n"
@ -117,7 +117,7 @@ static const char* coreLibSource =
"}\n"
"\n"
"class MapSequence is Sequence {\n"
" new(sequence, fn) {\n"
" this new(sequence, fn) {\n"
" _sequence = sequence\n"
" _fn = fn\n"
" }\n"
@ -127,7 +127,7 @@ static const char* coreLibSource =
"}\n"
"\n"
"class WhereSequence is Sequence {\n"
" new(sequence, fn) {\n"
" this new(sequence, fn) {\n"
" _sequence = sequence\n"
" _fn = fn\n"
" }\n"
@ -143,11 +143,11 @@ static const char* coreLibSource =
"}\n"
"\n"
"class String is Sequence {\n"
" bytes { new StringByteSequence(this) }\n"
" bytes { StringByteSequence.new(this) }\n"
"}\n"
"\n"
"class StringByteSequence is Sequence {\n"
" new(string) {\n"
" this new(string) {\n"
" _string = string\n"
" }\n"
"\n"
@ -176,8 +176,8 @@ static const char* coreLibSource =
"}\n"
"\n"
"class Map {\n"
" keys { new MapKeySequence(this) }\n"
" values { new MapValueSequence(this) }\n"
" keys { MapKeySequence.new(this) }\n"
" values { MapValueSequence.new(this) }\n"
"\n"
" toString {\n"
" var first = true\n"
@ -194,7 +194,7 @@ static const char* coreLibSource =
"}\n"
"\n"
"class MapKeySequence is Sequence {\n"
" new(map) {\n"
" this new(map) {\n"
" _map = map\n"
" }\n"
"\n"
@ -203,7 +203,7 @@ static const char* coreLibSource =
"}\n"
"\n"
"class MapValueSequence is Sequence {\n"
" new(map) {\n"
" this new(map) {\n"
" _map = map\n"
" }\n"
"\n"
@ -215,13 +215,9 @@ static const char* coreLibSource =
// A simple primitive that just returns "this". Used in a few different places:
//
// * The default constructor on Object needs no initialization so just uses
// this.
// * The default new() initializer on Object needs no initialization so just
// uses this.
// * String's toString method obviously can use this.
// * Fiber's instantiate method just returns the Fiber class. The new() method
// is responsible for creating the new fiber.
// * Fn's "constructor" is given the actual function as an argument, so the
// instantiate method just returns the Fn class.
DEF_PRIMITIVE(return_this)
{
RETURN_VAL(args[0]);
@ -244,11 +240,6 @@ DEF_PRIMITIVE(bool_toString)
}
}
DEF_PRIMITIVE(class_instantiate)
{
RETURN_VAL(wrenNewInstance(vm, AS_CLASS(args[0])));
}
DEF_PRIMITIVE(class_name)
{
RETURN_OBJ(AS_CLASS(args[0])->name);
@ -532,7 +523,7 @@ DEF_PRIMITIVE(fn_toString)
RETURN_VAL(CONST_STRING(vm, "<fn>"));
}
DEF_PRIMITIVE(list_instantiate)
DEF_PRIMITIVE(list_new)
{
RETURN_OBJ(wrenNewList(vm, 0));
}
@ -649,7 +640,7 @@ DEF_PRIMITIVE(list_subscriptSetter)
RETURN_VAL(args[2]);
}
DEF_PRIMITIVE(map_instantiate)
DEF_PRIMITIVE(map_new)
{
RETURN_OBJ(wrenNewMap(vm));
}
@ -998,11 +989,6 @@ DEF_PRIMITIVE(object_type)
RETURN_OBJ(wrenGetClass(vm, args[0]));
}
DEF_PRIMITIVE(object_instantiate)
{
RETURN_ERROR("Must provide a class to 'new' to construct.");
}
DEF_PRIMITIVE(range_from)
{
RETURN_NUM(AS_RANGE(args[0])->from);
@ -1303,16 +1289,14 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->objectClass, "!", object_not);
PRIMITIVE(vm->objectClass, "==(_)", object_eqeq);
PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq);
PRIMITIVE(vm->objectClass, "new", return_this);
PRIMITIVE(vm->objectClass, "this new()", return_this);
PRIMITIVE(vm->objectClass, "is(_)", object_is);
PRIMITIVE(vm->objectClass, "toString", object_toString);
PRIMITIVE(vm->objectClass, "type", object_type);
PRIMITIVE(vm->objectClass, "<instantiate>", object_instantiate);
// Now we can define Class, which is a subclass of Object.
vm->classClass = defineClass(vm, "Class");
wrenBindSuperclass(vm, vm->classClass, vm->objectClass);
PRIMITIVE(vm->classClass, "<instantiate>", class_instantiate);
PRIMITIVE(vm->classClass, "name", class_name);
PRIMITIVE(vm->classClass, "supertype", class_supertype);
PRIMITIVE(vm->classClass, "toString", class_toString);
@ -1361,7 +1345,6 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->boolClass, "!", bool_not);
vm->fiberClass = AS_CLASS(wrenFindVariable(vm, "Fiber"));
PRIMITIVE(vm->fiberClass->obj.classObj, "<instantiate>", return_this);
PRIMITIVE(vm->fiberClass->obj.classObj, "new(_)", fiber_new);
PRIMITIVE(vm->fiberClass->obj.classObj, "abort(_)", fiber_abort);
PRIMITIVE(vm->fiberClass->obj.classObj, "current", fiber_current);
@ -1376,7 +1359,6 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->fiberClass, "try()", fiber_try);
vm->fnClass = AS_CLASS(wrenFindVariable(vm, "Fn"));
PRIMITIVE(vm->fnClass->obj.classObj, "<instantiate>", return_this);
PRIMITIVE(vm->fnClass->obj.classObj, "new(_)", fn_new);
PRIMITIVE(vm->fnClass, "arity", fn_arity);
@ -1463,7 +1445,7 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->stringClass, "toString", return_this);
vm->listClass = AS_CLASS(wrenFindVariable(vm, "List"));
PRIMITIVE(vm->listClass->obj.classObj, "<instantiate>", list_instantiate);
PRIMITIVE(vm->listClass->obj.classObj, "new()", list_new);
PRIMITIVE(vm->listClass, "[_]", list_subscript);
PRIMITIVE(vm->listClass, "[_]=(_)", list_subscriptSetter);
PRIMITIVE(vm->listClass, "add(_)", list_add);
@ -1475,7 +1457,7 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->listClass, "removeAt(_)", list_removeAt);
vm->mapClass = AS_CLASS(wrenFindVariable(vm, "Map"));
PRIMITIVE(vm->mapClass->obj.classObj, "<instantiate>", map_instantiate);
PRIMITIVE(vm->mapClass->obj.classObj, "new()", map_new);
PRIMITIVE(vm->mapClass, "[_]", map_subscript);
PRIMITIVE(vm->mapClass, "[_]=(_)", map_subscriptSetter);
PRIMITIVE(vm->mapClass, "clear()", map_clear);

View File

@ -277,6 +277,8 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
break;
}
case CODE_CONSTRUCT: printf("CODE_CONSTRUCT\n"); break;
case CODE_CLASS:
{
int numFields = READ_BYTE();

View File

@ -150,6 +150,13 @@ OPCODE(RETURN)
// Pushes the created closure.
OPCODE(CLOSURE)
// Creates a new instance of a class.
//
// Assumes the class object is in slot zero, and replaces it with the new
// uninitialized instance of that class. This opcode is only emitted by the
// compiler-generated constructor metaclass methods.
OPCODE(CONSTRUCT)
// Creates a class. Top of stack is the superclass, or `null` if the class
// inherits Object. Below that is a string for the name of the class. Byte
// [arg] is the number of fields in the class.

View File

@ -1023,6 +1023,11 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
DISPATCH();
}
CASE_CODE(CONSTRUCT):
ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class.");
stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0]));
DISPATCH();
CASE_CODE(CLOSURE):
{
ObjFn* prototype = AS_FN(fn->constants[READ_SHORT()]);

View File

@ -1,13 +1,13 @@
// Ported from the Python version.
class Tree {
new(item, depth) {
this new(item, depth) {
_item = item
if (depth > 0) {
var item2 = item + item
depth = depth - 1
_left = new Tree(item2 - 1, depth)
_right = new Tree(item2, depth)
_left = Tree.new(item2 - 1, depth)
_right = Tree.new(item2, depth)
}
}
@ -27,9 +27,9 @@ var stretchDepth = maxDepth + 1
var start = IO.clock
IO.print("stretch tree of depth ", stretchDepth, " check: ",
new Tree(0, stretchDepth).check)
Tree.new(0, stretchDepth).check)
var longLivedTree = new Tree(0, maxDepth)
var longLivedTree = Tree.new(0, maxDepth)
// iterations = 2 ** maxDepth
var iterations = 1
@ -41,7 +41,7 @@ var depth = minDepth
while (depth < stretchDepth) {
var check = 0
for (i in 1..iterations) {
check = check + new Tree(i, depth).check + new Tree(-i, depth).check
check = check + Tree.new(i, depth).check + Tree.new(-i, depth).check
}
IO.print((iterations * 2), " trees of depth ", depth, " check: ", check)

View File

@ -50,7 +50,7 @@ var ORDERED = null
// disrupting current constraints. Strengths cannot be created outside
// this class, so == can be used for value comparison.
class Strength {
new(value, name) {
this new(value, name) {
_value = value
_name = name
}
@ -67,13 +67,13 @@ class Strength {
}
// Compile time computed constants.
REQUIRED = new Strength(0, "required")
STRONG_REFERRED = new Strength(1, "strongPreferred")
PREFERRED = new Strength(2, "preferred")
STRONG_DEFAULT = new Strength(3, "strongDefault")
NORMAL = new Strength(4, "normal")
WEAK_DEFAULT = new Strength(5, "weakDefault")
WEAKEST = new Strength(6, "weakest")
REQUIRED = Strength.new(0, "required")
STRONG_REFERRED = Strength.new(1, "strongPreferred")
PREFERRED = Strength.new(2, "preferred")
STRONG_DEFAULT = Strength.new(3, "strongDefault")
NORMAL = Strength.new(4, "normal")
WEAK_DEFAULT = Strength.new(5, "weakDefault")
WEAKEST = Strength.new(6, "weakest")
ORDERED = [
WEAKEST, WEAK_DEFAULT, NORMAL, STRONG_DEFAULT, PREFERRED, STRONG_REFERRED
@ -82,7 +82,7 @@ ORDERED = [
var ThePlanner
class Constraint {
new(strength) {
this new(strength) {
_strength = strength
}
@ -131,7 +131,7 @@ class Constraint {
// Abstract superclass for constraints having a single possible output variable.
class UnaryConstraint is Constraint {
new(myOutput, strength) {
this new(myOutput, strength) {
super(strength)
_satisfied = false
_myOutput = myOutput
@ -187,7 +187,7 @@ class UnaryConstraint is Constraint {
// change their output during plan execution. This is called "stay
// optimization".
class StayConstraint is UnaryConstraint {
new(variable, strength) {
this new(variable, strength) {
super(variable, strength)
}
@ -199,7 +199,7 @@ class StayConstraint is UnaryConstraint {
// A unary input constraint used to mark a variable that the client
// wishes to change.
class EditConstraint is UnaryConstraint {
EditConstraint(variable, strength) {
this new(variable, strength) {
super(variable, strength)
}
@ -219,7 +219,7 @@ var BACKWARD = 0
// Abstract superclass for constraints having two possible output
// variables.
class BinaryConstraint is Constraint {
new(v1, v2, strength) {
this new(v1, v2, strength) {
super(strength)
_v1 = v1
_v2 = v2
@ -328,7 +328,7 @@ class BinaryConstraint is Constraint {
// this relationship but the scale factor and offset are considered
// read-only.
class ScaleConstraint is BinaryConstraint {
new(src, scale, offset, dest, strength) {
this new(src, scale, offset, dest, strength) {
_scale = scale
_offset = offset
super(src, dest, strength)
@ -376,7 +376,7 @@ class ScaleConstraint is BinaryConstraint {
// Constrains two variables to have the same value.
class EqualityConstraint is BinaryConstraint {
new(v1, v2, strength) {
this new(v1, v2, strength) {
super(v1, v2, strength)
}
@ -391,7 +391,7 @@ class EqualityConstraint is BinaryConstraint {
// various parameters of interest to the DeltaBlue incremental
// constraint solver.
class Variable {
new(name, value) {
this new(name, value) {
_constraints = []
_determinedBy = null
_mark = 0
@ -430,7 +430,7 @@ class Variable {
// to resatisfy all currently satisfiable constraints in the face of
// one or more changing inputs.
class Plan {
new {
this new() {
_list = []
}
@ -448,7 +448,7 @@ class Plan {
}
class Planner {
new {
this new() {
_currentMark = 0
}
@ -521,7 +521,7 @@ class Planner {
// Assume: [sources] are all satisfied.
makePlan(sources) {
var mark = newMark
var plan = new Plan
var plan = Plan.new()
var todo = sources
while (todo.count > 0) {
var constraint = todo.removeAt(-1)
@ -622,23 +622,23 @@ var total = 0
// constraint so it cannot be accomodated. The cost in this case is,
// of course, very low. Typical situations lie somewhere between these
// two extremes.
var chainTest = new Fn {|n|
ThePlanner = new Planner
var chainTest = Fn.new {|n|
ThePlanner = Planner.new()
var prev = null
var first = null
var last = null
// Build chain of n equality constraints.
for (i in 0..n) {
var v = new Variable("v", 0)
if (prev != null) new EqualityConstraint(prev, v, REQUIRED)
var v = Variable.new("v", 0)
if (prev != null) EqualityConstraint.new(prev, v, REQUIRED)
if (i == 0) first = v
if (i == n) last = v
prev = v
}
new StayConstraint(last, STRONG_DEFAULT)
var edit = new EditConstraint(first, PREFERRED)
StayConstraint.new(last, STRONG_DEFAULT)
var edit = EditConstraint.new(first, PREFERRED)
var plan = ThePlanner.extractPlanFromConstraints([edit])
for (i in 0...100) {
first.value = i
@ -647,8 +647,8 @@ var chainTest = new Fn {|n|
}
}
var change = new Fn {|v, newValue|
var edit = new EditConstraint(v, PREFERRED)
var change = Fn.new {|v, newValue|
var edit = EditConstraint.new(v, PREFERRED)
var plan = ThePlanner.extractPlanFromConstraints([edit])
for (i in 0...10) {
v.value = newValue
@ -662,20 +662,20 @@ var change = new Fn {|v, newValue|
// other by a simple linear transformation (scale and offset). The
// time is measured to change a variable on either side of the
// mapping and to change the scale and offset factors.
var projectionTest = new Fn {|n|
ThePlanner = new Planner
var scale = new Variable("scale", 10)
var offset = new Variable("offset", 1000)
var projectionTest = Fn.new {|n|
ThePlanner = Planner.new()
var scale = Variable.new("scale", 10)
var offset = Variable.new("offset", 1000)
var src = null
var dst = null
var dests = []
for (i in 0...n) {
src = new Variable("src", i)
dst = new Variable("dst", i)
src = Variable.new("src", i)
dst = Variable.new("dst", i)
dests.add(dst)
new StayConstraint(src, NORMAL)
new ScaleConstraint(src, scale, offset, dst, REQUIRED)
StayConstraint.new(src, NORMAL)
ScaleConstraint.new(src, scale, offset, dst, REQUIRED)
}
change.call(src, 17)

View File

@ -5,7 +5,7 @@ var sum = 0
var start = IO.clock
for (i in 0...100000) {
fibers.add(new Fiber {
fibers.add(Fiber.new {
sum = sum + i
if (i < 99999) fibers[i + 1].call()
})

View File

@ -1,5 +1,5 @@
class Toggle {
new(startState) {
this new(startState) {
_state = startState
}
@ -11,7 +11,7 @@ class Toggle {
}
class NthToggle is Toggle {
new(startState, maxCounter) {
this new(startState, maxCounter) {
super(startState)
_countMax = maxCounter
_count = 0
@ -31,7 +31,7 @@ class NthToggle is Toggle {
var start = IO.clock
var n = 100000
var val = true
var toggle = new Toggle(val)
var toggle = Toggle.new(val)
for (i in 0...n) {
val = toggle.activate.value
@ -49,7 +49,7 @@ for (i in 0...n) {
IO.print(toggle.value)
val = true
var ntoggle = new NthToggle(val, 3)
var ntoggle = NthToggle.new(val, 3)
for (i in 0...n) {
val = ntoggle.activate.value

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
Fiber.abort("Error message.")
}

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("fiber")
}

View File

@ -1,6 +1,6 @@
var fiber
fiber = new Fiber {
fiber = Fiber.new {
fiber.call() // expect runtime error: Fiber has already been called.
}

View File

@ -1,11 +1,11 @@
var a
var b
a = new Fiber {
a = Fiber.new {
b.call() // expect runtime error: Fiber has already been called.
}
b = new Fiber {
b = Fiber.new {
a.call()
}

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("fiber")
}

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("fiber")
return "result"
}

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("call")
}

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("fiber")
}

View File

@ -1,6 +1,6 @@
var fiber
fiber = new Fiber {
fiber = Fiber.new {
fiber.call(2) // expect runtime error: Fiber has already been called.
}

View File

@ -1,11 +1,11 @@
var a
var b
a = new Fiber {
a = Fiber.new {
b.call(3) // expect runtime error: Fiber has already been called.
}
b = new Fiber {
b = Fiber.new {
a.call(2)
}

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("call")
}

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
"s".unknown
}

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("1")
Fiber.yield()
IO.print("2")

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
"s".unknown
}

View File

@ -1 +1 @@
var fiber = new Fiber("not fn") // expect runtime error: Argument must be a function.
var fiber = Fiber.new("not fn") // expect runtime error: Argument must be a function.

View File

@ -1,8 +1,8 @@
var b = new Fiber {
var b = Fiber.new {
IO.print("fiber b")
}
var a = new Fiber {
var a = Fiber.new {
IO.print("begin fiber a")
b.call()
IO.print("end fiber a")

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("fiber")
}

View File

@ -1,6 +1,6 @@
var fiber
fiber = new Fiber {
fiber = Fiber.new {
IO.print(1) // expect: 1
fiber.run()
IO.print(2) // expect: 2

View File

@ -1,13 +1,13 @@
var a
var b
a = new Fiber {
a = Fiber.new {
IO.print(2)
b.run()
IO.print("nope")
}
b = new Fiber {
b = Fiber.new {
IO.print(1)
a.run()
IO.print(3)

View File

@ -1,10 +1,10 @@
var a = new Fiber {
var a = Fiber.new {
IO.print("run")
}
// Run a through an intermediate fiber since it will get discarded and we need
// to return to the main one after a completes.
var b = new Fiber {
var b = Fiber.new {
a.run()
IO.print("nope")
}

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("fiber")
}

View File

@ -1,6 +1,6 @@
var fiber
fiber = new Fiber {
fiber = Fiber.new {
IO.print(1) // expect: 1
fiber.run("ignored")
IO.print(2) // expect: 2

View File

@ -1,13 +1,13 @@
var a
var b
a = new Fiber {
a = Fiber.new {
IO.print(2)
b.run("ignored")
IO.print("nope")
}
b = new Fiber {
b = Fiber.new {
IO.print(1)
a.run("ignored")
IO.print(3)

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("before")
true.unknownMethod
IO.print("after")

View File

@ -1,6 +1,6 @@
var fiber
fiber = new Fiber {
fiber = Fiber.new {
fiber.try() // expect runtime error: Fiber has already been called.
}

View File

@ -1,11 +1,11 @@
var a
var b
a = new Fiber {
a = Fiber.new {
b.try() // expect runtime error: Fiber has already been called.
}
b = new Fiber {
b = Fiber.new {
a.call()
}

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("try")
}

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("fiber")
}

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {}
var fiber = Fiber.new {}
IO.print(fiber is Fiber) // expect: true
IO.print(fiber is Object) // expect: true
IO.print(fiber is Bool) // expect: false

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("fiber 1")
Fiber.yield()
IO.print("fiber 2")

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("fiber 1")
var result = Fiber.yield()
IO.print(result)

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("fiber")
var result = Fiber.yield()
IO.print(result)

View File

@ -1,11 +1,11 @@
var a = new Fiber {
var a = Fiber.new {
IO.print("before") // expect: before
Fiber.yield()
}
// Run a chain of fibers. Since none of them are called, they all get discarded
// and there is no remaining caller.
var b = new Fiber { a.run() }
var c = new Fiber { b.run() }
var b = Fiber.new { a.run() }
var c = Fiber.new { b.run() }
c.run()
IO.print("not reached")

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {
var fiber = Fiber.new {
IO.print("fiber 1")
Fiber.yield("yield 1")
IO.print("fiber 2")

View File

@ -1,11 +1,11 @@
var a = new Fiber {
var a = Fiber.new {
IO.print("before") // expect: before
Fiber.yield(1)
}
// Run a chain of fibers. Since none of them are called, they all get discarded
// and there is no remaining caller.
var b = new Fiber { a.run() }
var c = new Fiber { b.run() }
var b = Fiber.new { a.run() }
var c = Fiber.new { b.run() }
c.run()
IO.print("not reached")

View File

@ -1,5 +1,5 @@
IO.print(new Fn {}.arity) // expect: 0
IO.print(new Fn {|a| a}.arity) // expect: 1
IO.print(new Fn {|a, b| a}.arity) // expect: 2
IO.print(new Fn {|a, b, c| a}.arity) // expect: 3
IO.print(new Fn {|a, b, c, d| a}.arity) // expect: 4
IO.print(Fn.new {}.arity) // expect: 0
IO.print(Fn.new {|a| a}.arity) // expect: 1
IO.print(Fn.new {|a, b| a}.arity) // expect: 2
IO.print(Fn.new {|a, b, c| a}.arity) // expect: 3
IO.print(Fn.new {|a, b, c, d| a}.arity) // expect: 4

View File

@ -1,7 +1,7 @@
var f0 = new Fn { IO.print("zero") }
var f1 = new Fn {|a| IO.print("one ", a) }
var f2 = new Fn {|a, b| IO.print("two ", a, " ", b) }
var f3 = new Fn {|a, b, c| IO.print("three ", a, " ", b, " ", c) }
var f0 = Fn.new { IO.print("zero") }
var f1 = Fn.new {|a| IO.print("one ", a) }
var f2 = Fn.new {|a, b| IO.print("two ", a, " ", b) }
var f3 = Fn.new {|a, b, c| IO.print("three ", a, " ", b, " ", c) }
f0.call("a") // expect: zero
f0.call("a", "b") // expect: zero

View File

@ -1,2 +1,2 @@
var f2 = new Fn {|a, b| IO.print(a, b) }
var f2 = Fn.new {|a, b| IO.print(a, b) }
f2.call("a") // expect runtime error: Function expects more arguments.

View File

@ -1,16 +1,16 @@
// Not structurally equal.
IO.print(new Fn { 123 } == new Fn { 123 }) // expect: false
IO.print(new Fn { 123 } != new Fn { 123 }) // expect: true
IO.print(Fn.new { 123 } == Fn.new { 123 }) // expect: false
IO.print(Fn.new { 123 } != Fn.new { 123 }) // expect: true
// Not equal to other types.
IO.print(new Fn { 123 } == 1) // expect: false
IO.print(new Fn { 123 } == false) // expect: false
IO.print(new Fn { 123 } == "fn 123") // expect: false
IO.print(new Fn { 123 } != 1) // expect: true
IO.print(new Fn { 123 } != false) // expect: true
IO.print(new Fn { 123 } != "fn 123") // expect: true
IO.print(Fn.new { 123 } == 1) // expect: false
IO.print(Fn.new { 123 } == false) // expect: false
IO.print(Fn.new { 123 } == "fn 123") // expect: false
IO.print(Fn.new { 123 } != 1) // expect: true
IO.print(Fn.new { 123 } != false) // expect: true
IO.print(Fn.new { 123 } != "fn 123") // expect: true
// Equal by identity.
var f = new Fn { 123 }
var f = Fn.new { 123 }
IO.print(f == f) // expect: true
IO.print(f != f) // expect: false

View File

@ -1 +1 @@
new Fn(3) // expect runtime error: Argument must be a function.
Fn.new(3) // expect runtime error: Argument must be a function.

View File

@ -1 +1 @@
IO.print(new Fn {}) // expect: <fn>
IO.print(Fn.new {}) // expect: <fn>

View File

@ -1,4 +1,4 @@
IO.print(new Fn { 0 } is Fn) // expect: true
IO.print(new Fn { 0 } is Object) // expect: true
IO.print(new Fn { 0 } is String) // expect: false
IO.print(new Fn { 0 }.type == Fn) // expect: true
IO.print(Fn.new { 0 } is Fn) // expect: true
IO.print(Fn.new { 0 } is Object) // expect: true
IO.print(Fn.new { 0 } is String) // expect: false
IO.print(Fn.new { 0 }.type == Fn) // expect: true

View File

@ -18,6 +18,6 @@ class Foo {
toString { "Foo.toString" }
}
IO.print([1, new Foo, 2].join(", ")) // expect: 1, Foo.toString, 2
IO.print([1, Foo.new(), 2].join(", ")) // expect: 1, Foo.toString, 2
// TODO: Handle lists that contain themselves.

View File

@ -1,4 +1,4 @@
var list = new List
var list = List.new()
IO.print(list.count) // expect: 0
IO.print(list) // expect: []

View File

@ -1,7 +1,7 @@
var a = [1, 4, 2, 1, 5]
var b = ["W", "o", "r", "l", "d"]
var max = new Fn {|a, b| a > b ? a : b }
var sum = new Fn {|a, b| a + b }
var max = Fn.new {|a, b| a > b ? a : b }
var sum = Fn.new {|a, b| a + b }
IO.print(a.reduce(max)) // expect: 5
IO.print(a.reduce(10, max)) // expect: 10

View File

@ -12,6 +12,6 @@ class Foo {
toString { "Foo.toString" }
}
IO.print([1, new Foo, 2]) // expect: [1, Foo.toString, 2]
IO.print([1, Foo.new(), 2]) // expect: [1, Foo.toString, 2]
// TODO: Handle lists that contain themselves.

View File

@ -1,4 +1,4 @@
var fiber = new Fiber {}
var fiber = Fiber.new {}
var map = {
null: "null value",

View File

@ -1,4 +1,4 @@
var map = new Map
var map = Map.new()
IO.print(map.count) // expect: 0
IO.print(map) // expect: {}

View File

@ -12,7 +12,7 @@ class Foo {
toString { "Foo.toString" }
}
IO.print({1: new Foo}) // expect: {1: Foo.toString}
IO.print({1: Foo.new()}) // expect: {1: Foo.toString}
// Since iteration order is unspecified, we don't know what order the results
// will be.

View File

@ -1,2 +1,2 @@
class Foo {}
IO.print(!(new Foo)) // expect: false
IO.print(!Foo.new()) // expect: false

View File

@ -26,15 +26,15 @@ IO.print(Object.same(Bool, Bool)) // expect: true
// Other types compare by identity.
class Foo {}
var foo = new Foo
var foo = Foo.new()
IO.print(Object.same(foo, foo)) // expect: true
IO.print(Object.same(foo, new Foo)) // expect: false
IO.print(Object.same(foo, Foo.new())) // expect: false
// Ignores == operators.
class Bar {
==(other) { true }
}
var bar = new Bar
var bar = Bar.new()
IO.print(Object.same(bar, bar)) // expect: true
IO.print(Object.same(bar, new Bar)) // expect: false
IO.print(Object.same(bar, Bar.new())) // expect: false

View File

@ -1,2 +1,2 @@
class Foo {}
IO.print((new Foo).toString == "instance of Foo") // expect: true
IO.print(Foo.new().toString == "instance of Foo") // expect: true

View File

@ -8,4 +8,4 @@ class TestSequence is Sequence {
iteratorValue(iterator) { iterator }
}
IO.print((new TestSequence).count) // expect: 10
IO.print(TestSequence.new().count) // expect: 10

View File

@ -7,4 +7,4 @@ class InfiniteSequence is Sequence {
}
// Should not try to iterate the whole sequence.
IO.print((new InfiniteSequence).isEmpty) // expect: false
IO.print(InfiniteSequence.new().isEmpty) // expect: false

View File

@ -1,6 +1,6 @@
// Infinite iterator demonstrating that Sequence.map is not eager
class FibIterator {
new {
this new() {
_current = 0
_next = 1
}
@ -16,7 +16,7 @@ class FibIterator {
class Fib is Sequence {
iterate(iterator) {
if (iterator == null) return new FibIterator
if (iterator == null) return FibIterator.new()
iterator.iterate
return iterator
}
@ -24,7 +24,7 @@ class Fib is Sequence {
iteratorValue(iterator) { iterator.value }
}
var squareFib = (new Fib).map {|fib| fib * fib }
var squareFib = Fib.new().map {|fib| fib * fib }
var iterator = null
IO.print(squareFib is Sequence) // expect: true

View File

@ -8,4 +8,4 @@ class TestSequence is Sequence {
iteratorValue(iterator) { iterator }
}
IO.print((new TestSequence).toList) // expect: [1, 2, 3]
IO.print(TestSequence.new().toList) // expect: [1, 2, 3]

View File

@ -1,6 +1,6 @@
// Infinite iterator demonstrating that Sequence.where is not eager
class FibIterator {
new {
this new() {
_current = 0
_next = 1
}
@ -16,7 +16,7 @@ class FibIterator {
class Fib is Sequence {
iterate(iterator) {
if (iterator == null) return new FibIterator
if (iterator == null) return FibIterator.new()
iterator.iterate
return iterator
}
@ -24,7 +24,7 @@ class Fib is Sequence {
iteratorValue(iterator) { iterator.value }
}
var largeFibs = (new Fib).where {|fib| fib > 100 }
var largeFibs = Fib.new().where {|fib| fib > 100 }
var iterator = null
IO.print(largeFibs is Sequence) // expect: true

View File

@ -3,7 +3,7 @@ class Foo {
}
// Calls toString on argument.
IO.print(new Foo) // expect: Foo.toString
IO.print(Foo.new()) // expect: Foo.toString
// With one argument, returns the argument.
IO.print(IO.print(1) == 1) // expect: 1

View File

@ -2,4 +2,4 @@ class BadToString {
toString { 3 }
}
IO.print(new BadToString) // expect: [invalid toString]
IO.print(BadToString.new()) // expect: [invalid toString]

View File

@ -2,5 +2,5 @@ class BadToString {
toString { 3 }
}
IO.write(new BadToString) // expect: [invalid toString]
IO.write(BadToString.new()) // expect: [invalid toString]
IO.print

View File

@ -1,4 +1,4 @@
new Fn {
Fn.new {
var a = "before"
IO.print(a) // expect: before

View File

@ -1,7 +1,7 @@
var f
for (i in [1, 2, 3]) {
var j = 4
f = new Fn { IO.print(i + j) }
f = Fn.new { IO.print(i + j) }
break
}

View File

@ -1,7 +1,7 @@
var f
while (true) {
var i = "i"
f = new Fn { IO.print(i) }
f = Fn.new { IO.print(i) }
break
}

View File

@ -1,6 +1,6 @@
var done = false
while (!done) {
new Fn {
Fn.new {
break // expect error
}
done = true

View File

@ -12,4 +12,4 @@ class foo {
}
foo.callFoo // expect: static foo method
(new foo).callFoo // expect: instance foo method
foo.new().callFoo // expect: instance foo method

View File

@ -11,4 +11,4 @@ class Foo {
}
Foo.sayName // expect: Foo!
(new Foo).sayName // expect: Foo!
Foo.new().sayName // expect: Foo!

View File

@ -1,6 +1,6 @@
class Foo {}
var foo = new Foo
var foo = Foo.new()
IO.print(foo is Foo) // expect: true
// TODO: Test precedence and grammar of what follows "new".

View File

@ -3,13 +3,13 @@ var g = null
{
var local = "local"
f = new Fn {
f = Fn.new {
IO.print(local)
local = "after f"
IO.print(local)
}
g = new Fn {
g = Fn.new {
IO.print(local)
local = "after g"
IO.print(local)

View File

@ -1,7 +1,7 @@
var f = null
new Fn {|param|
f = new Fn {
Fn.new {|param|
f = Fn.new {
IO.print(param)
}
}.call("param")

View File

@ -3,10 +3,10 @@
// would crash because it walked to the end of the upvalue list (correct), but
// then didn't handle not finding the variable.
new Fn {
Fn.new {
var a = "a"
var b = "b"
new Fn {
Fn.new {
IO.print(b) // expect: b
IO.print(a) // expect: a
}.call()

View File

@ -2,11 +2,11 @@ var F = null
class Foo {
method(param) {
F = new Fn {
F = Fn.new {
IO.print(param)
}
}
}
(new Foo).method("param")
Foo.new().method("param")
F.call() // expect: param

View File

@ -2,7 +2,7 @@ var f = null
{
var local = "local"
f = new Fn {
f = Fn.new {
IO.print(local)
}
}

View File

@ -1,3 +1,4 @@
// TODO: Is this right? Shouldn't it resolve to this.local?
var foo = null
{
@ -8,7 +9,7 @@ var foo = null
}
}
foo = new Foo
foo = Foo.new()
}
foo.method // expect: local

View File

@ -1,12 +1,12 @@
var f = null
new Fn {
Fn.new {
var a = "a"
new Fn {
Fn.new {
var b = "b"
new Fn {
Fn.new {
var c = "c"
f = new Fn {
f = Fn.new {
IO.print(a)
IO.print(b)
IO.print(c)

View File

@ -1,6 +1,6 @@
{
var local = "local"
new Fn {
Fn.new {
IO.print(local) // expect: local
}.call()
}

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