Skip to content

Commit c426b16

Browse files
committed
Improve plz formatting in diagnostics
1 parent 0ba31d6 commit c426b16

File tree

4 files changed

+188
-14
lines changed

4 files changed

+188
-14
lines changed

src/language/semantics/key-path.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
import { either } from '../../adts.js'
22
import type { Atom, Molecule } from '../parsing.js'
33
import { unparse } from '../unparsing.js'
4-
import { prettyPlz } from '../unparsing/pretty-plz.js'
4+
import { inlinePlz } from '../unparsing/inline-plz.js'
55

66
export type KeyPath = readonly Atom[]
77

88
export const stringifyKeyPathForEndUser = (keyPath: KeyPath): string =>
9-
either.match(
10-
// TODO: Use single-line plz notation.
11-
unparse(keyPathToMolecule(keyPath), prettyPlz),
12-
{
13-
right: stringifiedOutput => stringifiedOutput,
14-
left: error => `(unserializable key path: ${error.message})`,
15-
},
16-
)
9+
either.match(unparse(keyPathToMolecule(keyPath), inlinePlz), {
10+
right: stringifiedOutput => stringifiedOutput,
11+
left: error => `(unserializable key path: ${error.message})`,
12+
})
1713

1814
export const keyPathToMolecule = (keyPath: KeyPath): Molecule =>
1915
Object.fromEntries(keyPath.flatMap((key, index) => [[index, key]]))

src/language/semantics/semantic-graph.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
import type { Atom, Molecule } from '../parsing.js'
88
import type { Canonicalized } from '../parsing/syntax-tree.js'
99
import { unparse } from '../unparsing.js'
10-
import { prettyPlz } from '../unparsing/pretty-plz.js'
10+
import { inlinePlz } from '../unparsing/inline-plz.js'
1111
import { serializeFunctionNode, type FunctionNode } from './function-node.js'
1212
import { stringifyKeyPathForEndUser, type KeyPath } from './key-path.js'
1313
import {
@@ -170,10 +170,7 @@ export const stringifySemanticGraphForEndUser = (
170170
graph: SemanticGraph,
171171
): string =>
172172
either.match(
173-
either.flatMap(serialize(graph), output =>
174-
// TODO: Use single-line plz notation.
175-
unparse(output, prettyPlz),
176-
),
173+
either.flatMap(serialize(graph), output => unparse(output, inlinePlz)),
177174
{
178175
right: stringifiedOutput => stringifiedOutput,
179176
left: error => `(unserializable value: ${error.message})`,
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import kleur from 'kleur'
2+
import { either, type Either } from '../../adts.js'
3+
import type { Right } from '../../adts/either.js'
4+
import type { UnserializableValueError } from '../errors.js'
5+
import type { Atom, Molecule } from '../parsing.js'
6+
import { unquotedAtomParser } from '../parsing/atom.js'
7+
import {
8+
isSemanticGraph,
9+
readApplyExpression,
10+
readFunctionExpression,
11+
readLookupExpression,
12+
serialize,
13+
type KeyPath,
14+
type SemanticGraph,
15+
} from '../semantics.js'
16+
import { type Notation } from './unparsing-utilities.js'
17+
18+
// TODO: Share implementation details with pretty plz notation.
19+
20+
const dot = kleur.dim('.')
21+
const quote = kleur.dim('"')
22+
const colon = kleur.dim(':')
23+
const comma = kleur.dim(',')
24+
const openBrace = kleur.dim('{')
25+
const closeBrace = kleur.dim('}')
26+
const openParenthesis = kleur.dim('(')
27+
const closeParenthesis = kleur.dim(')')
28+
const arrow = kleur.dim('=>')
29+
30+
const escapeStringContents = (value: string) =>
31+
value.replace('\\', '\\\\').replace('"', '\\"')
32+
33+
const quoteIfNecessary = (value: string) =>
34+
!either.isLeft(unquotedAtomParser(value))
35+
? value
36+
: quote.concat(escapeStringContents(value)).concat(quote)
37+
38+
const atom = (node: string): Right<string> =>
39+
either.makeRight(
40+
quoteIfNecessary(
41+
/^@[^@]/.test(node) ? kleur.bold(kleur.underline(node)) : node,
42+
),
43+
)
44+
45+
const molecule = (
46+
value: Molecule,
47+
): Either<UnserializableValueError, string> => {
48+
const functionExpressionResult = readFunctionExpression(value)
49+
if (!either.isLeft(functionExpressionResult)) {
50+
return sugaredFunction(
51+
functionExpressionResult.value.parameter,
52+
functionExpressionResult.value.body,
53+
)
54+
} else {
55+
const applyExpressionResult = readApplyExpression(value)
56+
if (!either.isLeft(applyExpressionResult)) {
57+
return sugaredApply(
58+
applyExpressionResult.value.argument,
59+
applyExpressionResult.value.function,
60+
)
61+
} else {
62+
const lookupExpressionResult = readLookupExpression(value)
63+
if (!either.isLeft(lookupExpressionResult)) {
64+
return sugaredLookup(lookupExpressionResult.value.query)
65+
} else {
66+
return sugarFreeMolecule(value)
67+
}
68+
}
69+
}
70+
}
71+
72+
const sugaredLookup = (keyPathAsNode: Molecule | SemanticGraph) => {
73+
const keyPath = Object.entries(keyPathAsNode).reduce(
74+
(accumulator: KeyPath | 'invalid', [key, value]) => {
75+
if (accumulator === 'invalid') {
76+
return accumulator
77+
} else {
78+
if (key === String(accumulator.length) && typeof value === 'string') {
79+
return [...accumulator, value]
80+
} else {
81+
return 'invalid'
82+
}
83+
}
84+
},
85+
[],
86+
)
87+
88+
if (
89+
keyPath !== 'invalid' &&
90+
Object.keys(keyPathAsNode).length === keyPath.length &&
91+
keyPath.every(key => !either.isLeft(unquotedAtomParser(key)))
92+
) {
93+
return either.makeRight(kleur.cyan(colon.concat(keyPath.join(dot))))
94+
} else {
95+
return either.flatMap(serializeIfNeeded(keyPathAsNode), serializedKeyPath =>
96+
either.map(atomOrMolecule(serializedKeyPath), keyPathAsString =>
97+
kleur.cyan(colon.concat(keyPathAsString)),
98+
),
99+
)
100+
}
101+
}
102+
103+
const sugaredFunction = (
104+
parameterName: string,
105+
body: Molecule | SemanticGraph,
106+
) =>
107+
either.flatMap(serializeIfNeeded(body), serializedBody =>
108+
either.map(atomOrMolecule(serializedBody), bodyAsString =>
109+
[kleur.cyan(parameterName), arrow, bodyAsString].join(' '),
110+
),
111+
)
112+
113+
const sugaredApply = (
114+
argument: Molecule | SemanticGraph,
115+
functionToApply: Molecule | SemanticGraph,
116+
) =>
117+
either.flatMap(serializeIfNeeded(functionToApply), serializedFunction =>
118+
either.flatMap(
119+
atomOrMolecule(serializedFunction),
120+
functionToApplyAsString =>
121+
either.flatMap(serializeIfNeeded(argument), serializedArgument =>
122+
either.map(atomOrMolecule(serializedArgument), argumentAsString =>
123+
functionToApplyAsString
124+
.concat(openParenthesis)
125+
.concat(argumentAsString)
126+
.concat(closeParenthesis),
127+
),
128+
),
129+
),
130+
)
131+
132+
const sugarFreeMolecule = (value: Molecule) => {
133+
const entries = Object.entries(value)
134+
if (entries.length === 0) {
135+
return either.makeRight(openBrace + closeBrace)
136+
} else {
137+
const keyValuePairsAsStrings: string[] = []
138+
let ordinalPropertyKeyCounter = 0n
139+
for (const [propertyKey, propertyValue] of entries) {
140+
const valueAsStringResult = atomOrMolecule(propertyValue)
141+
if (either.isLeft(valueAsStringResult)) {
142+
return valueAsStringResult
143+
}
144+
145+
// Omit ordinal property keys:
146+
if (propertyKey === String(ordinalPropertyKeyCounter)) {
147+
keyValuePairsAsStrings.push(valueAsStringResult.value)
148+
ordinalPropertyKeyCounter += 1n
149+
} else {
150+
keyValuePairsAsStrings.push(
151+
kleur
152+
.cyan(quoteIfNecessary(propertyKey).concat(colon))
153+
.concat(' ')
154+
.concat(valueAsStringResult.value),
155+
)
156+
}
157+
}
158+
159+
return either.makeRight(
160+
openBrace
161+
.concat(' ')
162+
.concat(keyValuePairsAsStrings.join(comma.concat(' ')))
163+
.concat(' ')
164+
.concat(closeBrace),
165+
)
166+
}
167+
}
168+
169+
const serializeIfNeeded = (
170+
nodeOrMolecule: SemanticGraph | Molecule,
171+
): Either<UnserializableValueError, Atom | Molecule> =>
172+
isSemanticGraph(nodeOrMolecule)
173+
? serialize(nodeOrMolecule)
174+
: either.makeRight(nodeOrMolecule)
175+
176+
const atomOrMolecule = (value: Atom | Molecule) =>
177+
typeof value === 'string' ? atom(value) : molecule(value)
178+
179+
export const inlinePlz: Notation = { atom, molecule }

src/language/unparsing/pretty-plz.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
} from '../semantics.js'
1616
import { indent, type Notation } from './unparsing-utilities.js'
1717

18+
// TODO: Share implementation details with inline plz notation.
19+
1820
const dot = kleur.dim('.')
1921
const quote = kleur.dim('"')
2022
const colon = kleur.dim(':')

0 commit comments

Comments
 (0)