Skip to content

Commit 0731aa8

Browse files
committed
Add @ethdebug/bugc package to monorepo
Migrate the BUG language compiler from the separate bugc repository. This consolidates reference implementation work for the ethdebug format. Changes: - Copy packages/bugc with examples directory - Add @ethdebug/format and @ethdebug/pointers as dependencies - Configure tsconfig.json with project references - Add bugc to root build script and watch mode - Fix Iterator.every() compatibility for ES2020 target - Fix examples path in test discovery Future PRs will migrate @ethdebug/rpc and @ethdebug/bug-playground.
1 parent 04a3e6b commit 0731aa8

248 files changed

Lines changed: 43685 additions & 27 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules/
2+
dist/
23
*.tsbuildinfo
34
coverage/

bin/start

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ else
1010
fi
1111

1212
# Run the commands with concurrently
13-
concurrently --names=format,pointers,web,tests \
13+
concurrently --names=format,pointers,bugc,web,tests \
1414
"cd ./packages/format && yarn watch" \
1515
"cd ./packages/pointers && yarn watch" \
16+
"cd ./packages/bugc && yarn watch" \
1617
"cd ./packages/web && yarn start $DOCUSAURUS_NO_OPEN" \
1718
"sleep 5 && yarn test --ui --watch --coverage $VITEST_NO_OPEN"
1819

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"packages/*"
66
],
77
"scripts": {
8-
"build": "tsc --build packages/format packages/pointers",
8+
"build": "tsc --build packages/format packages/pointers packages/bugc",
99
"bundle": "tsx ./bin/bundle-schema.ts",
1010
"test": "vitest",
1111
"test:coverage": "vitest run --coverage",

packages/bugc/.ignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist

packages/bugc/bin/bugc.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/usr/bin/env tsx
2+
/* eslint-disable no-console */
3+
4+
/**
5+
* BUG compiler CLI
6+
*/
7+
8+
import { handleCompileCommand } from "#cli";
9+
10+
// Parse command line arguments
11+
const args = process.argv.slice(2);
12+
13+
// Show help if no arguments or help flag
14+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
15+
showHelp();
16+
process.exit(0);
17+
}
18+
19+
// Run the compiler
20+
handleCompileCommand(process.argv);
21+
22+
function showHelp(): void {
23+
console.log(`bugc - The BUG language compiler
24+
25+
Usage: bugc [options] <file.bug>
26+
27+
Options:
28+
-s, --stop-after <phase> Stop compilation after phase (ast, ir, bytecode, debug)
29+
Default: bytecode
30+
-O, --optimize <level> Set optimization level (0-3)
31+
Default: 0
32+
-f, --format <format> Output format (text, json)
33+
Default: text
34+
-o, --output <file> Write output to file instead of stdout
35+
-p, --pretty Pretty-print JSON output
36+
-d, --disassembly Show bytecode disassembly
37+
--validate Validate output (IR or debug info)
38+
--stats Show IR statistics
39+
--show-both Show both unoptimized and optimized IR
40+
-h, --help Show this help message
41+
42+
Examples:
43+
bugc program.bug # Compile to bytecode
44+
bugc -s ast program.bug # Show AST only
45+
bugc -s ir -O 2 program.bug # Show optimized IR
46+
bugc -s bytecode -d program.bug # Show bytecode with disassembly
47+
bugc -s debug -o debug.json program.bug # Generate debug info
48+
49+
Phase descriptions:
50+
ast Parse source and output abstract syntax tree
51+
ir Compile to intermediate representation
52+
bytecode Compile to EVM bytecode (default)
53+
debug Generate ethdebug/format debug information`);
54+
}

packages/bugc/examples/README.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# BUG Language Examples
2+
3+
This directory contains example programs demonstrating various BUG language
4+
features, organized by complexity.
5+
6+
## Directory Structure
7+
8+
```
9+
examples/
10+
basic/ # Simple single-concept examples
11+
intermediate/ # Multiple concepts combined
12+
advanced/ # Complex real-world patterns
13+
optimizations/ # Compiler optimization demos
14+
wip/ # Work-in-progress (not yet compiling)
15+
```
16+
17+
## Running Tests
18+
19+
All examples are automatically tested by the test suite:
20+
21+
```bash
22+
yarn test examples
23+
```
24+
25+
## Test Annotations
26+
27+
Examples can include special comments to control test behavior:
28+
29+
```bug
30+
// @wip - Skip test (work in progress)
31+
// @skip Reason - Skip test with reason
32+
// @expect-parse-error - Expected to fail parsing
33+
// @expect-typecheck-error - Expected to fail typechecking
34+
// @expect-ir-error - Expected to fail IR generation
35+
// @expect-bytecode-error - Expected to fail bytecode generation
36+
```
37+
38+
## Examples by Category
39+
40+
### Basic
41+
42+
Simple examples demonstrating single language features:
43+
44+
- `minimal.bug` - Simplest possible BUG contract
45+
- `conditionals.bug` - If/else statements
46+
- `functions.bug` - Function definitions and calls
47+
- `variables.bug` - Variable types and declarations
48+
- `array-length.bug` - Array length property
49+
- `constructor-init.bug` - Constructor initialization
50+
51+
### Intermediate
52+
53+
Examples combining multiple language features:
54+
55+
- `arrays.bug` - Array operations with loops
56+
- `mappings.bug` - Mapping access patterns
57+
- `scopes.bug` - Variable scoping and shadowing
58+
- `slices.bug` - Byte slice operations
59+
- `calldata.bug` - Calldata access via msg.data
60+
- `owner-counter.bug` - Owner checks with counters
61+
- `storage-arrays.bug` - Dynamic arrays in storage
62+
- `memory-arrays.bug` - Memory array allocation
63+
- `internal-functions.bug` - Internal function calls
64+
65+
### Advanced
66+
67+
Complex examples demonstrating real-world patterns:
68+
69+
- `nested-mappings.bug` - Mapping of mappings
70+
- `nested-arrays.bug` - Multi-dimensional arrays
71+
- `nested-structs.bug` - Nested struct storage
72+
- `voting-system.bug` - Realistic voting contract
73+
- `token-registry.bug` - Token with function selectors
74+
75+
### Optimizations
76+
77+
Examples showcasing compiler optimizations:
78+
79+
- `cse.bug` - Common subexpression elimination
80+
- `cse-simple.bug` - Simple CSE example
81+
- `constant-folding.bug` - Compile-time constant evaluation
82+
83+
### Work in Progress
84+
85+
Features not yet fully implemented:
86+
87+
- `transient-storage.bug` - TSTORE/TLOAD opcodes (no syntax)
88+
- `returndata.bug` - Return data access
89+
90+
---
91+
92+
## Storage Access Patterns
93+
94+
The BUG language has specific rules about how storage variables can be
95+
accessed and modified.
96+
97+
### Key Concept: Storage References vs Local Copies
98+
99+
In BUG, when you read a complex type (struct, array, or mapping) from storage
100+
into a local variable, you get a **copy** of the data, not a reference. This
101+
means:
102+
103+
-**Reading** from local copies works fine
104+
-**Writing** to local copies does NOT update storage
105+
106+
### Correct Patterns
107+
108+
```bug
109+
// Direct storage access - changes are persisted
110+
accounts[user].balance = 1000;
111+
votes[proposalId][0].amount = 100;
112+
allowances[owner][spender] = 500;
113+
114+
// Reading into locals is fine
115+
let currentBalance = accounts[user].balance;
116+
let voteCount = votes[proposalId][0].amount;
117+
```
118+
119+
### Incorrect Patterns
120+
121+
```bug
122+
// ❌ WRONG: Changes to local copies don't persist
123+
let userAccount = accounts[user];
124+
userAccount.balance = 1000; // This doesn't update storage!
125+
126+
// ❌ WRONG: Same issue with array elements
127+
let firstVote = votes[proposalId][0];
128+
firstVote.amount = 200; // This doesn't update storage!
129+
```
130+
131+
### Workaround
132+
133+
If you need to perform multiple operations on a storage struct, access each
134+
field directly:
135+
136+
```bug
137+
// Instead of:
138+
let account = accounts[user];
139+
account.balance = account.balance + 100;
140+
account.isActive = true;
141+
142+
// Do this:
143+
accounts[user].balance = accounts[user].balance + 100;
144+
accounts[user].isActive = true;
145+
```
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name MemoryToStorageCopy;
2+
3+
// Example 13: Memory to Storage Copy
4+
//
5+
// Expected IR:
6+
// // Read from memory
7+
// %id = read location="memory", offset=%mem_user, length=32
8+
// %name = read location="memory", offset=add(%mem_user, 32), length=32
9+
//
10+
// // Write to storage (consecutive slots)
11+
// write location="storage", slot=30, offset=0, length=32, value=%id
12+
// write location="storage", slot=31, offset=0, length=32, value=%name
13+
//
14+
// EVM Strategy: MLOAD fields from memory, SSTORE to consecutive slots
15+
//
16+
// Key Insight: Mixed locations (e.g., memory→storage copies) need explicit read/write pairs
17+
18+
define {
19+
struct User {
20+
id: uint256;
21+
name: bytes32;
22+
};
23+
}
24+
25+
storage {
26+
[30] stored_user: User;
27+
}
28+
29+
code {
30+
// Memory struct values that will be copied to storage
31+
let id: uint256 = 999;
32+
let name: bytes32 = 0x4a6f686e00000000000000000000000000000000000000000000000000000000;
33+
34+
// Copy to storage struct (would be field by field in IR)
35+
stored_user.id = id;
36+
stored_user.name = name;
37+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name NestedArrays;
2+
3+
// Example 7: Nested Arrays
4+
//
5+
// Expected IR:
6+
// // First dimension: matrix[i]
7+
// %outer_base = compute_slot kind="array", base=15
8+
// %inner_array_slot = add %outer_base, %i
9+
//
10+
// // Second dimension: matrix[i][j]
11+
// %inner_base = compute_slot kind="array", base=%inner_array_slot
12+
// %element_slot = add %inner_base, %j
13+
//
14+
// write location="storage", slot=%element_slot, offset=0, length=32, value=%value
15+
// %elem = read location="storage", slot=%element_slot, offset=0, length=32
16+
//
17+
// EVM Strategy: Double keccak256 for nested dynamic arrays
18+
//
19+
// Key Insight: Dynamic arrays in storage use keccak hashing at each nesting level
20+
21+
storage {
22+
[15] matrix: array<array<uint256>>;
23+
}
24+
25+
code {
26+
let i = 2;
27+
let j = 5;
28+
let value = 999;
29+
matrix[i][j] = value;
30+
let elem = matrix[i][j];
31+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name NestedMappings;
2+
3+
// Example 4: Nested Mappings
4+
//
5+
// Expected IR:
6+
// %slot1 = slot[10].mapping[%owner]
7+
// %slot2 = slot[%slot1].mapping[%spender]
8+
// storage[%slot2*] = %amount
9+
//
10+
// EVM Strategy: Double keccak256 hashing for nested mappings
11+
//
12+
// Key Insight: Each mapping level requires a separate compute_slot operation
13+
14+
storage {
15+
[10] approvals: mapping<address, mapping<address, uint256>>;
16+
}
17+
18+
code {
19+
let owner = msg.sender;
20+
let spender = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;
21+
let amount = 1000;
22+
approvals[owner][spender] = amount;
23+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name NestedStructsStorage;
2+
3+
// Example 9: Nested Structs in Storage
4+
//
5+
// Expected IR:
6+
// // CEO salary is at slot 4 (base slot 2 + struct layout)
7+
// write location="storage", slot=4, offset=0, length=32, value=1000000
8+
//
9+
// // CEO address is at slot 3, first 20 bytes
10+
// %ceo_addr = read location="storage", slot=3, offset=0, length=20
11+
//
12+
// EVM Strategy: Calculate nested struct slot offsets based on layout
13+
//
14+
// Key Insight: Nested structures need recursive offset computation,
15+
// especially when crossing slot boundaries
16+
17+
define {
18+
struct CEO {
19+
addr: address; // offset 0-19
20+
salary: uint256; // next slot
21+
};
22+
23+
struct Company {
24+
name: string; // slot 0 (relative)
25+
founded: uint64; // slot 1 (relative)
26+
ceo: CEO; // slots 2-3 (relative)
27+
};
28+
}
29+
30+
storage {
31+
[2] company: Company;
32+
}
33+
34+
code {
35+
company.ceo.salary = 1000000;
36+
company.founded = 1988;
37+
let ceo_addr = company.ceo.addr;
38+
let x = ceo_addr;
39+
}

0 commit comments

Comments
 (0)