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
46 changes: 46 additions & 0 deletions .github/workflows/update-wasm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Update WASM Playground

on:
schedule:
- cron: '17 * * * *'
workflow_dispatch:

permissions:
contents: write

jobs:
update-wasm:
name: Pull latest WASM artifacts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Download WASM release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p playground
gh release download wasm-latest \
--repo ChaiScript/ChaiScript \
--pattern 'chaiscript.js' \
--pattern 'chaiscript.wasm' \
--dir playground \
--clobber

- name: Check for changes
id: changes
run: |
git add playground/
if git diff --cached --quiet; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: Commit and push
if: steps.changes.outputs.changed == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -m "Update WASM playground artifacts from ChaiScript/ChaiScript"
git push
3 changes: 3 additions & 0 deletions _includes/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
<li {% if page.url == '/examples.html'%} class='active' {% endif %}>
<a href="/examples.html">Examples</a>
</li>
<li {% if page.url == '/playground.html'%} class='active' {% endif %}>
<a href="/playground.html">Playground</a>
</li>
<li>
<a href="https://github.com/ChaiScript/ChaiScript/blob/develop/cheatsheet.md">Cheatsheet</a>
</li>
Expand Down
196 changes: 196 additions & 0 deletions playground.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
---
title: Playground
---

<!DOCTYPE HTML>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />

<head>

<title>ChaiScript - Interactive Playground</title>
{% include common.html %}

<style>
.playground-wrap {
display: flex;
gap: 0;
min-height: 500px;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}
.playground-panel {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
.playground-panel-header {
background: #f5f5f5;
padding: 6px 12px;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #888;
border-bottom: 1px solid #ddd;
}
.playground-divider {
width: 2px;
background: #ddd;
}
#chai-input {
flex: 1;
background: #fff;
color: #333;
border: none;
padding: 12px;
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;
}
#chai-output {
flex: 1;
background: #fafafa;
padding: 12px;
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;
}
.chai-output-line { color: #333; }
.chai-output-error { color: #d9534f; }
#chai-status { font-size: 0.85rem; }
#chai-status.ready { color: #5cb85c; }
#chai-status.loading { color: #f0ad4e; }
#chai-status.error { color: #d9534f; }
.playground-controls {
margin-top: 10px;
display: flex;
gap: 8px;
align-items: center;
}
.playground-controls .hint {
margin-left: auto;
font-size: 0.75rem;
color: #999;
}
</style>

</head>

<body>

{% include header.html %}

<div class="well well-sm">
<h3>Interactive Playground <span id="chai-status" class="loading">Loading&hellip;</span></h3>
<p>Write ChaiScript code and run it directly in your browser using WebAssembly.</p>
</div>

<div class="body-with-margin">
<div class="playground-wrap">
<div class="playground-panel">
<div class="playground-panel-header">Input</div>
<textarea id="chai-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 &lt;= 1) { return 1 }
return n * factorial(n - 1)
}

print("5! = " + to_string(factorial(5)))
print("10! = " + to_string(factorial(10)))
</textarea>
</div>
<div class="playground-divider"></div>
<div class="playground-panel">
<div class="playground-panel-header">Output</div>
<div id="chai-output"></div>
</div>
</div>

<div class="playground-controls">
<button id="btn-run" class="btn btn-danger" disabled>Run</button>
<button id="btn-clear" class="btn btn-default">Clear</button>
<span class="hint">Ctrl+Enter to run</span>
</div>
</div>

<script>
var outputEl = document.getElementById('chai-output');
var inputEl = document.getElementById('chai-input');
var btnRun = document.getElementById('btn-run');
var btnClear = document.getElementById('btn-clear');
var statusEl = document.getElementById('chai-status');

function appendOutput(text, className) {
var line = document.createElement('div');
line.className = className || 'chai-output-line';
line.textContent = text;
outputEl.appendChild(line);
outputEl.scrollTop = outputEl.scrollHeight;
}

var Module = {
print: function(text) {
appendOutput(text);
},
printErr: function(text) {
appendOutput(text, 'chai-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...', 'chai-output-line');
try {
Module.eval(code);
} catch (e) {
appendOutput('Error: ' + e.message, 'chai-output-error');
}
appendOutput('', 'chai-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();
}
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="/playground/chaiscript.js"></script>

</body>
51 changes: 51 additions & 0 deletions test_playground.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash
# Regression test for issue #6: WASM playground
# Validates that all required files exist and contain expected content.
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "$0")" && pwd)"
FAIL=0

assert_file_exists() {
if [ ! -f "$REPO_ROOT/$1" ]; then
echo "FAIL: $1 does not exist"
FAIL=1
else
echo "PASS: $1 exists"
fi
}

assert_file_contains() {
if ! grep -q "$2" "$REPO_ROOT/$1" 2>/dev/null; then
echo "FAIL: $1 does not contain '$2'"
FAIL=1
else
echo "PASS: $1 contains '$2'"
fi
}

# 1. GitHub Actions workflow exists and runs hourly
assert_file_exists ".github/workflows/update-wasm.yml"
assert_file_contains ".github/workflows/update-wasm.yml" "schedule"
assert_file_contains ".github/workflows/update-wasm.yml" "cron:"
assert_file_contains ".github/workflows/update-wasm.yml" "wasm-latest"
assert_file_contains ".github/workflows/update-wasm.yml" "ChaiScript/ChaiScript"

# 2. Playground page exists with required elements
assert_file_exists "playground.html"
assert_file_contains "playground.html" "chaiscript.js"
assert_file_contains "playground.html" "Module"
assert_file_contains "playground.html" "header.html"

# 3. Navigation includes playground link
assert_file_contains "_includes/header.html" "playground"

if [ "$FAIL" -ne 0 ]; then
echo ""
echo "RESULT: SOME TESTS FAILED"
exit 1
fi

echo ""
echo "RESULT: ALL TESTS PASSED"
exit 0