Skip to content

Commit f57a3ed

Browse files
authored
Merge pull request #67 from mkantor/generalized-keyword-expression-sugar
Generalized keyword expression syntax sugar
2 parents 1778e16 + 7fef85a commit f57a3ed

38 files changed

+994
-645
lines changed

README.md

Lines changed: 81 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ git clone [email protected]:mkantor/please-lang-prototype.git
99
cd please-lang-prototype
1010
npm install
1111
npm run build
12-
echo '{@runtime, context => :context.program.start_time}' | ./please --output-format=json
12+
echo '@runtime { context => :context.program.start_time }' | ./please --output-format=json
1313
```
1414

1515
There are more example programs in [`./examples`](./examples).
@@ -45,7 +45,7 @@ data representation implied by the fact that a value is an atom (e.g. the atom
4545
`2` may be an integer in memory).
4646

4747
Bare words not containing any
48-
[reserved character sequences](./src/language/parsing/atom.ts#L33-L55) are
48+
[reserved character sequences](./src/language/parsing/atom.ts#L34-L57) are
4949
atoms:
5050

5151
```
@@ -178,13 +178,18 @@ expressions_. Most of the interesting stuff that Please does involves evaluating
178178
keyword expressions.
179179

180180
Under the hood, keyword expressions are modeled as objects. For example, `:foo`
181-
desugars to `{ @lookup, key: foo }`. All such expressions have a key `0`
182-
referring to a value that is an `@`-prefixed atom (the keyword). Keywords
183-
include `@apply`, `@check`, `@function`, `@if`, `@index`, `@lookup`, `@panic`,
184-
and `@runtime`.
181+
desugars to `{ 0: "@lookup", 1: { key: foo } }`. All such expressions have a
182+
property named `0` referring to a value that is an `@`-prefixed atom (the
183+
keyword). Most keyword expressions also require a property named `1` to pass an
184+
argument to the expression. Keywords include `@apply`, `@check`, `@function`,
185+
`@if`, `@index`, `@lookup`, `@panic`, and `@runtime`.
185186

186-
Currently only `@function`, `@lookup`, `@index`, and `@apply` have syntax
187-
sugars.
187+
In addition to the specific syntax sugars shown above, any keyword expression
188+
can be written using a generalized sugar:
189+
190+
```
191+
@keyword { … } // desugars to `{ 0: "@keyword", 1: { … } }`
192+
```
188193

189194
### Semantics
190195

@@ -210,7 +215,7 @@ function from other programming languages, except there can be any number of
210215
`@runtime` expressions in a given program. Here's an example:
211216

212217
```
213-
{@runtime, context => :context.program.start_time}
218+
@runtime { context => :context.program.start_time }
214219
```
215220

216221
Unsurprisingly, this program outputs the current time when run.
@@ -249,7 +254,7 @@ Take this example `plz` program:
249254
{
250255
language: Please
251256
message: :atom.prepend("Welcome to ")(:language)
252-
now: {@runtime, context => :context.program.start_time}
257+
now: @runtime { context => :context.program.start_time }
253258
}
254259
```
255260

@@ -259,40 +264,58 @@ It desugars to the following `plo` program:
259264
{
260265
language: Please
261266
message: {
262-
0: @apply
263-
function: {
264-
0: @apply
267+
0: "@apply"
268+
1: {
265269
function: {
266-
0: @index
267-
object: {
268-
0: @lookup
269-
key: atom
270+
0: "@apply"
271+
1: {
272+
function: {
273+
0: "@index"
274+
1: {
275+
object: {
276+
0: "@lookup"
277+
1: {
278+
key: atom
279+
}
280+
}
281+
query: {
282+
0: prepend
283+
}
284+
}
285+
}
286+
argument: "Welcome to "
270287
}
271-
query: {
272-
0: prepend
288+
}
289+
argument: {
290+
0: "@lookup"
291+
1: {
292+
key: language
273293
}
274294
}
275-
argument: "Welcome to "
276-
}
277-
argument: {
278-
0: @lookup
279-
key: language
280295
}
281296
}
282297
now: {
283-
0: @runtime
298+
0: "@runtime"
284299
1: {
285-
0: @function
286-
parameter: context
287-
body: {
288-
0: @index
289-
object: {
290-
0: @lookup
291-
key: context
292-
}
293-
query: {
294-
0: program
295-
1: start_time
300+
0: {
301+
0: "@function"
302+
1: {
303+
parameter: context
304+
body: {
305+
0: "@index"
306+
1: {
307+
object: {
308+
0: "@lookup"
309+
1: {
310+
key: context
311+
}
312+
}
313+
query: {
314+
0: program
315+
1: start_time
316+
}
317+
}
318+
}
296319
}
297320
}
298321
}
@@ -307,19 +330,27 @@ Which in turn compiles to the following `plt` program:
307330
language: Please
308331
message: "Welcome to Please"
309332
now: {
310-
0: @runtime
311-
function: {
312-
0: @function
313-
parameter: context
314-
body: {
315-
0: @index
316-
object: {
317-
0: @lookup
318-
key: context
319-
}
320-
query: {
321-
0: program
322-
1: start_time
333+
0: "@runtime"
334+
1: {
335+
function: {
336+
0: "@function"
337+
1: {
338+
parameter: context
339+
body: {
340+
0: "@index"
341+
1: {
342+
object: {
343+
0: "@lookup"
344+
1: {
345+
key: context
346+
}
347+
}
348+
query: {
349+
0: program
350+
1: start_time
351+
}
352+
}
353+
}
323354
}
324355
}
325356
}
@@ -333,7 +364,7 @@ Which produces the following runtime output:
333364
{
334365
language: Please
335366
message: "Welcome to Please"
336-
now: "2025-02-14T18:45:14.168Z"
367+
now: "2025-05-13T22:47:50.802Z"
337368
}
338369
```
339370

examples/fibonacci.plz

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
{
2-
fibonacci: n => {
3-
@if
4-
condition: :n < 2
5-
then: :n
6-
else: :fibonacci(:n - 1) + :fibonacci(:n - 2)
7-
}
2+
fibonacci: n =>
3+
@if {
4+
condition: :n < 2
5+
then: :n
6+
else: :fibonacci(:n - 1) + :fibonacci(:n - 2)
7+
}
88

9-
input: {
10-
@runtime
11-
context => :context.arguments.lookup(input)
9+
input: @runtime { context =>
10+
:context.arguments.lookup(input)
1211
}
1312

1413
output: :input match {
1514
none: _ => "missing input argument"
16-
some: input => {
17-
@if
15+
some: input => @if {
1816
condition: :natural_number.is(:input)
1917
then: :fibonacci(:input)
2018
else: "input must be a natural number"

examples/kitchen-sink.plz

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
add_one: :integer.add(1)
99
three: :add_one(:two)
1010
function: x => { value: :x }
11-
conditional_value: :function({ @if, :sky_is_blue, :two, :three })
12-
side_effect: { @runtime, context => :context.log("this goes to stderr") }
11+
conditional_value: :function(@if { :sky_is_blue, :two, :three })
12+
side_effect: @runtime { context => :context.log("this goes to stderr") }
1313
}

examples/lookup-environment-variable.plz

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22
* Given CLI arguments like `--variable=FOO`, looks up the environment
33
* variable named `FOO`.
44
*/
5-
{
6-
@runtime
7-
context =>
8-
:context.arguments.lookup(variable) match {
5+
@runtime { context =>
6+
:context.arguments.lookup(variable) match {
7+
none: {}
8+
some: :context.environment.lookup >> :match({
99
none: {}
10-
some: :context.environment.lookup >> :match({
11-
none: {}
12-
some: :identity
13-
})
14-
}
10+
some: :identity
11+
})
12+
}
1513
}

0 commit comments

Comments
 (0)