Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion bin/start
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ else
fi

# Run the commands with concurrently
concurrently --names=format,pointers,bugc,web,tests \
concurrently --names=format,pointers,bugc,playground,web,tests \
"cd ./packages/format && yarn watch" \
"cd ./packages/pointers && yarn watch" \
"cd ./packages/bugc && yarn watch" \
"cd ./packages/playground && yarn watch" \
"cd ./packages/web && yarn start $DOCUSAURUS_NO_OPEN" \
"sleep 5 && yarn test --ui --watch --coverage $VITEST_NO_OPEN"

22 changes: 22 additions & 0 deletions packages/playground/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: "@typescript-eslint/parser",
plugins: ["react-refresh"],
rules: {
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"no-console": "off",
},
};
1 change: 1 addition & 0 deletions packages/playground/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
app.bundle.js
56 changes: 56 additions & 0 deletions packages/playground/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# @ethdebug/bug-playground

A web-based playground for the BUG language, built with React, TypeScript, and Vite.

## Features

- **Monaco Editor**: Full-featured code editor with BUG syntax highlighting
- **Live Compilation**: See AST, IR (optimized and unoptimized), and bytecode output
- **Optimization Levels**: Choose from 4 optimization levels (0-3)
- **Example Programs**: Automatically loaded from the `examples/` directory
- **Error Highlighting**: Clear error messages and warnings from the compiler

## Development

Install dependencies:

```bash
yarn install
```

Start the development server:

```bash
yarn dev
```

Build for production:

```bash
yarn build
```

## Scripts

- `yarn start` - Start development server
- `yarn build` - Build for production
- `yarn preview` - Preview production build
- `yarn typecheck` - Run TypeScript type checking
- `yarn lint` - Run ESLint

## Architecture

The playground is organized by domain/logical concerns:

- `playground/` - Main playground UI and example programs
- `editor/` - Monaco editor integration and BUG language definition
- `compiler/` - Compiler integration and output visualization
- `visualization/` - AST, IR, and bytecode visualizations

## Technologies

- React 18
- TypeScript 5
- Vite 5
- Monaco Editor
- @ethdebug/bugc (BUG Compiler)
13 changes: 13 additions & 0 deletions packages/playground/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BUG Playground</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
39 changes: 39 additions & 0 deletions packages/playground/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@ethdebug/bug-playground",
"version": "0.1.0-0",
"private": true,
"type": "module",
"scripts": {
"start": "vite",
"build": "tsc && vite build",
"watch": "vite build --watch",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"lint": "eslint . --ext .ts,.tsx",
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
"format:check": "prettier --check \"src/**/*.{ts,tsx}\""
},
"dependencies": {
"@ethdebug/bugc": "^0.1.0-0",
"@monaco-editor/react": "^4.6.0",
"@types/dagre": "^0.7.52",
"dagre": "^0.8.5",
"monaco-editor": "^0.52.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-flow-renderer": "^10.3.17",
"vis-network": "^9.1.9"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.55.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"typescript": "^5.2.2",
"vite": "^5.0.8"
}
}
9 changes: 9 additions & 0 deletions packages/playground/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Playground } from "./playground/Playground";

export function App() {
return (
<div className="app">
<Playground />
</div>
);
}
70 changes: 70 additions & 0 deletions packages/playground/src/compiler/CompilerOutput.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
.compiler-output {
display: flex;
flex-direction: column;
height: 100%;
background-color: #1e1e1e;
}

.output-tabs {
display: flex;
background-color: #2d2d30;
border-bottom: 1px solid #3e3e42;
overflow-x: auto;
}

.output-tab {
padding: 0.5rem 1rem;
background: none;
border: none;
border-bottom: 2px solid transparent;
color: #969696;
font-size: 0.875rem;
cursor: pointer;
white-space: nowrap;
transition:
color 0.2s,
border-color 0.2s;
}

.output-tab:hover:not(:disabled) {
color: #cccccc;
}

.output-tab.active {
color: #ffffff;
border-bottom-color: #0e639c;
}

.output-tab:disabled {
opacity: 0.5;
cursor: not-allowed;
}

.output-content {
flex: 1;
overflow: auto;
padding: 1rem;
}

.output-warnings {
background-color: #332b00;
border-top: 1px solid #665500;
padding: 1rem;
color: #ffcc00;
}

.output-warnings h3 {
margin: 0 0 0.5rem 0;
font-size: 0.875rem;
font-weight: 600;
}

.output-warnings ul {
margin: 0;
padding-left: 1.5rem;
}

.output-warnings li {
font-size: 0.813rem;
line-height: 1.5;
}
75 changes: 75 additions & 0 deletions packages/playground/src/compiler/CompilerOutput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { useState } from "react";
import type { CompileResult } from "./types";
import { AstView } from "../visualization/AstView";
import { IrView } from "../visualization/IrView";
import { CfgView } from "../visualization/CfgView";
import { BytecodeView } from "../visualization/BytecodeView";
import { ErrorView } from "./ErrorView";
import type { SourceRange } from "../visualization/debugUtils";
import "./CompilerOutput.css";

interface CompilerOutputProps {
result: CompileResult;
onOpcodeHover?: (ranges: SourceRange[]) => void;
}

type TabType = "ast" | "ir" | "cfg" | "bytecode" | "error";

export function CompilerOutput({ result, onOpcodeHover }: CompilerOutputProps) {
const [activeTab, setActiveTab] = useState<TabType>(
result.success ? "ast" : "error",
);

if (!result.success) {
return <ErrorView error={result.error} warnings={result.warnings} />;
}

const tabs: { id: TabType; label: string; disabled?: boolean }[] = [
{ id: "ast", label: "AST" },
{ id: "ir", label: "IR" },
{ id: "cfg", label: "CFG" },
{ id: "bytecode", label: "Bytecode" },
];

return (
<div className="compiler-output">
<div className="output-tabs">
{tabs.map((tab) => (
<button
key={tab.id}
className={`output-tab ${activeTab === tab.id ? "active" : ""}`}
onClick={() => setActiveTab(tab.id)}
disabled={tab.disabled}
>
{tab.label}
</button>
))}
</div>

<div className="output-content">
{activeTab === "ast" && <AstView ast={result.ast} />}
{activeTab === "ir" && (
<IrView ir={result.ir} onOpcodeHover={onOpcodeHover} />
)}
{activeTab === "cfg" && <CfgView ir={result.ir} />}
{activeTab === "bytecode" && (
<BytecodeView
bytecode={result.bytecode}
onOpcodeHover={onOpcodeHover}
/>
)}
</div>

{result.warnings.length > 0 && (
<div className="output-warnings">
<h3>Warnings:</h3>
<ul>
{result.warnings.map((warning, i) => (
<li key={i}>{warning}</li>
))}
</ul>
</div>
)}
</div>
);
}
51 changes: 51 additions & 0 deletions packages/playground/src/compiler/ErrorView.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.error-view {
padding: 1rem;
height: 100%;
overflow: auto;
}

.error-content {
background-color: #5a1d1d;
border: 1px solid #f48771;
border-radius: 4px;
padding: 1rem;
margin-bottom: 1rem;
}

.error-content h3 {
color: #f48771;
margin: 0 0 0.5rem 0;
font-size: 1rem;
}

.error-message {
color: #ffcccc;
margin: 0;
font-family: "Consolas", "Monaco", "Courier New", monospace;
font-size: 0.875rem;
line-height: 1.5;
white-space: pre-wrap;
}

.error-warnings {
background-color: #332b00;
border: 1px solid #665500;
border-radius: 4px;
padding: 1rem;
color: #ffcc00;
}

.error-warnings h4 {
margin: 0 0 0.5rem 0;
font-size: 0.875rem;
}

.error-warnings ul {
margin: 0;
padding-left: 1.5rem;
}

.error-warnings li {
font-size: 0.813rem;
line-height: 1.5;
}
28 changes: 28 additions & 0 deletions packages/playground/src/compiler/ErrorView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import "./ErrorView.css";

interface ErrorViewProps {
error: string;
warnings?: string[];
}

export function ErrorView({ error, warnings }: ErrorViewProps) {
return (
<div className="error-view">
<div className="error-content">
<h3>Compilation Error</h3>
<pre className="error-message">{error}</pre>
</div>

{warnings && warnings.length > 0 && (
<div className="error-warnings">
<h4>Warnings:</h4>
<ul>
{warnings.map((warning, i) => (
<li key={i}>{warning}</li>
))}
</ul>
</div>
)}
</div>
);
}
Loading
Loading