781 lines
33 KiB
HTML
781 lines
33 KiB
HTML
<!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,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
||
|
||
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>
|