Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>SoulVM Source-Level Debugger</title> | |
| <style> | |
| :root { | |
| --bg-void: #020508; | |
| --bg-deep: #050b12; | |
| --line-medium: rgba(112, 220, 255, 0.28); | |
| --line-bright: rgba(112, 220, 255, 0.72); | |
| --text-primary: #e8fbff; | |
| --text-secondary: #8eb5c1; | |
| --text-muted: #52707a; | |
| --cyan: #56e8ff; | |
| --cyan-hot: #a9f7ff; | |
| --teal: #38ffc7; | |
| --violet: #ab7cff; | |
| --gold: #ffc857; | |
| --red: #ff5470; | |
| --green: #5dff9d; | |
| --shadow-cyan: 0 0 18px rgba(86, 232, 255, 0.2), 0 0 52px rgba(86, 232, 255, 0.08); | |
| --radius-sm: 10px; | |
| --radius-md: 16px; | |
| --radius-lg: 24px; | |
| --font-display: "Orbitron", "Rajdhani", "Segoe UI", sans-serif; | |
| --font-mono: "JetBrains Mono", "IBM Plex Mono", "Cascadia Code", monospace; | |
| --ease-system: cubic-bezier(0.22, 1, 0.36, 1); | |
| } | |
| * { box-sizing: border-box; } | |
| html { min-height: 100%; background: var(--bg-void); } | |
| body { | |
| min-height: 100vh; | |
| margin: 0; | |
| color: var(--text-primary); | |
| font-family: var(--font-mono); | |
| background: linear-gradient(180deg, #020508 0%, #041019 48%, #020609 100%); | |
| } | |
| button, input, textarea { font: inherit; color: inherit; } | |
| .debugger-shell { | |
| width: min(1800px, 100%); | |
| margin: 0 auto; | |
| padding: 18px; | |
| } | |
| .debugger-header { | |
| position: sticky; | |
| top: 12px; | |
| z-index: 30; | |
| display: flex; | |
| gap: 16px; | |
| align-items: center; | |
| min-height: 76px; | |
| padding: 14px 18px; | |
| border: 1px solid var(--line-medium); | |
| border-radius: var(--radius-lg); | |
| background: linear-gradient(135deg, rgba(9, 24, 36, 0.94), rgba(4, 12, 19, 0.91)); | |
| box-shadow: var(--shadow-cyan), inset 0 1px rgba(255, 255, 255, 0.04); | |
| backdrop-filter: blur(22px) saturate(135%); | |
| margin-bottom: 20px; | |
| } | |
| .debugger-title { | |
| font-family: var(--font-display); | |
| font-size: 1.35rem; | |
| font-weight: 700; | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| color: var(--teal); | |
| } | |
| .debugger-controls { | |
| display: flex; | |
| gap: 8px; | |
| margin-left: auto; | |
| } | |
| .control-btn { | |
| padding: 8px 14px; | |
| border: 1px solid var(--line-medium); | |
| background: linear-gradient(180deg, rgba(18, 47, 65, 0.9), rgba(6, 19, 28, 0.95)); | |
| color: var(--text-primary); | |
| border-radius: var(--radius-sm); | |
| cursor: pointer; | |
| transition: all 160ms var(--ease-system); | |
| font-size: 0.75rem; | |
| font-weight: bold; | |
| text-transform: uppercase; | |
| } | |
| .control-btn:hover { | |
| border-color: var(--line-bright); | |
| box-shadow: var(--shadow-cyan); | |
| } | |
| .control-btn[data-state="running"] { | |
| background: linear-gradient(135deg, var(--cyan-hot), var(--cyan)); | |
| color: #001115; | |
| border-color: transparent; | |
| } | |
| .debugger-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 16px; | |
| margin-bottom: 20px; | |
| } | |
| .panel { | |
| border: 1px solid var(--line-medium); | |
| border-radius: var(--radius-md); | |
| background: linear-gradient(160deg, rgba(10, 26, 38, 0.9), rgba(3, 11, 17, 0.96)); | |
| box-shadow: 0 20px 80px rgba(0, 0, 0, 0.26), inset 0 1px rgba(255, 255, 255, 0.035); | |
| overflow: hidden; | |
| } | |
| .panel-header { | |
| display: flex; | |
| gap: 12px; | |
| align-items: center; | |
| min-height: 52px; | |
| padding: 0 16px; | |
| border-bottom: 1px solid var(--line-medium); | |
| background: linear-gradient(180deg, rgba(255, 255, 255, 0.025), transparent); | |
| } | |
| .panel-title { | |
| color: var(--cyan-hot); | |
| font-family: var(--font-display); | |
| font-size: 0.78rem; | |
| letter-spacing: 0.14em; | |
| text-transform: uppercase; | |
| margin: 0; | |
| } | |
| .panel-body { | |
| padding: 14px; | |
| } | |
| textarea { | |
| width: 100%; | |
| height: 200px; | |
| padding: 12px; | |
| border: 1px solid rgba(86, 232, 255, 0.1); | |
| background: #03080d; | |
| color: var(--cyan); | |
| border-radius: var(--radius-sm); | |
| font-family: var(--font-mono); | |
| font-size: 0.9rem; | |
| resize: vertical; | |
| outline: 0; | |
| } | |
| textarea:focus { | |
| border-color: var(--cyan); | |
| box-shadow: 0 0 10px rgba(86, 232, 255, 0.1); | |
| } | |
| .bytecode-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| max-height: 300px; | |
| overflow-y: auto; | |
| font-size: 0.8rem; | |
| } | |
| .bytecode-line { | |
| padding: 6px 8px; | |
| border: 1px solid transparent; | |
| border-left: 2px solid rgba(86, 232, 255, 0.2); | |
| background: rgba(2, 10, 16, 0.4); | |
| cursor: pointer; | |
| transition: all 100ms var(--ease-system); | |
| } | |
| .bytecode-line:hover { | |
| border-left-color: var(--cyan); | |
| background: rgba(86, 232, 255, 0.05); | |
| } | |
| .bytecode-line[data-active="true"] { | |
| border-left: 3px solid var(--teal); | |
| background: rgba(56, 255, 199, 0.1); | |
| color: var(--teal-hot, #5dff9d); | |
| } | |
| .ip-marker { | |
| display: inline-block; | |
| width: 16px; | |
| height: 16px; | |
| margin-right: 8px; | |
| border-radius: 50%; | |
| background: var(--teal); | |
| font-size: 0.6rem; | |
| text-align: center; | |
| line-height: 16px; | |
| color: #020609; | |
| font-weight: bold; | |
| } | |
| .vm-state-panel { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 10px; | |
| } | |
| .vm-register { | |
| padding: 12px; | |
| border: 1px solid var(--line-medium); | |
| background: rgba(2, 10, 16, 0.55); | |
| border-radius: var(--radius-sm); | |
| } | |
| .vm-reg-label { | |
| color: var(--text-muted); | |
| font-size: 0.65rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.1em; | |
| margin-bottom: 6px; | |
| } | |
| .vm-reg-value { | |
| color: var(--teal); | |
| font-size: 1.1rem; | |
| font-weight: bold; | |
| font-family: var(--font-mono); | |
| } | |
| .execution-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 16px; | |
| } | |
| .execution-grid.full { | |
| grid-column: 1 / -1; | |
| } | |
| .stack-view { | |
| max-height: 250px; | |
| overflow-y: auto; | |
| display: flex; | |
| flex-direction: column-reverse; | |
| gap: 4px; | |
| } | |
| .stack-item { | |
| padding: 6px 8px; | |
| background: rgba(56, 255, 199, 0.1); | |
| border-left: 2px solid var(--teal); | |
| border-radius: 3px; | |
| font-size: 0.8rem; | |
| color: var(--teal); | |
| animation: stack-pulse 0.5s ease-out; | |
| } | |
| @keyframes stack-pulse { | |
| 0% { background: rgba(56, 255, 199, 0.3); transform: scale(1.05); } | |
| 100% { background: rgba(56, 255, 199, 0.1); transform: scale(1); } | |
| } | |
| .trace-entry { | |
| padding: 6px 8px; | |
| border-bottom: 1px solid rgba(112, 220, 255, 0.1); | |
| font-size: 0.75rem; | |
| color: var(--text-secondary); | |
| } | |
| .trace-entry:last-child { | |
| border-bottom: 0; | |
| } | |
| .trace-entry.current { | |
| background: rgba(112, 220, 255, 0.05); | |
| border-left: 2px solid var(--cyan); | |
| padding-left: 6px; | |
| color: var(--cyan); | |
| } | |
| .source-highlight { | |
| padding: 8px; | |
| background: rgba(171, 124, 255, 0.1); | |
| border-left: 2px solid var(--violet); | |
| border-radius: 3px; | |
| font-size: 0.8rem; | |
| color: var(--violet); | |
| margin-top: 12px; | |
| } | |
| .section-title { | |
| color: var(--text-muted); | |
| font-size: 0.7rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.1em; | |
| margin: 12px 0 8px; | |
| padding-top: 12px; | |
| border-top: 1px solid rgba(112, 220, 255, 0.1); | |
| } | |
| @media (max-width: 1024px) { | |
| .debugger-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .vm-state-panel { | |
| grid-template-columns: 1fr; | |
| } | |
| .execution-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="debugger-shell"> | |
| <div class="debugger-header"> | |
| <h1 class="debugger-title">🔬 SoulVM Debugger</h1> | |
| <span style="color: var(--text-muted); font-size: 0.8rem;">Source-level execution tracer</span> | |
| <div class="debugger-controls"> | |
| <button class="control-btn" onclick="compileAndStep()" title="Compile and step">⏭️ Step</button> | |
| <button class="control-btn" onclick="runFull()" data-state="running" title="Run to completion">▶️ Run</button> | |
| <button class="control-btn" onclick="reset()" title="Reset">↻ Reset</button> | |
| </div> | |
| </div> | |
| <div class="debugger-grid"> | |
| <!-- SOURCE CODE --> | |
| <div class="panel"> | |
| <div class="panel-header"> | |
| <h2 class="panel-title">📝 LISP Source</h2> | |
| </div> | |
| <div class="panel-body"> | |
| <textarea id="source-input" spellcheck="false">(+ 1 2)</textarea> | |
| <button onclick="compile()" style="width: 100%; margin-top: 12px; padding: 10px; border: 1px solid var(--line-medium); background: linear-gradient(135deg, var(--cyan-hot), var(--cyan)); color: #001115; border-radius: var(--radius-sm); cursor: pointer; font-weight: bold; text-transform: uppercase;">⚙️ Compile</button> | |
| </div> | |
| </div> | |
| <!-- BYTECODE --> | |
| <div class="panel"> | |
| <div class="panel-header"> | |
| <h2 class="panel-title">📦 Bytecode</h2> | |
| <span style="margin-left: auto; color: var(--text-muted); font-size: 0.7rem;" id="bytecode-count">0 ops</span> | |
| </div> | |
| <div class="panel-body"> | |
| <div class="bytecode-list" id="bytecode-list"> | |
| <div style="color: var(--text-muted); font-size: 0.75rem;">Compile source to view bytecode...</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- VM STATE --> | |
| <div class="panel"> | |
| <div class="panel-header"> | |
| <h2 class="panel-title">⚙️ VM State</h2> | |
| </div> | |
| <div class="panel-body"> | |
| <div class="vm-state-panel"> | |
| <div class="vm-register"> | |
| <div class="vm-reg-label">IP (Instr Ptr)</div> | |
| <div class="vm-reg-value" id="vm-ip">0</div> | |
| </div> | |
| <div class="vm-register"> | |
| <div class="vm-reg-label">SP (Stack Ptr)</div> | |
| <div class="vm-reg-value" id="vm-sp">0</div> | |
| </div> | |
| <div class="vm-register"> | |
| <div class="vm-reg-label">Instr Count</div> | |
| <div class="vm-reg-value" id="vm-instrs">0</div> | |
| </div> | |
| </div> | |
| <div class="section-title">Current Opcode</div> | |
| <div style="padding: 8px; background: rgba(56, 255, 199, 0.1); border-radius: 3px; color: var(--teal); font-weight: bold;" id="current-opcode">—</div> | |
| <div class="section-title">Source Expression</div> | |
| <div class="source-highlight" id="source-highlight">Awaiting execution...</div> | |
| </div> | |
| </div> | |
| <!-- STACK VIEW --> | |
| <div class="panel"> | |
| <div class="panel-header"> | |
| <h2 class="panel-title">📚 Stack</h2> | |
| <span style="margin-left: auto; color: var(--text-muted); font-size: 0.7rem;" id="stack-depth">depth: 0</span> | |
| </div> | |
| <div class="panel-body"> | |
| <div class="stack-view" id="stack-view"> | |
| <div style="color: var(--text-muted); font-size: 0.75rem;">Empty</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- EXECUTION TRACE --> | |
| <div class="panel"> | |
| <div class="panel-header"> | |
| <h2 class="panel-title">📋 Trace</h2> | |
| <span style="margin-left: auto; color: var(--text-muted); font-size: 0.7rem;" id="trace-count">0 steps</span> | |
| </div> | |
| <div class="panel-body"> | |
| <div style="max-height: 300px; overflow-y: auto;"> | |
| <div id="trace-list"> | |
| <div class="trace-entry" style="color: var(--text-muted);">Ready to execute...</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- FINAL RESULT --> | |
| <div class="panel execution-grid full"> | |
| <div class="panel-header"> | |
| <h2 class="panel-title">✨ Result</h2> | |
| </div> | |
| <div class="panel-body"> | |
| <div style="padding: 16px; background: rgba(93, 255, 157, 0.1); border: 1px solid rgba(93, 255, 157, 0.3); border-radius: var(--radius-sm);"> | |
| <div style="color: var(--text-muted); font-size: 0.75rem; margin-bottom: 6px;">Output</div> | |
| <div style="color: var(--green); font-size: 1.2rem; font-weight: bold; font-family: var(--font-mono);" id="result-output">—</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // ======================================================================== | |
| // State | |
| // ======================================================================== | |
| const state = { | |
| source: '', | |
| bytecode: [], | |
| stack: [], | |
| ip: 0, | |
| trace: [], | |
| result: null, | |
| sourceMap: {}, // Maps IP to source expression | |
| }; | |
| // ======================================================================== | |
| // LISP Compiler | |
| // ======================================================================== | |
| function tokenize(source) { | |
| const tokens = []; | |
| let i = 0; | |
| while (i < source.length) { | |
| const ch = source[i]; | |
| if (/\s/.test(ch)) { | |
| i++; | |
| } else if (ch === '(' || ch === ')') { | |
| tokens.push(ch); | |
| i++; | |
| } else if (/[-\d]/.test(ch)) { | |
| let num = ''; | |
| while (i < source.length && /[-\d]/.test(source[i])) { | |
| num += source[i]; | |
| i++; | |
| } | |
| tokens.push(parseInt(num)); | |
| } else { | |
| let sym = ''; | |
| while (i < source.length && !/[\s()]/. test(source[i])) { | |
| sym += source[i]; | |
| i++; | |
| } | |
| tokens.push(sym); | |
| } | |
| } | |
| return tokens; | |
| } | |
| function parse(tokens, pos = 0) { | |
| if (pos >= tokens.length) return { error: 'Unexpected end' }; | |
| const token = tokens[pos]; | |
| if (token === '(') { | |
| const list = []; | |
| pos++; | |
| while (pos < tokens.length && tokens[pos] !== ')') { | |
| const result = parse(tokens, pos); | |
| if (result.error) return result; | |
| list.push(result.ast); | |
| pos = result.pos; | |
| } | |
| if (pos >= tokens.length) return { error: 'Unmatched paren' }; | |
| return { ast: { type: 'list', elements: list }, pos: pos + 1 }; | |
| } else if (typeof token === 'number') { | |
| return { ast: { type: 'number', value: token }, pos: pos + 1 }; | |
| } else { | |
| return { ast: { type: 'symbol', value: token }, pos: pos + 1 }; | |
| } | |
| } | |
| function compileToByteCode(ast, sourceExpr = '') { | |
| const bytecode = []; | |
| const sourceMap = {}; | |
| function compile(node) { | |
| if (node.type === 'number') { | |
| const ip = bytecode.length; | |
| bytecode.push({ op: 'PUSH', operand: node.value }); | |
| sourceMap[ip] = `push ${node.value}`; | |
| return; | |
| } | |
| if (node.type === 'symbol') { | |
| const ip = bytecode.length; | |
| bytecode.push({ op: 'PUSH', operand: node.value }); | |
| sourceMap[ip] = `${node.value}`; | |
| return; | |
| } | |
| if (node.type === 'list' && node.elements.length > 0) { | |
| const [fn, ...args] = node.elements; | |
| // Compile args first | |
| args.forEach(arg => compile(arg)); | |
| // Then the operation | |
| if (fn.type === 'symbol') { | |
| const op = fn.value === '+' ? 'ADD' : | |
| fn.value === '-' ? 'SUB' : | |
| fn.value === '*' ? 'MUL' : 'UNKNOWN'; | |
| const ip = bytecode.length; | |
| bytecode.push({ op }); | |
| sourceMap[ip] = `(${fn.value} ...)`; | |
| } | |
| } | |
| } | |
| compile(ast); | |
| bytecode.push({ op: 'RET' }); | |
| return { bytecode, sourceMap }; | |
| } | |
| // ======================================================================== | |
| // SoulVM Executor | |
| // ======================================================================== | |
| function executeStep() { | |
| if (state.ip >= state.bytecode.length) return false; | |
| const instr = state.bytecode[state.ip]; | |
| state.trace.push({ | |
| ip: state.ip, | |
| op: instr.op, | |
| operand: instr.operand, | |
| stackBefore: [...state.stack], | |
| }); | |
| switch (instr.op) { | |
| case 'PUSH': | |
| state.stack.push(instr.operand); | |
| break; | |
| case 'ADD': | |
| state.stack.push(state.stack.pop() + state.stack.pop()); | |
| break; | |
| case 'SUB': | |
| const b = state.stack.pop(); | |
| state.stack.push(state.stack.pop() - b); | |
| break; | |
| case 'MUL': | |
| state.stack.push(state.stack.pop() * state.stack.pop()); | |
| break; | |
| case 'RET': | |
| state.result = state.stack.length > 0 ? state.stack[state.stack.length - 1] : null; | |
| return false; | |
| default: | |
| return false; | |
| } | |
| state.ip++; | |
| return state.ip < state.bytecode.length; | |
| } | |
| // ======================================================================== | |
| // UI Functions | |
| // ======================================================================== | |
| function compile() { | |
| const source = document.getElementById('source-input').value.trim(); | |
| if (!source) return; | |
| state.source = source; | |
| state.stack = []; | |
| state.ip = 0; | |
| state.trace = []; | |
| state.result = null; | |
| const tokens = tokenize(source); | |
| const parseResult = parse(tokens); | |
| if (parseResult.error) { | |
| alert('Parse error: ' + parseResult.error); | |
| return; | |
| } | |
| const compiled = compileToByteCode(parseResult.ast, source); | |
| state.bytecode = compiled.bytecode; | |
| state.sourceMap = compiled.sourceMap; | |
| renderBytecode(); | |
| renderVMState(); | |
| renderStack(); | |
| renderTrace(); | |
| } | |
| function compileAndStep() { | |
| if (state.bytecode.length === 0) { | |
| compile(); | |
| } | |
| executeStep(); | |
| renderVMState(); | |
| renderStack(); | |
| renderTrace(); | |
| } | |
| function runFull() { | |
| if (state.bytecode.length === 0) { | |
| compile(); | |
| } | |
| while (executeStep()) {} | |
| renderVMState(); | |
| renderStack(); | |
| renderTrace(); | |
| renderResult(); | |
| } | |
| function reset() { | |
| state.stack = []; | |
| state.ip = 0; | |
| state.trace = []; | |
| state.result = null; | |
| renderVMState(); | |
| renderStack(); | |
| renderTrace(); | |
| document.getElementById('result-output').textContent = '—'; | |
| } | |
| // ======================================================================== | |
| // Rendering | |
| // ======================================================================== | |
| function renderBytecode() { | |
| const list = document.getElementById('bytecode-list'); | |
| document.getElementById('bytecode-count').textContent = `${state.bytecode.length} ops`; | |
| list.innerHTML = state.bytecode | |
| .map((instr, idx) => ` | |
| <div class="bytecode-line" data-active="${idx === state.ip ? 'true' : 'false'}" onclick="jumpToIP(${idx})"> | |
| ${idx === state.ip ? '<div class="ip-marker">▶</div>' : '<span style="display:inline-block;width:24px;"></span>'} | |
| <strong>${idx}:</strong> ${instr.op}${instr.operand !== undefined ? ' ' + instr.operand : ''} | |
| </div> | |
| `) | |
| .join(''); | |
| } | |
| function renderVMState() { | |
| document.getElementById('vm-ip').textContent = state.ip; | |
| document.getElementById('vm-sp').textContent = state.stack.length; | |
| document.getElementById('vm-instrs').textContent = state.bytecode.length; | |
| const instr = state.bytecode[state.ip]; | |
| if (instr) { | |
| document.getElementById('current-opcode').textContent = `${instr.op}${instr.operand !== undefined ? ' ' + instr.operand : ''}`; | |
| document.getElementById('source-highlight').textContent = state.sourceMap[state.ip] || '(source mapping)'; | |
| } | |
| renderBytecode(); | |
| } | |
| function renderStack() { | |
| const view = document.getElementById('stack-view'); | |
| document.getElementById('stack-depth').textContent = `depth: ${state.stack.length}`; | |
| if (state.stack.length === 0) { | |
| view.innerHTML = '<div style="color: var(--text-muted); font-size: 0.75rem;">Empty</div>'; | |
| return; | |
| } | |
| view.innerHTML = state.stack | |
| .map((val, idx) => `<div class="stack-item">[${idx}] ${val}</div>`) | |
| .join(''); | |
| } | |
| function renderTrace() { | |
| const list = document.getElementById('trace-list'); | |
| document.getElementById('trace-count').textContent = `${state.trace.length} steps`; | |
| if (state.trace.length === 0) { | |
| list.innerHTML = '<div class="trace-entry" style="color: var(--text-muted);">No execution yet...</div>'; | |
| return; | |
| } | |
| list.innerHTML = state.trace | |
| .map((entry, idx) => ` | |
| <div class="trace-entry ${idx === state.trace.length - 1 ? 'current' : ''}"> | |
| <strong>Step ${idx + 1}:</strong> IP=${entry.ip}, OP=${entry.op} ${entry.operand !== undefined ? entry.operand : ''}<br> | |
| <span style="font-size: 0.7rem;">Stack: [${entry.stackBefore.join(', ')}]</span> | |
| </div> | |
| `) | |
| .join(''); | |
| } | |
| function renderResult() { | |
| if (state.result !== null) { | |
| document.getElementById('result-output').textContent = state.result; | |
| } | |
| } | |
| function jumpToIP(ip) { | |
| state.ip = ip; | |
| state.stack = state.trace[ip] ? [...state.trace[ip].stackBefore] : []; | |
| renderVMState(); | |
| renderStack(); | |
| } | |
| window.compile = compile; | |
| window.compileAndStep = compileAndStep; | |
| window.runFull = runFull; | |
| window.reset = reset; | |
| window.jumpToIP = jumpToIP; | |
| </script> | |
| </body> | |
| </html> | |