mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-12 14:48:40 +01:00
Compare commits
10 Commits
0.2.0
...
compound-a
| Author | SHA1 | Date | |
|---|---|---|---|
| c4497933e7 | |||
| 19548ec411 | |||
| 119fd7fd0f | |||
| 903f2eae07 | |||
| 4baef23d1d | |||
| 6c26f8aff5 | |||
| 234efa2e3a | |||
| 685f9b512d | |||
| 7267b23913 | |||
| a3f368d1ae |
41
CHANGELOG.md
41
CHANGELOG.md
@ -1,44 +1,3 @@
|
||||
## 0.2.0
|
||||
|
||||
0.2.0 spans a pretty wide time period with [around 290 commits](https://github.com/wren-lang/wren/compare/0.1.0...master).
|
||||
This includes many bug fixes, improvements, clarity in the
|
||||
code and documentation and so on. There's too many to explicitly list.
|
||||
Below is the obvious user facing stuff that was easy to spot in the history.
|
||||
|
||||
Most noteworthy is that 'relative imports' are a slightly breaking change,
|
||||
but help pave the way forward toward a consistency for modules.
|
||||
|
||||
### Language/VM
|
||||
|
||||
- `import` was made smarter, differentiating relative from logical
|
||||
- `Fiber` can now accept a value from the first `call`/`transfer`
|
||||
- Added `String.trim`, `String.trimEnd`, `String.trimStart` variants
|
||||
- Added `String.split`, `String.replace`, `String.fromByte`
|
||||
- Added `String.indexOf(needle, startIndex)`
|
||||
- Added `Sequence.take` and `Sequence.skip`
|
||||
- Added `List.filled(count, value)`
|
||||
- Added `Num.pow`, `Num.log`, `Num.round`
|
||||
- Added `Num.largest`, `Num.smallest`
|
||||
- Added `Map` iteration (`MapEntry`)
|
||||
|
||||
#### C API
|
||||
|
||||
- Added `wren.hpp` for use in c++
|
||||
- Added void* user data to `WrenVM`
|
||||
- Allow hosts with no module loader to still load optional modules.
|
||||
- Added `wrenAbortFiber`
|
||||
|
||||
### CLI
|
||||
Please note that beyond 0.2.0 the CLI will have it's own changelog.
|
||||
This list is not exhaustive. For a fuller history see the commit log above.
|
||||
|
||||
- Add path module
|
||||
- Add `--version`
|
||||
- Add REPL written in Wren
|
||||
- Add Stdin.isTerminal
|
||||
- Added Platform class
|
||||
- Rename `process` module to `os`
|
||||
|
||||
## 0.1.0
|
||||
|
||||
First declared version. Everything is new!
|
||||
|
||||
@ -1,90 +0,0 @@
|
||||
^title 0.2.0 and beyond
|
||||
30 Sep 2019
|
||||
|
||||
---
|
||||
|
||||
### 0.2.0 is here
|
||||
|
||||
It's time to tag a release!
|
||||
Let's check our goals from [the last blog post](http://wren.io/blog/hello-wren.html):
|
||||
|
||||
- <s>We're gonna clear out a bit of backlog, tidying up issues and PRs</s>
|
||||
- <s>Tidy up the website a bit, visually and structurally</s>
|
||||
- <s>Make sure all documentation is up to date with the current development</s>
|
||||
- <s>Tag 0.2.0 with a list of relevant changes since 0.1.0</s>
|
||||
|
||||
So far so good.
|
||||
|
||||
### Backlog
|
||||
|
||||
Clearing out the issues on a repo after some time has passed is always a bit tricky.
|
||||
|
||||
Many issues are outdated (or unrelated), and some need a proper response. Some are related to future ideals, things that will take a while to get to. Some are related to the Wren CLI. It can be difficult to reason about the state of the tasks when they're all over the place, so we've been consolidating.
|
||||
|
||||
The good news is the issue list has been drastically reduced, 70+ issues being closed (or resolved). Around 21 of those are marked for future consideration, and 23 moved to the Wren CLI repository. More consolidation will still continue.
|
||||
|
||||
**The goal is that the active issues are as relevant as possible in the immediate term.**
|
||||
|
||||
A tricky but important aspect to mention here is the perception of closing an issue...
|
||||
A closed issue doesn't necessarily mean anything final, it's just a categorization tool!
|
||||
|
||||
The other categorization tool which operates _within_ open/closed categories, is the _label_. Labels allow us to distinguish clearly the different types of issues, which makes open/closed less binary and more nuanced and rich. We rely on both to make sense of the list.
|
||||
|
||||
For example, discussions of future tasks, ideas or goals [are tagged `future`](https://github.com/wren-lang/wren/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Afuture+). Now we can refer to them later, and re-open them when they become active and relevant again.
|
||||
|
||||
**We're in this together.**
|
||||
Please don't be discouraged if an issue is closed! Discussion is absolutely encouraged and ideas, proposals and input is very necessary. Feel free to keep a discussion going, even if the issue it's attached to has been marked as closed.
|
||||
|
||||
### 0.2.0
|
||||
|
||||
We've been hammering away on Wren since 0.1.0 for quite a while. The [change list on Github](https://github.com/wren-lang/wren/compare/0.1.0...5338275dcdd97fd8d9fc614f420a645500836a59) is too long to display!
|
||||
|
||||
Most importantly, before we start iterating on the language further, I wanted to make sure we had a checkpoint to look back to. That's largely what 0.2.0 is about.
|
||||
|
||||
There's quite a lot of good changes, with **290 commits from 41 contributors!**
|
||||
Thanks to everyone getting involved, every little bit has helped Wren, no matter how small the contribution.
|
||||
|
||||
### 0.3.0
|
||||
|
||||
With 0.2.0 wrapped up, our next release won't be as far away this time.
|
||||
|
||||
**The primary goal for 0.3.0 is separating the VM from the CLI.**
|
||||
|
||||
This includes updated documentation, splitting the source repos, migrating all the tests, issues and more.
|
||||
All the code and documentation will still be easy to access in one place, but clarity around Wren as a project will improve a lot.
|
||||
|
||||
The migration has already started, you can [find the wren-cli repository here](https://github.com/wren-lang/wren-cli).
|
||||
I'm working on some of the refactoring on the [wren-cli-refactor branch.](https://github.com/wren-lang/wren/tree/wren-cli-refactor)
|
||||
|
||||
With that, we'll also have a cleaner build process for the CLI.
|
||||
On some platforms (Windows especially), there have been several pain points, these will be addressed.
|
||||
There's also gonna be an additional build target, namely emscripten, so we can easily run Wren examples on the Wren website and documentation.
|
||||
|
||||
And finally, we'll have some proper prebuilt releases with 0.3.0.
|
||||
I know many people have just wanted to grab an executable and give the language a go, but that hasn't been an option.
|
||||
We'll fix that with 0.3.0.
|
||||
|
||||
The 0.3.0 goals in simple form:
|
||||
- VM / CLI split
|
||||
- Build consistency/reliablity
|
||||
- Web build for embedding in docs
|
||||
- Prebuilt releases
|
||||
|
||||
### Beyond
|
||||
|
||||
I don't have any concrete plans for 0.4.0 right now, but once the dust settles from 0.3.0 we'll have a clearer view.
|
||||
|
||||
There's definitely things in the pipeline though, I've been playing with [adding compound assignments like `+=`](https://github.com/wren-lang/wren/pull/701).
|
||||
|
||||
More details about in development features and fixes can be found on the repo in the meantime.
|
||||
|
||||
Thanks for reading!
|
||||
|
||||
### More
|
||||
|
||||
- [The Wren Blog RSS](http://wren.io/blog/rss.xml)
|
||||
- Visit the [wren-lang organization](https://github.com/wren-lang) on Github to get involved.
|
||||
- Follow the developers [@munificentbob](https://twitter.com/munificentbob) or [@___discovery](https://twitter.com/___discovery) on twitter
|
||||
|
||||
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
^title Development blogs
|
||||
|
||||
[<h3>0.2.0 and beyond</h3>](0.2.0-and-beyond.html)
|
||||
> <date>30 Sep 2019</date> • Checkpoints, and the plans for 0.3.0.
|
||||
|
||||
[<h3>System.print("hello wren")</h3>](hello-wren.html)
|
||||
> <date>4 Feb 2019</date> • A short post introducing the blog, the new maintainer, and the immediate term plans for Wren.
|
||||
|
||||
|
||||
@ -3,13 +3,6 @@
|
||||
<link>https://wren.io/</link>
|
||||
<description>The development blog of the Wren programming language.</description>
|
||||
<language>en-us</language>
|
||||
<item>
|
||||
<title>0.2.0 and beyond</title>
|
||||
<link>https://wren.io/blog/0.2.0-and-beyond.html</link>
|
||||
<description>Checkpoints, and the plans for 0.3.0.</description>
|
||||
<guid>https://wren.io/blog/0.2.0-and-beyond.html</guid>
|
||||
<pubDate>Mon, 30 Sep 2019 00:00:00 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title>System.print("hello wren")</title>
|
||||
<link>https://wren.io/blog/hello-wren.html</link>
|
||||
|
||||
@ -107,7 +107,7 @@ of the body, like so:
|
||||
System.print("Hi, " + first + " " + last + "!")
|
||||
}
|
||||
|
||||
Here we're passing a function to `callMe` that takes two parameters, `first` and
|
||||
Here we're passing a function to `greet` that takes two parameters, `first` and
|
||||
`last`. They are passed to the function when it's called:
|
||||
|
||||
:::wren
|
||||
|
||||
@ -67,7 +67,7 @@ use `add` to append a single item to the end:
|
||||
|
||||
:::wren
|
||||
hirsute.add("goatee")
|
||||
System.print(hirsute.count) //> 5
|
||||
System.print(hirsute.count) //> 4
|
||||
|
||||
You can insert a new element at a specific position using `insert`:
|
||||
|
||||
|
||||
@ -7,11 +7,11 @@
|
||||
|
||||
// The Wren semantic version number components.
|
||||
#define WREN_VERSION_MAJOR 0
|
||||
#define WREN_VERSION_MINOR 2
|
||||
#define WREN_VERSION_MINOR 1
|
||||
#define WREN_VERSION_PATCH 0
|
||||
|
||||
// A human-friendly string representation of the version.
|
||||
#define WREN_VERSION_STRING "0.2.0"
|
||||
#define WREN_VERSION_STRING "0.1.0"
|
||||
|
||||
// A monotonically increasing numeric representation of the version number. Use
|
||||
// this if you want to do range checks over versions.
|
||||
|
||||
@ -66,15 +66,25 @@ typedef enum
|
||||
TOKEN_STAR,
|
||||
TOKEN_SLASH,
|
||||
TOKEN_PERCENT,
|
||||
TOKEN_PERCENTEQ,
|
||||
TOKEN_PLUS,
|
||||
TOKEN_MINUS,
|
||||
TOKEN_PLUSEQ,
|
||||
TOKEN_MINUSEQ,
|
||||
TOKEN_STAREQ,
|
||||
TOKEN_SLASHEQ,
|
||||
TOKEN_LTLT,
|
||||
TOKEN_GTGT,
|
||||
TOKEN_LTLTEQ,
|
||||
TOKEN_GTGTEQ,
|
||||
TOKEN_PIPE,
|
||||
TOKEN_PIPEPIPE,
|
||||
TOKEN_PIPEEQ,
|
||||
TOKEN_CARET,
|
||||
TOKEN_CARETEQ,
|
||||
TOKEN_AMP,
|
||||
TOKEN_AMPAMP,
|
||||
TOKEN_AMPEQ,
|
||||
TOKEN_BANG,
|
||||
TOKEN_TILDE,
|
||||
TOKEN_QUESTION,
|
||||
@ -965,16 +975,34 @@ static void nextToken(Parser* parser)
|
||||
case '}': makeToken(parser, TOKEN_RIGHT_BRACE); return;
|
||||
case ':': makeToken(parser, TOKEN_COLON); return;
|
||||
case ',': makeToken(parser, TOKEN_COMMA); return;
|
||||
case '*': makeToken(parser, TOKEN_STAR); return;
|
||||
case '%': makeToken(parser, TOKEN_PERCENT); return;
|
||||
case '^': makeToken(parser, TOKEN_CARET); return;
|
||||
case '+': makeToken(parser, TOKEN_PLUS); return;
|
||||
case '-': makeToken(parser, TOKEN_MINUS); return;
|
||||
case '*': twoCharToken(parser, '=', TOKEN_STAREQ, TOKEN_STAR); return;
|
||||
case '%': twoCharToken(parser, '=', TOKEN_PERCENTEQ, TOKEN_PERCENT); return;
|
||||
case '^': twoCharToken(parser, '=', TOKEN_CARETEQ, TOKEN_CARET); return;
|
||||
case '+': twoCharToken(parser, '=', TOKEN_PLUSEQ, TOKEN_PLUS); return;
|
||||
case '-': twoCharToken(parser, '=', TOKEN_MINUSEQ, TOKEN_MINUS); return;
|
||||
case '~': makeToken(parser, TOKEN_TILDE); return;
|
||||
case '?': makeToken(parser, TOKEN_QUESTION); return;
|
||||
|
||||
case '|': twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE); return;
|
||||
case '&': twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); return;
|
||||
case '|':
|
||||
if (matchChar(parser, '|'))
|
||||
{
|
||||
makeToken(parser, TOKEN_PIPEPIPE);
|
||||
return;
|
||||
}
|
||||
|
||||
twoCharToken(parser, '=', TOKEN_PIPEEQ, TOKEN_PIPE);
|
||||
return;
|
||||
|
||||
case '&':
|
||||
if (matchChar(parser, '&'))
|
||||
{
|
||||
makeToken(parser, TOKEN_AMPAMP);
|
||||
return;
|
||||
}
|
||||
|
||||
twoCharToken(parser, '=', TOKEN_AMPEQ, TOKEN_AMP);
|
||||
return;
|
||||
|
||||
case '=': twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ); return;
|
||||
case '!': twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG); return;
|
||||
|
||||
@ -1001,13 +1029,13 @@ static void nextToken(Parser* parser)
|
||||
break;
|
||||
}
|
||||
|
||||
makeToken(parser, TOKEN_SLASH);
|
||||
twoCharToken(parser, '=', TOKEN_SLASHEQ, TOKEN_SLASH);
|
||||
return;
|
||||
|
||||
case '<':
|
||||
if (matchChar(parser, '<'))
|
||||
{
|
||||
makeToken(parser, TOKEN_LTLT);
|
||||
twoCharToken(parser, '=', TOKEN_LTLTEQ, TOKEN_LTLT);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1018,7 +1046,7 @@ static void nextToken(Parser* parser)
|
||||
case '>':
|
||||
if (matchChar(parser, '>'))
|
||||
{
|
||||
makeToken(parser, TOKEN_GTGT);
|
||||
twoCharToken(parser, '=', TOKEN_GTGTEQ, TOKEN_GTGT);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1169,6 +1197,12 @@ static int emitByte(Compiler* compiler, int byte)
|
||||
return compiler->fn->code.count - 1;
|
||||
}
|
||||
|
||||
static void emitPush(Compiler* compiler, int count)
|
||||
{
|
||||
emitByte(compiler, CODE_PUSH);
|
||||
emitByte(compiler, count);
|
||||
}
|
||||
|
||||
// Emits one bytecode instruction.
|
||||
static void emitOp(Compiler* compiler, Code instruction)
|
||||
{
|
||||
@ -1253,11 +1287,9 @@ static int declareVariable(Compiler* compiler, Token* token)
|
||||
// Top-level module scope.
|
||||
if (compiler->scopeDepth == -1)
|
||||
{
|
||||
int line = -1;
|
||||
int symbol = wrenDefineVariable(compiler->parser->vm,
|
||||
compiler->parser->module,
|
||||
token->start, token->length,
|
||||
NULL_VAL, &line);
|
||||
token->start, token->length, NULL_VAL);
|
||||
|
||||
if (symbol == -1)
|
||||
{
|
||||
@ -1267,12 +1299,6 @@ static int declareVariable(Compiler* compiler, Token* token)
|
||||
{
|
||||
error(compiler, "Too many module variables defined.");
|
||||
}
|
||||
else if (symbol == -3)
|
||||
{
|
||||
error(compiler,
|
||||
"Variable '%.*s' referenced before this definition (first use at line %d).",
|
||||
token->length, token->start, line);
|
||||
}
|
||||
|
||||
return symbol;
|
||||
}
|
||||
@ -1599,6 +1625,31 @@ static void expression(Compiler* compiler);
|
||||
static void statement(Compiler* compiler);
|
||||
static void definition(Compiler* compiler);
|
||||
static void parsePrecedence(Compiler* compiler, Precedence precedence);
|
||||
//Compound assignment forward declares and helpers
|
||||
static void fieldAssignment(Compiler* compiler, int field, bool insideMethod);
|
||||
static void variableAssignment(Compiler* compiler, Variable variable);
|
||||
static void setterAssignment(Compiler* compiler, Code instruction, Signature signature);
|
||||
static void subscriptSetterAssignment(Compiler* compiler, Signature signature);
|
||||
static inline bool isAssignment(Compiler* compiler)
|
||||
{
|
||||
TokenType next = peek(compiler);
|
||||
switch(next) {
|
||||
case TOKEN_EQ:
|
||||
case TOKEN_PLUSEQ:
|
||||
case TOKEN_MINUSEQ:
|
||||
case TOKEN_STAREQ:
|
||||
case TOKEN_SLASHEQ:
|
||||
case TOKEN_LTLTEQ:
|
||||
case TOKEN_GTGTEQ:
|
||||
case TOKEN_PERCENTEQ:
|
||||
case TOKEN_CARETEQ:
|
||||
case TOKEN_PIPEEQ:
|
||||
case TOKEN_AMPEQ:
|
||||
return true;
|
||||
default: break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replaces the placeholder argument for a previous CODE_JUMP or CODE_JUMP_IF
|
||||
// instruction with an offset that jumps to the current end of bytecode.
|
||||
@ -1927,16 +1978,17 @@ static void namedCall(Compiler* compiler, bool canAssign, Code instruction)
|
||||
// Get the token for the method name.
|
||||
Signature signature = signatureFromToken(compiler, SIG_GETTER);
|
||||
|
||||
if (canAssign && match(compiler, TOKEN_EQ))
|
||||
if (canAssign && isAssignment(compiler))
|
||||
{
|
||||
ignoreNewlines(compiler);
|
||||
|
||||
// Compile the assignment and right-hand side.
|
||||
setterAssignment(compiler, instruction, signature);
|
||||
|
||||
// Build the setter signature.
|
||||
signature.type = SIG_SETTER;
|
||||
signature.arity = 1;
|
||||
|
||||
// Compile the assigned value.
|
||||
expression(compiler);
|
||||
callSignature(compiler, instruction, &signature);
|
||||
}
|
||||
else
|
||||
@ -2117,18 +2169,20 @@ static void field(Compiler* compiler, bool canAssign)
|
||||
}
|
||||
}
|
||||
|
||||
bool insideMethod = compiler->parent != NULL &&
|
||||
compiler->parent->enclosingClass == enclosingClass;
|
||||
|
||||
// If there's an "=" after a field name, it's an assignment.
|
||||
bool isLoad = true;
|
||||
if (canAssign && match(compiler, TOKEN_EQ))
|
||||
if (canAssign && isAssignment(compiler))
|
||||
{
|
||||
// Compile the right-hand side.
|
||||
expression(compiler);
|
||||
// Compile the assignment and right-hand side.
|
||||
fieldAssignment(compiler, field, insideMethod);
|
||||
isLoad = false;
|
||||
}
|
||||
|
||||
// If we're directly inside a method, use a more optimal instruction.
|
||||
if (compiler->parent != NULL &&
|
||||
compiler->parent->enclosingClass == enclosingClass)
|
||||
if (insideMethod)
|
||||
{
|
||||
emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD_THIS : CODE_STORE_FIELD_THIS,
|
||||
field);
|
||||
@ -2143,11 +2197,11 @@ static void field(Compiler* compiler, bool canAssign)
|
||||
// Compiles a read or assignment to [variable].
|
||||
static void bareName(Compiler* compiler, bool canAssign, Variable variable)
|
||||
{
|
||||
// If there's an "=" after a bare name, it's a variable assignment.
|
||||
if (canAssign && match(compiler, TOKEN_EQ))
|
||||
// If there's an assignment after a bare name, it's a variable assignment.
|
||||
if (canAssign && isAssignment(compiler))
|
||||
{
|
||||
// Compile the right-hand side.
|
||||
expression(compiler);
|
||||
// Compile the assignment and right-hand side.
|
||||
variableAssignment(compiler, variable);
|
||||
|
||||
// Emit the store instruction.
|
||||
switch (variable.scope)
|
||||
@ -2244,7 +2298,13 @@ static void name(Compiler* compiler, bool canAssign)
|
||||
token->start, token->length);
|
||||
if (variable.index == -1)
|
||||
{
|
||||
// Implicitly define a module-level variable in
|
||||
if (isLocalName(token->start))
|
||||
{
|
||||
error(compiler, "Undefined variable.");
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's a nonlocal name, implicitly define a module-level variable in
|
||||
// the hopes that we get a real definition later.
|
||||
variable.index = wrenDeclareVariable(compiler->parser->vm,
|
||||
compiler->parser->module,
|
||||
@ -2360,13 +2420,14 @@ static void subscript(Compiler* compiler, bool canAssign)
|
||||
finishArgumentList(compiler, &signature);
|
||||
consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after arguments.");
|
||||
|
||||
if (canAssign && match(compiler, TOKEN_EQ))
|
||||
if (canAssign && isAssignment(compiler))
|
||||
{
|
||||
signature.type = SIG_SUBSCRIPT_SETTER;
|
||||
// Compile the assignment and right-hand side.
|
||||
validateNumParameters(compiler, signature.arity + 1);
|
||||
subscriptSetterAssignment(compiler, signature);
|
||||
|
||||
// Compile the assigned value.
|
||||
validateNumParameters(compiler, ++signature.arity);
|
||||
expression(compiler);
|
||||
signature.arity++;
|
||||
signature.type = SIG_SUBSCRIPT_SETTER;
|
||||
}
|
||||
|
||||
callSignature(compiler, CODE_CALL_0, &signature);
|
||||
@ -2426,10 +2487,8 @@ static void conditional(Compiler* compiler, bool canAssign)
|
||||
patchJump(compiler, elseJump);
|
||||
}
|
||||
|
||||
void infixOp(Compiler* compiler, bool canAssign)
|
||||
void infixOpWithRule(Compiler* compiler, GrammarRule* rule)
|
||||
{
|
||||
GrammarRule* rule = getRule(compiler->parser->previous.type);
|
||||
|
||||
// An infix operator cannot end an expression.
|
||||
ignoreNewlines(compiler);
|
||||
|
||||
@ -2441,6 +2500,13 @@ void infixOp(Compiler* compiler, bool canAssign)
|
||||
callSignature(compiler, CODE_CALL_0, &signature);
|
||||
}
|
||||
|
||||
void infixOp(Compiler* compiler, bool canAssign)
|
||||
{
|
||||
GrammarRule* rule = getRule(compiler->parser->previous.type);
|
||||
infixOpWithRule(compiler, rule);
|
||||
}
|
||||
|
||||
|
||||
// Compiles a method signature for an infix operator.
|
||||
void infixSignature(Compiler* compiler, Signature* signature)
|
||||
{
|
||||
@ -2605,15 +2671,25 @@ GrammarRule rules[] =
|
||||
/* TOKEN_STAR */ INFIX_OPERATOR(PREC_FACTOR, "*"),
|
||||
/* TOKEN_SLASH */ INFIX_OPERATOR(PREC_FACTOR, "/"),
|
||||
/* TOKEN_PERCENT */ INFIX_OPERATOR(PREC_FACTOR, "%"),
|
||||
/* TOKEN_PERCENTEQ */ UNUSED,
|
||||
/* TOKEN_PLUS */ INFIX_OPERATOR(PREC_TERM, "+"),
|
||||
/* TOKEN_MINUS */ OPERATOR("-"),
|
||||
/* TOKEN_PLUSEQ */ UNUSED,
|
||||
/* TOKEN_MINUSEQ */ UNUSED,
|
||||
/* TOKEN_STAREQ */ UNUSED,
|
||||
/* TOKEN_SLASHEQ */ UNUSED,
|
||||
/* TOKEN_LTLT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, "<<"),
|
||||
/* TOKEN_GTGT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, ">>"),
|
||||
/* TOKEN_LTLTEQ */ UNUSED,
|
||||
/* TOKEN_GTGTEQ */ UNUSED,
|
||||
/* TOKEN_PIPE */ INFIX_OPERATOR(PREC_BITWISE_OR, "|"),
|
||||
/* TOKEN_PIPEPIPE */ INFIX(PREC_LOGICAL_OR, or_),
|
||||
/* TOKEN_PIPEEQ */ UNUSED,
|
||||
/* TOKEN_CARET */ INFIX_OPERATOR(PREC_BITWISE_XOR, "^"),
|
||||
/* TOKEN_CARETEQ */ UNUSED,
|
||||
/* TOKEN_AMP */ INFIX_OPERATOR(PREC_BITWISE_AND, "&"),
|
||||
/* TOKEN_AMPAMP */ INFIX(PREC_LOGICAL_AND, and_),
|
||||
/* TOKEN_AMPEQ */ UNUSED,
|
||||
/* TOKEN_BANG */ PREFIX_OPERATOR("!"),
|
||||
/* TOKEN_TILDE */ PREFIX_OPERATOR("~"),
|
||||
/* TOKEN_QUESTION */ INFIX(PREC_ASSIGNMENT, conditional),
|
||||
@ -2697,6 +2773,94 @@ void expression(Compiler* compiler)
|
||||
parsePrecedence(compiler, PREC_LOWEST);
|
||||
}
|
||||
|
||||
//Compound assigment handling
|
||||
TokenType compoundAssignmentStart(Compiler* compiler)
|
||||
{
|
||||
//Consume the compound assign token
|
||||
nextToken(compiler->parser);
|
||||
switch(compiler->parser->previous.type)
|
||||
{
|
||||
//For the case of a normal = we handle the RHS
|
||||
case TOKEN_EQ: {
|
||||
expression(compiler);
|
||||
return TOKEN_EQ;
|
||||
} break;
|
||||
|
||||
case TOKEN_PLUSEQ: return TOKEN_PLUS;
|
||||
case TOKEN_MINUSEQ: return TOKEN_MINUS;
|
||||
case TOKEN_STAREQ: return TOKEN_STAR;
|
||||
case TOKEN_SLASHEQ: return TOKEN_SLASH;
|
||||
case TOKEN_LTLTEQ: return TOKEN_LTLT;
|
||||
case TOKEN_GTGTEQ: return TOKEN_GTGT;
|
||||
case TOKEN_PERCENTEQ: return TOKEN_PERCENT;
|
||||
case TOKEN_PIPEEQ: return TOKEN_PIPE;
|
||||
case TOKEN_AMPEQ: return TOKEN_AMP;
|
||||
case TOKEN_CARETEQ: return TOKEN_CARET;
|
||||
default: break;
|
||||
};
|
||||
|
||||
UNREACHABLE();
|
||||
return TOKEN_ERROR;
|
||||
}
|
||||
|
||||
static void variableAssignment(Compiler* compiler, Variable variable)
|
||||
{
|
||||
TokenType infixToken = compoundAssignmentStart(compiler);
|
||||
if(infixToken == TOKEN_EQ) return;
|
||||
|
||||
//We're doing somevar = somevar + ...
|
||||
//so we load the variable again as the arg to the infix
|
||||
loadVariable(compiler, variable);
|
||||
infixOpWithRule(compiler, getRule(infixToken));
|
||||
}
|
||||
|
||||
static void fieldAssignment(Compiler* compiler, int field, bool insideMethod)
|
||||
{
|
||||
TokenType infixToken = compoundAssignmentStart(compiler);
|
||||
if(infixToken == TOKEN_EQ) return;
|
||||
|
||||
//We're doing _field = _field + ...
|
||||
//so we load the field as the arg for the infix
|
||||
if(insideMethod) {
|
||||
emitByteArg(compiler, CODE_LOAD_FIELD_THIS, field);
|
||||
} else {
|
||||
loadThis(compiler);
|
||||
emitByteArg(compiler, CODE_LOAD_FIELD, field);
|
||||
}
|
||||
//Then call the op which handles the RHS
|
||||
infixOpWithRule(compiler, getRule(infixToken));
|
||||
}
|
||||
|
||||
static void setterAssignment(Compiler* compiler, Code instruction, Signature signature)
|
||||
{
|
||||
TokenType infixToken = compoundAssignmentStart(compiler);
|
||||
if(infixToken == TOKEN_EQ) return;
|
||||
|
||||
//We're doing self.setter = self.getter + ...
|
||||
//so to call the getter, we duplicate the self on the stack
|
||||
emitPush(compiler, 1);
|
||||
callSignature(compiler, instruction, &signature);
|
||||
//Then call the op which handles the RHS
|
||||
infixOpWithRule(compiler, getRule(infixToken));
|
||||
|
||||
}
|
||||
|
||||
static void subscriptSetterAssignment(Compiler* compiler, Signature signature)
|
||||
{
|
||||
TokenType infixToken = compoundAssignmentStart(compiler);
|
||||
if(infixToken == TOKEN_EQ) return;
|
||||
|
||||
//We're doing self[_,_] = self[_,_] + ...
|
||||
//so to call the getter, we duplicate the
|
||||
//subscript self + args on the stack
|
||||
emitPush(compiler, signature.arity + 1);
|
||||
callSignature(compiler, CODE_CALL_0, &signature);
|
||||
//Then call the op which handles the RHS
|
||||
infixOpWithRule(compiler, getRule(infixToken));
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Returns the number of arguments to the instruction at [ip] in [fn]'s
|
||||
// bytecode.
|
||||
static int getNumArguments(const uint8_t* bytecode, const Value* constants,
|
||||
@ -2736,6 +2900,7 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants,
|
||||
case CODE_LOAD_FIELD:
|
||||
case CODE_STORE_FIELD:
|
||||
case CODE_CLASS:
|
||||
case CODE_PUSH:
|
||||
return 1;
|
||||
|
||||
case CODE_CONSTANT:
|
||||
|
||||
@ -1150,7 +1150,7 @@ static ObjClass* defineClass(WrenVM* vm, ObjModule* module, const char* name)
|
||||
|
||||
ObjClass* classObj = wrenNewSingleClass(vm, 0, nameString);
|
||||
|
||||
wrenDefineVariable(vm, module, name, nameString->length, OBJ_VAL(classObj), NULL);
|
||||
wrenDefineVariable(vm, module, name, nameString->length, OBJ_VAL(classObj));
|
||||
|
||||
wrenPopRoot(vm);
|
||||
return classObj;
|
||||
|
||||
@ -177,6 +177,11 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
|
||||
case CODE_STORE_FIELD: BYTE_INSTRUCTION("STORE_FIELD");
|
||||
|
||||
case CODE_POP: printf("POP\n"); break;
|
||||
case CODE_PUSH: {
|
||||
int count = READ_BYTE();
|
||||
printf("PUSH %d\n", count);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_CALL_0:
|
||||
case CODE_CALL_1:
|
||||
|
||||
@ -77,6 +77,9 @@ OPCODE(STORE_FIELD, -1)
|
||||
// Pop and discard the top of stack.
|
||||
OPCODE(POP, -1)
|
||||
|
||||
// Push N values to the stack, from the stack. The number is unused.
|
||||
OPCODE(PUSH, 0)
|
||||
|
||||
// Invoke the method with symbol [arg]. The number indicates the number of
|
||||
// arguments (not including the receiver).
|
||||
OPCODE(CALL_0, 0)
|
||||
|
||||
@ -456,7 +456,7 @@ static ObjClosure* compileInModule(WrenVM* vm, Value name, const char* source,
|
||||
wrenDefineVariable(vm, module,
|
||||
coreModule->variableNames.data[i]->value,
|
||||
coreModule->variableNames.data[i]->length,
|
||||
coreModule->variables.data[i], NULL);
|
||||
coreModule->variables.data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1146,6 +1146,19 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
||||
DROP();
|
||||
DISPATCH();
|
||||
|
||||
CASE_CODE(PUSH):
|
||||
{
|
||||
uint8_t count = READ_BYTE();
|
||||
wrenEnsureStack(vm, fiber, fiber->stackCapacity + count);
|
||||
Value* stackTopBefore = fiber->stackTop;
|
||||
for(uint8_t i = count; i > 0; --i)
|
||||
{
|
||||
Value* value = stackTopBefore - i;
|
||||
PUSH(*value);
|
||||
}
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
CASE_CODE(RETURN):
|
||||
{
|
||||
Value result = POP();
|
||||
@ -1498,15 +1511,8 @@ int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name,
|
||||
return wrenSymbolTableAdd(vm, &module->variableNames, name, length);
|
||||
}
|
||||
|
||||
// Returns `true` if [name] is a local variable name (starts with a lowercase
|
||||
// letter).
|
||||
static bool isLocalName(const char* name)
|
||||
{
|
||||
return name[0] >= 'a' && name[0] <= 'z';
|
||||
}
|
||||
|
||||
int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name,
|
||||
size_t length, Value value, int* line)
|
||||
size_t length, Value value)
|
||||
{
|
||||
if (module->variables.count == MAX_MODULE_VARS) return -2;
|
||||
|
||||
@ -1523,14 +1529,9 @@ int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name,
|
||||
}
|
||||
else if (IS_NUM(module->variables.data[symbol]))
|
||||
{
|
||||
// An implicitly declared variable's value will always be a number.
|
||||
// Now we have a real definition.
|
||||
if(line) *line = AS_NUM(module->variables.data[symbol]);
|
||||
// An implicitly declared variable's value will always be a number. Now we
|
||||
// have a real definition.
|
||||
module->variables.data[symbol] = value;
|
||||
|
||||
// If this was a localname we want to error if it was
|
||||
// referenced before this definition.
|
||||
if (isLocalName(name)) symbol = -3;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -163,15 +163,12 @@ Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name);
|
||||
int wrenDeclareVariable(WrenVM* vm, ObjModule* module, const char* name,
|
||||
size_t length, int line);
|
||||
|
||||
// Adds a new top-level variable named [name] to [module], and optionally
|
||||
// populates line with the line of the implicit first use (line can be NULL).
|
||||
// Adds a new top-level variable named [name] to [module].
|
||||
//
|
||||
// Returns the symbol for the new variable, -1 if a variable with the given name
|
||||
// is already defined, or -2 if there are too many variables defined.
|
||||
// Returns -3 if this is a top-level lowercase variable (localname) that was
|
||||
// used before being defined.
|
||||
int wrenDefineVariable(WrenVM* vm, ObjModule* module, const char* name,
|
||||
size_t length, Value value, int* line);
|
||||
size_t length, Value value);
|
||||
|
||||
// Pushes [closure] onto [fiber]'s callstack to invoke it. Expects [numArgs]
|
||||
// arguments (including the receiver) to be on the top of the stack already.
|
||||
|
||||
40
test/language/assignment/compound_field.wren
Normal file
40
test/language/assignment/compound_field.wren
Normal file
@ -0,0 +1,40 @@
|
||||
class Compound {
|
||||
value { _value }
|
||||
construct new() {
|
||||
_value = 0
|
||||
}
|
||||
|
||||
plus() {
|
||||
_value += 5
|
||||
}
|
||||
minus() {
|
||||
_value -= 1
|
||||
}
|
||||
star() {
|
||||
_value *= 4
|
||||
}
|
||||
slash() {
|
||||
_value /= 2
|
||||
}
|
||||
ltlt() {
|
||||
_value <<= 2
|
||||
}
|
||||
gtgt() {
|
||||
_value >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
var a = Compound.new()
|
||||
System.print(a.value) // expect: 0
|
||||
a.plus()
|
||||
System.print(a.value) // expect: 5
|
||||
a.minus()
|
||||
System.print(a.value) // expect: 4
|
||||
a.star()
|
||||
System.print(a.value) // expect: 16
|
||||
a.slash()
|
||||
System.print(a.value) // expect: 8
|
||||
a.ltlt()
|
||||
System.print(a.value) // expect: 32
|
||||
a.gtgt()
|
||||
System.print(a.value) // expect: 16
|
||||
57
test/language/assignment/compound_setter.wren
Normal file
57
test/language/assignment/compound_setter.wren
Normal file
@ -0,0 +1,57 @@
|
||||
class Compound {
|
||||
value { _value }
|
||||
value=(v) { _value = v }
|
||||
other { _other }
|
||||
other=(v) { _other = v }
|
||||
construct new() {
|
||||
_value = 0
|
||||
}
|
||||
}
|
||||
|
||||
var a = Compound.new()
|
||||
a.value = 0
|
||||
System.print(a.value) // expect: 0
|
||||
a.value += 5
|
||||
System.print(a.value) // expect: 5
|
||||
a.value -= 1
|
||||
System.print(a.value) // expect: 4
|
||||
a.value *= 4
|
||||
System.print(a.value) // expect: 16
|
||||
a.value /= 2
|
||||
System.print(a.value) // expect: 8
|
||||
a.value <<= 1
|
||||
System.print(a.value) // expect: 16
|
||||
a.value >>= 2
|
||||
System.print(a.value) // expect: 4
|
||||
|
||||
a.other = Compound.new()
|
||||
a.other.value = 4
|
||||
System.print(a.other.value) // expect: 4
|
||||
a.other.value += 10
|
||||
System.print(a.other.value) // expect: 14
|
||||
a.other.value -= 2
|
||||
System.print(a.other.value) // expect: 12
|
||||
a.other.value *= 2
|
||||
System.print(a.other.value) // expect: 24
|
||||
a.other.value /= 6
|
||||
System.print(a.other.value) // expect: 4
|
||||
a.other.value <<= 4
|
||||
System.print(a.other.value) // expect: 64
|
||||
a.other.value >>= 3
|
||||
System.print(a.other.value) // expect: 8
|
||||
|
||||
a.other.other = Compound.new()
|
||||
a.other.other.value = 2
|
||||
System.print(a.other.other.value) // expect: 2
|
||||
a.other.other.value += 8
|
||||
System.print(a.other.other.value) // expect: 10
|
||||
a.other.other.value *= 10
|
||||
System.print(a.other.other.value) // expect: 100
|
||||
a.other.other.value -= 1
|
||||
System.print(a.other.other.value) // expect: 99
|
||||
a.other.other.value /= 3
|
||||
System.print(a.other.other.value) // expect: 33
|
||||
a.other.other.value <<= 1
|
||||
System.print(a.other.other.value) // expect: 66
|
||||
a.other.other.value >>= 2
|
||||
System.print(a.other.other.value) // expect: 16
|
||||
67
test/language/assignment/compound_subscript.wren
Normal file
67
test/language/assignment/compound_subscript.wren
Normal file
@ -0,0 +1,67 @@
|
||||
|
||||
class Compound {
|
||||
value { _value }
|
||||
value=(v) { _value = v }
|
||||
other { _other }
|
||||
other=(v) { _other = v }
|
||||
|
||||
[index] { _value }
|
||||
[index]=(v) { _value = v }
|
||||
|
||||
[index, other, last] { _value }
|
||||
[index, other, last]=(v) { _value = v }
|
||||
|
||||
construct new() {
|
||||
_value = 0
|
||||
}
|
||||
}
|
||||
|
||||
var a = Compound.new()
|
||||
a[0] = 0
|
||||
System.print(a[0]) // expect: 0
|
||||
a[0] += 5
|
||||
System.print(a[0]) // expect: 5
|
||||
a[0] -= 1
|
||||
System.print(a[0]) // expect: 4
|
||||
a[0] *= 4
|
||||
System.print(a[0]) // expect: 16
|
||||
a[0] /= 2
|
||||
System.print(a[0]) // expect: 8
|
||||
a[0] <<= 2
|
||||
System.print(a[0]) // expect: 32
|
||||
a[0] >>= 4
|
||||
System.print(a[0]) // expect: 2
|
||||
|
||||
a.other = Compound.new()
|
||||
a.other[0] = 4
|
||||
System.print(a.other[0]) // expect: 4
|
||||
a.other[0] += 10
|
||||
System.print(a.other[0]) // expect: 14
|
||||
a.other[0] -= 2
|
||||
System.print(a.other[0]) // expect: 12
|
||||
a.other[0] *= 2
|
||||
System.print(a.other[0]) // expect: 24
|
||||
a.other[0] /= 6
|
||||
System.print(a.other[0]) // expect: 4
|
||||
a.other[0] <<= 5
|
||||
System.print(a.other[0]) // expect: 128
|
||||
a.other[0] >>= 3
|
||||
System.print(a.other[0]) // expect: 16
|
||||
|
||||
a.other.other = Compound.new()
|
||||
a.other.other[1] = 2
|
||||
System.print(a.other.other[1]) // expect: 2
|
||||
a.other.other[1] += 8
|
||||
System.print(a.other.other[1]) // expect: 10
|
||||
a.other.other[1] *= 10
|
||||
System.print(a.other.other[1]) // expect: 100
|
||||
a.other.other[1] -= 1
|
||||
System.print(a.other.other[1]) // expect: 99
|
||||
a.other.other[1] /= 3
|
||||
System.print(a.other.other[1]) // expect: 33
|
||||
a.other.other[1] <<= 1
|
||||
System.print(a.other.other[1]) // expect: 66
|
||||
a.other.other[1] >>= 3
|
||||
System.print(a.other.other[1]) // expect: 8
|
||||
|
||||
|
||||
20
test/language/assignment/compound_subscript_list.wren
Normal file
20
test/language/assignment/compound_subscript_list.wren
Normal file
@ -0,0 +1,20 @@
|
||||
var list = [1,2,3,4]
|
||||
|
||||
list[0] += 10
|
||||
System.print(list[0]) // expect: 11
|
||||
|
||||
list[1] -= 3
|
||||
System.print(list[1]) // expect: -1
|
||||
|
||||
list[2] *= 9
|
||||
System.print(list[2]) // expect: 27
|
||||
|
||||
list[3] /= 2
|
||||
System.print(list[3]) // expect: 2
|
||||
|
||||
list[0] <<= 2
|
||||
System.print(list[0]) // expect: 44
|
||||
|
||||
list[2] >>= 1
|
||||
System.print(list[2]) // expect: 13
|
||||
|
||||
64
test/language/assignment/compound_subscript_multi.wren
Normal file
64
test/language/assignment/compound_subscript_multi.wren
Normal file
@ -0,0 +1,64 @@
|
||||
|
||||
class Compound {
|
||||
value { _value }
|
||||
value=(v) { _value = v }
|
||||
other { _other }
|
||||
other=(v) { _other = v }
|
||||
|
||||
[index, other, last] { _value }
|
||||
[index, other, last]=(v) { _value = v }
|
||||
|
||||
construct new() {
|
||||
_value = 0
|
||||
}
|
||||
}
|
||||
|
||||
var a = Compound.new()
|
||||
a[0,1,2] = 0
|
||||
System.print(a[0,1,2]) // expect: 0
|
||||
a[0,1,2] += 5
|
||||
System.print(a[0,1,2]) // expect: 5
|
||||
a[0,1,2] -= 1
|
||||
System.print(a[0,1,2]) // expect: 4
|
||||
a[0,1,2] *= 4
|
||||
System.print(a[0,1,2]) // expect: 16
|
||||
a[0,1,2] /= 2
|
||||
System.print(a[0,1,2]) // expect: 8
|
||||
a[0,1,2] <<= 3
|
||||
System.print(a[0,1,2]) // expect: 64
|
||||
a[0,1,2] >>= 2
|
||||
System.print(a[0,1,2]) // expect: 16
|
||||
|
||||
a.other = Compound.new()
|
||||
a.other[0,1,2] = 4
|
||||
System.print(a.other[0,1,2]) // expect: 4
|
||||
a.other[0,1,2] += 10
|
||||
System.print(a.other[0,1,2]) // expect: 14
|
||||
a.other[0,1,2] -= 2
|
||||
System.print(a.other[0,1,2]) // expect: 12
|
||||
a.other[0,1,2] *= 2
|
||||
System.print(a.other[0,1,2]) // expect: 24
|
||||
a.other[0,1,2] /= 6
|
||||
System.print(a.other[0,1,2]) // expect: 4
|
||||
a.other[0,1,2] <<= 6
|
||||
System.print(a.other[0,1,2]) // expect: 256
|
||||
a.other[0,1,2] >>= 3
|
||||
System.print(a.other[0,1,2]) // expect: 32
|
||||
|
||||
a.other.other = Compound.new()
|
||||
a.other.other[1,2,3] = 2
|
||||
System.print(a.other.other[1,2,3]) // expect: 2
|
||||
a.other.other[1,2,3] += 8
|
||||
System.print(a.other.other[1,2,3]) // expect: 10
|
||||
a.other.other[1,2,3] *= 10
|
||||
System.print(a.other.other[1,2,3]) // expect: 100
|
||||
a.other.other[1,2,3] -= 1
|
||||
System.print(a.other.other[1,2,3]) // expect: 99
|
||||
a.other.other[1,2,3] /= 3
|
||||
System.print(a.other.other[1,2,3]) // expect: 33
|
||||
a.other.other[1,2,3] <<= 1
|
||||
System.print(a.other.other[1,2,3]) // expect: 66
|
||||
a.other.other[1,2,3] >>= 4
|
||||
System.print(a.other.other[1,2,3]) // expect: 4
|
||||
|
||||
|
||||
22
test/language/assignment/compound_variable.wren
Normal file
22
test/language/assignment/compound_variable.wren
Normal file
@ -0,0 +1,22 @@
|
||||
var a = 0
|
||||
System.print(a) // expect: 0
|
||||
a += 5
|
||||
System.print(a) // expect: 5
|
||||
a -= 1
|
||||
System.print(a) // expect: 4
|
||||
a *= 4
|
||||
System.print(a) // expect: 16
|
||||
a /= 2
|
||||
System.print(a) // expect: 8
|
||||
a <<= 8
|
||||
System.print(a) // expect: 2048
|
||||
a >>= 6
|
||||
System.print(a) // expect: 32
|
||||
a %= 3
|
||||
System.print(a) // expect: 2
|
||||
a ^= 4
|
||||
System.print(a) // expect: 6
|
||||
a |= 8
|
||||
System.print(a) // expect: 14
|
||||
a &= 6
|
||||
System.print(a) // expect: 6
|
||||
@ -1,5 +0,0 @@
|
||||
if (false) {
|
||||
System.print(a)
|
||||
}
|
||||
|
||||
var a = 123 // expect error: Error at '123': Variable 'a' referenced before this definition (first use at line 2).
|
||||
Reference in New Issue
Block a user