├── .gitignore ├── LICENSE ├── README.md ├── curryst.typ ├── examples ├── math-formula.svg ├── math-formula.typ ├── natural-deduction.svg ├── natural-deduction.typ ├── rule-as-premise.svg ├── rule-as-premise.typ ├── usage.svg └── usage.typ ├── tests ├── README.md ├── test-1.png ├── test-10.png ├── test-11.png ├── test-12.png ├── test-13.png ├── test-14.png ├── test-15.png ├── test-16.png ├── test-17.png ├── test-18.png ├── test-19.png ├── test-2.png ├── test-20.png ├── test-21.png ├── test-22.png ├── test-23.png ├── test-24.png ├── test-25.png ├── test-26.png ├── test-27.png ├── test-28.png ├── test-29.png ├── test-3.png ├── test-30.png ├── test-31.png ├── test-32.png ├── test-33.png ├── test-34.png ├── test-35.png ├── test-36.png ├── test-37.png ├── test-38.png ├── test-39.png ├── test-4.png ├── test-40.png ├── test-41.png ├── test-42.png ├── test-43.png ├── test-44.png ├── test-45.png ├── test-46.png ├── test-47.png ├── test-48.png ├── test-49.png ├── test-5.png ├── test-50.png ├── test-51.png ├── test-52.png ├── test-53.png ├── test-54.png ├── test-55.png ├── test-56.png ├── test-57.png ├── test-58.png ├── test-59.png ├── test-6.png ├── test-60.png ├── test-7.png ├── test-8.png ├── test-9.png └── tests.typ └── typst.toml /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Rémi Hutin - Paul Adam - Malo <@MDLC01> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Curryst 2 | 3 | A Typst package for typesetting proof trees. 4 | 5 | 6 | ## Import 7 | 8 | You can import the latest version of this package with: 9 | 10 | ```typst 11 | #import "@preview/curryst:0.5.1": rule, prooftree 12 | ``` 13 | 14 | ## Basic usage 15 | 16 | To display a proof tree, you first need to create a tree, using the `rule` function. Its first argument is the conclusion, and the other positional arguments are the premises. It also accepts a `name` for the rule name, displayed on the right of the bar, as well as a `label`, displayed on the left of the bar. 17 | 18 | ```typ 19 | #let tree = rule( 20 | label: [Label], 21 | name: [Rule name], 22 | [Conclusion], 23 | [Premise 1], 24 | [Premise 2], 25 | [Premise 3] 26 | ) 27 | ``` 28 | 29 | Then, you can display the tree with the `prooftree` function: 30 | 31 | ```typ 32 | #prooftree(tree) 33 | ``` 34 | 35 | In this case, we get the following result: 36 | 37 | ![A proof tree with three premises, a conclusion, and a rule name.](examples/usage.svg) 38 | 39 | Proof trees can be part of mathematical formulas: 40 | 41 | ```typ 42 | Consider the following tree: 43 | $ 44 | Pi quad = quad prooftree( 45 | rule( 46 | phi, 47 | Pi_1, 48 | Pi_2, 49 | ) 50 | ) 51 | $ 52 | $Pi$ constitutes a derivation of $phi$. 53 | ``` 54 | 55 | ![The rendered document.](examples/math-formula.svg) 56 | 57 | You can specify a rule as the premises of a rule in order to create a tree: 58 | 59 | ```typ 60 | #prooftree( 61 | rule( 62 | name: $R$, 63 | $C_1 or C_2 or C_3$, 64 | rule( 65 | name: $A$, 66 | $C_1 or C_2 or L$, 67 | rule( 68 | $C_1 or L$, 69 | $Pi_1$, 70 | ), 71 | ), 72 | rule( 73 | $C_2 or overline(L)$, 74 | $Pi_2$, 75 | ), 76 | ) 77 | ) 78 | ``` 79 | 80 | ![The rendered tree.](examples/rule-as-premise.svg) 81 | 82 | As an example, here is a natural deduction proof tree generated with Curryst: 83 | 84 | ![The rendered tree.](examples/natural-deduction.svg) 85 | 86 |
87 | Show code 88 | 89 | ```typ 90 | #let ax = rule.with(name: [ax]) 91 | #let and-el = rule.with(name: $and_e^ell$) 92 | #let and-er = rule.with(name: $and_e^r$) 93 | #let impl-i = rule.with(name: $scripts(->)_i$) 94 | #let impl-e = rule.with(name: $scripts(->)_e$) 95 | #let not-i = rule.with(name: $not_i$) 96 | #let not-e = rule.with(name: $not_e$) 97 | 98 | #prooftree( 99 | impl-i( 100 | $tack (p -> q) -> not (p and not q)$, 101 | not-i( 102 | $p -> q tack not (p and not q)$, 103 | not-e( 104 | $ underbrace(p -> q\, p and not q, Gamma) tack bot $, 105 | impl-e( 106 | $Gamma tack q$, 107 | ax($Gamma tack p -> q$), 108 | and-el( 109 | $Gamma tack p$, 110 | ax($Gamma tack p and not q$), 111 | ), 112 | ), 113 | and-er( 114 | $Gamma tack not q$, 115 | ax($Gamma tack p and not q$), 116 | ), 117 | ), 118 | ), 119 | ) 120 | ) 121 | ``` 122 |
123 | 124 | 125 | ## Advanced usage 126 | 127 | The `prooftree` function accepts multiple named arguments that let you customize the tree: 128 | 129 |
130 |
min-premise-spacing
131 |
The minimum amount of space between two premises.
132 | 133 |
title-inset
134 |
The amount to extend the horizontal bar beyond the content. Also determines how far from the bar labels and names are displayed.
135 | 136 |
stroke
137 |
The stroke to use for the horizontal bars.
138 | 139 |
vertical-spacing
140 |
The space between the bottom of the bar and the conclusion, and between the top of the bar and the premises.
141 | 142 |
min-bar-height
143 |
The minimum height of the box containing the horizontal bar.
144 | 145 |
dir
146 |
The orientation of the proof tree (either btt or ttb, btt being the default).
147 |
148 | 149 | For more information, please refer to the documentation in [`curryst.typ`](curryst.typ). 150 | -------------------------------------------------------------------------------- /curryst.typ: -------------------------------------------------------------------------------- 1 | /// Creates an inference rule. 2 | /// 3 | /// You can render a rule created with this function using the `prooftree` 4 | /// function. 5 | #let rule( 6 | /// The label of the rule, displayed on the left of the horizontal bar. 7 | label: none, 8 | /// The name of the rule, displayed on the right of the horizontal bar. 9 | name: none, 10 | /// The conclusion of the rule. 11 | conclusion, 12 | /// The premises of the rule. Might be other rules constructed with this 13 | /// function, or some content. 14 | ..premises 15 | ) = { 16 | assert.ne( 17 | type(conclusion), 18 | dictionary, 19 | message: "the conclusion of a rule must be some content (it cannot be another rule)", 20 | ) 21 | assert.eq( 22 | premises.named().len(), 23 | 0, 24 | message: "unexpected named arguments to `rule`", 25 | ) 26 | ( 27 | label: label, 28 | name: name, 29 | conclusion: conclusion, 30 | premises: premises.pos() 31 | ) 32 | } 33 | 34 | /// Lays out a proof tree. 35 | #let prooftree( 36 | /// The rule to lay out. 37 | /// 38 | /// Such a rule can be constructed using the `rule` function. 39 | rule, 40 | /// The minimum amount of space between two premises. 41 | min-premise-spacing: 15pt, 42 | /// The amount to extend the horizontal bar beyond the content. Also 43 | /// determines how far from the bar labels and names are displayed. 44 | title-inset: 2pt, 45 | /// The stroke to use for the horizontal bars. 46 | stroke: stroke(0.4pt), 47 | /// The space between the bottom of the bar and the conclusion, and between 48 | /// the top of the bar and the premises. 49 | /// 50 | /// Note that, in this case, "the bar" refers to the bounding box of the 51 | /// horizontal line and the rule name (if any). 52 | vertical-spacing: 0pt, 53 | /// The minimum height of the box containing the horizontal bar. 54 | /// 55 | /// The height of this box is normally determined by the height of the rule 56 | /// name because it is the biggest element of the box. This setting lets you 57 | /// set a minimum height. The default is 0.8em, is higher than a single line 58 | /// of content, meaning all parts of the tree will align properly by default, 59 | /// even if some rules have no name (unless a rule is higher than a single 60 | /// line). 61 | min-bar-height: 0.8em, 62 | /// The orientation of the proof tree. 63 | /// 64 | /// If set to ttb, the conclusion will be at the top, and the premises will 65 | /// be at the bottom. Defaults to btt, the conclusion being at the bottom 66 | /// and the premises at the top. 67 | dir: btt, 68 | ) = { 69 | /// Lays out some content. 70 | /// 71 | /// This function simply wraps the passed content in the usual 72 | /// `(content: .., left-blank: .., right-blank: ..)` dictionary. 73 | let layout-content(content) = { 74 | // We wrap the content in a box with fixed dimensions so that fractional units 75 | // don't come back to haunt us later. 76 | let dimensions = measure(content) 77 | ( 78 | content: box( 79 | // stroke: yellow + 0.3pt, // DEBUG 80 | ..dimensions, 81 | content, 82 | ), 83 | left-blank: 0pt, 84 | right-blank: 0pt, 85 | ) 86 | } 87 | 88 | 89 | /// Lays out multiple premises, spacing them properly. 90 | let layout-premises( 91 | /// Each laid out premise. 92 | /// 93 | /// Must be an array of ditionaries with `content`, `left-blank` and 94 | /// `right-blank` attributes. 95 | premises, 96 | /// The minimum amount between each premise. 97 | min-spacing, 98 | /// If the laid out premises have an inner width smaller than this, their 99 | /// spacing will be increased in order to reach this inner width. 100 | optimal-inner-width, 101 | ) = { 102 | let arity = premises.len() 103 | 104 | if arity == 0 { 105 | return layout-content(none) 106 | } 107 | 108 | if arity == 1 { 109 | return premises.at(0) 110 | } 111 | 112 | let left-blank = premises.at(0).left-blank 113 | let right-blank = premises.at(-1).right-blank 114 | 115 | let initial-content = stack( 116 | dir: ltr, 117 | spacing: min-spacing, 118 | ..premises.map(premise => premise.content), 119 | ) 120 | let initial-inner-width = measure(initial-content).width - left-blank - right-blank 121 | 122 | if initial-inner-width >= optimal-inner-width { 123 | return ( 124 | content: box(initial-content), 125 | left-blank: left-blank, 126 | right-blank: right-blank, 127 | ) 128 | } 129 | 130 | // Conclusion is wider than the premises: they need to be spaced out. 131 | let remaining-space = optimal-inner-width - initial-inner-width 132 | let final-content = stack( 133 | dir: ltr, 134 | spacing: min-spacing + remaining-space / (arity + 1), 135 | ..premises.map(premise => premise.content), 136 | ) 137 | 138 | ( 139 | content: box(final-content), 140 | left-blank: left-blank, 141 | right-blank: right-blank, 142 | ) 143 | } 144 | 145 | 146 | /// Lays out multiple leaf premises, onto multiple lines to respect the 147 | /// available horizontal space. 148 | /// 149 | /// This function is meant to be called when we already know the premises 150 | /// should be typeset onto multiple lines. In particular, if there is enough 151 | /// space to fit all the premises, they will not be spaced apart. 152 | let layout-leaf-premises( 153 | /// Each laid out premise. 154 | /// 155 | /// Must be an array of content-like (meaning `content`, `string`, etc.). 156 | premises, 157 | /// The minimum amount between each premise. 158 | min-spacing, 159 | /// The available width for the returned content. 160 | /// 161 | /// Ideally, the width of the returned content should be bounded by this 162 | /// value, although no guarantee is made. 163 | available-width, 164 | ) = { 165 | let line-builder = stack.with( 166 | dir: ltr, 167 | spacing: min-spacing, 168 | ) 169 | 170 | let lines = ((),) 171 | for premise in premises { 172 | let augmented-line = lines.last() + (premise,) 173 | if measure(line-builder(..augmented-line)).width <= available-width { 174 | lines.last() = augmented-line 175 | } else { 176 | lines.push((premise,)) 177 | } 178 | } 179 | 180 | layout-content({ 181 | set align(center) 182 | stack( 183 | dir: ttb, 184 | spacing: 0.7em, 185 | ..lines 186 | .filter(line => line.len() != 0) 187 | .map(line => line-builder(..line)), 188 | ) 189 | }) 190 | } 191 | 192 | 193 | /// Lays out the horizontal bar of a rule. 194 | let layout-bar( 195 | /// The stroke to use for the bar. 196 | stroke, 197 | /// The length of the bar, without taking hangs into account. 198 | length, 199 | /// How much to extend the bar to the left and to the right. 200 | hang, 201 | /// The label of the rule, displayed on the left of the bar. 202 | /// 203 | /// If this is `none`, no label is displayed. 204 | label, 205 | /// The name of the rule, displayed on the right of the bar. 206 | /// 207 | /// If this is `none`, no name is displayed. 208 | name, 209 | /// The space to leave between the label and the bar, and between the bar 210 | /// and the name. 211 | margin, 212 | /// The minimum height of the content to return. 213 | min-height, 214 | ) = { 215 | let bar = line( 216 | start: (0pt, 0pt), 217 | length: length + 2 * hang, 218 | stroke: stroke, 219 | ) 220 | 221 | let (width: label-width, height: label-height) = measure(label) 222 | let (width: name-width, height: name-height) = measure(name) 223 | 224 | let content = { 225 | show: box.with( 226 | // stroke: green + 0.3pt, // DEBUG 227 | height: calc.max(label-height, name-height, min-height), 228 | ) 229 | 230 | set align(horizon) 231 | 232 | let bake(body) = if body == none { 233 | none 234 | } else { 235 | move(dy: -0.15em, box(body, ..measure(body))) 236 | } 237 | 238 | let parts = ( 239 | bake(label), 240 | bar, 241 | bake(name), 242 | ).filter(p => p != none) 243 | 244 | stack( 245 | dir: ltr, 246 | spacing: margin, 247 | ..parts, 248 | ) 249 | } 250 | 251 | ( 252 | content: content, 253 | left-blank: 254 | if label == none { 255 | hang 256 | } else { 257 | hang + margin + label-width 258 | }, 259 | right-blank: 260 | if name == none { 261 | hang 262 | } else { 263 | hang + margin + name-width 264 | }, 265 | ) 266 | } 267 | 268 | 269 | /// Lays out the application of a rule. 270 | let layout-rule( 271 | /// The laid out premises. 272 | /// 273 | /// This must be a dictionary with `content`, `left-blank` 274 | /// and `right-blank` attributes. 275 | premises, 276 | /// The conclusion, displayed below the bar. 277 | conclusion, 278 | /// The stroke of the bar. 279 | bar-stroke, 280 | /// The amount by which to extend the bar on each side. 281 | bar-hang, 282 | /// The label of the rule, displayed on the left of the bar. 283 | /// 284 | /// If this is `none`, no label is displayed. 285 | label, 286 | /// The name of the rule, displayed on the right of the bar. 287 | /// 288 | /// If this is `none`, no name is displayed. 289 | name, 290 | /// The space to leave between the label and the bar, and between the bar 291 | /// and the name. 292 | bar-margin, 293 | /// The spacing above and below the bar. 294 | vertical-spacing, 295 | /// The minimum height of the bar element. 296 | min-bar-height, 297 | ) = { 298 | // Fix the dimensions of the conclusion and name to prevent problems with 299 | // fractional units later. 300 | conclusion = box(conclusion, ..measure(conclusion)) 301 | 302 | let premises-inner-width = measure(premises.content).width - premises.left-blank - premises.right-blank 303 | let conclusion-width = measure(conclusion).width 304 | 305 | let bar-length = calc.max(premises-inner-width, conclusion-width) 306 | 307 | let bar = layout-bar(bar-stroke, bar-length, bar-hang, label, name, bar-margin, min-bar-height) 308 | 309 | let left-start 310 | let right-start 311 | 312 | let premises-left-offset 313 | let conclusion-left-offset 314 | 315 | if premises-inner-width > conclusion-width { 316 | left-start = calc.max(premises.left-blank, bar.left-blank) 317 | right-start = calc.max(premises.right-blank, bar.right-blank) 318 | premises-left-offset = left-start - premises.left-blank 319 | conclusion-left-offset = left-start + (premises-inner-width - conclusion-width) / 2 320 | } else { 321 | let premises-left-hang = premises.left-blank - (bar-length - premises-inner-width) / 2 322 | let premises-right-hang = premises.right-blank - (bar-length - premises-inner-width) / 2 323 | left-start = calc.max(premises-left-hang, bar.left-blank) 324 | right-start = calc.max(premises-right-hang, bar.right-blank) 325 | premises-left-offset = left-start + (bar-length - premises-inner-width) / 2 - premises.left-blank 326 | conclusion-left-offset = left-start 327 | } 328 | let bar-left-offset = left-start - bar.left-blank 329 | 330 | let content = { 331 | // show: box.with(stroke: yellow + 0.3pt) // DEBUG 332 | 333 | let stack-dir = dir.inv() 334 | let align-y = dir.start() 335 | 336 | set align(align-y + left) 337 | 338 | stack( 339 | dir: stack-dir, 340 | spacing: vertical-spacing, 341 | h(premises-left-offset) + premises.content, 342 | h(bar-left-offset) + bar.content, 343 | h(conclusion-left-offset) + conclusion, 344 | ) 345 | } 346 | 347 | ( 348 | content: box(content), 349 | left-blank: left-start + (bar-length - conclusion-width) / 2, 350 | right-blank: right-start + (bar-length - conclusion-width) / 2, 351 | ) 352 | } 353 | 354 | 355 | /// Lays out an entire proof tree. 356 | /// 357 | /// All lengths passed to this function must be resolved. 358 | let layout-tree( 359 | /// The rule containing the tree to lay out. 360 | rule, 361 | /// The available width for the tree. 362 | /// 363 | /// `none` is interpreted as infinite available width. 364 | /// 365 | /// Ideally, the width of the returned tree should be bounded by this value, 366 | /// although no guarantee is made. 367 | available-width, 368 | /// The minimum amount between each premise. 369 | min-premise-spacing, 370 | /// The stroke of the bar. 371 | bar-stroke, 372 | /// The amount by which to extend the bar on each side. 373 | bar-hang, 374 | /// The space to leave between the label and the bar, and between the bar 375 | /// and the name. 376 | bar-margin, 377 | /// The margin above and below the bar. 378 | vertical-spacing, 379 | /// The minimum height of the bar element. 380 | min-bar-height, 381 | ) = { 382 | if type(rule) != dictionary { 383 | return layout-content(rule) 384 | } 385 | 386 | let layout-with-baked-premises(premises) = { 387 | layout-rule( 388 | premises, 389 | rule.conclusion, 390 | bar-stroke, 391 | bar-hang, 392 | rule.label, 393 | rule.name, 394 | bar-margin, 395 | vertical-spacing, 396 | min-bar-height, 397 | ) 398 | } 399 | 400 | let side-to-side-premises = layout-premises( 401 | rule.premises.map(premise => layout-tree( 402 | premise, 403 | none, 404 | min-premise-spacing, 405 | bar-stroke, 406 | bar-hang, 407 | bar-margin, 408 | vertical-spacing, 409 | min-bar-height, 410 | )), 411 | min-premise-spacing, 412 | measure(rule.conclusion).width, 413 | ) 414 | let result = layout-with-baked-premises(side-to-side-premises) 415 | 416 | let premises-are-all-leaves = rule.premises.all(premise => type(premise) != dictionary) 417 | if available-width == none or measure(result.content).width <= available-width or not premises-are-all-leaves { 418 | return result 419 | } 420 | 421 | // If the premises are all leaves, they can be typeset in multiple lines 422 | // when there is not enough horizontal space. 423 | let used-width = bar-hang * 2 424 | if rule.name != none { 425 | used-width += bar-margin + measure(rule.name).width 426 | } 427 | if rule.label != none { 428 | used-width += bar-margin + measure(rule.label).width 429 | } 430 | let stacked-premises = layout-leaf-premises( 431 | rule.premises, 432 | min-premise-spacing, 433 | available-width - used-width, 434 | ) 435 | layout-with-baked-premises(stacked-premises) 436 | } 437 | 438 | 439 | layout(available => { 440 | let tree = layout-tree( 441 | rule, 442 | available.width, 443 | min-premise-spacing.to-absolute(), 444 | stroke, 445 | title-inset.to-absolute(), 446 | title-inset.to-absolute(), 447 | vertical-spacing.to-absolute(), 448 | min-bar-height.to-absolute(), 449 | ).content 450 | 451 | block( 452 | // stroke : black + 0.3pt, // DEBUG 453 | ..measure(tree), 454 | breakable: false, 455 | tree, 456 | ) 457 | }) 458 | } 459 | -------------------------------------------------------------------------------- /examples/math-formula.svg: -------------------------------------------------------------------------------- 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 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | -------------------------------------------------------------------------------- /examples/math-formula.typ: -------------------------------------------------------------------------------- 1 | #import "../curryst.typ": rule, prooftree 2 | #set document(date: none) 3 | #set page(width: auto, height: auto, margin: 0.5cm, fill: white) 4 | 5 | Consider the following tree: 6 | $ 7 | Pi quad = quad prooftree( 8 | rule( 9 | phi, 10 | Pi_1, 11 | Pi_2, 12 | ) 13 | ) 14 | $ 15 | $Pi$ constitutes a derivation of $phi$. 16 | -------------------------------------------------------------------------------- /examples/natural-deduction.typ: -------------------------------------------------------------------------------- 1 | #import "../curryst.typ": rule, prooftree 2 | #set document(date: none) 3 | #set page(width: auto, height: auto, margin: 0.5cm, fill: white) 4 | 5 | #let ax = rule.with(name: [ax]) 6 | #let and-el = rule.with(name: $and_e^ell$) 7 | #let and-er = rule.with(name: $and_e^r$) 8 | #let impl-i = rule.with(name: $scripts(->)_i$) 9 | #let impl-e = rule.with(name: $scripts(->)_e$) 10 | #let not-i = rule.with(name: $not_i$) 11 | #let not-e = rule.with(name: $not_e$) 12 | 13 | #prooftree( 14 | impl-i( 15 | $tack (p -> q) -> not (p and not q)$, 16 | not-i( 17 | $p -> q tack not (p and not q)$, 18 | not-e( 19 | $ underbrace(p -> q\, p and not q, Gamma) tack bot $, 20 | impl-e( 21 | $Gamma tack q$, 22 | ax($Gamma tack p -> q$), 23 | and-el( 24 | $Gamma tack p$, 25 | ax($Gamma tack p and not q$), 26 | ), 27 | ), 28 | and-er( 29 | $Gamma tack not q$, 30 | ax($Gamma tack p and not q$), 31 | ), 32 | ), 33 | ), 34 | ) 35 | ) 36 | -------------------------------------------------------------------------------- /examples/rule-as-premise.svg: -------------------------------------------------------------------------------- 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 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | -------------------------------------------------------------------------------- /examples/rule-as-premise.typ: -------------------------------------------------------------------------------- 1 | #import "../curryst.typ": rule, prooftree 2 | #set document(date: none) 3 | #set page(width: auto, height: auto, margin: 0.5cm, fill: white) 4 | 5 | #prooftree( 6 | rule( 7 | name: $R$, 8 | $C_1 or C_2 or C_3$, 9 | rule( 10 | name: $A$, 11 | $C_1 or C_2 or L$, 12 | rule( 13 | $C_1 or L$, 14 | $Pi_1$, 15 | ), 16 | ), 17 | rule( 18 | $C_2 or overline(L)$, 19 | $Pi_2$, 20 | ), 21 | ) 22 | ) 23 | -------------------------------------------------------------------------------- /examples/usage.svg: -------------------------------------------------------------------------------- 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 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /examples/usage.typ: -------------------------------------------------------------------------------- 1 | #import "../curryst.typ": rule, prooftree 2 | #set document(date: none) 3 | #set page(width: auto, height: auto, margin: 0.5cm, fill: white) 4 | 5 | #let tree = rule( 6 | label: [Label], 7 | name: [Rule name], 8 | [Conclusion], 9 | [Premise 1], 10 | [Premise 2], 11 | [Premise 3] 12 | ) 13 | 14 | #prooftree(tree) 15 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Curryst test suite 2 | 3 | To run the tests, use 4 | ``` 5 | $ typst compile tests.typ 'test-{p}.png' --root .. 6 | ``` 7 | 8 | This will override the images. If no image changes, this means the tests passed. 9 | -------------------------------------------------------------------------------- /tests/test-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-1.png -------------------------------------------------------------------------------- /tests/test-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-10.png -------------------------------------------------------------------------------- /tests/test-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-11.png -------------------------------------------------------------------------------- /tests/test-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-12.png -------------------------------------------------------------------------------- /tests/test-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-13.png -------------------------------------------------------------------------------- /tests/test-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-14.png -------------------------------------------------------------------------------- /tests/test-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-15.png -------------------------------------------------------------------------------- /tests/test-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-16.png -------------------------------------------------------------------------------- /tests/test-17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-17.png -------------------------------------------------------------------------------- /tests/test-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-18.png -------------------------------------------------------------------------------- /tests/test-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-19.png -------------------------------------------------------------------------------- /tests/test-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-2.png -------------------------------------------------------------------------------- /tests/test-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-20.png -------------------------------------------------------------------------------- /tests/test-21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-21.png -------------------------------------------------------------------------------- /tests/test-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-22.png -------------------------------------------------------------------------------- /tests/test-23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-23.png -------------------------------------------------------------------------------- /tests/test-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-24.png -------------------------------------------------------------------------------- /tests/test-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-25.png -------------------------------------------------------------------------------- /tests/test-26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-26.png -------------------------------------------------------------------------------- /tests/test-27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-27.png -------------------------------------------------------------------------------- /tests/test-28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-28.png -------------------------------------------------------------------------------- /tests/test-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-29.png -------------------------------------------------------------------------------- /tests/test-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-3.png -------------------------------------------------------------------------------- /tests/test-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-30.png -------------------------------------------------------------------------------- /tests/test-31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-31.png -------------------------------------------------------------------------------- /tests/test-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-32.png -------------------------------------------------------------------------------- /tests/test-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-33.png -------------------------------------------------------------------------------- /tests/test-34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-34.png -------------------------------------------------------------------------------- /tests/test-35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-35.png -------------------------------------------------------------------------------- /tests/test-36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-36.png -------------------------------------------------------------------------------- /tests/test-37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-37.png -------------------------------------------------------------------------------- /tests/test-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-38.png -------------------------------------------------------------------------------- /tests/test-39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-39.png -------------------------------------------------------------------------------- /tests/test-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-4.png -------------------------------------------------------------------------------- /tests/test-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-40.png -------------------------------------------------------------------------------- /tests/test-41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-41.png -------------------------------------------------------------------------------- /tests/test-42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-42.png -------------------------------------------------------------------------------- /tests/test-43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-43.png -------------------------------------------------------------------------------- /tests/test-44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-44.png -------------------------------------------------------------------------------- /tests/test-45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-45.png -------------------------------------------------------------------------------- /tests/test-46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-46.png -------------------------------------------------------------------------------- /tests/test-47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-47.png -------------------------------------------------------------------------------- /tests/test-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-48.png -------------------------------------------------------------------------------- /tests/test-49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-49.png -------------------------------------------------------------------------------- /tests/test-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-5.png -------------------------------------------------------------------------------- /tests/test-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-50.png -------------------------------------------------------------------------------- /tests/test-51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-51.png -------------------------------------------------------------------------------- /tests/test-52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-52.png -------------------------------------------------------------------------------- /tests/test-53.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-53.png -------------------------------------------------------------------------------- /tests/test-54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-54.png -------------------------------------------------------------------------------- /tests/test-55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-55.png -------------------------------------------------------------------------------- /tests/test-56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-56.png -------------------------------------------------------------------------------- /tests/test-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-57.png -------------------------------------------------------------------------------- /tests/test-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-58.png -------------------------------------------------------------------------------- /tests/test-59.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-59.png -------------------------------------------------------------------------------- /tests/test-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-6.png -------------------------------------------------------------------------------- /tests/test-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-60.png -------------------------------------------------------------------------------- /tests/test-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-7.png -------------------------------------------------------------------------------- /tests/test-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-8.png -------------------------------------------------------------------------------- /tests/test-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pauladam94/curryst/acacb67eef5fc0a5d2e804f4182f939dbd244f30/tests/test-9.png -------------------------------------------------------------------------------- /tests/tests.typ: -------------------------------------------------------------------------------- 1 | #import "../curryst.typ" : rule, prooftree 2 | #set document(date: none) 3 | #set page(margin: 0.5cm, width: auto, height: auto) 4 | 5 | #let test(width: auto, config: (:), ..args) = { 6 | for dir in (btt, ttb) { 7 | pagebreak(weak: true) 8 | block( 9 | stroke: 0.3pt + red, 10 | width: width, 11 | prooftree(dir: dir, rule(..args), ..config) 12 | ) 13 | } 14 | } 15 | 16 | 17 | #test( 18 | [Conclusion], 19 | ) 20 | 21 | 22 | #test( 23 | name: [Axiom], 24 | [Conclusion], 25 | ) 26 | 27 | 28 | #test( 29 | label: [Label], 30 | name: [Axiom], 31 | [Conclusion], 32 | ) 33 | 34 | 35 | #test( 36 | label: [Label], 37 | name: [Name], 38 | [Long conclusion], 39 | [Premise], 40 | ) 41 | 42 | 43 | #test( 44 | label: [Label], 45 | name: [Name], 46 | [Conclusion], 47 | [Long premise], 48 | ) 49 | 50 | 51 | #test( 52 | label: [Label], 53 | name: [Name], 54 | [Conclusion], 55 | [Premise 1], 56 | [Premise 2], 57 | ) 58 | 59 | 60 | #test( 61 | label: [Label], 62 | name: [Name], 63 | [Very long conclusion], 64 | [Prem. 1], 65 | [Prem. 2], 66 | ) 67 | 68 | 69 | #test( 70 | label: [Label], 71 | name: [Name], 72 | [Very long conclusion], 73 | rule( 74 | [Prem. 1], 75 | [Hypothesis 1], 76 | ), 77 | [Prem. 2], 78 | ) 79 | 80 | 81 | #test( 82 | label: [Label], 83 | name: [Name], 84 | [Very long conclusion], 85 | rule( 86 | label: [Other label], 87 | name: [Other name], 88 | [Prem. 1], 89 | [Hypothesis 1], 90 | ), 91 | [Prem. 2], 92 | ) 93 | 94 | 95 | #test( 96 | name: [Name], 97 | [Very long conclusion], 98 | rule( 99 | [Prem. 1], 100 | [Hypothesis 1], 101 | ), 102 | rule( 103 | name: [Other name], 104 | [Prem. 2], 105 | [Hypothesis 2], 106 | ), 107 | ) 108 | 109 | 110 | #let ax(ccl) = rule(name: [ax], ccl) 111 | #let and-el(ccl, p) = rule(name: $and_e^l$, ccl, p) 112 | #let and-er(ccl, p) = rule(name: $and_e^r$, ccl, p) 113 | #let impl-i(ccl, p) = rule(name: $attach(->, br: i)$, ccl, p) 114 | #let impl-e(ccl, pi, p1) = rule(name: $attach(->, br: e)$, ccl, pi, p1) 115 | #let not-i(ccl, p) = rule(name: $not_i$, ccl, p) 116 | #let not-e(ccl, pf, pt) = rule(name: $not_e$, ccl, pf, pt) 117 | 118 | #test( 119 | [Conclusion], 120 | impl-i( 121 | $tack (p -> q) -> not (p and not q)$, 122 | not-i( 123 | $p -> q tack not (p and not q)$, 124 | not-e( 125 | $p -> q, p and not q tack bot$, 126 | impl-e( 127 | $Gamma tack q$, 128 | ax($Gamma tack p -> q$), 129 | and-el( 130 | $Gamma tack p$, 131 | ax($Gamma tack p and not q$), 132 | ), 133 | ), 134 | and-er( 135 | $Gamma tack not q$, 136 | ax($Gamma tack p and not q$), 137 | ), 138 | ), 139 | ), 140 | ), 141 | ) 142 | 143 | 144 | #test( 145 | [This is a very wide conclusion, wider than all premises combined], 146 | [Premise], 147 | [Premise], 148 | [Premise], 149 | ) 150 | 151 | #test( 152 | [Conclusion], 153 | rule( 154 | [Premise], 155 | [Short], 156 | ), 157 | rule( 158 | [Premise], 159 | [Short], 160 | ), 161 | rule( 162 | [Premise], 163 | [Very long premise to a premise in a tree], 164 | ), 165 | ) 166 | 167 | #test( 168 | [Conclusion], 169 | rule( 170 | [Premise], 171 | [Very long premise to a premise in a tree], 172 | ), 173 | rule( 174 | [Premise], 175 | [Short], 176 | ), 177 | rule( 178 | [Premise], 179 | [Short], 180 | ), 181 | ) 182 | 183 | 184 | #let ax(ccl) = rule(name: "aaaaaaaa", ccl) 185 | #let and-el(ccl, p) = rule(name: "aaaaaaaaaaaaaaaaaaaaaaaa", ccl, p) 186 | #let and-er(ccl, p) = rule(name: "aaaaaaaa", ccl, p) 187 | #let impl-i(ccl, p) = rule(name: "aaaaaaaa", ccl, p) 188 | #let impl-e(ccl, pi, p1) = rule(name: "aaaaaaaaaaaaaaaaa", ccl, pi, p1) 189 | #let not-i(ccl, p) = rule(name: "aaaaaaaa", ccl, p) 190 | #let not-e(ccl, pf, pt) = rule(name: "aaaaaaaa", ccl, pf, pt) 191 | 192 | #test( 193 | config: (min-premise-spacing: 8pt), 194 | [Conclusion], 195 | impl-i( 196 | $tack (p -> q) -> not (p and not q)$, 197 | not-i( 198 | $p -> q tack not (p and not q)$, 199 | not-e( 200 | $p -> q, p and not q tack bot$, 201 | impl-e( 202 | $Gamma tack q$, 203 | ax($Gamma tack p -> q$), 204 | and-el( 205 | $Gamma tack p$, 206 | ax($Gamma tack p and not q$), 207 | ), 208 | ), 209 | and-er( 210 | $Gamma tack not q$, 211 | ax($Gamma tack p and not q$), 212 | ), 213 | ), 214 | ), 215 | ), 216 | ) 217 | 218 | 219 | #test( 220 | config: (stroke: stroke(paint: blue, thickness: 2pt, cap: "round", dash: "dashed")), 221 | name: [Name], 222 | [Conclusion], 223 | [Premise], 224 | ) 225 | 226 | 227 | #test( 228 | config: ( 229 | min-premise-spacing: 2cm, 230 | title-inset: 1cm, 231 | vertical-spacing: 0.2cm, 232 | min-bar-height: 0.3cm, 233 | ), 234 | name: [Name], 235 | [Conclusion], 236 | rule( 237 | label: [Label 1], 238 | [Premise 1], 239 | [Hypothesis 1], 240 | ), 241 | rule( 242 | name: [Name 2], 243 | [Premise 2], 244 | [Hypothesis 2], 245 | ), 246 | rule( 247 | label: [Label 3], 248 | name: [Name 3], 249 | [Premise 3], 250 | [Hypothesis 3], 251 | ), 252 | ) 253 | 254 | 255 | #test( 256 | config: ( 257 | min-premise-spacing: 0pt, 258 | title-inset: 0pt, 259 | vertical-spacing: 0pt, 260 | min-bar-height: 0pt, 261 | ), 262 | name: [Name], 263 | [Conclusion], 264 | rule( 265 | label: [Label 1], 266 | [Premise 1], 267 | [Hypothesis 1], 268 | ), 269 | rule( 270 | name: [Name 2], 271 | [Premise 2], 272 | [Hypothesis 2], 273 | ), 274 | rule( 275 | label: [Label 3], 276 | name: [Name 3], 277 | [Premise 3], 278 | [Hypothesis 3], 279 | ), 280 | ) 281 | 282 | 283 | // Test leafs are shown on multiple lines when appropriate. 284 | 285 | #test( 286 | width: 5cm, 287 | name: $or_e$, 288 | $Gamma tack psi$, 289 | $Gamma tack phi_1 or phi_2$, 290 | $Gamma, phi_1 tack psi$, 291 | $Gamma, phi_2 tack psi$, 292 | ) 293 | 294 | #test( 295 | width: 5cm, 296 | config: ( 297 | min-premise-spacing: 1cm 298 | ), 299 | [The conclusion], 300 | rect(width: 1cm), 301 | rect(width: 1cm), 302 | rect(width: 1cm), 303 | ) 304 | 305 | #test( 306 | width: 5cm, 307 | config: ( 308 | min-premise-spacing: 1cm 309 | ), 310 | [The conclusion is a bit wide], 311 | rect(width: 1cm), 312 | rect(width: 1cm), 313 | rect(width: 1cm), 314 | ) 315 | 316 | #test( 317 | width: 5cm, 318 | config: ( 319 | min-premise-spacing: 1cm 320 | ), 321 | [The conclusion is hugely wide!!!], 322 | rect(width: 1cm), 323 | rect(width: 1cm), 324 | rect(width: 1cm), 325 | ) 326 | 327 | #test( 328 | width: 5cm, 329 | config: ( 330 | min-premise-spacing: 1cm, 331 | title-inset: 0pt, 332 | ), 333 | [The conclusion], 334 | rect(width: 1cm), 335 | rect(width: 1cm), 336 | rect(width: 1cm), 337 | ) 338 | 339 | #test( 340 | width: 8cm, 341 | config: ( 342 | min-premise-spacing: 1cm, 343 | title-inset: 0.5cm, 344 | ), 345 | name: rect(width: 0.5cm), 346 | label: rect(width: 0.5cm), 347 | [The conclusion], 348 | rect(width: 1cm), 349 | rect(width: 1cm), 350 | rect(width: 1cm), 351 | ) 352 | 353 | #test( 354 | width: 7.9cm, 355 | config: ( 356 | min-premise-spacing: 1cm, 357 | title-inset: 0.5cm, 358 | ), 359 | name: rect(width: 0.5cm), 360 | label: rect(width: 0.5cm), 361 | [The conclusion], 362 | rect(width: 1cm), 363 | rect(width: 1cm), 364 | rect(width: 1cm), 365 | ) 366 | 367 | #{ 368 | // This test triggers a very specific issue. I can't find a way to reproduce 369 | // it without using Libertinus Serif as the font. 370 | // The issue is that rules are incorrectly laid out vertically due to rounding 371 | // errors. Note that, for this test to work, the container in the `test` 372 | // function should be a `block` and not a `box`. 373 | set text(font: "Libertinus Serif") 374 | test( 375 | name: [......................], 376 | [...], 377 | [................], 378 | [................], 379 | ) 380 | } 381 | 382 | 383 | #test( 384 | [This is a very wide conclusion, wider than all premises combined], 385 | [Premise], 386 | [Another premise.], 387 | ) 388 | 389 | #test( 390 | [This is a very wide conclusion, wider than all premises combined], 391 | rule( 392 | [Premise], 393 | [Very, very wide hypothesis...] 394 | ), 395 | [Another premise.], 396 | ) 397 | 398 | #test( 399 | [This is a very wide conclusion, wider than all premises combined], 400 | rule( 401 | [Premise], 402 | [Hyyyyyyyyyyyyyyyyyyypothesis] 403 | ), 404 | rule( 405 | [Premise], 406 | [Hyyyyyyyyyyyyyyyyyyypothesis as well] 407 | ), 408 | ) 409 | 410 | 411 | #test( 412 | [This is a wide conclusion, but not the widest], 413 | rule( 414 | [Premise], 415 | [Hyyyyyyyyyyyyyyyyyyypothesis] 416 | ), 417 | rule( 418 | [Premise], 419 | [Hyyyyyyyyyyyyyyyyyyypothesis as well] 420 | ), 421 | ) 422 | -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "curryst" 3 | version = "0.5.1" 4 | entrypoint = "curryst.typ" 5 | authors = ["Rémi Hutin <@remih23>", "Paul Adam <@pauladam94>", "Malo <@MDLC01>"] 6 | license = "MIT" 7 | description = "Typeset trees of inference rules." 8 | repository = "https://github.com/pauladam94/curryst" 9 | keywords = ["proof tree", "proof trees", "prooftree", "prooftrees", "inference", "logic", "deduction"] 10 | categories = ["components", "visualization", "integration"] 11 | disciplines = ["computer-science", "mathematics"] 12 | compiler = "0.12.0" 13 | exclude = [".gitignore", "examples/*", "tests/*"] 14 | --------------------------------------------------------------------------------