Skip to content

Commit 1a321ee

Browse files
committed
Require quotes around @ in atoms
This carves out syntax space for a planned generalized keyword expression sugar (`@` will become relevant to parsing).
1 parent dc6b02b commit 1a321ee

File tree

8 files changed

+51
-48
lines changed

8 files changed

+51
-48
lines changed

README.md

Lines changed: 19 additions & 19 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,7 +178,7 @@ 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 property
181+
desugars to `{ "@lookup", { key: foo } }`. All such expressions have a property
182182
named `0` referring to a value that is an `@`-prefixed atom (the keyword). Most
183183
keyword expressions also require a property named `1` to pass an argument to the
184184
expression. Keywords include `@apply`, `@check`, `@function`, `@if`, `@index`,
@@ -211,7 +211,7 @@ function from other programming languages, except there can be any number of
211211
`@runtime` expressions in a given program. Here's an example:
212212

213213
```
214-
{@runtime, { context => :context.program.start_time }}
214+
{"@runtime", { context => :context.program.start_time }}
215215
```
216216

217217
Unsurprisingly, this program outputs the current time when run.
@@ -250,7 +250,7 @@ Take this example `plz` program:
250250
{
251251
language: Please
252252
message: :atom.prepend("Welcome to ")(:language)
253-
now: {@runtime, { context => :context.program.start_time }}
253+
now: {"@runtime", { context => :context.program.start_time }}
254254
}
255255
```
256256

@@ -260,16 +260,16 @@ It desugars to the following `plo` program:
260260
{
261261
language: Please
262262
message: {
263-
0: @apply
263+
0: "@apply"
264264
1: {
265265
function: {
266-
0: @apply
266+
0: "@apply"
267267
1: {
268268
function: {
269-
0: @index
269+
0: "@index"
270270
1: {
271271
object: {
272-
0: @lookup
272+
0: "@lookup"
273273
1: {
274274
key: atom
275275
}
@@ -283,25 +283,25 @@ It desugars to the following `plo` program:
283283
}
284284
}
285285
argument: {
286-
0: @lookup
286+
0: "@lookup"
287287
1: {
288288
key: language
289289
}
290290
}
291291
}
292292
}
293293
now: {
294-
0: @runtime
294+
0: "@runtime"
295295
1: {
296296
0: {
297-
0: @function
297+
0: "@function"
298298
1: {
299299
parameter: context
300300
body: {
301-
0: @index
301+
0: "@index"
302302
1: {
303303
object: {
304-
0: @lookup
304+
0: "@lookup"
305305
1: {
306306
key: context
307307
}
@@ -326,17 +326,17 @@ Which in turn compiles to the following `plt` program:
326326
language: Please
327327
message: "Welcome to Please"
328328
now: {
329-
0: @runtime
329+
0: "@runtime"
330330
1: {
331331
function: {
332-
0: @function
332+
0: "@function"
333333
1: {
334334
parameter: context
335335
body: {
336-
0: @index
336+
0: "@index"
337337
1: {
338338
object: {
339-
0: @lookup
339+
0: "@lookup"
340340
1: {
341341
key: context
342342
}
@@ -360,7 +360,7 @@ Which produces the following runtime output:
360360
{
361361
language: Please
362362
message: "Welcome to Please"
363-
now: "2025-05-13T22:11:56.804Z"
363+
now: "2025-05-13T22:47:50.802Z"
364364
}
365365
```
366366

examples/fibonacci.plz

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
fibonacci: n => {
3-
@if
3+
"@if"
44
{
55
condition: :n < 2
66
then: :n
@@ -9,14 +9,14 @@
99
}
1010

1111
input: {
12-
@runtime
12+
"@runtime"
1313
{ context => :context.arguments.lookup(input) }
1414
}
1515

1616
output: :input match {
1717
none: _ => "missing input argument"
1818
some: input => {
19-
@if
19+
"@if"
2020
{
2121
condition: :natural_number.is(:input)
2222
then: :fibonacci(:input)

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* variable named `FOO`.
44
*/
55
{
6-
@runtime
6+
"@runtime"
77
{
88
context =>
99
:context.arguments.lookup(variable) match {

src/end-to-end.test.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,21 @@ testCases(endToEnd, code => code)('end-to-end tests', [
2828
['{,a,b,c,}', either.makeRight({ 0: 'a', 1: 'b', 2: 'c' })],
2929
['{a,1:overwritten,c}', either.makeRight({ 0: 'a', 1: 'c' })],
3030
['{overwritten,0:a,c}', either.makeRight({ 0: 'a', 1: 'c' })],
31-
['{@check, {type:true, value:true}}', either.makeRight('true')],
31+
['{"@check", {type:true, value:true}}', either.makeRight('true')],
3232
[
33-
'{@panic}',
33+
'{"@panic"}',
3434
result => {
3535
assert(either.isLeft(result))
3636
assert('kind' in result.value)
3737
assert.deepEqual(result.value.kind, 'panic')
3838
},
3939
],
40-
['{a:A, b:{@lookup, {a}}}', either.makeRight({ a: 'A', b: 'A' })],
41-
['{a:A, {@lookup, {a}}}', either.makeRight({ a: 'A', 0: 'A' })],
40+
['{a:A, b:{"@lookup", {a}}}', either.makeRight({ a: 'A', b: 'A' })],
41+
['{a:A, {"@lookup", {a}}}', either.makeRight({ a: 'A', 0: 'A' })],
4242
['{a:A, b: :a}', either.makeRight({ a: 'A', b: 'A' })],
4343
['{a:A, :a}', either.makeRight({ a: 'A', 0: 'A' })],
4444
[
45-
'{@runtime, {_ => {@panic}}}',
45+
'{"@runtime", {_ => {"@panic"}}}',
4646
result => {
4747
assert(either.isLeft(result))
4848
assert('kind' in result.value)
@@ -90,12 +90,12 @@ testCases(endToEnd, code => code)('end-to-end tests', [
9090
// foo: bar
9191
"static data":"blah blah blah"
9292
"evaluated data": {
93-
0:@runtime
93+
0:"@runtime"
9494
1:{
9595
function:{
96-
0:@apply
96+
0:"@apply"
9797
1:{
98-
function:{0:@index, 1:{object:{0:@lookup, 1:{key:object}}, query:{0:lookup}}}
98+
function:{0:"@index", 1:{object:{0:"@lookup", 1:{key:object}}, query:{0:lookup}}}
9999
argument:"key which does not exist in runtime context"
100100
}
101101
}
@@ -153,7 +153,7 @@ testCases(endToEnd, code => code)('end-to-end tests', [
153153
],
154154
[':match({ a: A })({ tag: a, value: {} })', either.makeRight('A')],
155155
[
156-
`{@runtime, { context =>
156+
`{"@runtime", { context =>
157157
:identity(:context).program.start_time
158158
}}`,
159159
output => {
@@ -181,7 +181,7 @@ testCases(endToEnd, code => code)('end-to-end tests', [
181181
[`(1 - 2) - 3`, either.makeRight('-4')],
182182
[':flow(:atom.append(b))(:atom.append(a))(z)', either.makeRight('zab')],
183183
[
184-
`{@runtime
184+
`{"@runtime"
185185
{ :object.lookup("key which does not exist in runtime context") }
186186
}`,
187187
either.makeRight({ tag: 'none', value: {} }),
@@ -213,7 +213,7 @@ testCases(endToEnd, code => code)('end-to-end tests', [
213213
either.makeRight({ true: 'true', false: 'false' }),
214214
],
215215
[
216-
`{@runtime, {
216+
`{"@runtime", {
217217
:flow(
218218
:match({
219219
none: "environment does not exist"
@@ -260,7 +260,7 @@ testCases(endToEnd, code => code)('end-to-end tests', [
260260
either.makeRight({ 0: 'a', 1: 'b', 2: 'c', 3: 'd' }),
261261
],
262262
[
263-
`{@runtime, { context =>
263+
`{"@runtime", { context =>
264264
:context.environment.lookup(PATH)
265265
}}`,
266266
output => {
@@ -273,10 +273,10 @@ testCases(endToEnd, code => code)('end-to-end tests', [
273273
},
274274
],
275275
[
276-
`{@if, {
276+
`{"@if", {
277277
true
278278
"it works!"
279-
{@panic}
279+
{"@panic"}
280280
}}`,
281281
either.makeRight('it works!'),
282282
],
@@ -289,19 +289,19 @@ testCases(endToEnd, code => code)('end-to-end tests', [
289289
either.makeRight({ 0: 'a', 1: 'b', 2: 'c' }),
290290
],
291291
[
292-
`{@runtime, { context =>
293-
{@if, {
292+
`{"@runtime", { context =>
293+
{"@if", {
294294
:boolean.not(:boolean.is(:context))
295295
"it works!"
296-
{@panic}
296+
{"@panic"}
297297
}}
298298
}}`,
299299
either.makeRight('it works!'),
300300
],
301301
[
302302
`{
303303
fibonacci: n => {
304-
@if, {
304+
"@if", {
305305
:integer.less_than(2)(:n)
306306
then: :n
307307
else: :fibonacci(:n - 1) + :fibonacci(:n - 2)
@@ -362,7 +362,7 @@ testCases(endToEnd, code => code)('end-to-end tests', [
362362
either.makeRight('2'),
363363
],
364364
[
365-
`{@runtime, { context =>
365+
`{"@runtime", { context =>
366366
(
367367
PATH
368368
|> :context.environment.lookup

src/language/compiling/unparsing.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ testCases(
2424
[{}, either.makeRight('{}')],
2525
['a', either.makeRight('a')],
2626
['Hello, world!', either.makeRight('"Hello, world!"')],
27-
['@test', either.makeRight('@test')],
27+
['@test', either.makeRight('"@test"')],
2828
[{ 0: 'a' }, either.makeRight('{ a }')],
2929
[{ 1: 'a' }, either.makeRight('{ 1: a }')],
3030
[
@@ -87,7 +87,7 @@ testCases(
8787
},
8888
},
8989
either.makeRight(
90-
'{ @runtime, { context => :context.program.start_time } }',
90+
'{ "@runtime", { context => :context.program.start_time } }',
9191
),
9292
],
9393
[
@@ -118,7 +118,7 @@ testCases(
118118
[{}, either.makeRight('{}')],
119119
['a', either.makeRight('a')],
120120
['Hello, world!', either.makeRight('"Hello, world!"')],
121-
['@test', either.makeRight('@test')],
121+
['@test', either.makeRight('"@test"')],
122122
[{ 0: 'a' }, either.makeRight('{\n a\n}')],
123123
[{ 1: 'a' }, either.makeRight('{\n 1: a\n}')],
124124
[
@@ -183,7 +183,7 @@ testCases(
183183
},
184184
},
185185
either.makeRight(
186-
'{\n @runtime\n {\n context => :context.program.start_time\n }\n}',
186+
'{\n "@runtime"\n {\n context => :context.program.start_time\n }\n}',
187187
),
188188
],
189189
[

src/language/parsing/atom.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
zeroOrMore,
1212
} from '@matt.kantor/parsing'
1313
import {
14+
atSign,
1415
backslash,
1516
closingBlockCommentDelimiter,
1617
closingBrace,
@@ -31,6 +32,7 @@ import { whitespace } from './trivia.js'
3132
export type Atom = string
3233

3334
const atomComponentsRequiringQuotation = [
35+
atSign,
3436
backslash,
3537
closingBlockCommentDelimiter,
3638
closingBrace,

src/language/parsing/literals.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { literal } from '@matt.kantor/parsing'
22

33
export const arrow = literal('=>')
44
export const asterisk = literal('*')
5+
export const atSign = literal('@')
56
export const backslash = literal('\\')
67
export const closingBlockCommentDelimiter = literal('*/')
78
export const closingBrace = literal('}')

0 commit comments

Comments
 (0)