Skip to content

Commit 69a41d3

Browse files
authored
Merge pull request #30 from peterhuene/splat
Implement spreading of instantiation args and exports.
2 parents 513e61b + d536019 commit 69a41d3

33 files changed

Lines changed: 1051 additions & 333 deletions

LANGUAGE.md

Lines changed: 150 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ In the simplest terms, a composition is a collection of components that
99
are instantiated in a topological order and certain exports from those
1010
instances are made available as exports of the composition itself.
1111

12-
WAC currently has three statements that extend the WIT language: imports,
13-
`let`, and exports.
12+
WAC currently has three statements that extend the WIT language: import
13+
statements, let statements, and export statements.
1414

1515
### Import Statements
1616

@@ -41,11 +41,11 @@ resulting composition; in the above example, the import name would be
4141
The `as` keyword can be used to rename the imported item:
4242

4343
```wac
44-
import greeter as "my-greeter": example:greeter/greeter;
44+
import greeter as my-greeter: example:greeter/greeter;
4545
```
4646

47-
Here the name of the import becomes `my-greeter`; the name may be any valid import
48-
name in the component model.
47+
Here the name of the import becomes `my-greeter`; the name may be any valid
48+
import name in the component model.
4949

5050
The local name `greeter` can then be used to refer to the imported item from
5151
the rest of the composition.
@@ -95,7 +95,8 @@ let my-instance = new example:my-component { ... };
9595

9696
The special `...` syntax indicates that any arguments that were not specified
9797
as part of the `new` expression should be imported from the composition and
98-
passed through to the instantiation.
98+
passed through to the instantiation. The `...` syntax must be used as the last
99+
argument to the `new` expression.
99100

100101
The above is equivalent to:
101102

@@ -154,19 +155,20 @@ The `let` statement allows for binding a local name in the composition to the
154155
result of an expression.
155156

156157
Note that local names are not variables and cannot be reassigned; a
157-
redefinition of a name is an error.
158+
redefinition of a previous name is an error.
158159

159-
There are currently four expressions in WAC:
160+
There are currently five expressions in WAC:
160161

162+
* A local name (e.g. `a`), resulting in the value of the name.
161163
* The `new` expression (e.g `new a:b { }`)
162164
* The postfix access expression (e.g. `a.b`).
163165
* The postfix named access expression (e.g. `a["b"]`)
164166
* The nested expression (e.g. `(<expr>)`).
165167

166168
#### New Expressions
167169

168-
A `new` expression instantiates a component and returns an instance
169-
representing the _exports_ of the component.
170+
A `new` expression instantiates a component and returns a component instance
171+
representing its _exports_.
170172

171173
The `new` expression takes the name of the package (i.e. component) to
172174
instantiate and a list of instantiation arguments (i.e. imports).
@@ -178,35 +180,40 @@ arguments to the instantiation.
178180
If `...` is not specified, then all instantiation arguments must be explicitly
179181
specified.
180182

181-
There are two forms of instantiation arguments: implicitly named and explicitly
182-
named.
183+
There are three forms of instantiation arguments: inferred arguments,
184+
named arguments, and spread arguments.
183185

184-
An implicitly named argument is passed by identifier alone:
186+
##### Inferred Arguments
187+
188+
An inferred argument is passed directly by local name:
185189

186190
```wac
187191
let i = new a:b { c };
188192
```
189193

190194
Where `i` is the name of the bound instance, `a:b` is the package being
191-
instantiated, and `c` is the instantiation argument.
195+
instantiated, and the local name `c` is the instantiation argument.
192196

193197
The name of the instantiation argument is inferred according to these rules (in
194198
order of precedence):
195199

196-
* If `c` is an instance of an interface with an associated package path (e.g.
197-
`x:y/z`) and package `a:b` has an import with a matching path, then the path
198-
will be used as the argument name.
200+
* If the local name is bound to an instance with an associated package path (
201+
e.g. `foo:bar/baz`) and the component being instantiated has an import with a
202+
matching path, then the path will be used as the argument name.
203+
204+
* If the local name is bound to an explicit import or an access of an instance
205+
export and the component being instantiated has an import with a matching name,
206+
then the import/export name will be used as the argument name.
199207

200-
* If `c` is an explicit import or an access of an instance export and the
201-
package `a:b` has an import with a matching name, then the import/export name
202-
will be used as the argument name.
208+
* If the component being instantiated has exactly one import that has a path
209+
which ends with the local name (e.g. `foo:bar/c`), then the path will be used
210+
as the argument name.
203211

204-
* If component `a:b` has exactly one import with `c` as the final component of
205-
a path (e.g. `x:y/c`), then the path will be used as the argument name.
212+
* Lastly, the local name will be used as the argument name.
206213

207-
* Lastly, `c` will be used as the argument name.
214+
##### Named Arguments
208215

209-
Explicitly named arguments are passed as a name/value pair:
216+
Named arguments provide the name of the instantiation argument to use:
210217

211218
```wac
212219
let i = new a:b { c: d };
@@ -216,19 +223,67 @@ Where `i` is the name of the bound instance, `a:b` is the package being
216223
instantiated, `c` is the name of the instantiation argument, and `d` is the
217224
value of the argument.
218225

219-
Even with this form, the name of the instantiation argument is inferred
220-
according to the last two rules stated above; for example, the explicit
221-
argument `input-stream: stream` may be used to refer to an import of `wasi:io/
222-
input-stream` or an import of `input-stream`.
226+
The name may be specified as a kebab-case identifier (e.g. `foo-bar`) or as a
227+
string (e.g. `"foo-bar"`).
228+
229+
When using a kebab-case identifier, a special rule is used to determine the
230+
actual name of the argument:
223231

224-
Explicit argument names may also be strings:
232+
* If the component being instantiated has exactly one import that has a path
233+
which ends with the local name (e.g. `foo:bar/foo-bar`), then the path will be
234+
used as the argument name.
235+
236+
* Otherwise, the kebab-case identifier will be used as the argument name.
237+
238+
With this rule, the following example is valid:
225239

226240
```wac
227-
let i = new a:b { "c": d };
241+
let stream = new my:stream {};
242+
243+
// Assumption: `a:b` has an import with path `wasi:io/input-stream`
244+
let i = new a:b { input-stream: stream };
228245
```
229246

230-
This is equivalent to the previous example except that the argument name `c` is
231-
_always_ used; no inference is performed.
247+
##### Spread Arguments
248+
249+
Spread arguments allow for specifying the _exports of an instance_ as arguments
250+
to the instantiation of a component.
251+
252+
Like the `...` syntax for implicit imports, a `...` precedes a local name as an
253+
indication to spread the instance's exports as arguments to the instantiation:
254+
255+
```wac
256+
let i = new a:b { ...c };
257+
```
258+
259+
Where `i` is the name of the bound instance, `a:b` is the package being
260+
instantiated and `c` is the local name of an instance.
261+
262+
It in an error for the local name used in a spread argument to name anything
263+
other than an instance.
264+
265+
With this syntax, the exports of the instance `c` will be spread to any
266+
_unspecified_ and _unsatisfied_ instantiation arguments.
267+
268+
Note that spread arguments apply _after_ inferred and named arguments and are
269+
applied _in-order_; this means that the order of spread arguments is important:
270+
271+
```
272+
let i = new a:b { foo: bar, ...c, baz, ...d };
273+
```
274+
275+
In the above example, arguments `foo` and `baz` will bind to the instantiation
276+
arguments of `a:b` first.
277+
278+
Any unsatisfied arguments will then be satisfied by the matching exports of
279+
instance `c`, followed by any matching the exports of instance `d`.
280+
281+
The above behavior differs from JavaScript's spread argument syntax, which is
282+
the inspiration for this syntax, because component instantiation arguments are
283+
_named_ and not _positional_.
284+
285+
Additionally, it is an evaluation error if a spread argument has no matching
286+
exports.
232287

233288
#### Access Expressions
234289

@@ -242,14 +297,17 @@ let i = new a:b { ... };
242297
let s = i.outgoing-stream;
243298
``````
244299
245-
Access expressions use the following rules to determine the name of the export:
300+
Taking the above example into consideration, access expressions use the
301+
following rules to determine the name of the export:
246302
247303
* If component `a:b` has exactly one export with `outgoing-stream` as the final
248304
component of a path (e.g. `wasi:io/outgoing-stream`), then the path will be
249305
used as the export name.
250306
251307
* Otherwise, `outgoing-stream` will be used as the export name.
252308
309+
It is invalid to use an access expression on anything other than an instance.
310+
253311
#### Named Access Expressions
254312
255313
Similar to the postfix access expression, the postfix named access expression
@@ -264,48 +322,83 @@ let s = i["wasi:io/outgoing-stream"];
264322
```
265323

266324
This is equivalent to the previous example except that the export name is
267-
_exactly_ what was specified; no inference is performed.
325+
_exactly_ what is specified by the string; no inference is performed.
268326

269327
The string may be any legal export name in the component model.
270328

329+
It is invalid to use a named access expression on anything other than an
330+
instance.
331+
271332
### Export Statements
272333

273334
Export statements are used to export the result of an expression from the
274-
composition itself.
335+
composition itself:
275336

276-
The export statement can be used to export the item represented by a local name:
337+
```wac
338+
let my-run = new a:b { ... }.run;
339+
export my-run;
340+
341+
// note: this is also equivalent to:
342+
// export new a:b { ... }.run;
343+
```
344+
345+
In the above example, component `a:b` is instantiated and the local name
346+
`my-run` is bound to the export of `run` from the instance, which we can assume
347+
in this example to be a function of type `func()`.
348+
349+
The `export` statement is then used to export the `my-run` function from the
350+
composition using the export name `run` as that is the name that was accessed
351+
from the instance.
352+
353+
The `as` keyword can be used to rename the export:
277354

278355
```wac
279-
let i = new a:b { ... };
280-
let s = i.streams;
281-
export s;
356+
let my-run = new a:b { ... }.run;
357+
export my-run as "my-run";
282358
```
283359

284-
In the above example, component `a:b` is instantiated and `s` is bound to the
285-
export of `wasi:io/streams` from the instance.
360+
Like spread arguments in `new` expressions, exports of an instance may be
361+
_spread_ as exports of the composition:
286362

287-
The `export` statement is then used to export `s` from the composition.
363+
```wac
364+
let my-instance = new a:b { ... };
365+
export my-instance...;
366+
```
367+
368+
If we assume that component `a:b` in the above example has the following world:
369+
370+
```wit
371+
interface setup {
372+
setup: func(config: string);
373+
}
288374
289-
The name of the export is inferred according to these rules (in order of
290-
precedence):
375+
world a:b {
376+
export run: func();
377+
export setup: setup;
378+
}
379+
```
291380

292-
* If the item is an instance of an interface with an associated package path (e.g.
293-
`x:y/z`), then the path is used as the export name.
381+
Then the composition will export a `run` function of type `func()` and an
382+
instance with name `a:b/setup`.
294383

295-
* If the item is an explicit import or an access of an instance export, then
296-
that name is used.
384+
It is an evaluation error if the instance being spread has no exports; it is a
385+
syntax error to use an `as` clause with a spread export.
297386

298-
In the above example, the export will be named `wasi:io/streams` as that is
299-
ultimately the name of the export that was accessed from instance `i`.
387+
Spread exports will only create new exports that do not conflict with
388+
previously exported items.
300389

301-
If the export name cannot be inferred, then the export name must be explicitly
302-
specified with the `as` keyword:
390+
If we extend the above example to be:
303391

304392
```wac
305-
let i = new a:b { ... };
306-
export i as "my-instance";
393+
let my-instance = new a:b { ... };
394+
let run = b:c { ... }.run;
395+
export run;
396+
export my-instance...;
307397
```
308398

399+
Then the `run` function exported by the composition will be from the instance
400+
named `b:c` and not the instance named `a:b`.
401+
309402
### WAC Grammar
310403

311404
The current WAC grammar:
@@ -403,14 +496,15 @@ expr ::= primary-expr postfix-expr*
403496
primary-expr ::= new-expr | nested-expr | id
404497
new-expr ::= 'new' package-name '{' instantiation-args '}'
405498
instantiation-args ::= instantiation-arg (',' instantiation-arg)* (',' '...'?)?
406-
instantiation-arg ::= named-instantiation-arg | id
499+
instantiation-arg ::= id | '...' id | named-instantiation-arg
407500
named-instantiation-arg ::= (id | string) ':' expr
408501
nested-expr ::= '(' expr ')'
409502
postfix-expr ::= access-expr | named-access-expr
410503
access-expr ::= '.' id
411504
named-access-expr ::= '[' string ']'
412505
413-
export-statement ::= 'export' expr ('as' (id | string))? ';'
506+
export-statement ::= 'export' expr (export-options)? ';'
507+
export-options ::= `...` | 'as' (id | string)
414508
415509
id ::= '%'?[a-z][a-z0-9]*('-'[a-z][a-z0-9]*)*
416510
string ::= '"' character-that-is-not-a-double-quote* '"'

crates/wac-parser/src/ast.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -258,18 +258,13 @@ pub(crate) trait Parse<'a>: Sized {
258258

259259
fn parse_delimited<'a, T: Parse<'a> + Peek>(
260260
lexer: &mut Lexer<'a>,
261-
until: &[Token],
261+
until: Token,
262262
with_commas: bool,
263263
) -> ParseResult<Vec<T>> {
264-
assert!(
265-
!until.is_empty(),
266-
"must have at least one token to parse until"
267-
);
268-
269264
let mut items = Vec::new();
270265
loop {
271266
let mut lookahead = Lookahead::new(lexer);
272-
if until.iter().any(|t| lookahead.peek(*t)) {
267+
if lookahead.peek(until) {
273268
break;
274269
}
275270

@@ -279,9 +274,13 @@ fn parse_delimited<'a, T: Parse<'a> + Peek>(
279274

280275
items.push(Parse::parse(lexer)?);
281276

282-
if with_commas {
283-
if let Some((Ok(Token::Comma), _)) = lexer.peek() {
284-
lexer.next();
277+
if let Some((Ok(next), _)) = lexer.peek() {
278+
if next == until {
279+
break;
280+
}
281+
282+
if with_commas {
283+
parse_token(lexer, Token::Comma)?;
285284
}
286285
}
287286
}

0 commit comments

Comments
 (0)