feat: web playground + LSP server + docs update
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bux Playground</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: 'Fira Code', 'JetBrains Mono', monospace; background: #1a1b26; color: #a9b1d6; min-height: 100vh; }
|
||||
.header { background: #24283b; padding: 12px 20px; font-size: 14px; border-bottom: 1px solid #414868; display: flex; align-items: center; gap: 12px; }
|
||||
.header .logo { font-weight: bold; color: #7aa2f7; font-size: 18px; }
|
||||
.header .version { color: #565f89; font-size: 12px; }
|
||||
.main { display: flex; flex-direction: column; height: calc(100vh - 50px); }
|
||||
.toolbar { display: flex; gap: 8px; padding: 8px 16px; background: #1e2030; border-bottom: 1px solid #292e42; }
|
||||
.toolbar button { padding: 6px 16px; border: 1px solid #414868; background: #24283b; color: #a9b1d6; cursor: pointer; border-radius: 4px; font-size: 13px; font-family: inherit; }
|
||||
.toolbar button:hover { background: #2f334a; }
|
||||
.toolbar button.run { background: #9ece6a; color: #1a1b26; border-color: #9ece6a; font-weight: bold; }
|
||||
.toolbar button.run:hover { background: #b9f27c; }
|
||||
.editor { flex: 1; display: flex; }
|
||||
.editor textarea { flex: 1; background: #1a1b26; color: #a9b1d6; border: none; padding: 16px; font-family: 'Fira Code', 'JetBrains Mono', monospace; font-size: 14px; line-height: 1.6; resize: none; outline: none; tab-size: 4; }
|
||||
.output { background: #13141c; border-top: 1px solid #292e42; min-height: 0; height: 30%; overflow-y: auto; padding: 12px 16px; font-size: 13px; line-height: 1.5; white-space: pre-wrap; word-break: break-all; }
|
||||
.output.error { color: #f7768e; }
|
||||
.output.success { color: #9ece6a; }
|
||||
.shortcuts { color: #565f89; font-size: 11px; padding: 4px 16px; background: #1e2030; border-top: 1px solid #292e42; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<span class="logo">Bux Playground</span>
|
||||
<span class="version">Try Bux in your browser — no install needed</span>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="toolbar">
|
||||
<button class="run" onclick="runCode()" title="Ctrl+Enter">▶ Run</button>
|
||||
<button onclick="clearOutput()">Clear</button>
|
||||
</div>
|
||||
<div class="editor">
|
||||
<textarea id="code" spellcheck="false" placeholder="Write your Bux code here...">import Std::Io::PrintLine;
|
||||
|
||||
func Main() -> int {
|
||||
PrintLine("Hello, Bux!");
|
||||
return 0;
|
||||
}</textarea>
|
||||
</div>
|
||||
<div class="output" id="output"></div>
|
||||
<div class="shortcuts">Ctrl+Enter to run</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const codeArea = document.getElementById('code');
|
||||
const outputDiv = document.getElementById('output');
|
||||
|
||||
async function runCode() {
|
||||
const code = codeArea.value;
|
||||
outputDiv.textContent = 'Running...';
|
||||
outputDiv.className = 'output';
|
||||
|
||||
try {
|
||||
const resp = await fetch('/compile', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
body: code
|
||||
});
|
||||
const data = await resp.json();
|
||||
|
||||
// Filter build lines (only show compile errors + program output)
|
||||
let output = data.output || '';
|
||||
let lines = output.split('\n');
|
||||
let filtered = [];
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
// Skip verbose build headers
|
||||
if (line.includes('[Package]') || line.includes('[Build]') ||
|
||||
line.includes('Hint:') || line.includes('Bux compiler')) continue;
|
||||
if (line.startsWith('Compiling') || line.startsWith('Linking')) continue;
|
||||
filtered.push(line);
|
||||
}
|
||||
outputDiv.textContent = filtered.join('\n').trim() || '(no output)';
|
||||
outputDiv.className = 'output' + (data.isError ? ' error' : ' success');
|
||||
} catch (e) {
|
||||
outputDiv.textContent = 'Error: ' + e.message;
|
||||
outputDiv.className = 'output error';
|
||||
}
|
||||
}
|
||||
|
||||
function clearOutput() {
|
||||
outputDiv.textContent = '';
|
||||
outputDiv.className = 'output';
|
||||
}
|
||||
|
||||
// Ctrl+Enter shortcut
|
||||
codeArea.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
runCode();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,105 @@
|
||||
# playground.nim — Bux language web playground
|
||||
# Uses hunos HTTP server to accept Bux code, compile with buxc, run, and return output
|
||||
#
|
||||
# Usage: nim c -r tools/playground.nim
|
||||
# Then open http://localhost:8407 in browser
|
||||
|
||||
import std/[os, osproc, strutils, json]
|
||||
import hunos, hunos/router
|
||||
|
||||
const
|
||||
Buxc = "./buxc" # path to buxc binary
|
||||
Port = 8407 # playground port
|
||||
TimeoutMs = 5000 # max execution time (5 seconds)
|
||||
|
||||
# --------------- HTML frontend ---------------
|
||||
const PlaygroundHTML = staticRead("playground.html")
|
||||
|
||||
# --------------- helpers ---------------
|
||||
|
||||
proc compileAndRun(code: string): tuple[output: string, exitCode: int, isError: bool] =
|
||||
## Compile code with buxc, run the resulting binary, capture output.
|
||||
var tmpDir = "/tmp/bux-playground-" & $getCurrentProcessId()
|
||||
createDir(tmpDir)
|
||||
createDir(tmpDir / "src")
|
||||
|
||||
# Write Main.bux
|
||||
writeFile(tmpDir / "src" / "Main.bux", code)
|
||||
|
||||
# Write bux.toml
|
||||
writeFile(tmpDir / "bux.toml", "[package]\nname = \"playground\"\nversion = \"0.1.0\"\ntype = \"bin\"\n\n[build]\noutput = \"Bin\"\n")
|
||||
|
||||
# Build with buxc
|
||||
let buildResult = execCmdEx(Buxc & " build " & tmpDir)
|
||||
let buildOutput = buildResult.output
|
||||
|
||||
if buildResult.exitCode != 0:
|
||||
removeDir(tmpDir)
|
||||
return (buildOutput, buildResult.exitCode, true)
|
||||
|
||||
# Run the binary (named "playground" from bux.toml)
|
||||
let binary = tmpDir / "build" / "playground"
|
||||
if not fileExists(binary):
|
||||
removeDir(tmpDir)
|
||||
return ("Binary not found after build:\n" & buildOutput, 1, true)
|
||||
|
||||
let runResult = execCmdEx("timeout " & $TimeoutMs div 1000 & " " & binary)
|
||||
let runOutput = runResult.output
|
||||
|
||||
# Cleanup
|
||||
removeDir(tmpDir)
|
||||
|
||||
return (buildOutput & "\n" & runOutput, runResult.exitCode, runResult.exitCode != 0)
|
||||
|
||||
# --------------- route handlers ---------------
|
||||
|
||||
proc handlePlayground(request: Request) =
|
||||
var headers: HttpHeaders
|
||||
headers["Content-Type"] = "text/html; charset=utf-8"
|
||||
request.respond(200, headers, PlaygroundHTML)
|
||||
|
||||
proc handleCompile(request: Request) =
|
||||
if request.httpMethod != HttpPost:
|
||||
var headers: HttpHeaders
|
||||
headers["Content-Type"] = "text/plain"
|
||||
request.respond(405, headers, "Method not allowed")
|
||||
return
|
||||
|
||||
let code = request.body
|
||||
|
||||
if code.len == 0:
|
||||
var headers: HttpHeaders
|
||||
headers["Content-Type"] = "application/json"
|
||||
request.respond(400, headers, $(%*{"error": "no code provided"}))
|
||||
return
|
||||
|
||||
let (output, exitCode, isError) = compileAndRun(code)
|
||||
|
||||
var headers: HttpHeaders
|
||||
headers["Content-Type"] = "application/json"
|
||||
headers["Access-Control-Allow-Origin"] = "*"
|
||||
|
||||
let response = $(%*{
|
||||
"output": output,
|
||||
"exitCode": exitCode,
|
||||
"isError": isError
|
||||
})
|
||||
|
||||
request.respond(200, headers, response)
|
||||
|
||||
# --------------- main ---------------
|
||||
|
||||
proc main() =
|
||||
echo "Bux Playground"
|
||||
echo "Serving on http://localhost:" & $Port
|
||||
echo "Open your browser and start coding!"
|
||||
|
||||
var router: Router
|
||||
router.get("/", handlePlayground)
|
||||
router.post("/compile", handleCompile)
|
||||
|
||||
let server = newServer(router)
|
||||
server.serve(Port(Port))
|
||||
|
||||
when isMainModule:
|
||||
main()
|
||||
Reference in New Issue
Block a user