From bfc6cb1ac561d2780ec3655db54e0a528e629552 Mon Sep 17 00:00:00 2001 From: Jan Dolinar Date: Sun, 11 Aug 2024 13:03:06 +0200 Subject: [PATCH] add github pages --- .github/workflows/pages.yml | 35 ++++++++ .gitignore | 1 + emscripten.sh | 15 ++++ web/glue.js | 154 +++++++++++++++++++++++++++++++++++ web/index.html | 155 ++++++++++++++++++++++++++++++++++++ web/pegof.css | 86 ++++++++++++++++++++ web/simple.peg | 6 ++ 7 files changed, 452 insertions(+) create mode 100644 .github/workflows/pages.yml create mode 100755 emscripten.sh create mode 100644 web/glue.js create mode 100644 web/index.html create mode 100644 web/pegof.css create mode 100644 web/simple.peg diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..2f3fdca --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,35 @@ +name: Github pages + +on: + push: + branches: [ master, github-pages ] + +jobs: + build: + runs-on: ubuntu-24.04 + + permissions: + pages: write + id-token: write + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + - name: Install dependencies + shell: bash + run: | + set -ex + sudo apt-get update + sudo apt-get -y --no-install-recommends -o APT::Immediate-Configure=false install cmake emscripten + - name: Build github pages + run: ./emscripten.sh build site + - name: Upload + uses: actions/upload-pages-artifact@v3 + with: + path: ./site + - name: Deploy + uses: actions/deploy-pages@v4 + with: + token: ${{ github.token }} diff --git a/.gitignore b/.gitignore index 84c048a..32aa6e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /build/ +/_site/ diff --git a/emscripten.sh b/emscripten.sh new file mode 100755 index 0000000..9d31a28 --- /dev/null +++ b/emscripten.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -ex + +BUILDDIR="${1:-build_js}" +SITEDIR="${2:-_site}" + +emcmake cmake -B "$BUILDDIR" -DCMAKE_BUILD_TYPE=Release +export EMCC_CFLAGS="-sEXPORTED_RUNTIME_METHODS=callMain,FS -Wno-unused-command-line-argument" +cmake --build "$BUILDDIR" -v + +mkdir -p "$SITEDIR" +cp -v "$BUILDDIR"/pegof.{js,wasm} "$SITEDIR/" +cp -v benchmark/grammars/*.peg "$SITEDIR/" +cp -v web/* "$SITEDIR/" diff --git a/web/glue.js b/web/glue.js new file mode 100644 index 0000000..9bcf83d --- /dev/null +++ b/web/glue.js @@ -0,0 +1,154 @@ +let input = document.getElementById("input"); +let output = document.getElementById("output"); +let logs = document.getElementById("log"); +let load = document.getElementById("load"); +let runBtn = document.getElementById("btnRun"); +let auto = document.getElementById("u"); +let peg = null; +let loaded = false; + +function compress(c){var x="charCodeAt",b,e={},f=c.split(""),d=[],a=f[0],g=256;for(b=1;ba?d[b]:e[a]?e[a]:f+c,g.push(a),c=a.charAt(0),e[o]=f+c,o++,f=a;return g.join("")} + +function base64Decode(base64) { + const binString = atob(base64); + return new TextDecoder().decode(Uint8Array.from(binString, (m) => m.codePointAt(0))); +} + +function base64Encode(str) { + const binString = Array.from(new TextEncoder().encode(str), (byte) => String.fromCodePoint(byte)).join(""); + return btoa(binString); +} + +function serializeState() { + let state = { o: [], v: {} }; + if (input.value != peg) { + state.i = input.value; + } else { + state.p = load.selectedOptions[0].id; + } + document.querySelectorAll("input").forEach(x => { + let v = (x.type == "checkbox") ? (x.checked ? 1 : 0) : x.value; + if (x.dataset.default != v) { + state.v[x.id] = v; + } + }); + document.querySelectorAll("option:checked").forEach(x => { + if (x.dataset.default == undefined) { + state.o.push(x.id); + } + }); + console.log("Serialized", state); + history.replaceState(null,null,"#" + base64Encode(compress(JSON.stringify(state)))); +} + +function deserializeState() { + let raw = location.hash.substr(1); + if (!raw) { + loadPeg(); + return; + } + let state = JSON.parse(decompress(base64Decode(raw))); + console.log("Deserialized", state); + + if (state.i) { + input.innerHTML = state.i; + peg = null; + } else { + document.getElementById(state.p).selected = true; + loadPeg(); + } + state.o.forEach(opt => document.getElementById(opt).selected = true); + Object.entries(state.v).forEach(([k,v]) => { + let x = document.getElementById(k); + if (x.type == "checkbox") { + x.checked = v == 1; + } else { + x.value = v; + } + }); +} + +function launch() { + FS.writeFile("/tmp/input.peg", input.value); + output.innerHTML = ""; + logs.innerHTML = ""; + + let options = []; + document.querySelectorAll(".opt option:checked").forEach(x => options.push(...x.value.split(" "))); + document.querySelectorAll("input[type=checkbox].opt").forEach(x => options.push(x.checked ? x.value : null)); + document.querySelectorAll("input[type=number].opt").forEach(x => options.push(x.dataset.opt, x.value)); + options.push("/tmp/input.peg"); + options = options.filter(x => x && x.length > 0); + + console.log("options:", options); + console.log("input:", input.value.substr(0, 50)); + + runBtn.disabled = true; + runBtn.innerText = "Running ..."; + serializeState(); + setTimeout(() => { + Module.callMain(options); + runBtn.disabled = false; + runBtn.innerText = "Run!"; + }, 1); +} + +function launchAuto() { + console.log("launchAuto:", auto.checked, loaded, input.value != ""); + if (auto.checked && loaded && input.value != "") { + launch(); + } +} + +function loadPeg() { + console.log("Loading", load.value); + fetch(load.value) + .then(response => response.text()) + .then(data => { + input.innerHTML = data; + peg = data; + console.log("Loaded", load.value, data.substr(0, 50)); + launchAuto(); + }); +} + +function init() { + deserializeState(); + + load.addEventListener("change", loadPeg); + + document.querySelectorAll(".opt").forEach(x => x.addEventListener("change", launchAuto)); + runBtn.addEventListener("click", launch); + + let timer = null; + input.addEventListener("keydown", () => clearTimeout(timer)); + input.addEventListener("keyup", e => { + if(e.key === "Enter" && (e.metaKey || e.ctrlKey)) { + launch(); + } else { + clearTimeout(timer); + timer = setTimeout(launchAuto, 1000); + } + }); + + document.addEventListener("DOMContentLoaded", function() { + }); +} + +var Module = { + noInitialRun: true, + onRuntimeInitialized: () => { + console.log("Runtime initialized"); + loaded = true; + runBtn.disabled = false; + runBtn.innerText = "Run!"; + launchAuto(); + }, + + print: line => output.innerHTML += line + "\n", + printErr: line => logs.innerHTML += line + "\n" +}; + +init(); diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..fda4fc8 --- /dev/null +++ b/web/index.html @@ -0,0 +1,155 @@ + + + + + Pegof online + + + +

Pegof online

+
+
+

What is pegof?

+
+ Pegof is a command-line tool to format and optimize PEG grammars. + It supports any grammar supported by PackCC parser generator. + You can learn more about it in its GitHub repository. +
+

How does it work?

+
+ Pegof parses peg grammar from input file and extracts it's AST. + Then, based on the command it either just prints it out in nice and consistent format or directly as AST. + It can also perform multi-pass optimization process that goes through the AST and tries to simplify + it as much as possible to reduce number of rules and terms, which results in faster code in the generated parser. +
+

How to use this site?

+
+ You can update the grammar in the input field and also the configuration on the right. + The output and log fields will be automatically updated, unless auto-update is turned off. + Note that this is just a demo, the command-line tool provides more functionality, + that would be difficult to show in the web environment. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Output type: + +
Add header: + +
Quotes: + +
Wrap threshold:
+
+
Verbosity: + +
Debug:
Skip validation:
+
+
Inline limit:
+
+
+ +
+
+ +
+ + +
+
+
+

Input:

+ +
+
+

Output:

+

+        
+
+

Log:

+

+        
+ + + + diff --git a/web/pegof.css b/web/pegof.css new file mode 100644 index 0000000..cccd5d5 --- /dev/null +++ b/web/pegof.css @@ -0,0 +1,86 @@ +h1 { + text-align: center; +} + +h3 { + margin: 2px; +} + +#controls { + margin: 0.5%; +} + +#optimizations { + width: auto; + overflow: auto; +} + +button { + width: 100%; +} + +td { + text-align: left; +} + +td:first-child { + text-align: right; +} + +input, +select { + width: 100px; + margin-left: 0; + margin-right: 0; + vertical-align: top; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +input:invalid { + background-color: #faa; +} + +input[type=checkbox] { + width: auto; +} + +label { + display: block; + text-align: right; + margin-top: 2px; + margin-bottom: 2px; +} + +.container { + vertical-align: top; + display: inline-block; + margin-right: 1%; + max-width: 45%; +} + +.main { + vertical-align: top; + display: inline-block; + width: 30%; + height: 400px; + margin: 0.5%; +} + +.main pre, +.main textarea { + background: lightyellow; + width: 100%; + height: 100%; + border: 0; + margin: 0; + padding: 1%; + overflow: auto; +} + +textarea { + text-wrap: nowrap; + resize: none; +} diff --git a/web/simple.peg b/web/simple.peg new file mode 100644 index 0000000..ae9b2a4 --- /dev/null +++ b/web/simple.peg @@ -0,0 +1,6 @@ +start <- d* end +a <- "A" +b <- [B] +c <- [f-zxa-g]+ +d <- a b / c +end <- !.