Skip to content

Commit 6edec0a

Browse files
authored
Merge pull request #451 from sugarlabs/gsoc-dmp-2025/week-9/safwan
GSoC/DMP 2025 Week 9: feat(Program): Add Compiler Module
2 parents d5e53da + f4501f1 commit 6edec0a

File tree

15 files changed

+2697
-41
lines changed

15 files changed

+2697
-41
lines changed
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import { BasicBlockManager } from './basic-block-manager';
2+
import { SymDeclareInstruction } from '../../../runtime/src/interpreter/instructions/sym-declare-instruction';
3+
import { SymAssignInstruction } from '../../../runtime/src/interpreter/instructions/sym-assign-instruction';
4+
import { CallInstruction } from '../../../runtime/src/interpreter/instructions/call-instruction';
5+
import { JumpInstruction } from '../../../runtime/src/interpreter/instructions/jump-instruction';
6+
import { CompareJumpInstruction } from '../../../runtime/src/interpreter/instructions/compare-jump-instruction';
7+
8+
describe('BasicBlockManager', () => {
9+
let manager: BasicBlockManager;
10+
11+
beforeEach(() => {
12+
manager = new BasicBlockManager();
13+
});
14+
15+
it('should be defined', () => {
16+
expect(manager).toBeDefined();
17+
});
18+
19+
it('should create basic blocks with unique labels', () => {
20+
const block1 = manager.createBlock('entry');
21+
const block2 = manager.createBlock('loop');
22+
const block3 = manager.createBlock('exit');
23+
24+
expect(block1.label).toBe('entry_0');
25+
expect(block2.label).toBe('loop_1');
26+
expect(block3.label).toBe('exit_2');
27+
});
28+
29+
it('should generate incremental labels for same prefix', () => {
30+
const block1 = manager.createBlock('entry');
31+
const block2 = manager.createBlock('entry');
32+
const block3 = manager.createBlock('entry');
33+
34+
expect(block1.label).toBe('entry_0');
35+
expect(block2.label).toBe('entry_1');
36+
expect(block3.label).toBe('entry_2');
37+
});
38+
39+
it('should track all created blocks', () => {
40+
const entryBlock = manager.createBlock('entry');
41+
const loopBlock = manager.createBlock('loop');
42+
const exitBlock = manager.createBlock('exit');
43+
44+
const allBlocks = manager.getAllBlocks();
45+
expect(allBlocks).toHaveLength(3);
46+
expect(allBlocks).toContain(entryBlock);
47+
expect(allBlocks).toContain(loopBlock);
48+
expect(allBlocks).toContain(exitBlock);
49+
});
50+
51+
it('should retrieve blocks by label', () => {
52+
const entryBlock = manager.createBlock('entry');
53+
const loopBlock = manager.createBlock('loop');
54+
55+
expect(manager.getBlock('entry_0')).toBe(entryBlock);
56+
expect(manager.getBlock('loop_1')).toBe(loopBlock);
57+
expect(manager.getBlock('nonexistent_2')).toBeUndefined();
58+
});
59+
60+
it('should support adding instructions to blocks', () => {
61+
const block = manager.createBlock('test');
62+
63+
const declareInstruction = new SymDeclareInstruction('x');
64+
const assignInstruction = new SymAssignInstruction('x', 42);
65+
66+
block.instructions.push(declareInstruction);
67+
block.instructions.push(assignInstruction);
68+
69+
expect(block.instructions).toHaveLength(2);
70+
expect(block.instructions[0]).toBe(declareInstruction);
71+
expect(block.instructions[1]).toBe(assignInstruction);
72+
});
73+
74+
it('should handle control flow with jump instructions', () => {
75+
const entryBlock = manager.createBlock('entry');
76+
const loopBlock = manager.createBlock('loop');
77+
const exitBlock = manager.createBlock('exit');
78+
79+
// Entry block: declare variable and jump to loop
80+
entryBlock.instructions.push(new SymDeclareInstruction('i'));
81+
entryBlock.instructions.push(new SymAssignInstruction('i', 0));
82+
entryBlock.instructions.push(new JumpInstruction(loopBlock.label));
83+
84+
// Loop block: do something and conditional jump
85+
loopBlock.instructions.push(new CallInstruction('doSomething', []));
86+
loopBlock.instructions.push(
87+
new CompareJumpInstruction('lessThan', 'i', 10, exitBlock.label, loopBlock.label),
88+
);
89+
90+
// Exit block: finish
91+
exitBlock.instructions.push(new CallInstruction('finish', []));
92+
93+
expect(entryBlock.instructions).toHaveLength(3);
94+
expect(loopBlock.instructions).toHaveLength(2);
95+
expect(exitBlock.instructions).toHaveLength(1);
96+
97+
// Verify instruction types
98+
expect(entryBlock.instructions[0]).toBeInstanceOf(SymDeclareInstruction);
99+
expect(entryBlock.instructions[1]).toBeInstanceOf(SymAssignInstruction);
100+
expect(entryBlock.instructions[2]).toBeInstanceOf(JumpInstruction);
101+
expect(loopBlock.instructions[0]).toBeInstanceOf(CallInstruction);
102+
expect(loopBlock.instructions[1]).toBeInstanceOf(CompareJumpInstruction);
103+
});
104+
105+
it('should support complex control flow patterns', () => {
106+
// Test nested loops and conditional branches
107+
const entryBlock = manager.createBlock('entry');
108+
const outerLoopInit = manager.createBlock('outer_loop_init');
109+
const outerLoopCondition = manager.createBlock('outer_loop_condition');
110+
const innerLoopInit = manager.createBlock('inner_loop_init');
111+
const innerLoopCondition = manager.createBlock('inner_loop_condition');
112+
const innerLoopBody = manager.createBlock('inner_loop_body');
113+
const innerLoopIncrement = manager.createBlock('inner_loop_increment');
114+
const outerLoopIncrement = manager.createBlock('outer_loop_increment');
115+
const exitBlock = manager.createBlock('exit');
116+
117+
// Entry block
118+
entryBlock.instructions.push(new SymDeclareInstruction('outerVar'));
119+
entryBlock.instructions.push(new JumpInstruction(outerLoopInit.label));
120+
121+
// Outer loop initialization
122+
outerLoopInit.instructions.push(new SymAssignInstruction('outerVar', 0));
123+
outerLoopInit.instructions.push(new JumpInstruction(outerLoopCondition.label));
124+
125+
// Outer loop condition
126+
outerLoopCondition.instructions.push(
127+
new CompareJumpInstruction(
128+
'lessThan',
129+
'outerVar',
130+
3,
131+
innerLoopInit.label,
132+
exitBlock.label,
133+
),
134+
);
135+
136+
// Inner loop initialization
137+
innerLoopInit.instructions.push(new SymDeclareInstruction('innerVar'));
138+
innerLoopInit.instructions.push(new SymAssignInstruction('innerVar', 0));
139+
innerLoopInit.instructions.push(new JumpInstruction(innerLoopCondition.label));
140+
141+
// Inner loop condition
142+
innerLoopCondition.instructions.push(
143+
new CompareJumpInstruction(
144+
'lessThan',
145+
'innerVar',
146+
5,
147+
innerLoopBody.label,
148+
outerLoopIncrement.label,
149+
),
150+
);
151+
152+
// Inner loop body
153+
innerLoopBody.instructions.push(new CallInstruction('processItem', []));
154+
innerLoopBody.instructions.push(new JumpInstruction(innerLoopIncrement.label));
155+
156+
// Inner loop increment
157+
innerLoopIncrement.instructions.push(
158+
new SymAssignInstruction('innerVar', { op: 'add', left: 'innerVar', right: 1 }),
159+
);
160+
innerLoopIncrement.instructions.push(new JumpInstruction(innerLoopCondition.label));
161+
162+
// Outer loop increment
163+
outerLoopIncrement.instructions.push(
164+
new SymAssignInstruction('outerVar', { op: 'add', left: 'outerVar', right: 1 }),
165+
);
166+
outerLoopIncrement.instructions.push(new JumpInstruction(outerLoopCondition.label));
167+
168+
// Exit block
169+
exitBlock.instructions.push(new CallInstruction('cleanup', []));
170+
171+
const allBlocks = manager.getAllBlocks();
172+
expect(allBlocks).toHaveLength(9);
173+
174+
// Verify each block has the expected number of instructions
175+
expect(entryBlock.instructions).toHaveLength(2);
176+
expect(outerLoopInit.instructions).toHaveLength(2);
177+
expect(outerLoopCondition.instructions).toHaveLength(1);
178+
expect(innerLoopInit.instructions).toHaveLength(3);
179+
expect(innerLoopCondition.instructions).toHaveLength(1);
180+
expect(innerLoopBody.instructions).toHaveLength(2);
181+
expect(innerLoopIncrement.instructions).toHaveLength(2);
182+
expect(outerLoopIncrement.instructions).toHaveLength(2);
183+
expect(exitBlock.instructions).toHaveLength(1);
184+
});
185+
186+
it('should create blocks for Music Blocks specific patterns', () => {
187+
// Test Music Blocks specific control flow patterns
188+
const startBlock = manager.createBlock('start');
189+
const noteSetupBlock = manager.createBlock('note_setup');
190+
const instrumentContextBlock = manager.createBlock('instrument_context');
191+
const playBlock = manager.createBlock('play');
192+
const volumeContextBlock = manager.createBlock('volume_context');
193+
const endBlock = manager.createBlock('end');
194+
195+
// Start block
196+
startBlock.instructions.push(new CallInstruction('clear', []));
197+
startBlock.instructions.push(new JumpInstruction(noteSetupBlock.label));
198+
199+
// Note setup block
200+
noteSetupBlock.instructions.push(new CallInstruction('setKey', ['C major']));
201+
noteSetupBlock.instructions.push(new CallInstruction('setMasterVolume', [80]));
202+
noteSetupBlock.instructions.push(new JumpInstruction(instrumentContextBlock.label));
203+
204+
// Instrument context block
205+
instrumentContextBlock.instructions.push(
206+
new CallInstruction('setContext_instrument', ['guitar']),
207+
);
208+
instrumentContextBlock.instructions.push(new JumpInstruction(playBlock.label));
209+
210+
// Play block
211+
playBlock.instructions.push(new CallInstruction('playNote', ['C4', 0.25]));
212+
playBlock.instructions.push(new JumpInstruction(volumeContextBlock.label));
213+
214+
// Volume context block
215+
volumeContextBlock.instructions.push(new CallInstruction('setContext_volume', [5]));
216+
volumeContextBlock.instructions.push(new CallInstruction('scalarStep', [1]));
217+
volumeContextBlock.instructions.push(new CallInstruction('resetContext_volume', []));
218+
volumeContextBlock.instructions.push(new CallInstruction('resetContext_instrument', []));
219+
volumeContextBlock.instructions.push(new JumpInstruction(endBlock.label));
220+
221+
// End block
222+
endBlock.instructions.push(new CallInstruction('finish', []));
223+
224+
const allBlocks = manager.getAllBlocks();
225+
expect(allBlocks).toHaveLength(6);
226+
227+
// Verify Music Blocks pattern structure
228+
expect(startBlock.instructions).toHaveLength(2);
229+
expect(noteSetupBlock.instructions).toHaveLength(3);
230+
expect(instrumentContextBlock.instructions).toHaveLength(2);
231+
expect(playBlock.instructions).toHaveLength(2);
232+
expect(volumeContextBlock.instructions).toHaveLength(5);
233+
expect(endBlock.instructions).toHaveLength(1);
234+
235+
// Verify block labels
236+
expect(startBlock.label).toBe('start_0');
237+
expect(noteSetupBlock.label).toBe('note_setup_1');
238+
expect(instrumentContextBlock.label).toBe('instrument_context_2');
239+
expect(playBlock.label).toBe('play_3');
240+
expect(volumeContextBlock.label).toBe('volume_context_4');
241+
expect(endBlock.label).toBe('end_5');
242+
});
243+
244+
it('should maintain block order in creation sequence', () => {
245+
const blocks = [];
246+
const blockNames = ['entry', 'init', 'loop', 'condition', 'body', 'increment', 'exit'];
247+
248+
// Create blocks in sequence
249+
for (const name of blockNames) {
250+
blocks.push(manager.createBlock(name));
251+
}
252+
253+
const allBlocks = manager.getAllBlocks();
254+
expect(allBlocks).toHaveLength(blockNames.length);
255+
256+
// Verify blocks are created in order
257+
for (let i = 0; i < blocks.length; i++) {
258+
expect(blocks[i].label).toBe(`${blockNames[i]}_${i}`);
259+
expect(allBlocks).toContain(blocks[i]);
260+
}
261+
});
262+
263+
it('should support creating multiple managers independently', () => {
264+
const manager1 = new BasicBlockManager();
265+
const manager2 = new BasicBlockManager();
266+
267+
manager1.createBlock('function1_entry');
268+
manager1.createBlock('function1_loop');
269+
270+
manager2.createBlock('function2_entry');
271+
manager2.createBlock('function2_condition');
272+
273+
expect(manager1.getAllBlocks()).toHaveLength(2);
274+
expect(manager2.getAllBlocks()).toHaveLength(2);
275+
276+
// Verify that the managers have different blocks (different actual labels)
277+
const manager1Labels = manager1.getAllBlocks().map((block) => block.label);
278+
const manager2Labels = manager2.getAllBlocks().map((block) => block.label);
279+
280+
// Check that each manager has the expected pattern (with possible suffixes)
281+
expect(manager1Labels.some((label) => label.startsWith('function1_entry'))).toBe(true);
282+
expect(manager1Labels.some((label) => label.startsWith('function1_loop'))).toBe(true);
283+
expect(manager2Labels.some((label) => label.startsWith('function2_entry'))).toBe(true);
284+
expect(manager2Labels.some((label) => label.startsWith('function2_condition'))).toBe(true);
285+
286+
// Verify they don't share any identical labels
287+
const sharedLabels = manager1Labels.filter((label) => manager2Labels.includes(label));
288+
expect(sharedLabels).toHaveLength(0);
289+
});
290+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { IRBasicBlock } from '../../../runtime/src/interpreter/ir-basic-block';
2+
3+
export class BasicBlockManager {
4+
private blocks: Map<string, IRBasicBlock> = new Map();
5+
private blockCounter = 0;
6+
7+
public createBlock(prefix: string): IRBasicBlock {
8+
const label = `${prefix}_${this.blockCounter++}`;
9+
const block = new IRBasicBlock(label, []);
10+
this.blocks.set(label, block);
11+
return block;
12+
}
13+
14+
public getBlock(label: string): IRBasicBlock | undefined {
15+
return this.blocks.get(label);
16+
}
17+
18+
public getAllBlocks(): IRBasicBlock[] {
19+
return Array.from(this.blocks.values());
20+
}
21+
}

0 commit comments

Comments
 (0)