Skip to content

Commit bab859d

Browse files
authored
Merge pull request #38 from mkantor/update-plz-unparsers
Update plz unparsers to support `@index` sugar (and update `@lookup` sugar)
2 parents ac2c1b1 + b9ace3d commit bab859d

File tree

4 files changed

+163
-54
lines changed

4 files changed

+163
-54
lines changed

src/language/compiling/unparsing.test.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,32 @@ testCases(
6565
1: {
6666
0: '@function',
6767
parameter: 'context',
68-
body: { 0: '@lookup', key: 'context.program.start_time' },
68+
body: {
69+
0: '@index',
70+
object: { 0: '@lookup', key: 'context' },
71+
query: { 0: 'program', 1: 'start_time' },
72+
},
6973
},
7074
},
7175
either.makeRight('{ @runtime, context => :context.program.start_time }'),
7276
],
77+
[
78+
{
79+
'a.b': {
80+
'c "d"': {
81+
'e.f': 'g',
82+
},
83+
},
84+
test: {
85+
0: '@index',
86+
object: { 0: '@lookup', 1: 'a.b' },
87+
query: { 0: 'c "d"', 1: 'e.f' },
88+
},
89+
},
90+
either.makeRight(
91+
'{ a.b: { "c \\"d"": { e.f: g } }, test: :"a.b"."c \\"d""."e.f" }',
92+
),
93+
],
7394
])
7495

7596
testCases(
@@ -125,13 +146,34 @@ testCases(
125146
1: {
126147
0: '@function',
127148
parameter: 'context',
128-
body: { 0: '@lookup', key: 'context.program.start_time' },
149+
body: {
150+
0: '@index',
151+
object: { 0: '@lookup', key: 'context' },
152+
query: { 0: 'program', 1: 'start_time' },
153+
},
129154
},
130155
},
131156
either.makeRight(
132157
'{\n @runtime\n context => :context.program.start_time\n}',
133158
),
134159
],
160+
[
161+
{
162+
'a.b': {
163+
'c "d"': {
164+
'e.f': 'g',
165+
},
166+
},
167+
test: {
168+
0: '@index',
169+
object: { 0: '@lookup', 1: 'a.b' },
170+
query: { 0: 'c "d"', 1: 'e.f' },
171+
},
172+
},
173+
either.makeRight(
174+
'{\n a.b: {\n "c \\"d"": {\n e.f: g\n }\n }\n test: :"a.b"."c \\"d""."e.f"\n}',
175+
),
176+
],
135177
])
136178

137179
testCases(

src/language/unparsing/inline-plz.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import type { Atom, Molecule } from '../parsing.js'
33
import {
44
closeBrace,
55
comma,
6+
moleculeAsKeyValuePairStrings,
67
moleculeUnparser,
78
openBrace,
8-
sugarFreeMoleculeAsKeyValuePairStrings,
99
unparseAtom,
1010
} from './plz-utilities.js'
1111
import type { Notation } from './unparsing-utilities.js'
@@ -15,7 +15,9 @@ const unparseSugarFreeMolecule = (value: Molecule) => {
1515
return either.makeRight(openBrace + closeBrace)
1616
} else {
1717
return either.map(
18-
sugarFreeMoleculeAsKeyValuePairStrings(value, unparseAtomOrMolecule),
18+
moleculeAsKeyValuePairStrings(value, unparseAtomOrMolecule, {
19+
ordinalKeys: 'omit',
20+
}),
1921
keyValuePairsAsStrings =>
2022
openBrace
2123
.concat(' ')

src/language/unparsing/plz-utilities.ts

Lines changed: 111 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ import {
88
isSemanticGraph,
99
readApplyExpression,
1010
readFunctionExpression,
11+
readIndexExpression,
1112
readLookupExpression,
1213
serialize,
1314
type ApplyExpression,
1415
type FunctionExpression,
16+
type IndexExpression,
17+
type KeyPath,
1518
type LookupExpression,
1619
type SemanticGraph,
1720
} from '../semantics.js'
@@ -35,52 +38,40 @@ export const moleculeUnparser =
3538
) => Either<UnserializableValueError, string>,
3639
) =>
3740
(value: Molecule): Either<UnserializableValueError, string> => {
38-
const functionExpressionResult = readFunctionExpression(value)
39-
if (!either.isLeft(functionExpressionResult)) {
40-
return unparseSugaredFunction(
41-
functionExpressionResult.value,
42-
unparseAtomOrMolecule,
43-
)
44-
} else {
45-
const applyExpressionResult = readApplyExpression(value)
46-
if (!either.isLeft(applyExpressionResult)) {
47-
return unparseSugaredApply(
48-
applyExpressionResult.value,
49-
unparseAtomOrMolecule,
50-
)
51-
} else {
52-
const lookupExpressionResult = readLookupExpression(value)
53-
if (!either.isLeft(lookupExpressionResult)) {
54-
return unparseSugaredLookup(
55-
lookupExpressionResult.value,
56-
unparseAtomOrMolecule,
57-
)
58-
} else {
59-
return unparseSugarFreeMolecule(value, unparseAtomOrMolecule)
60-
}
61-
}
41+
switch (value['0']) {
42+
case '@apply':
43+
return either.match(readApplyExpression(value), {
44+
left: _ => unparseSugarFreeMolecule(value, unparseAtomOrMolecule),
45+
right: applyExpression =>
46+
unparseSugaredApply(applyExpression, unparseAtomOrMolecule),
47+
})
48+
case '@function':
49+
return either.match(readFunctionExpression(value), {
50+
left: _ => unparseSugarFreeMolecule(value, unparseAtomOrMolecule),
51+
right: functionExpression =>
52+
unparseSugaredFunction(functionExpression, unparseAtomOrMolecule),
53+
})
54+
case '@index':
55+
return either.match(readIndexExpression(value), {
56+
left: _ => unparseSugarFreeMolecule(value, unparseAtomOrMolecule),
57+
right: indexExpression =>
58+
unparseSugaredIndex(indexExpression, unparseAtomOrMolecule),
59+
})
60+
case '@lookup':
61+
return either.match(readLookupExpression(value), {
62+
left: _ => unparseSugarFreeMolecule(value, unparseAtomOrMolecule),
63+
right: lookupExpression =>
64+
unparseSugaredLookup(lookupExpression, unparseAtomOrMolecule),
65+
})
66+
default:
67+
return unparseSugarFreeMolecule(value, unparseAtomOrMolecule)
6268
}
6369
}
6470

65-
export const quoteIfNecessary = (value: string): string => {
66-
const unquotedAtomResult = parsing.parse(unquotedAtomParser, value)
67-
if (either.isLeft(unquotedAtomResult)) {
68-
return quote.concat(escapeStringContents(value)).concat(quote)
69-
} else {
70-
return value
71-
}
72-
}
73-
74-
export const serializeIfNeeded = (
75-
nodeOrMolecule: SemanticGraph | Molecule,
76-
): Either<UnserializableValueError, Atom | Molecule> =>
77-
isSemanticGraph(nodeOrMolecule)
78-
? serialize(nodeOrMolecule)
79-
: either.makeRight(nodeOrMolecule)
80-
81-
export const sugarFreeMoleculeAsKeyValuePairStrings = (
71+
export const moleculeAsKeyValuePairStrings = (
8272
value: Molecule,
8373
unparseAtomOrMolecule: UnparseAtomOrMolecule,
74+
options: { readonly ordinalKeys: 'omit' | 'preserve' },
8475
): Either<UnserializableValueError, readonly string[]> => {
8576
const entries = Object.entries(value)
8677

@@ -93,13 +84,16 @@ export const sugarFreeMoleculeAsKeyValuePairStrings = (
9384
}
9485

9586
// Omit ordinal property keys:
96-
if (propertyKey === String(ordinalPropertyKeyCounter)) {
87+
if (
88+
propertyKey === String(ordinalPropertyKeyCounter) &&
89+
options.ordinalKeys === 'omit'
90+
) {
9791
keyValuePairsAsStrings.push(valueAsStringResult.value)
9892
ordinalPropertyKeyCounter += 1n
9993
} else {
10094
keyValuePairsAsStrings.push(
10195
kleur
102-
.cyan(quoteIfNecessary(propertyKey).concat(colon))
96+
.cyan(quoteAtomIfNecessary(propertyKey).concat(colon))
10397
.concat(' ')
10498
.concat(valueAsStringResult.value),
10599
)
@@ -111,10 +105,35 @@ export const sugarFreeMoleculeAsKeyValuePairStrings = (
111105
export const unparseAtom = (atom: string): Right<string> =>
112106
either.makeRight(
113107
/^@[^@]/.test(atom)
114-
? kleur.bold(kleur.underline(quoteIfNecessary(atom)))
115-
: quoteIfNecessary(atom),
108+
? kleur.bold(kleur.underline(quoteAtomIfNecessary(atom)))
109+
: quoteAtomIfNecessary(atom),
116110
)
117111

112+
const quoteAtomIfNecessary = (value: string): string => {
113+
const unquotedAtomResult = parsing.parse(unquotedAtomParser, value)
114+
if (either.isLeft(unquotedAtomResult)) {
115+
return quote.concat(escapeStringContents(value)).concat(quote)
116+
} else {
117+
return value
118+
}
119+
}
120+
121+
const quoteKeyPathComponentIfNecessary = (value: string): string => {
122+
const unquotedAtomResult = parsing.parse(unquotedAtomParser, value)
123+
if (either.isLeft(unquotedAtomResult) || value.includes('.')) {
124+
return quote.concat(escapeStringContents(value)).concat(quote)
125+
} else {
126+
return value
127+
}
128+
}
129+
130+
const serializeIfNeeded = (
131+
nodeOrMolecule: SemanticGraph | Molecule,
132+
): Either<UnserializableValueError, Atom | Molecule> =>
133+
isSemanticGraph(nodeOrMolecule)
134+
? serialize(nodeOrMolecule)
135+
: either.makeRight(nodeOrMolecule)
136+
118137
type UnparseAtomOrMolecule = (
119138
value: Atom | Molecule,
120139
) => Either<UnserializableValueError, string>
@@ -167,10 +186,54 @@ const unparseSugaredFunction = (
167186
),
168187
)
169188

189+
const unparseSugaredIndex = (
190+
expression: IndexExpression,
191+
unparseAtomOrMolecule: UnparseAtomOrMolecule,
192+
) => {
193+
const objectUnparseResult = either.flatMap(
194+
serializeIfNeeded(expression.object),
195+
unparseAtomOrMolecule,
196+
)
197+
if (either.isLeft(objectUnparseResult)) {
198+
return objectUnparseResult
199+
}
200+
201+
const keyPath = Object.entries(expression.query).reduce(
202+
(accumulator: KeyPath | 'invalid', [key, value]) => {
203+
if (accumulator === 'invalid') {
204+
return accumulator
205+
} else {
206+
if (key === String(accumulator.length) && typeof value === 'string') {
207+
return [...accumulator, value]
208+
} else {
209+
return 'invalid'
210+
}
211+
}
212+
},
213+
[],
214+
)
215+
216+
if (
217+
keyPath === 'invalid' ||
218+
Object.keys(expression.query).length !== keyPath.length
219+
) {
220+
return either.makeLeft({
221+
kind: 'unserializableValue',
222+
message: 'invalid key path',
223+
})
224+
}
225+
226+
return either.makeRight(
227+
objectUnparseResult.value
228+
.concat(dot)
229+
.concat(keyPath.map(quoteKeyPathComponentIfNecessary).join(dot)),
230+
)
231+
}
232+
170233
const unparseSugaredLookup = (
171234
expression: LookupExpression,
172-
unparseAtomOrMolecule: UnparseAtomOrMolecule,
235+
_unparseAtomOrMolecule: UnparseAtomOrMolecule,
173236
) =>
174-
either.map(unparseAtomOrMolecule(expression.key), key =>
175-
kleur.cyan(colon.concat(key)),
237+
either.makeRight(
238+
kleur.cyan(colon.concat(quoteKeyPathComponentIfNecessary(expression.key))),
176239
)

src/language/unparsing/pretty-plz.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import either from '@matt.kantor/either'
22
import type { Atom, Molecule } from '../parsing.js'
33
import {
44
closeBrace,
5+
moleculeAsKeyValuePairStrings,
56
moleculeUnparser,
67
openBrace,
7-
sugarFreeMoleculeAsKeyValuePairStrings,
88
unparseAtom,
99
} from './plz-utilities.js'
1010
import { indent, type Notation } from './unparsing-utilities.js'
@@ -14,7 +14,9 @@ const unparseSugarFreeMolecule = (value: Molecule) => {
1414
return either.makeRight(openBrace + closeBrace)
1515
} else {
1616
return either.map(
17-
sugarFreeMoleculeAsKeyValuePairStrings(value, unparseAtomOrMolecule),
17+
moleculeAsKeyValuePairStrings(value, unparseAtomOrMolecule, {
18+
ordinalKeys: 'omit',
19+
}),
1820
keyValuePairsAsStrings =>
1921
openBrace
2022
.concat('\n')

0 commit comments

Comments
 (0)