forked from rulespace/rulespace
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rsp2latex.js
125 lines (119 loc) · 2.88 KB
/
rsp2latex.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { analyzeProgram } from './analyzer.js';
export function rsp2latex(rsp)
{
const analysis = analyzeProgram(rsp);
let sb = ``;
for (const stratum of analysis.strata())
{
sb += `\\paragraph{Stratum ${stratum.id}}\n`;
for (const pred of analysis.stratumPreds(stratum))
{
const rules = [...analysis.predRules(pred)];
const chunkedRules = chunkify(rules, 8);
sb += `
$\\mathsf{${termEnc(pred.name)}}$
\\begin{mathpar}
`;
sb += chunkedRules.map(rules => rules.map(visit).join(`\\and\n`)).join('\n\\end{mathpar}\n\\begin{mathpar}\n');
sb += `
\\end{mathpar}
`
}
}
return sb;
}
function chunkify(a, n)
{
return a.reduce((acc, value, i) =>
{
if (i % n === 0 && i !== 0)
{
acc.push([]);
}
acc[acc.length - 1].push(value);
return acc;
}, [[]]);
}
function visit(rsp)
{
switch (rsp.constructor.name)
{
case 'Program':
return `
${rsp.rules.map(visit).join('\n\n')}
`;
case 'Rule':
return `
\\inferrule[Rule${rsp._id}]
{
${rsp.body.length === 0 ? '\\quad' : rsp.body.map(visit).join('\\\\\n')}
}
{
${visit(rsp.head)}
}
`;
case 'Atom':
return `\\mathsf{${termEnc(rsp.pred)}}(${rsp.terms.map(visit).join(', ')})`;
case 'App':
return `${visit(rsp.operator)}(${rsp.operands.map(visit).join(', ')})`;
case 'Assign':
return `${visit(rsp.left)} ${visit(rsp.operator)} ${visit(rsp.right)}`;
case 'Neg':
return `\\neg ${visit(rsp.atom)}`;
case 'Lit':
if (typeof rsp.value === 'string')
{
return `\\texttt{\"${rsp.value}\"}`;
}
else if (rsp.value === true)
{
return `\\#t`;
}
else if (rsp.value === false)
{
return `\\#f`;
}
else
{
return String(rsp);
}
case 'Var':
{
const name = rsp.name;
const liu = name.lastIndexOf('_');
if (liu === -1 || liu === (name.length - 1))
{
return termEnc(name);
}
return `${termEnc(name.substring(0, liu))}_{${termEnc(name.substring(liu + 1))}}`;
}
case 'String':
{
return String(rsp);
}
default:
throw new Error(`cannot handle ${rsp} of type ${rsp.constructor.name}`);
}
}
const encMap = new Map();
encMap.set(`‘`, `'`);
encMap.set(`’`, `'`);
encMap.set(`$`, `\\$`);
encMap.set(`_`, `\\_`);
encMap.set(`#`, `\\#`);
encMap.set(`κ`, `\\kappa`);
function termEnc(str)
{
let sb = "";
for (const c of str)
{
const replace = encMap.get(c) ?? c;
// c === "“" || c === "”" ||
// c === "«" || c === "»" ||
// c === "…" || c === "?" ||
// c === "+" || c === "-" || c === "*" || c === "/" || c === "=" || c === "<" || c === ">"
// )
sb += replace;
}
return sb;
}