Skip to content

Commit 77c515b

Browse files
authored
Merge pull request #55 from mkantor/require-commas-or-newlines
Syntax change: require commas or newlines to delimit properties
2 parents 9224aa3 + 1246ee3 commit 77c515b

File tree

8 files changed

+58
-47
lines changed

8 files changed

+58
-47
lines changed

README.md

Lines changed: 10 additions & 11 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).
@@ -72,22 +72,21 @@ Objects are maps of key/value pairs ("properties"), where keys must be atoms:
7272
{ greeting: "Hello, World!" }
7373
```
7474

75-
Properties are delimited by whitespace and/or commas:
75+
Properties are delimited by newlines or commas:
7676

7777
```
78-
// These all mean the same thing:
78+
// These mean the same thing:
7979
{
8080
a: 1
8181
b: 2
8282
}
8383
{ a: 1, b: 2 }
84-
{ a: 1 b: 2 }
8584
```
8685

8786
Properties without explicitly-written keys are automatically enumerated:
8887

8988
```
90-
{ a b } // is the same as { 0: a, 1: b }
89+
{ a, b } // is the same as { 0: a, 1: b }
9190
```
9291

9392
#### Lookups
@@ -157,10 +156,10 @@ expressions_. Most of the interesting stuff that Please does involves evaluating
157156
keyword expressions.
158157

159158
Under the hood, keyword expressions are modeled as objects. For example, `:foo`
160-
desugars to `{@lookup key: foo}`. All such expressions have a key `0` referring
161-
to a value that is an `@`-prefixed atom (the keyword). Keywords include
162-
`@apply`, `@check`, `@function`, `@if`, `@index`, `@lookup`, `@panic`, and
163-
`@runtime`.
159+
desugars to `{ @lookup, key: foo }`. All such expressions have a key `0`
160+
referring to a value that is an `@`-prefixed atom (the keyword). Keywords
161+
include `@apply`, `@check`, `@function`, `@if`, `@index`, `@lookup`, `@panic`,
162+
and `@runtime`.
164163

165164
Currently only `@function`, `@lookup`, `@index`, and `@apply` have syntax
166165
sugars.
@@ -188,7 +187,7 @@ function from other programming languages, except there can be any number of
188187
`@runtime` expressions in a given program. Here's an example:
189188

190189
```
191-
{@runtime context => :context.program.start_time}
190+
{@runtime, context => :context.program.start_time}
192191
```
193192

194193
Unsurprisingly, this program outputs the current time when run.
@@ -227,7 +226,7 @@ Take this example `plz` program:
227226
{
228227
language: Please
229228
message: :atom.prepend("Welcome to ")(:language)
230-
now: {@runtime context => :context.program.start_time}
229+
now: {@runtime, context => :context.program.start_time}
231230
}
232231
```
233232

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 :integer.less_than(2)(:n)
3+
@if, :integer.less_than(2)(:n)
44
then: :n
55
else: :integer.add(
66
:fibonacci(:integer.subtract(2)(:n))
@@ -9,13 +9,13 @@
99
)
1010
}
1111

12-
input: { @runtime context => :context.arguments.lookup(input) }
12+
input: { @runtime, context => :context.arguments.lookup(input) }
1313

1414
output: :apply(:input)(
1515
:match({
1616
none: _ => "missing input argument"
1717
some: input => {
18-
@if :natural_number.is(:input)
18+
@if, :natural_number.is(:input)
1919
then: :fibonacci(:input)
2020
else: "input must be a natural number"
2121
}

examples/kitchen-sink.plz

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
foo: bar
44
bar: :foo
55
sky_is_blue: :boolean.not(false)
6-
colors: { red green blue }
6+
colors: { red, green, blue }
77
two: :integer.add(1)(1)
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
@@ -1,4 +1,4 @@
1-
{@runtime context =>
1+
{@runtime, context =>
22
:flow({
33
:context.arguments.lookup
44
:match({

src/end-to-end.test.ts

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ testCases(endToEnd, code => code)('end-to-end tests', [
2222
['{{{}}}', either.makeRight({ 0: { 0: {} } })],
2323
['"hello world"', either.makeRight('hello world')],
2424
['{foo:bar}', either.makeRight({ foo: 'bar' })],
25+
['{hi}', either.makeRight({ 0: 'hi' })],
2526
['{a,b,c}', either.makeRight({ 0: 'a', 1: 'b', 2: 'c' })],
2627
['{,a,b,c,}', either.makeRight({ 0: 'a', 1: 'b', 2: 'c' })],
2728
['{a,1:overwritten,c}', either.makeRight({ 0: 'a', 1: 'c' })],
2829
['{overwritten,0:a,c}', either.makeRight({ 0: 'a', 1: 'c' })],
29-
['{@check type:true value:true}', either.makeRight('true')],
30+
['{@check, type:true, value:true}', either.makeRight('true')],
3031
[
3132
'{@panic}',
3233
result => {
@@ -36,17 +37,17 @@ testCases(endToEnd, code => code)('end-to-end tests', [
3637
},
3738
],
3839
[
39-
'{@runtime _ => {@panic}}',
40+
'{@runtime, _ => {@panic}}',
4041
result => {
4142
assert(either.isLeft(result))
4243
assert('kind' in result.value)
4344
assert.deepEqual(result.value.kind, 'panic')
4445
},
4546
],
46-
['{a:A b:{@lookup a}}', either.makeRight({ a: 'A', b: 'A' })],
47-
['{a:A b: :a}', either.makeRight({ a: 'A', b: 'A' })],
48-
['{a:A {@lookup a}}', either.makeRight({ a: 'A', 0: 'A' })],
49-
['{a:A :a}', either.makeRight({ a: 'A', 0: 'A' })],
47+
['{a:A, b:{@lookup, a}}', either.makeRight({ a: 'A', b: 'A' })],
48+
['{a:A, b: :a}', either.makeRight({ a: 'A', b: 'A' })],
49+
['{a:A, {@lookup, a}}', either.makeRight({ a: 'A', 0: 'A' })],
50+
['{a:A, :a}', either.makeRight({ a: 'A', 0: 'A' })],
5051
['{ a: (a => :a)(A) }', either.makeRight({ a: 'A' })],
5152
['{ a: ( a => :a )( A ) }', either.makeRight({ a: 'A' })],
5253
['(a => :a)(A)', either.makeRight('A')],
@@ -101,7 +102,7 @@ testCases(endToEnd, code => code)('end-to-end tests', [
101102
c: z => {
102103
d: y => x => {
103104
e: {
104-
f: w => { g: { :z :y :x :w } }
105+
f: w => { g: { :z, :y, :x, :w, } }
105106
}
106107
}
107108
}
@@ -116,13 +117,12 @@ testCases(endToEnd, code => code)('end-to-end tests', [
116117
['{ ("a"): A }', either.makeRight({ a: 'A' })],
117118
['{ a: :(b), b: B }', either.makeRight({ a: 'B', b: 'B' })],
118119
['{ a: :("b"), b: B }', either.makeRight({ a: 'B', b: 'B' })],
119-
['{ (a: A) (b: B) }', either.makeRight({ a: 'A', b: 'B' })],
120-
['( { ((a): :(b)) ( ( b ): B ) } )', either.makeRight({ a: 'B', b: 'B' })],
120+
['{ (a: A), (b: B) }', either.makeRight({ a: 'A', b: 'B' })],
121+
['( { ((a): :(b)), ( ( b ): B ) } )', either.makeRight({ a: 'B', b: 'B' })],
121122
['{ (a: :(")")), (")": (B)) }', either.makeRight({ a: 'B', ')': 'B' })],
122123
[`/**/a/**/`, either.makeRight('a')],
123124
['hello//world', either.makeRight('hello')],
124125
[`"hello//world"`, either.makeRight('hello//world')],
125-
[`{a/* this works as a delimiter */b}`, either.makeRight({ 0: 'a', 1: 'b' })],
126126
[
127127
`/**/{/**/a:/**/b/**/,/**/c:/**/d/**/}/**/`,
128128
either.makeRight({ a: 'b', c: 'd' }),
@@ -133,7 +133,7 @@ testCases(endToEnd, code => code)('end-to-end tests', [
133133
],
134134
[':match({ a: A })({ tag: a, value: {} })', either.makeRight('A')],
135135
[':atom.prepend(a)(b)', either.makeRight('ab')],
136-
[':flow({ :atom.append(a) :atom.append(b) })(z)', either.makeRight('zab')],
136+
[':flow({ :atom.append(a), :atom.append(b) })(z)', either.makeRight('zab')],
137137
[
138138
`{
139139
// foo: bar
@@ -142,7 +142,7 @@ testCases(endToEnd, code => code)('end-to-end tests', [
142142
0:@runtime
143143
function:{
144144
0:@apply
145-
function:{0:@index object:{0:@lookup key:object} query:{0:lookup}}
145+
function:{0:@index, object:{0:@lookup, key:object}, query:{0:lookup}}
146146
argument:"key which does not exist in runtime context"
147147
}
148148
}
@@ -159,15 +159,15 @@ testCases(endToEnd, code => code)('end-to-end tests', [
159159
either.makeRight({ tag: 'none', value: {} }),
160160
],
161161
[
162-
`{@runtime {@apply :flow {
163-
{@apply :object.lookup environment}
164-
{@apply :match {
162+
`{@runtime, {@apply, :flow, {
163+
{@apply, :object.lookup, environment}
164+
{@apply, :match, {
165165
none: "environment does not exist"
166-
some: {@apply :flow {
167-
{@apply :object.lookup lookup}
168-
{@apply :match {
166+
some: {@apply, :flow, {
167+
{@apply, :object.lookup, lookup}
168+
{@apply, :match, {
169169
none: "environment.lookup does not exist"
170-
some: {@apply :apply PATH}
170+
some: {@apply, :apply, PATH}
171171
}}
172172
}}
173173
}}
@@ -182,7 +182,7 @@ testCases(endToEnd, code => code)('end-to-end tests', [
182182
},
183183
],
184184
[
185-
`{@runtime :flow({
185+
`{@runtime, :flow({
186186
:object.lookup(environment)
187187
:match({
188188
none: "environment does not exist"
@@ -205,7 +205,7 @@ testCases(endToEnd, code => code)('end-to-end tests', [
205205
},
206206
],
207207
[
208-
`{@runtime context =>
208+
`{@runtime, context =>
209209
:identity(:context).program.start_time
210210
}`,
211211
output => {
@@ -216,7 +216,7 @@ testCases(endToEnd, code => code)('end-to-end tests', [
216216
},
217217
],
218218
[
219-
`{@runtime context =>
219+
`{@runtime, context =>
220220
:context.environment.lookup(PATH)
221221
}`,
222222
output => {
@@ -265,8 +265,8 @@ testCases(endToEnd, code => code)('end-to-end tests', [
265265
either.makeRight({ true: 'true', false: 'false' }),
266266
],
267267
[
268-
`{@runtime context =>
269-
{@if :boolean.not(:boolean.is(:context))
268+
`{@runtime, context =>
269+
{@if, :boolean.not(:boolean.is(:context))
270270
"it works!"
271271
{@panic}
272272
}
@@ -275,7 +275,8 @@ testCases(endToEnd, code => code)('end-to-end tests', [
275275
],
276276
[
277277
`{
278-
fibonacci: n => {@if :integer.less_than(2)(:n)
278+
fibonacci: n => {
279+
@if, :integer.less_than(2)(:n)
279280
then: :n
280281
else: :integer.add(
281282
:fibonacci(:integer.subtract(2)(:n))

src/language/compiling/semantics/keyword-handlers/lookup-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const lookup = ({
6868
left: _ =>
6969
// Lookups should not resolve to expression properties.
7070
// For example the value of the lookup expression in `a => :parameter` (which desugars
71-
// to `{@function parameter: a, body: {@lookup key: parameter}}`) should not be `a`.
71+
// to `{@function, parameter: a, body: {@lookup, key: parameter}}`) should not be `a`.
7272
isExpression(scope)
7373
? option.none
7474
: applyKeyPathToSemanticGraph(scope, [key]),

src/language/parsing/molecule.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ import {
2121
colon,
2222
comma,
2323
dot,
24+
newline,
2425
openingBrace,
2526
openingParenthesis,
2627
} from './literals.js'
2728
import { optionallySurroundedByParentheses } from './parentheses.js'
28-
import { optionalTrivia, trivia } from './trivia.js'
29+
import { optionalTrivia, trivia, triviaExceptNewlines } from './trivia.js'
2930

3031
export type Molecule = { readonly [key: Atom]: Molecule | Atom }
3132

@@ -66,7 +67,7 @@ const property = (index: Indexer) =>
6667

6768
const propertyDelimiter = oneOf([
6869
sequence([optionalTrivia, comma, optionalTrivia]),
69-
trivia,
70+
sequence([optional(triviaExceptNewlines), newline, optionalTrivia]),
7071
])
7172

7273
const argument = map(
@@ -97,6 +98,7 @@ const moleculeAsEntries = (
9798
map(
9899
sequence([
99100
openingBrace,
101+
optional(trivia),
100102
// Allow initial property not preceded by a delimiter (e.g. `{a b}`).
101103
optional(property(index)),
102104
zeroOrMore(
@@ -106,13 +108,16 @@ const moleculeAsEntries = (
106108
),
107109
),
108110
optional(propertyDelimiter),
111+
optional(trivia),
109112
closingBrace,
110113
]),
111114
([
112115
_openingBrace,
116+
_optionalLeadingTrivia,
113117
optionalInitialProperty,
114118
remainingProperties,
115-
_delimiter,
119+
_optionalDelimiter,
120+
_optionalTrailingTrivia,
116121
_closingBrace,
117122
]) =>
118123
optionalInitialProperty === undefined

src/language/parsing/trivia.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,9 @@ export const trivia = oneOrMore(
4141
)
4242

4343
export const optionalTrivia = oneOf([trivia, nothing])
44+
45+
export const whitespaceExceptNewlines = regularExpression(/[^\S\n]+/)
46+
47+
export const triviaExceptNewlines = oneOrMore(
48+
oneOf([whitespaceExceptNewlines, singleLineComment, blockComment]),
49+
)

0 commit comments

Comments
 (0)