mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
* Fix #472: Add Emscripten/WebAssembly build for browser-based ChaiScript Port Rob Loach's ChaiScript.js work (https://github.com/RobLoach/ChaiScript.js) into the main repository as an Emscripten build target. Adds a GitHub Actions workflow that builds ChaiScript to WebAssembly and publishes artifacts (JS, WASM, HTML) for embedding in the official ChaiScript.com website. Includes an HTML interactive playground frontend and a native test validating the eval API surface. Co-Authored-By: Rob Loach <robloach@gmail.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address review: publish WASM assets as release under wasm-latest tag Add a publish job to the emscripten workflow that creates a prerelease tagged wasm-latest with chaiscript.js, chaiscript.wasm, and chaiscript.html as downloadable assets. Runs only on pushes to the develop branch. The website repo can fetch these via the public GitHub Releases API on a daily cron without any cross-repo auth. Requested by @lefticus in PR #662 review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: leftibot <leftibot@users.noreply.github.com> Co-authored-by: Rob Loach <robloach@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
238 lines
5.9 KiB
HTML
238 lines
5.9 KiB
HTML
<!doctype html>
|
|
<!--
|
|
ChaiScript Emscripten Frontend
|
|
Based on work by Rob Loach: https://github.com/RobLoach/ChaiScript.js
|
|
Copyright 2019, Rob Loach
|
|
Copyright 2009-2018, Jason Turner
|
|
Licensed under the BSD License. See "license.txt" for details.
|
|
-->
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>ChaiScript</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
background: #1a1a2e;
|
|
color: #e0e0e0;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
header {
|
|
background: #16213e;
|
|
padding: 1rem 2rem;
|
|
border-bottom: 2px solid #0f3460;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
}
|
|
header h1 {
|
|
font-size: 1.5rem;
|
|
color: #e94560;
|
|
font-weight: 700;
|
|
}
|
|
header span {
|
|
font-size: 0.85rem;
|
|
color: #888;
|
|
}
|
|
#status {
|
|
margin-left: auto;
|
|
font-size: 0.85rem;
|
|
padding: 0.3rem 0.8rem;
|
|
border-radius: 4px;
|
|
background: #0f3460;
|
|
}
|
|
#status.ready { color: #4ecca3; }
|
|
#status.loading { color: #e9c46a; }
|
|
#status.error { color: #e94560; }
|
|
main {
|
|
flex: 1;
|
|
display: flex;
|
|
gap: 0;
|
|
}
|
|
.panel {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
}
|
|
.panel-header {
|
|
background: #16213e;
|
|
padding: 0.5rem 1rem;
|
|
font-size: 0.8rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
color: #888;
|
|
border-bottom: 1px solid #0f3460;
|
|
}
|
|
#input {
|
|
flex: 1;
|
|
background: #0d1117;
|
|
color: #c9d1d9;
|
|
border: none;
|
|
padding: 1rem;
|
|
font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace;
|
|
font-size: 0.9rem;
|
|
line-height: 1.6;
|
|
resize: none;
|
|
outline: none;
|
|
tab-size: 2;
|
|
}
|
|
.divider {
|
|
width: 2px;
|
|
background: #0f3460;
|
|
}
|
|
#output {
|
|
flex: 1;
|
|
background: #0d1117;
|
|
padding: 1rem;
|
|
font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace;
|
|
font-size: 0.9rem;
|
|
line-height: 1.6;
|
|
overflow-y: auto;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
}
|
|
.output-line { color: #c9d1d9; }
|
|
.output-error { color: #e94560; }
|
|
footer {
|
|
background: #16213e;
|
|
padding: 0.5rem 1rem;
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
align-items: center;
|
|
border-top: 2px solid #0f3460;
|
|
}
|
|
button {
|
|
background: #e94560;
|
|
color: #fff;
|
|
border: none;
|
|
padding: 0.4rem 1.2rem;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
}
|
|
button:hover { background: #c73652; }
|
|
button:disabled { background: #555; cursor: not-allowed; }
|
|
#btn-clear {
|
|
background: #0f3460;
|
|
}
|
|
#btn-clear:hover { background: #1a4a8a; }
|
|
.hint {
|
|
margin-left: auto;
|
|
font-size: 0.75rem;
|
|
color: #555;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>ChaiScript</h1>
|
|
<span>Interactive Playground</span>
|
|
<div id="status" class="loading">Loading...</div>
|
|
</header>
|
|
<main>
|
|
<div class="panel">
|
|
<div class="panel-header">Input</div>
|
|
<textarea id="input" spellcheck="false">// Welcome to ChaiScript!
|
|
// Write your code here and click Run (or press Ctrl+Enter).
|
|
|
|
def greet(name) {
|
|
return "Hello, " + name + "!"
|
|
}
|
|
|
|
print(greet("World"))
|
|
print(greet("ChaiScript"))
|
|
|
|
// Math example
|
|
def factorial(n) {
|
|
if (n <= 1) { return 1 }
|
|
return n * factorial(n - 1)
|
|
}
|
|
|
|
print("5! = " + to_string(factorial(5)))
|
|
print("10! = " + to_string(factorial(10)))
|
|
</textarea>
|
|
</div>
|
|
<div class="divider"></div>
|
|
<div class="panel">
|
|
<div class="panel-header">Output</div>
|
|
<div id="output"></div>
|
|
</div>
|
|
</main>
|
|
<footer>
|
|
<button id="btn-run" disabled>Run</button>
|
|
<button id="btn-clear">Clear</button>
|
|
<span class="hint">Ctrl+Enter to run</span>
|
|
</footer>
|
|
|
|
<script>
|
|
var outputEl = document.getElementById('output');
|
|
var inputEl = document.getElementById('input');
|
|
var btnRun = document.getElementById('btn-run');
|
|
var btnClear = document.getElementById('btn-clear');
|
|
var statusEl = document.getElementById('status');
|
|
|
|
function appendOutput(text, className) {
|
|
var line = document.createElement('div');
|
|
line.className = className || 'output-line';
|
|
line.textContent = text;
|
|
outputEl.appendChild(line);
|
|
outputEl.scrollTop = outputEl.scrollHeight;
|
|
}
|
|
|
|
var Module = {
|
|
print: function(text) {
|
|
appendOutput(text);
|
|
},
|
|
printErr: function(text) {
|
|
appendOutput(text, 'output-error');
|
|
},
|
|
onRuntimeInitialized: function() {
|
|
statusEl.textContent = 'Ready';
|
|
statusEl.className = 'ready';
|
|
btnRun.disabled = false;
|
|
}
|
|
};
|
|
|
|
function runCode() {
|
|
var code = inputEl.value;
|
|
if (!code.trim()) return;
|
|
|
|
appendOutput('> Running...', 'output-line');
|
|
try {
|
|
Module.eval(code);
|
|
} catch (e) {
|
|
appendOutput('Error: ' + e.message, 'output-error');
|
|
}
|
|
appendOutput('', 'output-line');
|
|
}
|
|
|
|
btnRun.addEventListener('click', runCode);
|
|
btnClear.addEventListener('click', function() {
|
|
outputEl.innerHTML = '';
|
|
});
|
|
|
|
inputEl.addEventListener('keydown', function(e) {
|
|
if (e.ctrlKey && e.key === 'Enter') {
|
|
e.preventDefault();
|
|
runCode();
|
|
}
|
|
// Tab key inserts spaces
|
|
if (e.key === 'Tab') {
|
|
e.preventDefault();
|
|
var start = this.selectionStart;
|
|
var end = this.selectionEnd;
|
|
this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
|
|
this.selectionStart = this.selectionEnd = start + 2;
|
|
}
|
|
});
|
|
</script>
|
|
<script src="chaiscript.js"></script>
|
|
</body>
|
|
</html>
|