Skip to content

Commit b9ace3d

Browse files
committed
Fix @lookup unparsing & add sugar for @index unparsing
1 parent 8b4b8fa commit b9ace3d

File tree

2 files changed

+113
-9
lines changed

2 files changed

+113
-9
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/plz-utilities.ts

Lines changed: 69 additions & 7 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'
@@ -48,6 +51,12 @@ export const moleculeUnparser =
4851
right: functionExpression =>
4952
unparseSugaredFunction(functionExpression, unparseAtomOrMolecule),
5053
})
54+
case '@index':
55+
return either.match(readIndexExpression(value), {
56+
left: _ => unparseSugarFreeMolecule(value, unparseAtomOrMolecule),
57+
right: indexExpression =>
58+
unparseSugaredIndex(indexExpression, unparseAtomOrMolecule),
59+
})
5160
case '@lookup':
5261
return either.match(readLookupExpression(value), {
5362
left: _ => unparseSugarFreeMolecule(value, unparseAtomOrMolecule),
@@ -84,7 +93,7 @@ export const moleculeAsKeyValuePairStrings = (
8493
} else {
8594
keyValuePairsAsStrings.push(
8695
kleur
87-
.cyan(quoteIfNecessary(propertyKey).concat(colon))
96+
.cyan(quoteAtomIfNecessary(propertyKey).concat(colon))
8897
.concat(' ')
8998
.concat(valueAsStringResult.value),
9099
)
@@ -96,11 +105,11 @@ export const moleculeAsKeyValuePairStrings = (
96105
export const unparseAtom = (atom: string): Right<string> =>
97106
either.makeRight(
98107
/^@[^@]/.test(atom)
99-
? kleur.bold(kleur.underline(quoteIfNecessary(atom)))
100-
: quoteIfNecessary(atom),
108+
? kleur.bold(kleur.underline(quoteAtomIfNecessary(atom)))
109+
: quoteAtomIfNecessary(atom),
101110
)
102111

103-
const quoteIfNecessary = (value: string): string => {
112+
const quoteAtomIfNecessary = (value: string): string => {
104113
const unquotedAtomResult = parsing.parse(unquotedAtomParser, value)
105114
if (either.isLeft(unquotedAtomResult)) {
106115
return quote.concat(escapeStringContents(value)).concat(quote)
@@ -109,6 +118,15 @@ const quoteIfNecessary = (value: string): string => {
109118
}
110119
}
111120

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+
112130
const serializeIfNeeded = (
113131
nodeOrMolecule: SemanticGraph | Molecule,
114132
): Either<UnserializableValueError, Atom | Molecule> =>
@@ -168,10 +186,54 @@ const unparseSugaredFunction = (
168186
),
169187
)
170188

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+
171233
const unparseSugaredLookup = (
172234
expression: LookupExpression,
173-
unparseAtomOrMolecule: UnparseAtomOrMolecule,
235+
_unparseAtomOrMolecule: UnparseAtomOrMolecule,
174236
) =>
175-
either.map(unparseAtomOrMolecule(expression.key), key =>
176-
kleur.cyan(colon.concat(key)),
237+
either.makeRight(
238+
kleur.cyan(colon.concat(quoteKeyPathComponentIfNecessary(expression.key))),
177239
)

0 commit comments

Comments
 (0)