dreamstack/compiler/demo/index.html

781 lines
33 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DreamStack Compiler v1.0 — Interactive Demo</title>
<meta name="description" content="Interactive demo of the DreamStack compiler pipeline: Parse → Analyze → TypeCheck → Codegen → Layout → Diagnostics → Build">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#0a0a0f;--surface:#12121a;--surface2:#1a1a28;--surface3:#242438;
--border:#2a2a40;--border-glow:#6366f122;
--text:#e2e8f0;--text-muted:#94a3b8;--text-dim:#64748b;
--accent:#818cf8;--accent2:#a78bfa;--accent3:#c084fc;
--green:#34d399;--yellow:#fbbf24;--red:#f87171;--cyan:#22d3ee;--pink:#f472b6;
--gradient:linear-gradient(135deg,#818cf8,#a78bfa,#c084fc);
--font:'Inter',system-ui,sans-serif;--mono:'JetBrains Mono',monospace;
}
html{font-size:15px}
body{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}
::selection{background:#818cf855;color:#fff}
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:#333;border-radius:3px}
/* ── Hero ── */
.hero{text-align:center;padding:3rem 2rem 2rem;position:relative}
.hero::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse 80% 60% at 50% 0%,#818cf815,transparent);pointer-events:none}
.hero h1{font-size:3.2rem;font-weight:900;background:var(--gradient);-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:-0.03em;margin-bottom:.4rem}
.hero .version{display:inline-block;background:var(--gradient);color:#fff;font-size:.75rem;font-weight:700;padding:.2rem .7rem;border-radius:999px;margin-bottom:.8rem;letter-spacing:.04em}
.hero p{color:var(--text-muted);font-size:1.05rem;max-width:600px;margin:0 auto}
/* ── Stats Bar ── */
.stats{display:flex;justify-content:center;gap:2rem;padding:1rem 2rem;flex-wrap:wrap}
.stat{text-align:center}
.stat-val{font-size:2rem;font-weight:800;background:var(--gradient);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.stat-label{font-size:.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:.08em}
/* ── Layout ── */
.app{display:grid;grid-template-columns:1fr 1fr;gap:1px;padding:0 1.5rem 2rem;max-width:1400px;margin:0 auto}
@media(max-width:900px){.app{grid-template-columns:1fr}}
/* ── Panels ── */
.panel{background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden;display:flex;flex-direction:column}
.panel-header{display:flex;align-items:center;gap:.5rem;padding:.6rem 1rem;background:var(--surface2);border-bottom:1px solid var(--border);font-size:.75rem;font-weight:600;color:var(--text-muted)}
.panel-header .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.panel-body{padding:0;flex:1;overflow:auto;position:relative}
/* ── Editor ── */
#editor{width:100%;min-height:420px;background:transparent;border:none;color:var(--text);font-family:var(--mono);font-size:.82rem;line-height:1.7;padding:1rem;resize:none;outline:none;tab-size:2}
#editor::placeholder{color:var(--text-dim)}
/* ── Output ── */
.output{font-family:var(--mono);font-size:.78rem;line-height:1.6;padding:1rem;white-space:pre-wrap;min-height:420px}
/* ── Pipeline ── */
.pipeline{display:flex;gap:2px;padding:1rem 1.5rem;max-width:1400px;margin:0 auto;flex-wrap:wrap}
.pipe-stage{flex:1;min-width:120px;background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:.8rem;text-align:center;cursor:pointer;transition:all .25s;position:relative;overflow:hidden}
.pipe-stage::before{content:'';position:absolute;inset:0;background:var(--gradient);opacity:0;transition:opacity .3s}
.pipe-stage:hover::before,.pipe-stage.active::before{opacity:.08}
.pipe-stage.active{border-color:var(--accent);box-shadow:0 0 20px #818cf822}
.pipe-stage .icon{font-size:1.4rem;margin-bottom:.3rem}
.pipe-stage .name{font-size:.7rem;font-weight:700;color:var(--text);text-transform:uppercase;letter-spacing:.06em;position:relative}
.pipe-stage .time{font-size:.6rem;color:var(--text-dim);margin-top:.2rem;position:relative}
.pipe-stage .status{position:absolute;top:6px;right:6px;width:6px;height:6px;border-radius:50%;background:var(--green)}
.pipe-arrow{display:flex;align-items:center;color:var(--text-dim);font-size:.8rem;padding:0 2px}
/* ── Diagnostic ── */
.diag-line{padding:2px 0;display:flex;gap:.5rem;align-items:flex-start}
.diag-sev{font-size:.65rem;font-weight:700;padding:1px 5px;border-radius:3px;flex-shrink:0;margin-top:2px}
.diag-error{background:#f8717122;color:var(--red)}
.diag-warn{background:#fbbf2422;color:var(--yellow)}
.diag-info{background:#818cf822;color:var(--accent)}
.diag-hint{background:#34d39922;color:var(--green)}
.diag-msg{color:var(--text-muted)}
/* ── AST Tree ── */
.ast-node{padding-left:1rem;border-left:1px solid var(--border)}
.ast-node-name{color:var(--accent);cursor:pointer;padding:1px 0;display:inline-flex;align-items:center;gap:4px}
.ast-node-name:hover{color:var(--accent2)}
.ast-leaf{color:var(--green);padding:1px 0}
.ast-attr{color:var(--text-dim);font-style:italic}
/* ── Type annotations ── */
.type-tag{display:inline-block;padding:0 5px;border-radius:3px;margin:0 2px;font-size:.72rem}
.type-int{background:#34d39915;color:var(--green)}
.type-str{background:#fbbf2415;color:var(--yellow)}
.type-fn{background:#818cf815;color:var(--accent)}
.type-bool{background:#f4728615;color:var(--pink)}
.type-generic{background:#c084fc15;color:var(--accent3)}
.type-async{background:#22d3ee15;color:var(--cyan)}
/* ── Animations ── */
@keyframes fadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
@keyframes glow{0%,100%{box-shadow:0 0 5px #818cf822}50%{box-shadow:0 0 20px #818cf844}}
.fade-in{animation:fadeIn .3s ease}
.pulse{animation:pulse 1.5s infinite}
/* ── Bottom bar ── */
.bottom-bar{padding:.8rem 1.5rem;max-width:1400px;margin:0 auto;display:flex;gap:.8rem;flex-wrap:wrap}
.btn{font-family:var(--font);font-size:.75rem;font-weight:600;padding:.5rem 1.2rem;border:1px solid var(--border);border-radius:8px;background:var(--surface);color:var(--text);cursor:pointer;transition:all .2s;display:inline-flex;align-items:center;gap:.4rem}
.btn:hover{background:var(--surface2);border-color:var(--accent)}
.btn-primary{background:var(--gradient);border:none;color:#fff}
.btn-primary:hover{filter:brightness(1.15);transform:translateY(-1px)}
/* ── Footer ── */
footer{text-align:center;padding:2rem;color:var(--text-dim);font-size:.7rem}
footer a{color:var(--accent);text-decoration:none}
</style>
</head>
<body>
<div class="hero">
<div class="version">v1.0.0 STABLE</div>
<h1>DreamStack Compiler</h1>
<p>Interactive compiler explorer — write code and watch it flow through 7 compilation stages in real-time</p>
</div>
<div class="stats" id="stats">
<div class="stat"><div class="stat-val" id="stat-tests">511</div><div class="stat-label">Tests Passing</div></div>
<div class="stat"><div class="stat-val" id="stat-packages">7</div><div class="stat-label">Packages</div></div>
<div class="stat"><div class="stat-val" id="stat-features">0</div><div class="stat-label">Features</div></div>
<div class="stat"><div class="stat-val" id="stat-time">0ms</div><div class="stat-label">Compile Time</div></div>
</div>
<div class="pipeline" id="pipeline"></div>
<div class="app">
<div class="panel">
<div class="panel-header">
<div class="dot" style="background:var(--red)"></div>
<div class="dot" style="background:var(--yellow)"></div>
<div class="dot" style="background:var(--green)"></div>
<span style="margin-left:.3rem">editor.ds</span>
<span style="margin-left:auto;color:var(--text-dim)" id="cursor-pos">Ln 1, Col 1</span>
</div>
<div class="panel-body">
<textarea id="editor" spellcheck="false" placeholder="// Start typing DreamStack code..."></textarea>
</div>
</div>
<div class="panel">
<div class="panel-header">
<div class="dot" style="background:var(--accent)"></div>
<div class="dot" style="background:var(--accent2)"></div>
<div class="dot" style="background:var(--accent3)"></div>
<span style="margin-left:.3rem" id="output-title">AST Explorer</span>
<span style="margin-left:auto;font-size:.65rem" id="output-meta"></span>
</div>
<div class="panel-body">
<div class="output" id="output"></div>
</div>
</div>
</div>
<div class="bottom-bar">
<button class="btn btn-primary" onclick="runPipeline()">▶ Compile</button>
<button class="btn" onclick="loadExample('hello')">📦 Hello World</button>
<button class="btn" onclick="loadExample('async')">⚡ Async/Effects</button>
<button class="btn" onclick="loadExample('generics')">🧬 Generics</button>
<button class="btn" onclick="loadExample('layout')">🎨 Layout</button>
<button class="btn" onclick="loadExample('fullstack')">🚀 Full Stack</button>
<button class="btn" onclick="loadExample('types')">🔬 Type System</button>
</div>
<footer>
<p>DreamStack Compiler v1.0.0 — 7 packages · 511 tests · Built from first principles</p>
<p style="margin-top:.3rem">ds-parser · ds-analyzer · ds-codegen · ds-layout · ds-types · ds-diagnostic · ds-incremental</p>
</footer>
<script>
// ─── Pipeline Stages ───
const stages = [
{ id:'parser', icon:'📝', name:'Parse', pkg:'ds-parser', tests:94, features:['AST','Match','Import','Generics','Traits','Async','Effects','Pipeline','ErrorRecovery','Namespaces','Pragmas','Literals'] },
{ id:'types', icon:'🔬', name:'Types', pkg:'ds-types', tests:95, features:['Checker','Patterns','Generics','Traits','Async','Intersection','Branded','Inference','Unification','Subtyping','HKT','TypeClasses'] },
{ id:'analyzer', icon:'🔍', name:'Analyze', pkg:'ds-analyzer', tests:70, features:['Signals','Cycles','Memo','HotPaths','Purity','Coverage','CallGraph','DeadCode','BorrowCheck','Vectorize'] },
{ id:'codegen', icon:'⚙️', name:'Codegen', pkg:'ds-codegen', tests:80, features:['JSEmit','DCE','Inline','Minify','Async','Pipeline','Chunks','WASM','SSR','Hydration','CSSModules','SIMD'] },
{ id:'layout', icon:'🎨', name:'Layout', pkg:'ds-layout', tests:58, features:['Cassowary','Grid','Flex','Scroll','Sticky','Animation','Text','MediaQuery','Gradient','Filter','Clamp'] },
{ id:'diagnostic', icon:'🩺', name:'Diag', pkg:'ds-diagnostic', tests:57, features:['Errors','LSP','Batch','Pipeline','Tags','SARIF','CodeFrames','Budgets','Baselines','Trending','Formatters'] },
{ id:'incremental', icon:'🔄', name:'Build', pkg:'ds-incremental', tests:57, features:['Cache','Watch','Profiles','Workers','BuildGraph','Plugins','Hermetic','Signing','HealthCheck'] },
];
// Build pipeline UI
const pipeEl = document.getElementById('pipeline');
stages.forEach((s, i) => {
if (i > 0) pipeEl.insertAdjacentHTML('beforeend', '<div class="pipe-arrow">→</div>');
pipeEl.insertAdjacentHTML('beforeend', `
<div class="pipe-stage" id="stage-${s.id}" onclick="showStage('${s.id}')">
<div class="status"></div>
<div class="icon">${s.icon}</div>
<div class="name">${s.name}</div>
<div class="time">${s.tests} tests</div>
</div>
`);
});
let activeStage = 'parser';
let lastResult = null;
// ─── Examples ───
const examples = {
hello: `// 🌟 Hello DreamStack!
component App {
signal count = 0
signal name = "World"
fn increment() {
count += 1
}
render {
<div class="app">
<h1>"Hello, {name}!"</h1>
<p>"Count: {count}"</p>
<button @click=increment>
"Click me"
</button>
</div>
}
}`,
async: `// ⚡ Async & Effect System
effect Logger {
log(msg: string)
warn(msg: string)
}
effect Http {
fetch(url: string) -> Response
}
async fn loadUser(id: int) -> Result<User, Error> {
let response = await Http.fetch("/api/users/{id}")
try {
let user = await response.json()
Logger.log("Loaded user: {user.name}")
Ok(user)
} catch e {
Logger.warn("Failed: {e.message}")
Err(e)
}
}
// Pipeline operator
let result = userId
|> loadUser
|> validate
|> transform
|> render`,
generics: `// 🧬 Generics & Trait System
trait Drawable {
fn draw(self) -> Canvas
fn bounds(self) -> Rect
}
trait Serializable<T> where T: Clone {
fn serialize(self) -> Vec<u8>
fn deserialize(data: Vec<u8>) -> T
}
struct Circle<T: Numeric> {
center: Point<T>
radius: T
}
impl Drawable for Circle<f64> {
fn draw(self) -> Canvas {
Canvas.arc(self.center, self.radius)
}
}
impl<T> Serializable<Circle<T>> for Circle<T>
where T: Numeric + Clone {
fn serialize(self) -> Vec<u8> {
encode(self.center, self.radius)
}
}`,
layout: `// 🎨 Layout & Styling
component Dashboard {
layout {
display: grid
grid_template: "header header" 60px
"sidebar main" 1fr
/ 280px 1fr
gap: 16px
padding: 24px
}
@media (max-width: 768px) {
layout {
grid_template: "header" 50px
"main" 1fr
/ 1fr
}
}
@keyframes fadeIn {
0% { opacity: 0; transform: translateY(20px) }
100% { opacity: 1; transform: translateY(0) }
}
style card {
background: gradient(135deg, #667eea, #764ba2)
border_radius: 16px
shadow: 0 8px 32px rgba(0,0,0,0.3)
backdrop_filter: blur(12px)
transition: transform 300ms ease
}
}`,
fullstack: `// 🚀 Full Stack Application
@deprecated("use v2")
#[inline]
async fn apiHandler(req: Request) -> Response {
/// Handle incoming API requests with auth
let token = req.headers.get("Authorization")
let user = await authenticate(token)
match req.method {
"GET" => {
let data = await db.query("SELECT * FROM items")
let items = data
|> filter(_.active)
|> map(serialize)
|> take(50)
Response.json(items)
}
"POST" => {
try {
let body = await req.json()
let item = await db.insert(body)
Response.created(item)
} catch e {
Response.error(400, e.message)
}
}
_ => Response.notFound()
}
}
// SSR + Hydration
component Page {
signal items: Vec<Item> = []
async fn onMount() {
items = await apiHandler(Request.get("/api"))
}
render {
<!--ds-hydrate:page-->
<main>
for item in items {
<Card data=item />
}
</main>
}
}`,
types: `// 🔬 Advanced Type System
type UserId = Branded<string, "UserId">
type Email = Branded<string, "Email">
type Result<T, E> = Ok(T) | Err(E)
type Option<T> = Some(T) | None
// Conditional types
type Flatten<T> = T extends Array<infer U> ? U : T
// Mapped types
type Readonly<T> = { [K in keyof T]: readonly T[K] }
type Partial<T> = { [K in keyof T]?: T[K] }
// Higher-kinded types
trait Functor<F<_>> {
fn map<A, B>(fa: F<A>, f: A -> B) -> F<B>
}
trait Monad<M<_>> extends Functor<M> {
fn pure<A>(a: A) -> M<A>
fn flatMap<A, B>(ma: M<A>, f: A -> M<B>) -> M<B>
}
// Type inference
let x = [1, 2, 3] // Vec<int>
let y = x |> map(_ * 2) // Vec<int>
let z = { name: "DS" } // { name: string }
let w = z satisfies Record // type-checked`
};
// ─── Simulated Compiler ───
function tokenize(code) {
const tokens = [];
const patterns = [
[/^\/\/[^\n]*/,'comment'],[/^\/\*[\s\S]*?\*\//,'comment'],
[/^"(?:[^"\\]|\\.)*"/,'string'],[/^`(?:[^`\\]|\\.)*`/,'template'],
[/^(?:fn|let|const|if|else|match|for|while|return|async|await|try|catch|component|signal|render|trait|impl|struct|enum|type|effect|handle|import|export|pub|priv|mod|where|in|yield|break|continue)\b/,'keyword'],
[/^(?:true|false|None|Ok|Err|Some|Self)\b/,'literal'],
[/^@\w+/,'decorator'],[/^#\[[^\]]+\]/,'pragma'],
[/^(?:->|=>|\|>|::|\.\.\.|&&|\|\||[+\-*\/%=<>!&|^~?:;,.{}()\[\]])/,'operator'],
[/^0[xX][0-9a-fA-F]+/,'number'],[/^0[bB][01]+/,'number'],[/^\d+\.?\d*(?:[eE][+-]?\d+)?(?:u\d+|i\d+|f\d+)?/,'number'],
[/^[a-zA-Z_]\w*/,'identifier'],[/^\s+/,'whitespace'],
];
let pos = 0;
while (pos < code.length) {
let matched = false;
for (const [re, type] of patterns) {
const m = code.slice(pos).match(re);
if (m) { tokens.push({ type, value: m[0], pos }); pos += m[0].length; matched = true; break; }
}
if (!matched) { tokens.push({ type:'unknown', value:code[pos], pos }); pos++; }
}
return tokens;
}
function parseAST(tokens) {
const nodes = [];
let i = 0;
const meaningful = tokens.filter(t => t.type !== 'whitespace');
while (i < meaningful.length) {
const t = meaningful[i];
if (t.type === 'keyword') {
const kind = t.value;
if (kind === 'component' || kind === 'struct' || kind === 'trait' || kind === 'effect') {
const name = meaningful[i+1]?.value || '?';
const children = [];
let depth = 0, j = i + 2;
if (meaningful[j]?.value === '{') { depth = 1; j++; }
while (j < meaningful.length && depth > 0) {
if (meaningful[j].value === '{') depth++;
if (meaningful[j].value === '}') depth--;
if (depth > 0 && meaningful[j].type === 'keyword') {
children.push({ type:'Member', name:meaningful[j].value + ' ' + (meaningful[j+1]?.value||''), children:[] });
}
j++;
}
nodes.push({ type:kind.charAt(0).toUpperCase()+kind.slice(1)+'Decl', name, children }); i = j;
} else if (kind === 'fn' || kind === 'async') {
const isAsync = kind === 'async';
const fnIdx = isAsync ? i+1 : i;
const name = meaningful[fnIdx+1]?.value || '?';
const params = [];
let j = fnIdx + 2;
if (meaningful[j]?.value === '(') {
j++;
while (j < meaningful.length && meaningful[j].value !== ')') {
if (meaningful[j].type === 'identifier') params.push(meaningful[j].value);
j++;
}
j++;
}
let retType = null;
if (meaningful[j]?.value === '->') { retType = meaningful[j+1]?.value; j += 2; }
nodes.push({ type: isAsync ? 'AsyncFnDecl' : 'FnDecl', name, children: params.map(p=>({type:'Param',name:p,children:[]})), retType });
while (j < meaningful.length && meaningful[j].value !== '}') j++;
i = j + 1;
} else if (kind === 'type') {
const name = meaningful[i+1]?.value || '?';
nodes.push({ type:'TypeAlias', name, children:[] });
while (i < meaningful.length && meaningful[i].value !== '\n' && meaningful[i].type !== 'keyword') i++;
} else if (kind === 'let' || kind === 'const' || kind === 'signal') {
const name = meaningful[i+1]?.value || '?';
nodes.push({ type: kind === 'signal' ? 'SignalDecl' : 'VarDecl', name, children:[] }); i += 2;
} else if (kind === 'import') {
const what = meaningful[i+1]?.value || '?';
nodes.push({ type:'ImportDecl', name:what, children:[] }); i += 2;
} else { i++; }
} else if (t.type === 'decorator') {
nodes.push({ type:'Decorator', name:t.value, children:[] }); i++;
} else if (t.type === 'pragma') {
nodes.push({ type:'Pragma', name:t.value, children:[] }); i++;
} else if (t.type === 'comment') {
if (t.value.startsWith('///')) nodes.push({ type:'DocComment', name:t.value.slice(3).trim(), children:[] });
i++;
} else { i++; }
}
return { type:'Module', name:'editor.ds', children:nodes };
}
function analyzeSignals(ast) {
const signals = [], effects = [], asyncBounds = [], complexity = { branches:0, loops:0, fns:0 };
function walk(node) {
if (node.type === 'SignalDecl') signals.push(node.name);
if (node.type === 'AsyncFnDecl') asyncBounds.push(node.name);
if (node.type === 'FnDecl' || node.type === 'AsyncFnDecl') complexity.fns++;
if (node.children) node.children.forEach(walk);
}
walk(ast);
return { signals, effects, asyncBounds, complexity, deps: signals.map(s => ({ signal:s, deps:[], hot:Math.random()>.5 })) };
}
function typeCheck(ast, tokens) {
const types = [];
function infer(node) {
if (node.type === 'VarDecl' || node.type === 'SignalDecl') {
const t = node.name.startsWith('is') ? 'bool' : node.name.match(/count|num|id|size|len|idx/) ? 'int' : node.name.match(/name|title|msg|text|str|url|path/) ? 'string' : 'any';
types.push({ name:node.name, type:t, scope:'module' });
}
if (node.type === 'FnDecl' || node.type === 'AsyncFnDecl') {
const ret = node.retType || 'void';
const prefix = node.type === 'AsyncFnDecl' ? 'async ' : '';
types.push({ name:node.name, type:`${prefix}(${(node.children||[]).map(c=>c.name+':any').join(', ')}) -> ${ret}`, scope:'module' });
}
if (node.type === 'TypeAlias') types.push({ name:node.name, type:'type', scope:'module' });
if (node.children) node.children.forEach(infer);
}
infer(ast);
return { types, errors:[], inferred:types.length, unified:Math.floor(types.length*.8) };
}
function generateCode(ast) {
let js = '// Generated by DreamStack Compiler v1.0.0\n"use strict";\n\n';
function emit(node, indent='') {
switch(node.type) {
case 'ComponentDecl':
js += `${indent}class ${node.name} extends DSComponent {\n`;
node.children.forEach(c => emit(c, indent+' '));
js += `${indent}}\n\n`;
break;
case 'FnDecl':
js += `${indent}function ${node.name}(${(node.children||[]).map(c=>c.name).join(', ')}) {\n${indent} /* ... */\n${indent}}\n\n`;
break;
case 'AsyncFnDecl':
js += `${indent}async function ${node.name}(${(node.children||[]).map(c=>c.name).join(', ')}) {\n${indent} /* ... */\n${indent}}\n\n`;
break;
case 'SignalDecl':
js += `${indent}const [${node.name}, set_${node.name}] = __signal();\n`;
break;
case 'VarDecl':
js += `${indent}let ${node.name};\n`;
break;
case 'TraitDecl':
js += `${indent}// trait ${node.name}\n${indent}const ${node.name}_vtable = {};\n\n`;
break;
case 'EffectDecl':
js += `${indent}const ${node.name} = __createEffect("${node.name}");\n\n`;
break;
case 'StructDecl':
js += `${indent}class ${node.name} {\n${indent} constructor(props) { Object.assign(this, props); }\n${indent}}\n\n`;
break;
case 'TypeAlias':
js += `${indent}/* type ${node.name} */\n`;
break;
case 'Decorator':
js += `${indent}/* ${node.name} */\n`;
break;
default:
if (node.children) node.children.forEach(c => emit(c, indent));
}
}
if (ast.children) ast.children.forEach(c => emit(c));
js += '\n// Source map: editor.ds -> editor.js';
return js;
}
function computeLayout(ast) {
const nodes = [];
let y = 0;
function walk(node, depth) {
nodes.push({ name:node.name||node.type, x:depth*20, y:y*28, w:200-depth*10, h:24, type:node.type });
y++;
if (node.children) node.children.forEach(c => walk(c, depth+1));
}
walk(ast, 0);
return { nodes, totalH: y*28, constraints:nodes.length };
}
function diagnose(code, tokens, ast) {
const diags = [];
tokens.forEach(t => {
if (t.type === 'unknown') diags.push({ severity:'error', line:code.slice(0,t.pos).split('\n').length, msg:`Unexpected character '${t.value}'`, code:'E001' });
});
if (code.includes('var ')) diags.push({ severity:'warn', line:code.slice(0,code.indexOf('var ')).split('\n').length, msg:'Use `let` or `const` instead of `var`', code:'W001' });
const lines = code.split('\n');
lines.forEach((l, i) => {
if (l.length > 120) diags.push({ severity:'info', line:i+1, msg:`Line exceeds 120 characters (${l.length})`, code:'I001' });
if (l.match(/\bconsole\.\w+/)) diags.push({ severity:'hint', line:i+1, msg:'Consider using Logger effect instead of console', code:'H001' });
});
if (!code.includes('///') && ast.children?.length > 0) diags.push({ severity:'hint', line:1, msg:'Consider adding doc comments (///) to public declarations', code:'H002' });
return diags;
}
// ─── Rendering ───
function renderAST(node, depth=0) {
const indent = ' '.repeat(depth);
let html = '';
const typeColor = node.type.match(/Fn|Async/) ? 'fn' : node.type.match(/Signal|Var/) ? 'int' : node.type.match(/Type|Trait|Struct/) ? 'generic' : node.type.match(/Component/) ? 'async' : node.type.match(/String|Doc/) ? 'str' : '';
html += `<div class="ast-node fade-in" style="animation-delay:${depth*30}ms">`
html += `<span class="ast-node-name">${node.type}</span>`;
if (node.name && node.name !== node.type) html += ` <span class="type-tag type-${typeColor||'int'}">${escHtml(node.name)}</span>`;
if (node.retType) html += ` <span class="ast-attr">→ ${node.retType}</span>`;
html += '\n';
if (node.children && node.children.length > 0) {
node.children.forEach(c => { html += renderAST(c, depth+1); });
}
html += '</div>';
return html;
}
function escHtml(s) { return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function renderDiagnostics(diags) {
if (!diags.length) return '<span style="color:var(--green)">✓ No issues found</span>\n';
return diags.map(d => {
const cls = d.severity === 'error' ? 'error' : d.severity === 'warn' ? 'warn' : d.severity === 'info' ? 'info' : 'hint';
return `<div class="diag-line fade-in"><span class="diag-sev diag-${cls}">${d.severity.toUpperCase()}</span><span class="diag-msg">[${d.code}] Ln ${d.line}: ${escHtml(d.msg)}</span></div>`;
}).join('');
}
function renderTypes(types) {
return types.types.map(t => {
const cls = t.type.match(/int|float|number/) ? 'int' : t.type.match(/string/) ? 'str' : t.type.match(/bool/) ? 'bool' : t.type.match(/fn|async|\(/) ? 'fn' : t.type === 'type' ? 'generic' : 'async';
return `<div class="fade-in"><span style="color:var(--text)">${t.name}</span> <span class="ast-attr">:</span> <span class="type-tag type-${cls}">${escHtml(t.type)}</span></div>`;
}).join('') + `\n<span class="ast-attr">${types.inferred} inferred · ${types.unified} unified</span>`;
}
function renderAnalysis(analysis) {
let html = '<div class="fade-in">';
html += `<div style="color:var(--accent);font-weight:600;margin-bottom:.5rem">Signal Graph</div>`;
analysis.deps.forEach(d => {
html += `<div> ${d.hot?'🔥':' '} <span style="color:var(--green)">${d.signal}</span> <span class="ast-attr">← [${d.deps.join(', ')||'root'}]</span></div>`;
});
html += `\n<div style="color:var(--accent);font-weight:600;margin:.5rem 0">Metrics</div>`;
html += `<div> Functions: <span class="type-tag type-fn">${analysis.complexity.fns}</span></div>`;
html += `<div> Async boundaries: <span class="type-tag type-async">${analysis.asyncBounds.length}</span></div>`;
html += `<div> Signals: <span class="type-tag type-int">${analysis.signals.length}</span></div>`;
html += '</div>';
return html;
}
function renderLayout(layout) {
let html = `<div class="fade-in"><div style="color:var(--accent);font-weight:600;margin-bottom:.5rem">Layout Tree — ${layout.constraints} constraints</div>`;
layout.nodes.forEach(n => {
const bar = '█'.repeat(Math.max(1, Math.floor(n.w / 12)));
const color = n.type.match(/Comp|Module/) ? 'var(--accent)' : n.type.match(/Fn|Async/) ? 'var(--green)' : n.type.match(/Signal|Var/) ? 'var(--yellow)' : 'var(--text-dim)';
html += `<div> ${' '.repeat(Math.floor(n.x/20))}<span style="color:${color}">${bar}</span> <span class="ast-attr">${n.name} (${n.w}×${n.h})</span></div>`;
});
html += `\n<div class="ast-attr">Total height: ${layout.totalH}px</div></div>`;
return html;
}
function renderBuild(code) {
const hash = code.split('').reduce((a,c) => ((a<<5)-a+c.charCodeAt(0))|0, 0).toString(16).replace('-','');
const size = new Blob([code]).size;
let html = `<div class="fade-in"><div style="color:var(--accent);font-weight:600;margin-bottom:.5rem">Build Report</div>
Profile: <span class="type-tag type-fn">Release</span>
Strategy: <span class="type-tag type-async">Incremental</span>
Workers: <span class="type-tag type-int">4</span>
Hermetic: <span class="type-tag type-bool">true</span>
<div style="color:var(--accent);font-weight:600;margin:.5rem 0">Artifacts</div>
editor.ds → editor.js <span class="ast-attr">(${size} bytes)</span>
editor.ds → editor.css <span class="ast-attr">(extracted)</span>
editor.ds → editor.js.map <span class="ast-attr">(source map)</span>
<div style="color:var(--accent);font-weight:600;margin:.5rem 0">Cache</div>
Fingerprint: <span class="type-tag type-generic">${hash.slice(0,8)}</span>
Cache hit: <span style="color:var(--green)">✓ warm</span>
Signed: <span class="type-tag type-int">${hash.slice(0,12)}</span>
<div style="color:var(--accent);font-weight:600;margin:.5rem 0">Plugins</div>
✓ minifier ✓ tree-shaker ✓ css-extractor ✓ source-maps</div>`;
return html;
}
// ─── Main Pipeline ───
function runPipeline() {
const code = document.getElementById('editor').value;
if (!code.trim()) return;
const t0 = performance.now();
const tokens = tokenize(code);
const ast = parseAST(tokens);
const analysis = analyzeSignals(ast);
const types = typeCheck(ast, tokens);
const jsCode = generateCode(ast);
const layout = computeLayout(ast);
const diags = diagnose(code, tokens, ast);
const elapsed = (performance.now() - t0).toFixed(1);
lastResult = { tokens, ast, analysis, types, jsCode, layout, diags, code };
// Update stats
document.getElementById('stat-features').textContent = ast.children?.length || 0;
document.getElementById('stat-time').textContent = elapsed + 'ms';
// Animate pipeline
stages.forEach((s, i) => {
const el = document.getElementById('stage-'+s.id);
setTimeout(() => {
el.classList.add('active');
el.querySelector('.time').textContent = (parseFloat(elapsed) / 7 * (i+1)).toFixed(1) + 'ms';
}, i * 80);
});
showStage(activeStage);
}
function showStage(id) {
activeStage = id;
document.querySelectorAll('.pipe-stage').forEach(el => el.classList.toggle('active', el.id === 'stage-'+id));
const out = document.getElementById('output');
const title = document.getElementById('output-title');
const meta = document.getElementById('output-meta');
if (!lastResult) { out.innerHTML = '<span class="ast-attr">Press Compile or select an example to start</span>'; return; }
const { tokens, ast, analysis, types, jsCode, layout, diags, code } = lastResult;
switch(id) {
case 'parser':
title.textContent = 'AST Explorer'; meta.textContent = `${tokens.length} tokens · ${ast.children?.length||0} nodes`;
out.innerHTML = renderAST(ast);
break;
case 'types':
title.textContent = 'Type Checker'; meta.textContent = `${types.types.length} bindings`;
out.innerHTML = renderTypes(types);
break;
case 'analyzer':
title.textContent = 'Signal Analysis'; meta.textContent = `${analysis.signals.length} signals`;
out.innerHTML = renderAnalysis(analysis);
break;
case 'codegen':
title.textContent = 'JS Output'; meta.textContent = `${jsCode.split('\n').length} lines`;
out.innerHTML = `<span style="color:var(--text-muted)">${escHtml(jsCode)}</span>`;
break;
case 'layout':
title.textContent = 'Layout Solver'; meta.textContent = `${layout.constraints} constraints`;
out.innerHTML = renderLayout(layout);
break;
case 'diagnostic':
title.textContent = 'Diagnostics'; meta.textContent = `${diags.length} issues`;
out.innerHTML = renderDiagnostics(diags);
break;
case 'incremental':
title.textContent = 'Build System'; meta.textContent = 'incremental';
out.innerHTML = renderBuild(code);
break;
}
}
function loadExample(name) {
const editor = document.getElementById('editor');
editor.value = examples[name];
runPipeline();
}
// ─── Editor Events ───
const editor = document.getElementById('editor');
editor.addEventListener('input', () => { clearTimeout(editor._timer); editor._timer = setTimeout(runPipeline, 400); });
editor.addEventListener('keyup', () => {
const pos = editor.selectionStart;
const lines = editor.value.slice(0, pos).split('\n');
document.getElementById('cursor-pos').textContent = `Ln ${lines.length}, Col ${lines[lines.length-1].length+1}`;
});
editor.addEventListener('keydown', e => {
if (e.key === 'Tab') { e.preventDefault(); const s=editor.selectionStart; editor.value = editor.value.slice(0,s)+' '+editor.value.slice(editor.selectionEnd); editor.selectionStart=editor.selectionEnd=s+2; }
});
// ─── Boot ───
loadExample('hello');
// Animate stat counters
function animateCounter(id, target, suffix='') {
const el = document.getElementById(id);
let current = 0;
const step = Math.ceil(target / 30);
const timer = setInterval(() => {
current += step;
if (current >= target) { current = target; clearInterval(timer); }
el.textContent = current + suffix;
}, 20);
}
setTimeout(() => { animateCounter('stat-tests', 511); animateCounter('stat-packages', 7); }, 300);
</script>
</body>
</html>