├── Projects ├── .gitignore ├── Stable │ ├── Stable.lean │ ├── .gitignore │ ├── lean-toolchain │ ├── lake-manifest.json │ ├── build.sh │ └── lakefile.lean ├── MathlibDemo │ ├── lean-toolchain │ ├── .gitignore │ ├── MathlibDemo.lean │ ├── lakefile.toml │ ├── MathlibDemo │ │ ├── Ring.lean │ │ ├── Logic.lean │ │ ├── Rational.lean │ │ └── Bijection.lean │ ├── build.sh │ └── lake-manifest.json ├── create_project.sh └── README.md ├── .npmrc ├── client ├── src │ ├── vite-env.d.ts │ ├── utils │ │ ├── SaveToFile.tsx │ │ ├── WindowWidth.tsx │ │ ├── Entries.ts │ │ └── UrlParsing.tsx │ ├── index.tsx │ ├── css │ │ ├── index.css │ │ ├── App.css │ │ ├── Modal.css │ │ ├── Navigation.css │ │ └── Editor.css │ ├── Popups │ │ ├── Impressum.tsx │ │ ├── LoadUrl.tsx │ │ ├── PrivacyPolicy.tsx │ │ ├── LoadZulip.tsx │ │ ├── Settings.tsx │ │ └── Tools.tsx │ ├── config │ │ ├── config.tsx │ │ ├── docs.tsx │ │ └── settings.ts │ ├── assets │ │ ├── zulip.svg │ │ └── logo.svg │ ├── Navigation.tsx │ └── App.tsx └── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── fonts │ ├── JuliaMono-Regular.ttf │ └── LICENSE-JuliaMono │ └── manifest.json ├── cypress ├── fixtures │ └── zulip-msg-1.txt ├── tsconfig.json ├── support │ ├── e2e.ts │ ├── assertions.ts │ ├── commands.ts │ └── index.d.ts └── e2e │ └── spec.cy.ts ├── ecosystem.config.cjs ├── .gitignore ├── .eslintrc.cjs ├── doc ├── Development.md ├── Usage.md └── Installation.md ├── cypress.config.ts ├── server ├── build.sh ├── bubblewrap.sh └── index.mjs ├── index.html ├── tsconfig.json ├── vite.config.ts ├── README.md ├── package.json ├── .github └── workflows │ └── build.yml └── LICENSE /Projects/.gitignore: -------------------------------------------------------------------------------- 1 | v4.* -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true -------------------------------------------------------------------------------- /Projects/Stable/Stable.lean: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Projects/Stable/.gitignore: -------------------------------------------------------------------------------- 1 | /.lake 2 | -------------------------------------------------------------------------------- /Projects/Stable/lean-toolchain: -------------------------------------------------------------------------------- 1 | leanprover/lean4:stable 2 | -------------------------------------------------------------------------------- /Projects/MathlibDemo/lean-toolchain: -------------------------------------------------------------------------------- 1 | leanprover/lean4:v4.21.0-rc3 2 | -------------------------------------------------------------------------------- /client/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /Projects/MathlibDemo/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /lake-packages/* 3 | *.olean 4 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leanprover-community/lean4web/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leanprover-community/lean4web/HEAD/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leanprover-community/lean4web/HEAD/client/public/logo512.png -------------------------------------------------------------------------------- /client/public/fonts/JuliaMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leanprover-community/lean4web/HEAD/client/public/fonts/JuliaMono-Regular.ttf -------------------------------------------------------------------------------- /Projects/Stable/lake-manifest.json: -------------------------------------------------------------------------------- 1 | {"version": "1.1.0", 2 | "packagesDir": ".lake/packages", 3 | "packages": [], 4 | "name": "Stable", 5 | "lakeDir": ".lake"} 6 | -------------------------------------------------------------------------------- /Projects/Stable/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Operate in the directory where this file is located 4 | cd $(dirname $0) 5 | 6 | lake update -R 7 | lake build 8 | -------------------------------------------------------------------------------- /Projects/MathlibDemo/MathlibDemo.lean: -------------------------------------------------------------------------------- 1 | import Mathlib 2 | import ProofWidgets 3 | import Plausible 4 | 5 | import MathlibDemo.Bijection 6 | import MathlibDemo.Logic 7 | import MathlibDemo.Rational 8 | import MathlibDemo.Ring 9 | -------------------------------------------------------------------------------- /client/src/utils/SaveToFile.tsx: -------------------------------------------------------------------------------- 1 | import saveAs from 'file-saver'; 2 | 3 | export const save = (content: string) => { 4 | var blob = new Blob([content], {type: "text/plain;charset=utf-8"}); 5 | saveAs(blob, "Lean4WebDownload.lean"); 6 | } 7 | -------------------------------------------------------------------------------- /cypress/fixtures/zulip-msg-1.txt: -------------------------------------------------------------------------------- 1 | An example snippet 2 | ```lean 3 | #eval Lean.toolchain 4 | #check Classical.em 5 | ``` 6 | which should be parsed. 7 | 8 | You could also consider `Cons : Set (Fin n) → pre_simple_graph n → pre_simple_graph (n + 1)`. -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["ES2022", "ES2020", "DOM", "DOM.Iterable"], 5 | "types": ["cypress", "node", "cypress-real-events"] 6 | }, 7 | "include": ["**/*.ts"] 8 | } -------------------------------------------------------------------------------- /Projects/Stable/lakefile.lean: -------------------------------------------------------------------------------- 1 | import Lake 2 | open Lake DSL 3 | 4 | package «Stable» where 5 | -- add package configuration options here 6 | 7 | @[default_target] 8 | lean_lib «Stable» where 9 | -- add library configuration options here 10 | -------------------------------------------------------------------------------- /client/src/index.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import App from './App.tsx' 3 | import './css/index.css' 4 | import { StrictMode } from 'react' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | import './assertions' 2 | import './commands' 3 | import 'cypress-iframe'; 4 | import 'cypress-real-events' 5 | 6 | Cypress.on('uncaught:exception', (err, runnable) => { 7 | // returning false here prevents Cypress from 8 | // failing the test 9 | return false; 10 | }); -------------------------------------------------------------------------------- /ecosystem.config.cjs: -------------------------------------------------------------------------------- 1 | // This is a configuration file for pm2, a production process manager for nodejs 2 | module.exports = { 3 | apps : [{ 4 | name : "lean4web", 5 | script : "server/index.mjs", 6 | env: { 7 | NODE_ENV: "production", 8 | PORT: 8001 9 | }, 10 | }] 11 | } 12 | -------------------------------------------------------------------------------- /Projects/MathlibDemo/lakefile.toml: -------------------------------------------------------------------------------- 1 | name = "MathlibDemo" 2 | defaultTargets = ["MathlibDemo"] 3 | 4 | [leanOptions] 5 | pp.unicode.fun = true 6 | 7 | [[require]] 8 | name = "mathlib" 9 | git = "https://github.com/leanprover-community/mathlib4" 10 | rev = "master" 11 | 12 | [[lean_lib]] 13 | name = "MathlibDemo" 14 | -------------------------------------------------------------------------------- /cypress/support/assertions.ts: -------------------------------------------------------------------------------- 1 | chai.use(function (_chai, utils) { 2 | _chai.Assertion.addProperty('open', function() { 3 | const isOpen = utils.flag(this, 'object').attr('open') != null 4 | 5 | this.assert( 6 | isOpen, 7 | 'expected #{this} to be open but it was not', 8 | 'expected #{this} to not be open but it was', 9 | isOpen 10 | ) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | cypress/screenshots 16 | cypress/videos 17 | 18 | **/.lake 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | .DS_Store 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | -------------------------------------------------------------------------------- /Projects/MathlibDemo/MathlibDemo/Ring.lean: -------------------------------------------------------------------------------- 1 | import Mathlib.Tactic.Ring -- import the `ring` tactic 2 | 3 | -- let R be a commutative ring (for example the real numbers) 4 | variable (R : Type) [CommRing R] 5 | 6 | -- let x and y be elements of R 7 | variable (x y : R) 8 | 9 | -- then (x+y)*(x+2y)=x^2+3xy+2y^2 10 | 11 | example : (x+y)*(x+2*y)=x^2+3*x*y+2*y^2 := by 12 | -- the `ring` tactic solves this goal automatically 13 | ring 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /client/src/css/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: Arial, Helvetica, sans-serif; 4 | -webkit-font-smoothing: antialiased; 5 | -moz-osx-font-smoothing: grayscale; 6 | background-color: #2B2B2B; 7 | color: #fff; 8 | } 9 | 10 | #root { 11 | height: 100vh; 12 | } 13 | 14 | @media (prefers-color-scheme: light) { 15 | body { 16 | background: #fff; 17 | color: #000; 18 | } 19 | } 20 | 21 | @font-face { 22 | font-family: "JuliaMono"; 23 | src: 24 | local('JuliaMono'), 25 | url("/fonts/JuliaMono-Regular.ttf"), 26 | } 27 | -------------------------------------------------------------------------------- /Projects/MathlibDemo/MathlibDemo/Logic.lean: -------------------------------------------------------------------------------- 1 | import Mathlib.Logic.Basic -- basic facts in logic 2 | -- theorems in Lean's mathematics library 3 | 4 | -- Let P and Q be true-false statements 5 | variable (P Q : Prop) 6 | 7 | -- The following is a basic result in logic 8 | example : ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q := by 9 | -- its proof is already in Lean's mathematics library 10 | exact not_and_or 11 | 12 | -- Here is another basic result in logic 13 | example : ¬ (P ∨ Q) ↔ ¬ P ∧ ¬ Q := by 14 | apply? -- we can search for the proof in the library 15 | -- we can also replace `apply?` with its output 16 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Lean 4 Web", 3 | "name": "Lean 4 running in the browser", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /client/src/utils/WindowWidth.tsx: -------------------------------------------------------------------------------- 1 | import {useState, useEffect} from 'react' 2 | 3 | function getWindowDimensions() { 4 | const {innerWidth: width, innerHeight: height } = window 5 | return {width, height} 6 | } 7 | 8 | export function useWindowDimensions() { 9 | const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()) 10 | 11 | useEffect(() => { 12 | function handleResize() { 13 | setWindowDimensions(getWindowDimensions()) 14 | } 15 | window.addEventListener('resize', handleResize) 16 | return () => window.removeEventListener('resize', handleResize) 17 | 18 | }, []) 19 | 20 | return windowDimensions 21 | } 22 | -------------------------------------------------------------------------------- /client/src/Popups/Impressum.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import { Popup } from '../Navigation' 3 | import lean4webConfig from '../config/config' 4 | 5 | /** The popup with the privacy policy. */ 6 | const ImpressumPopup: FC<{ 7 | open: boolean 8 | handleClose: () => void 9 | }> = ({open, handleClose}) => { 10 | return 11 |

Impressum

12 | 13 | { lean4webConfig.contactDetails && 14 |

15 | Contact details
16 | {lean4webConfig.contactDetails} 17 |

18 | } 19 | 20 | { lean4webConfig.impressum } 21 |
22 | } 23 | 24 | export default ImpressumPopup 25 | -------------------------------------------------------------------------------- /doc/Development.md: -------------------------------------------------------------------------------- 1 | - [Back to README](../README.md) 2 | - [User Manual](./Usage.md) 3 | - [Installation](./Installation.md) 4 | - Development 5 | 6 | 7 | ## Development Instructions 8 | 9 | Install [npm](https://www.npmjs.com/) and clone this repository. Inside the repository, run: 10 | 11 | 1. `npm install`, to install dependencies 12 | 2. `npm run build:server`, to build contained lean projects under `Projects/` (or run `lake build` manually inside any lean project) 13 | 3. `npm start`, to start the server. 14 | 15 | The project can be accessed via http://localhost:3000. (Internally, websocket requests to `ws://localhost:3000/`websockets will be forwarded to a Lean server running on port 8080.) 16 | -------------------------------------------------------------------------------- /Projects/MathlibDemo/MathlibDemo/Rational.lean: -------------------------------------------------------------------------------- 1 | import Mathlib.Data.Rat.Init -- import notation ℚ for rationals 2 | import Mathlib.Tactic.Linarith -- a linear arithmetic tactic 3 | import Mathlib.Tactic.Ring -- import the `ring` tactic 4 | 5 | -- let `x`, `y` and `z` be rationals 6 | variable (x y z : ℚ) 7 | 8 | -- let's prove that if `x < y` and `y + 3 < z + 10` then `x + 37 < z + 44` 9 | example (h₁ : x < y) (h₂ : y + 3 < z + 10) : x + 37 < z + 44 := by 10 | linarith -- the `linarith` tactic can do this automatically 11 | 12 | -- let's prove that (x + y) ^ 2 = x ^ 2 + 2 * x * y + y ^ 2 13 | 14 | example : (x + y) ^ 2 = x ^ 2 + 2 * x * y + y ^ 2 := by 15 | ring -- the `ring` tactic can do this automatically 16 | -------------------------------------------------------------------------------- /client/src/utils/Entries.ts: -------------------------------------------------------------------------------- 1 | /** Typing hinter for Object.entries of an interface. 2 | We have some interface `T`, and thus the type of keys/attributes of as `keyof T`. 3 | `[K in keyof T]` is a key of `T` lifted to a type. 4 | `-?` means that the key in the `{...}` object type we are building is required. 5 | `[K, T[K]]` is the type of pairs of the key and its value. 6 | So `{[K in keyof T]-?: [K, T[K]]}` is the type of objects with keys of T 7 | (protomed to types) and values as the tuples of entries of T. 8 | We index into this object with `[keyof T]` to get the pairs of key,value, 9 | and finally with a `[]` to get the array of pairs. */ 10 | export type Entries = { 11 | [K in keyof T]-?: [K, T[K]]; 12 | }[keyof T][]; 13 | -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | 3 | export default defineConfig({ 4 | defaultCommandTimeout: 15000, 5 | retries: 2, 6 | e2e: { 7 | baseUrl: "http://localhost:3000", 8 | setupNodeEvents(on, config) { 9 | on('before:browser:launch', (browser, launchOptions) => { 10 | if (browser.name === 'chromium' && process.env.CYPRESS_USER_AGENT_OS) { 11 | launchOptions.args.push(`--user-agent=\"Mozilla/5.0 (${process.env.CYPRESS_USER_AGENT_OS}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36\"`) 12 | return launchOptions 13 | } 14 | }) 15 | }, 16 | }, 17 | }); -------------------------------------------------------------------------------- /cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | Cypress.Commands.add('map', {prevSubject: true}, (subject: unknown[], iteratee) => { 2 | return cy.wrap(Cypress._.map(subject, iteratee), {log: false}); 3 | }); 4 | 5 | const containsAllFn: Cypress.ContainsAllFn = (subject, selector, contents, options) => { 6 | if (Cypress._.isArray(selector)) { 7 | options = contents as Cypress.ContainsAllOptions 8 | contents = selector 9 | selector = 'body' 10 | } 11 | subject = subject || cy.$$(selector) 12 | 13 | return contents.reduce((chain, content) => { 14 | return chain.contains(selector, content, options) 15 | }, cy.wrap(subject, options)) 16 | } 17 | 18 | Cypress.Commands.add('containsAll', {prevSubject: ['optional', 'element']}, containsAllFn) 19 | -------------------------------------------------------------------------------- /server/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd "$(dirname $0)/../Projects" 4 | 5 | # Iterate over subfolders in Projects and look for a build file `build.sh` 6 | for folder in "."/*; do 7 | if [ -d "$folder" ]; then 8 | build_script="$folder/build.sh" 9 | if [ -f "$build_script" ]; then 10 | SECONDS=0 11 | echo "Start building $folder" 12 | echo "Start building $folder" | logger -t lean4web 13 | 14 | "$build_script" 15 | 16 | duration=$SECONDS 17 | echo "Finished $folder in $(($duration / 60)):$(($duration % 60)) min" 18 | echo "Finished $folder in $(($duration / 60)):$(($duration % 60)) min" | logger -t lean4web 19 | else 20 | echo "Skipping $folder: build.sh missing" 21 | fi 22 | 23 | fi 24 | done 25 | -------------------------------------------------------------------------------- /client/src/css/App.css: -------------------------------------------------------------------------------- 1 | .app { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | } 6 | 7 | .loading-ring { 8 | margin: auto; 9 | display: inline-block; 10 | width: 80px; 11 | height: 80px; 12 | } 13 | 14 | .loading-ring:after { 15 | content: " "; 16 | display: block; 17 | width: 64px; 18 | height: 64px; 19 | margin: 8px; 20 | border-radius: 50%; 21 | /* note: vscode theme not loaded yet, don't use `--vscode-XXX` variables */ 22 | border: 6px solid #aaa; 23 | border-color: #aaa transparent #aaa transparent; 24 | animation: loading-ring 1.2s linear infinite; 25 | } 26 | 27 | @keyframes loading-ring { 28 | 0% { 29 | transform: rotate(0deg); 30 | } 31 | 100% { 32 | transform: rotate(360deg); 33 | } 34 | } 35 | 36 | .hidden { 37 | display: none !important; 38 | } 39 | -------------------------------------------------------------------------------- /client/src/config/config.tsx: -------------------------------------------------------------------------------- 1 | import { LeanWebConfig } from './docs' // look here for documentation of the individual config options 2 | 3 | const lean4webConfig : LeanWebConfig = { 4 | "projects": [ 5 | { "folder": "MathlibDemo", 6 | "name": "Latest Mathlib", 7 | "examples": [ 8 | { "file": "MathlibDemo/Bijection.lean", 9 | "name": "Bijection" }, 10 | { "file": "MathlibDemo/Logic.lean", 11 | "name": "Logic" }, 12 | { "file": "MathlibDemo/Ring.lean", 13 | "name": "Ring" }, 14 | { "file": "MathlibDemo/Rational.lean", 15 | "name": "Rational" }]}, 16 | { "folder": "Stable", 17 | "name": "Stable Lean" }, 18 | ], 19 | "serverCountry": null, 20 | "contactDetails": null, 21 | "impressum": null 22 | } 23 | 24 | export default lean4webConfig 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Lean 4 Web 6 | 7 | 8 | 9 |
10 | 22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /client/src/assets/zulip.svg: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./client/dist/", 4 | "composite": true, 5 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 6 | "target": "ES2020", 7 | "useDefineForClassFields": true, 8 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 9 | "module": "ESNext", 10 | "skipLibCheck": true, 11 | 12 | /* Bundler mode */ 13 | "moduleResolution": "bundler", 14 | "allowImportingTsExtensions": true, 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "moduleDetection": "force", 18 | "noEmit": true, 19 | "jsx": "react-jsx", 20 | 21 | /* Linting */ 22 | // "strict": true, 23 | // "noUnusedLocals": true, 24 | // "noUnusedParameters": true, 25 | // "noFallthroughCasesInSwitch": true 26 | }, 27 | // "include": ["client/src"] 28 | "exclude": [ 29 | "server", 30 | "node_modules" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /Projects/MathlibDemo/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Operate in the directory where this file is located 4 | cd $(dirname $0) 5 | 6 | # Updating Mathlib: We follow the instructions at 7 | # https://github.com/leanprover-community/mathlib4/wiki/Using-mathlib4-as-a-dependency#updating-mathlib4 8 | 9 | # Note: we had once problems with the `lake-manifest` when a new dependency got added 10 | # to `mathlib`, we may need to add `rm lake-manifest.json` again if that's still a problem. 11 | 12 | # currently the mathlib post-update-hook is not good enough to update the lean-toolchain. 13 | # things break if the new lakefile is not valid in the old lean version 14 | curl -L https://raw.githubusercontent.com/leanprover-community/mathlib4/master/lean-toolchain -o lean-toolchain 15 | 16 | # note: mathlib has now a post-update hook that modifies the `lean-toolchain` 17 | # and calls `lake exe cache get`. 18 | 19 | lake update -R 20 | lake build 21 | lake build Batteries 22 | -------------------------------------------------------------------------------- /Projects/MathlibDemo/MathlibDemo/Bijection.lean: -------------------------------------------------------------------------------- 1 | import Mathlib.Logic.Equiv.Basic -- import the theory 2 | -- of bijections as functions with two-sided inverses 3 | 4 | -- Let X, Y and Z be sets 5 | variable (X Y Z : Type) 6 | 7 | -- Then there's a bijection between functions `X × Y → Z` 8 | -- and functions from `X` to (the space of functions from `Y` to `Z`). 9 | 10 | example : (X × Y → Z) ≃ (X → (Y → Z)) := 11 | { -- to go from f : X × Y → Z to a function X → (Y → Z), 12 | -- send x and y to f(x,y) 13 | toFun := fun f ↦ (fun x y ↦ f (x,y)) 14 | -- to go from g : X → (Y → Z) to a function X × Y → Z, 15 | -- send a pair p=(x,y) to (g p.1) (p.2) where p.i is the i'th element of p 16 | invFun := fun g ↦ (fun p ↦ g p.1 p.2) 17 | -- Here we prove that if f ↦ g ↦ f' then f' = f 18 | left_inv := by 19 | intro f -- let f : X × Y → Z be arbitrary. 20 | rfl -- turns out f' = f by definition 21 | -- here we prove that if g ↦ f ↦ g' then g' = g 22 | right_inv := by 23 | intro g -- let f : X → (Y → Z) by arbitrary 24 | rfl -- turns out that g' = g by definition 25 | } 26 | -------------------------------------------------------------------------------- /client/src/Popups/LoadUrl.tsx: -------------------------------------------------------------------------------- 1 | import { Popup } from '../Navigation' 2 | import { FC, FormEvent, useRef, useState } from 'react' 3 | 4 | const LoadUrlPopup: FC<{ 5 | open: boolean 6 | handleClose: () => void 7 | loadFromUrl: (url: string) => void 8 | }> = ({open, handleClose, loadFromUrl}) => { 9 | 10 | const [error, setError] = useState(''); 11 | const urlRef = useRef(null); 12 | 13 | const handleLoad = (ev: FormEvent) => { 14 | ev.preventDefault() 15 | let url = urlRef.current?.value 16 | if (!url) { 17 | setError(`Please specify a URL.`) 18 | return 19 | } 20 | if (!url.startsWith('http://') && !url.startsWith('https://')) { 21 | url = 'https://' + url 22 | } 23 | loadFromUrl(url) 24 | handleClose() 25 | } 26 | 27 | return 28 |

Load from URL

29 | {error ?

{error}

: null} 30 |
31 | 32 | 33 |
34 |
35 | } 36 | 37 | export default LoadUrlPopup 38 | -------------------------------------------------------------------------------- /client/src/Popups/PrivacyPolicy.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import { Popup } from '../Navigation' 3 | import lean4webConfig from '../config/config' 4 | 5 | /** The popup with the privacy policy. */ 6 | const PrivacyPopup: FC<{ 7 | open: boolean 8 | handleClose: () => void 9 | }> = ({open, handleClose}) => { 10 | return 11 |

Privacy Policy

12 | 13 |

Our server collects metadata (such as IP address, browser, operating system) 14 | and the data that the user enters into the editor. The data is used to 15 | compute the Lean output and display it to the user. The information will be stored 16 | as long as the user stays on our website and will be deleted immediately afterwards. 17 | We keep logs to improve our software, but the contained data is anonymized.

18 | 19 |

We don't use cookies but you can choose to save the settings in the browser store 20 | by activating the option in the settings. 21 |

22 | { lean4webConfig.serverCountry && 23 |

Our server is located in {lean4webConfig.serverCountry}.

24 | } 25 | { lean4webConfig.contactDetails && 26 |

27 | Contact details
28 | {lean4webConfig.contactDetails} 29 |

30 | } 31 |
32 | } 33 | 34 | export default PrivacyPopup 35 | -------------------------------------------------------------------------------- /server/bubblewrap.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | # Limit CPU time per process to 1h 4 | ulimit -t 3600 5 | # NB: The RSS limit (ulimit -m) is not supported by modern linux! 6 | 7 | LEAN_ROOT="$(cd $1 && lean --print-prefix)" 8 | LEAN_PATH="$(cd $1 && lake env printenv LEAN_PATH)" 9 | 10 | PROJECT_NAME="$(realpath $1)" 11 | 12 | # # print commands as they are executed 13 | # set -x 14 | 15 | if ! command -v bwrap >/dev/null 2>&1; then 16 | echo "bwrap is not installed! You could try to run the development server instead." 17 | # # Could run without bubblewrap like this, but this might be an unwanted 18 | # # security risk. 19 | # (exec 20 | # cd $1 21 | # lake serve -- 22 | # ) 23 | else 24 | (exec bwrap\ 25 | --ro-bind "$1" "/$PROJECT_NAME" \ 26 | --ro-bind "$LEAN_ROOT" /lean \ 27 | --ro-bind /usr /usr \ 28 | --dev /dev \ 29 | --tmpfs /tmp \ 30 | --proc /proc \ 31 | --symlink usr/lib /lib\ 32 | --symlink usr/lib64 /lib64\ 33 | --symlink usr/bin /bin\ 34 | --symlink usr/sbin /sbin\ 35 | --clearenv \ 36 | --setenv PATH "/bin:/usr/bin:/lean/bin" \ 37 | --setenv LEAN_PATH "$LEAN_PATH" \ 38 | --unshare-user \ 39 | --unshare-pid \ 40 | --unshare-net \ 41 | --unshare-uts \ 42 | --unshare-cgroup \ 43 | --die-with-parent \ 44 | --chdir "/$PROJECT_NAME/" \ 45 | lake serve -- 46 | ) 47 | fi 48 | -------------------------------------------------------------------------------- /client/src/utils/UrlParsing.tsx: -------------------------------------------------------------------------------- 1 | /** Expected arguments which can be provided in the URL. */ 2 | interface UrlArgs { 3 | project: string | null 4 | url: string | null 5 | code: string | null 6 | codez: string | null 7 | } 8 | 9 | /** Escape `(` and `)` in URL. */ 10 | export function fixedEncodeURIComponent(str: string) { 11 | return encodeURIComponent(str).replace(/[()]/g, function(c) { 12 | return '%' + c.charCodeAt(0).toString(16); 13 | }) 14 | } 15 | 16 | /** 17 | * Format the arguments for displaying in the URL, i.e. join them 18 | * in the form `#project=Mathlib&url=...` 19 | */ 20 | export function formatArgs(args: UrlArgs): string { 21 | let out = '#' + 22 | Object.entries(args).filter(([_key, val]) => (val !== null && val.trim().length > 0)).map(([key, val]) => (`${key}=${val}`)).join('&') 23 | if (out == '#') { 24 | return ' ' 25 | } 26 | return out 27 | } 28 | 29 | /** 30 | * Parse arguments from URL. These are of the form `#project=Mathlib&url=...` 31 | */ 32 | export function parseArgs(): UrlArgs { 33 | let _args = window.location.hash.replace('#', '').split('&').map((s) => s.split('=')).filter(x => x[0]) 34 | return Object.fromEntries(_args) 35 | } 36 | 37 | /** 38 | * Tries to lookup and replace some common URLs, such as a github url. 39 | * 40 | * - change link to github file to its raw content. 41 | */ 42 | export function lookupUrl(url: string): string { 43 | const regex = RegExp('https://github.com/(.+)/blob/(.+)') 44 | 45 | if (regex.test(url)) { 46 | url = url.replace(regex, 'https://raw.githubusercontent.com/$1/refs/heads/$2') 47 | } 48 | 49 | return url 50 | } -------------------------------------------------------------------------------- /cypress/support/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | declare namespace Cypress { 5 | type Flatten = T extends Iterable ? Item : never 6 | type ArrayIterator = (value: T, index: number, collection: T[]) => TResult 7 | type ContainsAllOptions = Partial 8 | type ContainsAllFn = CommandFnWithSubject<"containsAll", void | JQueryWithSelector> 9 | interface Chainable { 10 | map, K extends keyof Item>(iteratee: K): Chainable 11 | map, TResult>(iteratee: ArrayIterator): Chainable 12 | containsAll(contents: (string | number | RegExp)[], options?: ContainsAllOptions): Chainable 13 | containsAll( 14 | contents: (string | number | RegExp)[], 15 | options?: ContainsAllOptions 16 | ): Chainable> 17 | containsAll( 18 | selector: string, 19 | contents: (string | number | RegExp)[], 20 | options?: Partial 21 | ): Chainable> 22 | containsAll( 23 | selector: string, 24 | contents: (string | number | RegExp)[], 25 | options?: Partial 26 | ): Chainable> 27 | } 28 | interface Chainer { 29 | (chainer: 'be.open'): Chainable 30 | (chainer: 'not.be.open'): Chainable 31 | } 32 | } -------------------------------------------------------------------------------- /client/src/Popups/LoadZulip.tsx: -------------------------------------------------------------------------------- 1 | import { Popup } from '../Navigation' 2 | import { FC, FormEvent, useRef, useState } from 'react' 3 | 4 | const LoadZulipPopup: FC<{ 5 | open: boolean 6 | handleClose: () => void 7 | setContent: (code: string) => void 8 | }> = ({open, handleClose, setContent}) => { 9 | 10 | const [error, setError] = useState(''); 11 | const textInputRef = useRef(null) 12 | 13 | const handleLoad = (ev: FormEvent) => { 14 | ev.preventDefault() 15 | let md = textInputRef.current?.value // TODO: not a URL but text, update the var names 16 | 17 | console.log(`received: ${md}`) 18 | 19 | // regex 1 finds the code-blocks 20 | let regex1 = /(`{3,})\s*(lean)?\s*\n(.+?)\1/gs 21 | // regex 2 extracts the code from a codeblock 22 | let regex2 = /^(`{3,})\s*(?:lean)?\s*\n\s*(.+)\s*\1$/s 23 | 24 | let res = md?.match(regex1) 25 | 26 | if (res) { 27 | let code = res.map(s => { 28 | return s.match(regex2)![2]}).join('\n\n-- new codeblock\n\n').trim() + '\n' 29 | setContent(code) 30 | //setError('') 31 | handleClose() 32 | } else { 33 | setError('Could not find a code-block in the message') 34 | } 35 | } 36 | 37 | return 38 |

Link to Zulip message

39 |

Copy paste a zulip message here to extract 40 | code-blocks. (mobile: "copy to clipboard", 41 | web: "view message source") 42 |

43 | {error ?

{error}

: null} 44 |
45 |