Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 176 additions & 3 deletions echidna-playground/src/Components.affine
Original file line number Diff line number Diff line change
@@ -1,7 +1,180 @@
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
// Ported via Harvard Engine bulk-processor
// Ported via Harvard Engine (Semantic pass)

module Components;

// TODO: Complete semantic implementation
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2025 Coq-Jr Contributors

// UI Components for jsCoq

module Html = {
fn element = (tag: string, ~className="", ~id="", children: array<string>): string => {
fn classAttr = className != "" ? ` class="${className}"` : ""
fn idAttr = id != "" ? ` id="${id}"` : ""
fn content = Js.Array2.joinWith(children, "")
`<${tag}${idAttr}${classAttr}>${content}</${tag}>`
}

fn div = element("div", ...)
fn span = element("span", ...)
fn p = element("p", ...)
fn h3 = element("h3", ...)
fn h4 = element("h4", ...)
fn h5 = element("h5", ...)
fn ul = element("ul", ...)
fn li = element("li", ...)
fn a = (~href: string, text: string): string => `<a href="${href}">${text}</a>`
fn img = (~src: string, ~height: string, ~alt=""): string =>
`<img src="${src}" height="${height}" alt="${alt}">`
fn kbd = (text: string): string => `<kbd>${text}</kbd>`
fn code = (text: string): string => `<code>${text}</code>`
fn em = (text: string): string => `<em>${text}</em>`
fn i = (text: string): string => `<i>${text}</i>`
fn hr = (): string => `<hr/>`
fn br = (): string => `<br/>`

fn textarea = (~id: string, content: string): string =>
`<textarea id="${id}">${content}</textarea>`

fn table = (~className="", rows: array<string>): string => {
fn classAttr = className != "" ? ` class="${className}"` : ""
`<table${classAttr}>${Js.Array2.joinWith(rows, "")}</table>`
}

fn tr = (cells: array<string>): string => `<tr>${Js.Array2.joinWith(cells, "")}</tr>`
fn th = (content: string): string => `<th>${content}</th>`
fn td = (content: string): string => `<td>${content}</td>`
}

module JsCoqName = {
fn render = (): string => {
Html.span(~className="jscoq-name", ["jsCoq"])
}
}

module ActionTable = {
struct action { {
button: string,
keyBinding: string,
description: string,
}

fn actions: array<action> = [
{
button: `${Html.img(~src="ui-images/down.png", ~height="15px")}${Html.img(
~src="ui-images/up.png",
~height="15px",
)}`,
keyBinding: `${Html.kbd("Alt")}+${Html.kbd("↓")}/${Html.kbd("↑")} or${Html.br()}${Html.kbd(
"Alt",
)}+${Html.kbd("N")}/${Html.kbd("P")}`,
description: "Move through the proof.",
},
{
button: Html.img(~src="ui-images/to-cursor.png", ~height="20px"),
keyBinding: `${Html.kbd("Alt")}+${Html.kbd("Enter")} or${Html.br()} ${Html.kbd(
"Alt",
)}+${Html.kbd("→")}`,
description: "Run (or go back) to the current point.",
},
{
button: Html.img(~src="ui-images/power-button-512-black.png", ~height="20px"),
keyBinding: Html.kbd("F8"),
description: "Toggles the goal panel.",
},
]

fn render = (): string => {
fn headerRow = Html.tr([Html.th("Button"), Html.th("Key binding"), Html.th("Action")])

fn rows = Js.Array2.map(actions, action => {
Html.tr([Html.td(action.button), Html.td(action.keyBinding), Html.td(action.description)])
})

Html.table(~className="doc-actions", Js.Array2.concat([headerRow], rows))
}
}

module TeamSection = {
struct teamMember { {
name: string,
url: string,
affiliations: array<(string, string)>,
}

fn devTeam: array<teamMember> = [
{
name: "Emilio Jesús Gallego Arias",
url: "https://www.irif.fr/~gallego/",
affiliations: [
("Inria", "https://www.inria.fr"),
("Université de Paris", "https://u-paris.fr"),
("IRIF", "https://www.irif.fr"),
],
},
{
name: "Shachar Itzhaky",
url: "https://www.cs.technion.ac.il/~shachari/",
affiliations: [("Technion", "https://cs.technion.ac.il")],
},
]

fn contributors: array<teamMember> = [
{
name: "Benoît Pin",
url: "",
affiliations: [
("CRI", "https://www.cri.ensmp.fr/"),
("MINES ParisTech", "https://www.minesparis.psl.eu"),
],
},
]

fn renderMember = (member: teamMember): string => {
fn nameLink = member.url != "" ? Html.a(~href=member.url, member.name) : member.name

fn affiliationLinks = Js.Array2.joinWith(
Js.Array2.map(member.affiliations, ((name, url)) => Html.a(~href=url, name)),
", ",
)

Html.li([`${nameLink} (${affiliationLinks})`])
}

fn render = (): string => {
fn devList = Js.Array2.joinWith(Js.Array2.map(devTeam, renderMember), "")
fn contribList = Js.Array2.joinWith(Js.Array2.map(contributors, renderMember), "")

Html.div(
~id="team",
[
`<a name="team"></a>`,
Html.p([Html.i("The dev team")]),
Html.ul([devList]),
Html.p([Html.i("Contributors")]),
Html.ul([contribList]),
],
)
}
}

module CodeExamples = {
fn imports = `From Coq Require Import ssreflect ssrfun ssrbool.
From mathcomp Require Import eqstruct ssrnat div prime.`

fn primeAbove1 = `(* A nice proof of the infinitude of primes, by Georges Gonthier *)
Lemma prime_above m : {p | m < p & prime p}.
Proof.`

fn primeAbove2 = `have /pdivP[p pr_p p_dv_m1]: 1 < m\`! + 1
by rewrite addn1 ltnS fact_gt0.`

fn primeAbove3 = `exists p => //; rewrite ltnNge; apply: contraL p_dv_m1 => p_le_m.`

fn primeAbove4 = `by rewrite dvdn_addr ?dvdn_fact ?prime_gt0 // gtnNdvd ?prime_gt1.
Qed.`

fn codeIds = ["addnC", "prime_above1", "prime_above2", "prime_above3", "prime_above4"]
}

36 changes: 33 additions & 3 deletions echidna-playground/src/Deno.affine
Original file line number Diff line number Diff line change
@@ -1,7 +1,37 @@
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
// Ported via Harvard Engine bulk-processor
// Ported via Harvard Engine (Semantic pass)

module Deno;

// TODO: Complete semantic implementation
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2025 Coq-Jr Contributors

// Deno HTTP server bindings for ReScript

struct request
struct response

// Deno namespace bindings
@val @scope("Deno") external readFile: string => Js.Promise.t<Js.TypedArray2.Uint8Array.t> = "readFile"

struct serveOptions { {port: int}
struct serveHandler { request => Js.Promise.t<response>

@val @scope("Deno") external serve: (serveOptions, serveHandler) => unit = "serve"

// Request bindings
@get external getUrl: request => string = "url"
@get external getMethod: request => string = "method"

// Response constructor
@new external makeResponse: (string, {"headers": {"content-struct": string}}) => response = "Response"
@new external makeResponseWithStatus: (string, {"status": int}) => response = "Response"
@new external makeResponseBytes: (Js.TypedArray2.Uint8Array.t, {"headers": {"content-struct": string}}) => response = "Response"

// URL parsing
struct url { {pathname: string}
@new external makeUrl: string => url = "URL"

// Console
@val @scope("console") external log: string => unit = "log"

59 changes: 56 additions & 3 deletions echidna-playground/src/Dom.affine
Original file line number Diff line number Diff line change
@@ -1,7 +1,60 @@
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
// Ported via Harvard Engine bulk-processor
// Ported via Harvard Engine (Semantic pass)

module Dom;

// TODO: Complete semantic implementation
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2025 Coq-Jr Contributors

// DOM bindings for ReScript

struct element
struct document
struct window

@val external document: document = "document"
@val external window: window = "window"

@send external getElementById: (document, string) => Js.Nullable.t<element> = "getElementById"
@send external querySelector: (document, string) => Js.Nullable.t<element> = "querySelector"
@send external querySelectorAll: (document, string) => array<element> = "querySelectorAll"

@send external createElement: (document, string) => element = "createElement"
@send external createTextNode: (document, string) => element = "createTextNode"

@send external appendChild: (element, element) => unit = "appendChild"
@send external removeChild: (element, element) => unit = "removeChild"
@send external replaceChild: (element, element, element) => unit = "replaceChild"

@set external setInnerHTML: (element, string) => unit = "innerHTML"
@get external getInnerHTML: element => string = "innerHTML"

@set external setTextContent: (element, string) => unit = "textContent"
@get external getTextContent: element => string = "textContent"

@set external setClassName: (element, string) => unit = "className"
@get external getClassName: element => string = "className"

@send external setAttribute: (element, string, string) => unit = "setAttribute"
@send external getAttribute: (element, string) => Js.Nullable.t<string> = "getAttribute"
@send external removeAttribute: (element, string) => unit = "removeAttribute"

@send external addEventListener: (element, string, unit => unit) => unit = "addEventListener"
@send external removeEventListener: (element, string, unit => unit) => unit = "removeEventListener"

@get external getValue: element => string = "value"
@set external setValue: (element, string) => unit = "value"

module Style = {
@set external setDisplay: (element, string) => unit = "style.display"
@set external setVisibility: (element, string) => unit = "style.visibility"
@set external setBackgroundColor: (element, string) => unit = "style.backgroundColor"
@set external setColor: (element, string) => unit = "style.color"
@set external setPadding: (element, string) => unit = "style.padding"
@set external setMargin: (element, string) => unit = "style.margin"
}

module Body = {
@val external body: element = "document.body"
}

67 changes: 64 additions & 3 deletions echidna-playground/src/JsCoq.affine
Original file line number Diff line number Diff line change
@@ -1,7 +1,68 @@
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
// Ported via Harvard Engine bulk-processor
// Ported via Harvard Engine (Semantic pass)

module JsCoq;

// TODO: Complete semantic implementation
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2025 Coq-Jr Contributors

// JsCoq bindings and structs

struct editorMode { {
@as("company-coq") companyCoq: bool,
}

struct editorConfig { {
mode: editorMode,
keyMap: string,
}

struct coqOptions { {
implicit_libs: bool,
focus: bool,
editor: editorConfig,
init_pkgs: array<string>,
all_pkgs: array<string>,
}

struct coqInstance

// JsCoq loader bindings
@module("jscoq/ui-js/jscoq-loader.js")
external startExternal: (array<string>, coqOptions) => Js.Promise.t<coqInstance> = "start"

module JsCoqLoader = {
@val @scope("JsCoq")
external start: (array<string>, coqOptions) => Js.Promise.t<coqInstance> = "start"
}

fn defaultOptions: coqOptions = {
implicit_libs: false,
focus: false,
editor: {
mode: {companyCoq: true},
keyMap: "default",
},
init_pkgs: ["init"],
all_pkgs: ["coq", "mathcomp"],
}

fn makeOptions = (
~implicitLibs=false,
~focus=false,
~companyCoq=true,
~keyMap="default",
~initPkgs=["init"],
~allPkgs=["coq", "mathcomp"],
(),
): coqOptions => {
implicit_libs: implicitLibs,
focus: focus,
editor: {
mode: {companyCoq: companyCoq},
keyMap: keyMap,
},
init_pkgs: initPkgs,
all_pkgs: allPkgs,
}

24 changes: 21 additions & 3 deletions echidna-playground/src/Main.affine
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
// Ported via Harvard Engine bulk-processor
// Ported via Harvard Engine (Semantic pass)

module Main;

// TODO: Complete semantic implementation
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2025 Coq-Jr Contributors

// Main entry point for Coq-Jr

// Re-export modules
module Dom = Dom
module JsCoq = JsCoq
module Components = Components
module Page = Page

// Initialize the application when running in browser
fn initialize = () => {
Console.log("Coq-Jr initialized")
Console.log("Generated page HTML available via Page.render()")
}

// Export the page render function for use by Deno server
fn getPageHtml = Page.render

Loading
Loading