Skip to content

Commit a6018fc

Browse files
committed
Support plz as an output format
1 parent 0269227 commit a6018fc

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed

src/language/cli/output.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { either, type Either } from '../../adts.js'
33
import { type SyntaxTree } from '../parsing/syntax-tree.js'
44
import { unparse, type Notation } from '../unparsing.js'
55
import { prettyJson } from '../unparsing/pretty-json.js'
6+
import { prettyPlz } from '../unparsing/pretty-plz.js'
67

78
export const handleOutput = async (
89
process: NodeJS.Process,
@@ -22,6 +23,8 @@ export const handleOutput = async (
2223
let notation: Notation
2324
if (outputFormat === 'json') {
2425
notation = prettyJson
26+
} else if (outputFormat === 'plz') {
27+
notation = prettyPlz
2528
} else {
2629
throw new Error(`Unsupported output format: "${outputFormat}"`)
2730
}

src/language/semantics.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export { prelude } from './semantics/prelude.js'
6060
export {
6161
applyKeyPathToSemanticGraph,
6262
containsAnyUnelaboratedNodes,
63+
isSemanticGraph,
6364
serialize,
6465
stringifySemanticGraphForEndUser,
6566
updateValueAtKeyPathInSemanticGraph,
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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 { indent, type Notation } from './unparsing-utilities.js'
17+
18+
const dot = kleur.dim('.')
19+
const quote = kleur.dim('"')
20+
const colon = kleur.dim(':')
21+
const openBrace = kleur.dim('{')
22+
const closeBrace = kleur.dim('}')
23+
const openParenthesis = kleur.dim('(')
24+
const closeParenthesis = kleur.dim(')')
25+
const arrow = kleur.dim('=>')
26+
27+
const escapeStringContents = (value: string) =>
28+
value.replace('\\', '\\\\').replace('"', '\\"')
29+
30+
const quoteIfNecessary = (value: string) =>
31+
!either.isLeft(unquotedAtomParser(value))
32+
? value
33+
: quote.concat(escapeStringContents(value)).concat(quote)
34+
35+
const atom = (node: string): Right<string> =>
36+
either.makeRight(
37+
quoteIfNecessary(
38+
/^@[^@]/.test(node) ? kleur.bold(kleur.underline(node)) : node,
39+
),
40+
)
41+
42+
const molecule = (
43+
value: Molecule,
44+
): Either<UnserializableValueError, string> => {
45+
const functionExpressionResult = readFunctionExpression(value)
46+
if (!either.isLeft(functionExpressionResult)) {
47+
return sugaredFunction(
48+
functionExpressionResult.value.parameter,
49+
functionExpressionResult.value.body,
50+
)
51+
} else {
52+
const applyExpressionResult = readApplyExpression(value)
53+
if (!either.isLeft(applyExpressionResult)) {
54+
return sugaredApply(
55+
applyExpressionResult.value.argument,
56+
applyExpressionResult.value.function,
57+
)
58+
} else {
59+
const lookupExpressionResult = readLookupExpression(value)
60+
if (!either.isLeft(lookupExpressionResult)) {
61+
return sugaredLookup(lookupExpressionResult.value.query)
62+
} else {
63+
return sugarFreeMolecule(value)
64+
}
65+
}
66+
}
67+
}
68+
69+
const sugaredLookup = (keyPathAsNode: Molecule | SemanticGraph) => {
70+
const keyPath = Object.entries(keyPathAsNode).reduce(
71+
(accumulator: KeyPath | 'invalid', [key, value]) => {
72+
if (accumulator === 'invalid') {
73+
return accumulator
74+
} else {
75+
if (key === String(accumulator.length) && typeof value === 'string') {
76+
return [...accumulator, value]
77+
} else {
78+
return 'invalid'
79+
}
80+
}
81+
},
82+
[],
83+
)
84+
85+
if (
86+
keyPath !== 'invalid' &&
87+
Object.keys(keyPathAsNode).length === keyPath.length &&
88+
keyPath.every(
89+
key => typeof key === 'string' && !either.isLeft(unquotedAtomParser(key)),
90+
)
91+
) {
92+
return either.makeRight(kleur.cyan(colon.concat(keyPath.join(dot))))
93+
} else {
94+
return either.flatMap(serializeIfNeeded(keyPathAsNode), serializedKeyPath =>
95+
either.map(atomOrMolecule(serializedKeyPath), keyPathAsString =>
96+
kleur.cyan(colon.concat(keyPathAsString)),
97+
),
98+
)
99+
}
100+
}
101+
102+
const sugaredFunction = (
103+
parameterName: string,
104+
body: Molecule | SemanticGraph,
105+
) =>
106+
either.flatMap(serializeIfNeeded(body), serializedBody =>
107+
either.map(atomOrMolecule(serializedBody), bodyAsString =>
108+
[kleur.cyan(parameterName), arrow, bodyAsString].join(' '),
109+
),
110+
)
111+
112+
const sugaredApply = (
113+
argument: Molecule | SemanticGraph,
114+
functionToApply: Molecule | SemanticGraph,
115+
) =>
116+
either.flatMap(serializeIfNeeded(functionToApply), serializedFunction =>
117+
either.flatMap(
118+
atomOrMolecule(serializedFunction),
119+
functionToApplyAsString =>
120+
either.flatMap(serializeIfNeeded(argument), serializedArgument =>
121+
either.map(atomOrMolecule(serializedArgument), argumentAsString =>
122+
functionToApplyAsString
123+
.concat(openParenthesis)
124+
.concat(argumentAsString)
125+
.concat(closeParenthesis),
126+
),
127+
),
128+
),
129+
)
130+
131+
const sugarFreeMolecule = (value: Molecule) => {
132+
const entries = Object.entries(value)
133+
if (entries.length === 0) {
134+
return either.makeRight(openBrace + closeBrace)
135+
} else {
136+
const keyValuePairsAsStrings: string[] = []
137+
for (const [propertyKey, propertyValue] of entries) {
138+
const valueAsStringResult = atomOrMolecule(propertyValue)
139+
if (either.isLeft(valueAsStringResult)) {
140+
return valueAsStringResult
141+
}
142+
143+
keyValuePairsAsStrings.push(
144+
// TODO: Intelligently omit all ordinal property keys, not just `0`.
145+
(propertyKey === '0'
146+
? ''
147+
: kleur.cyan(quoteIfNecessary(propertyKey).concat(colon)).concat(' ')
148+
).concat(valueAsStringResult.value),
149+
)
150+
}
151+
152+
return either.makeRight(
153+
openBrace
154+
.concat('\n')
155+
.concat(indent(2, keyValuePairsAsStrings.join('\n')))
156+
.concat('\n')
157+
.concat(closeBrace),
158+
)
159+
}
160+
}
161+
162+
const serializeIfNeeded = (
163+
nodeOrMolecule: SemanticGraph | Molecule,
164+
): Either<UnserializableValueError, Atom | Molecule> =>
165+
isSemanticGraph(nodeOrMolecule)
166+
? serialize(nodeOrMolecule)
167+
: either.makeRight(nodeOrMolecule)
168+
169+
const atomOrMolecule = (value: Atom | Molecule) =>
170+
typeof value === 'string' ? atom(value) : molecule(value)
171+
172+
export const prettyPlz: Notation = { atom, molecule }

0 commit comments

Comments
 (0)