Skip to content
Draft
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules/
dist/
*.tsbuildinfo
coverage/
3 changes: 2 additions & 1 deletion bin/start
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ else
fi

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"packages/*"
],
"scripts": {
"build": "tsc --build packages/format packages/pointers",
"build": "tsc --build packages/format packages/pointers packages/bugc",
"bundle": "tsx ./bin/bundle-schema.ts",
"test": "vitest",
"test:coverage": "vitest run --coverage",
Expand Down
1 change: 1 addition & 0 deletions packages/bugc/.ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
54 changes: 54 additions & 0 deletions packages/bugc/bin/bugc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env tsx
/* eslint-disable no-console */

/**
* BUG compiler CLI
*/

import { handleCompileCommand } from "#cli";

// Parse command line arguments
const args = process.argv.slice(2);

// Show help if no arguments or help flag
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
showHelp();
process.exit(0);
}

// Run the compiler
handleCompileCommand(process.argv);

function showHelp(): void {
console.log(`bugc - The BUG language compiler

Usage: bugc [options] <file.bug>

Options:
-s, --stop-after <phase> Stop compilation after phase (ast, ir, bytecode, debug)
Default: bytecode
-O, --optimize <level> Set optimization level (0-3)
Default: 0
-f, --format <format> Output format (text, json)
Default: text
-o, --output <file> Write output to file instead of stdout
-p, --pretty Pretty-print JSON output
-d, --disassembly Show bytecode disassembly
--validate Validate output (IR or debug info)
--stats Show IR statistics
--show-both Show both unoptimized and optimized IR
-h, --help Show this help message

Examples:
bugc program.bug # Compile to bytecode
bugc -s ast program.bug # Show AST only
bugc -s ir -O 2 program.bug # Show optimized IR
bugc -s bytecode -d program.bug # Show bytecode with disassembly
bugc -s debug -o debug.json program.bug # Generate debug info

Phase descriptions:
ast Parse source and output abstract syntax tree
ir Compile to intermediate representation
bytecode Compile to EVM bytecode (default)
debug Generate ethdebug/format debug information`);
}
145 changes: 145 additions & 0 deletions packages/bugc/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# BUG Language Examples

This directory contains example programs demonstrating various BUG language
features, organized by complexity.

## Directory Structure

```
examples/
basic/ # Simple single-concept examples
intermediate/ # Multiple concepts combined
advanced/ # Complex real-world patterns
optimizations/ # Compiler optimization demos
wip/ # Work-in-progress (not yet compiling)
```

## Running Tests

All examples are automatically tested by the test suite:

```bash
yarn test examples
```

## Test Annotations

Examples can include special comments to control test behavior:

```bug
// @wip - Skip test (work in progress)
// @skip Reason - Skip test with reason
// @expect-parse-error - Expected to fail parsing
// @expect-typecheck-error - Expected to fail typechecking
// @expect-ir-error - Expected to fail IR generation
// @expect-bytecode-error - Expected to fail bytecode generation
```

## Examples by Category

### Basic

Simple examples demonstrating single language features:

- `minimal.bug` - Simplest possible BUG contract
- `conditionals.bug` - If/else statements
- `functions.bug` - Function definitions and calls
- `variables.bug` - Variable types and declarations
- `array-length.bug` - Array length property
- `constructor-init.bug` - Constructor initialization

### Intermediate

Examples combining multiple language features:

- `arrays.bug` - Array operations with loops
- `mappings.bug` - Mapping access patterns
- `scopes.bug` - Variable scoping and shadowing
- `slices.bug` - Byte slice operations
- `calldata.bug` - Calldata access via msg.data
- `owner-counter.bug` - Owner checks with counters
- `storage-arrays.bug` - Dynamic arrays in storage
- `memory-arrays.bug` - Memory array allocation
- `internal-functions.bug` - Internal function calls

### Advanced

Complex examples demonstrating real-world patterns:

- `nested-mappings.bug` - Mapping of mappings
- `nested-arrays.bug` - Multi-dimensional arrays
- `nested-structs.bug` - Nested struct storage
- `voting-system.bug` - Realistic voting contract
- `token-registry.bug` - Token with function selectors

### Optimizations

Examples showcasing compiler optimizations:

- `cse.bug` - Common subexpression elimination
- `cse-simple.bug` - Simple CSE example
- `constant-folding.bug` - Compile-time constant evaluation

### Work in Progress

Features not yet fully implemented:

- `transient-storage.bug` - TSTORE/TLOAD opcodes (no syntax)
- `returndata.bug` - Return data access

---

## Storage Access Patterns

The BUG language has specific rules about how storage variables can be
accessed and modified.

### Key Concept: Storage References vs Local Copies

In BUG, when you read a complex type (struct, array, or mapping) from storage
into a local variable, you get a **copy** of the data, not a reference. This
means:

- ✅ **Reading** from local copies works fine
- ❌ **Writing** to local copies does NOT update storage

### Correct Patterns

```bug
// Direct storage access - changes are persisted
accounts[user].balance = 1000;
votes[proposalId][0].amount = 100;
allowances[owner][spender] = 500;

// Reading into locals is fine
let currentBalance = accounts[user].balance;
let voteCount = votes[proposalId][0].amount;
```

### Incorrect Patterns

```bug
// ❌ WRONG: Changes to local copies don't persist
let userAccount = accounts[user];
userAccount.balance = 1000; // This doesn't update storage!

// ❌ WRONG: Same issue with array elements
let firstVote = votes[proposalId][0];
firstVote.amount = 200; // This doesn't update storage!
```

### Workaround

If you need to perform multiple operations on a storage struct, access each
field directly:

```bug
// Instead of:
let account = accounts[user];
account.balance = account.balance + 100;
account.isActive = true;

// Do this:
accounts[user].balance = accounts[user].balance + 100;
accounts[user].isActive = true;
```
37 changes: 37 additions & 0 deletions packages/bugc/examples/advanced/memory-to-storage.bug
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name MemoryToStorageCopy;

// Example 13: Memory to Storage Copy
//
// Expected IR:
// // Read from memory
// %id = read location="memory", offset=%mem_user, length=32
// %name = read location="memory", offset=add(%mem_user, 32), length=32
//
// // Write to storage (consecutive slots)
// write location="storage", slot=30, offset=0, length=32, value=%id
// write location="storage", slot=31, offset=0, length=32, value=%name
//
// EVM Strategy: MLOAD fields from memory, SSTORE to consecutive slots
//
// Key Insight: Mixed locations (e.g., memory→storage copies) need explicit read/write pairs

define {
struct User {
id: uint256;
name: bytes32;
};
}

storage {
[30] stored_user: User;
}

code {
// Memory struct values that will be copied to storage
let id: uint256 = 999;
let name: bytes32 = 0x4a6f686e00000000000000000000000000000000000000000000000000000000;

// Copy to storage struct (would be field by field in IR)
stored_user.id = id;
stored_user.name = name;
}
31 changes: 31 additions & 0 deletions packages/bugc/examples/advanced/nested-arrays.bug
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name NestedArrays;

// Example 7: Nested Arrays
//
// Expected IR:
// // First dimension: matrix[i]
// %outer_base = compute_slot kind="array", base=15
// %inner_array_slot = add %outer_base, %i
//
// // Second dimension: matrix[i][j]
// %inner_base = compute_slot kind="array", base=%inner_array_slot
// %element_slot = add %inner_base, %j
//
// write location="storage", slot=%element_slot, offset=0, length=32, value=%value
// %elem = read location="storage", slot=%element_slot, offset=0, length=32
//
// EVM Strategy: Double keccak256 for nested dynamic arrays
//
// Key Insight: Dynamic arrays in storage use keccak hashing at each nesting level

storage {
[15] matrix: array<array<uint256>>;
}

code {
let i = 2;
let j = 5;
let value = 999;
matrix[i][j] = value;
let elem = matrix[i][j];
}
23 changes: 23 additions & 0 deletions packages/bugc/examples/advanced/nested-mappings.bug
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name NestedMappings;

// Example 4: Nested Mappings
//
// Expected IR:
// %slot1 = slot[10].mapping[%owner]
// %slot2 = slot[%slot1].mapping[%spender]
// storage[%slot2*] = %amount
//
// EVM Strategy: Double keccak256 hashing for nested mappings
//
// Key Insight: Each mapping level requires a separate compute_slot operation

storage {
[10] approvals: mapping<address, mapping<address, uint256>>;
}

code {
let owner = msg.sender;
let spender = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;
let amount = 1000;
approvals[owner][spender] = amount;
}
39 changes: 39 additions & 0 deletions packages/bugc/examples/advanced/nested-structs.bug
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name NestedStructsStorage;

// Example 9: Nested Structs in Storage
//
// Expected IR:
// // CEO salary is at slot 4 (base slot 2 + struct layout)
// write location="storage", slot=4, offset=0, length=32, value=1000000
//
// // CEO address is at slot 3, first 20 bytes
// %ceo_addr = read location="storage", slot=3, offset=0, length=20
//
// EVM Strategy: Calculate nested struct slot offsets based on layout
//
// Key Insight: Nested structures need recursive offset computation,
// especially when crossing slot boundaries

define {
struct CEO {
addr: address; // offset 0-19
salary: uint256; // next slot
};

struct Company {
name: string; // slot 0 (relative)
founded: uint64; // slot 1 (relative)
ceo: CEO; // slots 2-3 (relative)
};
}

storage {
[2] company: Company;
}

code {
company.ceo.salary = 1000000;
company.founded = 1988;
let ceo_addr = company.ceo.addr;
let x = ceo_addr;
}
Loading
Loading