|
1 | 1 | // SPDX-License-Identifier: MPL-2.0 |
2 | | -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell |
3 | | -// Ported via Harvard Engine bulk-processor |
| 2 | +// Ported via Harvard Engine (Semantic pass) |
4 | 3 |
|
5 | 4 | module Components; |
6 | 5 |
|
7 | | -// TODO: Complete semantic implementation |
| 6 | +// SPDX-License-Identifier: MPL-2.0 |
| 7 | +// SPDX-FileCopyrightText: 2025 Coq-Jr Contributors |
| 8 | + |
| 9 | +// UI Components for jsCoq |
| 10 | + |
| 11 | +module Html = { |
| 12 | + fn element = (tag: string, ~className="", ~id="", children: array<string>): string => { |
| 13 | + fn classAttr = className != "" ? ` class="${className}"` : "" |
| 14 | + fn idAttr = id != "" ? ` id="${id}"` : "" |
| 15 | + fn content = Js.Array2.joinWith(children, "") |
| 16 | + `<${tag}${idAttr}${classAttr}>${content}</${tag}>` |
| 17 | + } |
| 18 | + |
| 19 | + fn div = element("div", ...) |
| 20 | + fn span = element("span", ...) |
| 21 | + fn p = element("p", ...) |
| 22 | + fn h3 = element("h3", ...) |
| 23 | + fn h4 = element("h4", ...) |
| 24 | + fn h5 = element("h5", ...) |
| 25 | + fn ul = element("ul", ...) |
| 26 | + fn li = element("li", ...) |
| 27 | + fn a = (~href: string, text: string): string => `<a href="${href}">${text}</a>` |
| 28 | + fn img = (~src: string, ~height: string, ~alt=""): string => |
| 29 | + `<img src="${src}" height="${height}" alt="${alt}">` |
| 30 | + fn kbd = (text: string): string => `<kbd>${text}</kbd>` |
| 31 | + fn code = (text: string): string => `<code>${text}</code>` |
| 32 | + fn em = (text: string): string => `<em>${text}</em>` |
| 33 | + fn i = (text: string): string => `<i>${text}</i>` |
| 34 | + fn hr = (): string => `<hr/>` |
| 35 | + fn br = (): string => `<br/>` |
| 36 | + |
| 37 | + fn textarea = (~id: string, content: string): string => |
| 38 | + `<textarea id="${id}">${content}</textarea>` |
| 39 | + |
| 40 | + fn table = (~className="", rows: array<string>): string => { |
| 41 | + fn classAttr = className != "" ? ` class="${className}"` : "" |
| 42 | + `<table${classAttr}>${Js.Array2.joinWith(rows, "")}</table>` |
| 43 | + } |
| 44 | + |
| 45 | + fn tr = (cells: array<string>): string => `<tr>${Js.Array2.joinWith(cells, "")}</tr>` |
| 46 | + fn th = (content: string): string => `<th>${content}</th>` |
| 47 | + fn td = (content: string): string => `<td>${content}</td>` |
| 48 | +} |
| 49 | + |
| 50 | +module JsCoqName = { |
| 51 | + fn render = (): string => { |
| 52 | + Html.span(~className="jscoq-name", ["jsCoq"]) |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +module ActionTable = { |
| 57 | + struct action { { |
| 58 | + button: string, |
| 59 | + keyBinding: string, |
| 60 | + description: string, |
| 61 | + } |
| 62 | + |
| 63 | + fn actions: array<action> = [ |
| 64 | + { |
| 65 | + button: `${Html.img(~src="ui-images/down.png", ~height="15px")}${Html.img( |
| 66 | + ~src="ui-images/up.png", |
| 67 | + ~height="15px", |
| 68 | + )}`, |
| 69 | + keyBinding: `${Html.kbd("Alt")}+${Html.kbd("↓")}/${Html.kbd("↑")} or${Html.br()}${Html.kbd( |
| 70 | + "Alt", |
| 71 | + )}+${Html.kbd("N")}/${Html.kbd("P")}`, |
| 72 | + description: "Move through the proof.", |
| 73 | + }, |
| 74 | + { |
| 75 | + button: Html.img(~src="ui-images/to-cursor.png", ~height="20px"), |
| 76 | + keyBinding: `${Html.kbd("Alt")}+${Html.kbd("Enter")} or${Html.br()} ${Html.kbd( |
| 77 | + "Alt", |
| 78 | + )}+${Html.kbd("→")}`, |
| 79 | + description: "Run (or go back) to the current point.", |
| 80 | + }, |
| 81 | + { |
| 82 | + button: Html.img(~src="ui-images/power-button-512-black.png", ~height="20px"), |
| 83 | + keyBinding: Html.kbd("F8"), |
| 84 | + description: "Toggles the goal panel.", |
| 85 | + }, |
| 86 | + ] |
| 87 | + |
| 88 | + fn render = (): string => { |
| 89 | + fn headerRow = Html.tr([Html.th("Button"), Html.th("Key binding"), Html.th("Action")]) |
| 90 | + |
| 91 | + fn rows = Js.Array2.map(actions, action => { |
| 92 | + Html.tr([Html.td(action.button), Html.td(action.keyBinding), Html.td(action.description)]) |
| 93 | + }) |
| 94 | + |
| 95 | + Html.table(~className="doc-actions", Js.Array2.concat([headerRow], rows)) |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +module TeamSection = { |
| 100 | + struct teamMember { { |
| 101 | + name: string, |
| 102 | + url: string, |
| 103 | + affiliations: array<(string, string)>, |
| 104 | + } |
| 105 | + |
| 106 | + fn devTeam: array<teamMember> = [ |
| 107 | + { |
| 108 | + name: "Emilio Jesús Gallego Arias", |
| 109 | + url: "https://www.irif.fr/~gallego/", |
| 110 | + affiliations: [ |
| 111 | + ("Inria", "https://www.inria.fr"), |
| 112 | + ("Université de Paris", "https://u-paris.fr"), |
| 113 | + ("IRIF", "https://www.irif.fr"), |
| 114 | + ], |
| 115 | + }, |
| 116 | + { |
| 117 | + name: "Shachar Itzhaky", |
| 118 | + url: "https://www.cs.technion.ac.il/~shachari/", |
| 119 | + affiliations: [("Technion", "https://cs.technion.ac.il")], |
| 120 | + }, |
| 121 | + ] |
| 122 | + |
| 123 | + fn contributors: array<teamMember> = [ |
| 124 | + { |
| 125 | + name: "Benoît Pin", |
| 126 | + url: "", |
| 127 | + affiliations: [ |
| 128 | + ("CRI", "https://www.cri.ensmp.fr/"), |
| 129 | + ("MINES ParisTech", "https://www.minesparis.psl.eu"), |
| 130 | + ], |
| 131 | + }, |
| 132 | + ] |
| 133 | + |
| 134 | + fn renderMember = (member: teamMember): string => { |
| 135 | + fn nameLink = member.url != "" ? Html.a(~href=member.url, member.name) : member.name |
| 136 | + |
| 137 | + fn affiliationLinks = Js.Array2.joinWith( |
| 138 | + Js.Array2.map(member.affiliations, ((name, url)) => Html.a(~href=url, name)), |
| 139 | + ", ", |
| 140 | + ) |
| 141 | + |
| 142 | + Html.li([`${nameLink} (${affiliationLinks})`]) |
| 143 | + } |
| 144 | + |
| 145 | + fn render = (): string => { |
| 146 | + fn devList = Js.Array2.joinWith(Js.Array2.map(devTeam, renderMember), "") |
| 147 | + fn contribList = Js.Array2.joinWith(Js.Array2.map(contributors, renderMember), "") |
| 148 | + |
| 149 | + Html.div( |
| 150 | + ~id="team", |
| 151 | + [ |
| 152 | + `<a name="team"></a>`, |
| 153 | + Html.p([Html.i("The dev team")]), |
| 154 | + Html.ul([devList]), |
| 155 | + Html.p([Html.i("Contributors")]), |
| 156 | + Html.ul([contribList]), |
| 157 | + ], |
| 158 | + ) |
| 159 | + } |
| 160 | +} |
| 161 | + |
| 162 | +module CodeExamples = { |
| 163 | + fn imports = `From Coq Require Import ssreflect ssrfun ssrbool. |
| 164 | +From mathcomp Require Import eqstruct ssrnat div prime.` |
| 165 | + |
| 166 | + fn primeAbove1 = `(* A nice proof of the infinitude of primes, by Georges Gonthier *) |
| 167 | +Lemma prime_above m : {p | m < p & prime p}. |
| 168 | +Proof.` |
| 169 | + |
| 170 | + fn primeAbove2 = `have /pdivP[p pr_p p_dv_m1]: 1 < m\`! + 1 |
| 171 | + by rewrite addn1 ltnS fact_gt0.` |
| 172 | + |
| 173 | + fn primeAbove3 = `exists p => //; rewrite ltnNge; apply: contraL p_dv_m1 => p_le_m.` |
| 174 | + |
| 175 | + fn primeAbove4 = `by rewrite dvdn_addr ?dvdn_fact ?prime_gt0 // gtnNdvd ?prime_gt1. |
| 176 | +Qed.` |
| 177 | + |
| 178 | + fn codeIds = ["addnC", "prime_above1", "prime_above2", "prime_above3", "prime_above4"] |
| 179 | +} |
| 180 | + |
0 commit comments