Skip to content

Commit 48d304e

Browse files
authored
Add footnotes to typst export (#12)
* feat: add conditions to typst export * fix: fix typst export * fix: use predicate identifier value * fix: allow empty lhs on create, fix typst footnotes
1 parent 48a4f7e commit 48d304e

File tree

6 files changed

+48
-15
lines changed

6 files changed

+48
-15
lines changed

backend/src/lib/derivation/statement.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use super::formula::{Formula, Identifier};
1616
Serialize, Deserialize, Debug, Clone, ToSchema, IntoParams, Ord, PartialEq, PartialOrd, Eq,
1717
)]
1818
pub struct Statement {
19+
#[serde(default)]
1920
pub lhs: Vec<Formula>,
2021
pub formula: Formula,
2122
pub sidecondition: Vec<SideCondition>,

frontend/src/lib/components/createExercise.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ const CreateExerciseForm = () => {
9898
createExerciseRequest: {
9999
statement: {
100100
formula: rhs,
101-
lhs: lhs,
101+
lhs: lhs || [],
102102
sidecondition: sideCon,
103103
},
104104
},

frontend/src/lib/components/exercise/exerciseInterface.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const ExerciseInterface = ({ exercise }: ExerciseInterfaceProps) => {
4545
</Box>
4646
)}
4747
<Flex pt={0}>
48-
{exercise.sidecondition.map((sidecondition, index) => (
48+
{exercise.sidecondition?.map((sidecondition, index) => (
4949
<SideCondition sideCondition={sidecondition} key={index} />
5050
))}
5151
</Flex>

frontend/src/lib/components/exerciseListElement.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ const ExerciseListElment = ({ exercise }: exerciseListElementProps) => {
108108
</Indicator>
109109
</Link>
110110
</Flex>
111-
{exercise.exercise.sidecondition.map((sideCondition, i) => (
111+
{exercise.exercise.sidecondition?.map((sideCondition, i) => (
112112
<SideCondition sideCondition={sideCondition} key={i} />
113113
))}
114114
</Card>

frontend/src/lib/utils/export.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,56 @@ import { getTypstRuleByName } from "./rule";
55
export function exportToTypst(root: NodeType, nodes: Array<NodeType>): string {
66
const imp = `#import "@preview/curryst:0.5.0": rule, prooftree`;
77
const page = `#set page(fill: none, width: auto, height: auto, margin: (x: 1em, y: 1em))`;
8-
const prooftree = `#prooftree(${exportSubformula(root, nodes)})`;
9-
return `${imp}\n${page}\n${prooftree}`;
8+
const [formula, footnotes] = exportSubformula(root, nodes, 1);
9+
const prooftree = `#prooftree(${formula})`;
10+
11+
const conditions = (root.statement.sidecondition || []).map(({ NotFree: { element, placeholder } }) => {
12+
return `${element.value} "not occuring freely in" ${placeholder.value}`
13+
});
14+
conditions.push(...footnotes.map(([id, note]) => `$"${id}:" ${note}$`));
15+
16+
let typstStr = `${imp}\n${page}\n${prooftree}`;
17+
18+
if (conditions.length > 0) {
19+
typstStr += "\n#set text(size: 7pt)"
20+
typstStr += "\n#let footnotes(body) = context {\n\tpad(top: 4pt, line(length: measure(body).width, stroke: 0.5pt + black))\n\tbody\n}";
21+
typstStr += `\n#footnotes[#stack(dir: ttb, spacing: 4pt, ${conditions.join(", ")})]`
22+
}
23+
24+
return typstStr;
1025
}
1126

12-
function exportSubformula(node: NodeType, nodes: Array<NodeType>): string {
13-
const name = getTypstRuleByName(node.rule as Rules);
27+
function exportSubformula(node: NodeType, nodes: Array<NodeType>, footnoteNumber: number): [string, Array<[number, string]>] {
28+
let name = getTypstRuleByName(node.rule as Rules, footnoteNumber);
1429
const lhs = node.statement.lhs.map(formulaToTypst).join(", ") || "emptyset";
1530
const current = formulaToTypst(node.statement.formula);
31+
let currentFootnoteNumber = footnoteNumber;
32+
const footnotes: Array<[number, string]> = [];
33+
if (Array.isArray(name)) {
34+
currentFootnoteNumber++;
35+
const formula = node.statement.formula as Formula & { type: "Exists" | "Forall"};
36+
const footnote = name[1]
37+
.replaceAll("%%identifier%%", formula.body.identifier.value)
38+
.replaceAll("%%lhs%%", lhs)
39+
.replaceAll("%%rhs%%", current);
40+
footnotes.push([footnoteNumber, footnote]);
41+
name = name[0];
42+
};
1643

1744
const premisses = node.premisses
1845
.map((premisse) => {
1946
return nodes.find((node) => node.name === premisse);
2047
})
2148
.filter(Boolean)
22-
.map((node) => exportSubformula(node as NodeType, nodes))
49+
.map((node) => {
50+
const [subformula, extraFootnotes] = exportSubformula(node as NodeType, nodes, currentFootnoteNumber);
51+
currentFootnoteNumber += extraFootnotes.length;
52+
footnotes.push(...extraFootnotes);
53+
return subformula;
54+
})
2355
.join(",\n");
2456

25-
return `rule(name: $${name}$,$${lhs} tack ${current}$,${premisses})`;
57+
return [`rule(name: $${name}$,$${lhs} tack ${current}$,${premisses})`, footnotes];
2658
}
2759

2860
function formulaToTypst(formula: Formula): string {
@@ -47,7 +79,7 @@ function formulaToTypst(formula: Formula): string {
4779
return `exists ${formulaToTypst({ type: "Ident", body: formula.body.identifier })}. ${formulaToTypst(formula.body.formula)}`;
4880
case "Predicate": {
4981
const vars = formula.body.identifiers.map((id) => id.value).join(", ");
50-
return `${formula.body.identifier}(${vars})`;
82+
return `${formula.body.identifier.value}(${vars})`;
5183
}
5284
default:
5385
return "";

frontend/src/lib/utils/rule.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function getRuleByName(name: Rules): string {
4141
}
4242
}
4343

44-
export function getTypstRuleByName(name: Rules): string {
44+
export function getTypstRuleByName(name: Rules, footnote: number): string | [string, string] {
4545
switch (name) {
4646
case "AndElimL":
4747
return 'and "EL"';
@@ -52,15 +52,15 @@ export function getTypstRuleByName(name: Rules): string {
5252
case "Ax":
5353
return '"AXIOM"';
5454
case "ExistsElim":
55-
return 'exists "E**"';
55+
return [`exists "E"^(${footnote})`, '%%identifier%% "does not occur freely in any formula in" %%lhs%% "or" %%rhs%%'];
5656
case "ExistsIntro":
5757
return 'exists "I"';
5858
case "FalseElim":
5959
return 'bot "E"';
6060
case "ForallElim":
6161
return 'forall "E"';
6262
case "ForallIntro":
63-
return 'forall "I*"';
63+
return [`forall "I"^$(${footnote})`, '%%identifier%% "does not occur freely in any formula in" %%lhs%%'];
6464
case "ImplElim":
6565
return 'arrow.r "E"';
6666
case "ImplIntro":
@@ -76,8 +76,8 @@ export function getTypstRuleByName(name: Rules): string {
7676
case "OrIntroR":
7777
return 'or "I"';
7878
case "AlphaExists":
79-
return 'alpha exists "***"';
79+
return [`alpha exists^(${footnote})`, '"the binding structure is preserved"'];
8080
case "AlphaForall":
81-
return 'alpha forall "***"';
81+
return [`alpha forall^(${footnote})`, '"the binding structure is preserved"'];
8282
}
8383
}

0 commit comments

Comments
 (0)