Skip to content

Commit cb4c069

Browse files
implemented setpixel
1 parent f0ceb9f commit cb4c069

File tree

13 files changed

+357
-66
lines changed

13 files changed

+357
-66
lines changed

__tests__/apps.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ const apps = [
4040
name: "while statements",
4141
input: "var f = 0 while (f < 5) f = (f + 1) print f endwhile",
4242
output: [1, 2, 3, 4, 5]
43+
},
44+
{
45+
name: "setpixel statements",
46+
input: "setpixel 1 2 3",
47+
output: [] as any[],
48+
pixels: [[201, 3]]
4349
}
4450
];
4551

__tests__/compiler.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,25 @@ import apps from "./apps";
33

44
const executeCode = async (code: string, done: jest.DoneCallback) => {
55
const output: any[] = [];
6+
const display = new Uint8Array(10000);
7+
const pixels: any[] = [];
68

79
try {
810
const tick = await runtime(code, {
9-
print: d => output.push(d)
11+
print: d => output.push(d),
12+
display
1013
});
1114
tick();
1215

16+
// find any pixels that have been written to
17+
display.forEach((value, index) => {
18+
if (value !== 0) {
19+
pixels.push([index, value]);
20+
}
21+
});
22+
1323
done();
14-
return { output };
24+
return { output, pixels };
1525
} catch (e) {
1626
console.error(e);
1727
done.fail();
@@ -23,6 +33,9 @@ describe("compiler", () => {
2333
test(app.name, async done => {
2434
const result = await executeCode(app.input, done);
2535
expect(result.output).toEqual(app.output);
36+
if (app.pixels || result.pixels.length) {
37+
expect(result.pixels).toEqual(app.pixels);
38+
}
2639
});
2740
});
2841
});

__tests__/interpreter.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,35 @@ import apps from "./apps";
44
// execute the app, recording print statements and pixel writes
55
const executeCode = async (code: string) => {
66
const output: any[] = [];
7-
7+
const pixels: any[] = [];
8+
const display = new Uint8Array(10000);
9+
810
const tick = await runtime(code, {
9-
print: d => output.push(d)
11+
print: d => output.push(d),
12+
display
1013
});
1114
tick();
1215

13-
return { output };
16+
// find any pixels that have been written to
17+
display.forEach((value, index) => {
18+
if (value !== 0) {
19+
pixels.push([index, value]);
20+
}
21+
});
22+
23+
return {
24+
output, pixels
25+
};
1426
};
1527

1628
describe("interpreter", () => {
1729
apps.forEach(app => {
1830
test(app.name, async () => {
1931
const result = await executeCode(app.input);
2032
expect(result.output).toEqual(app.output);
33+
if (app.pixels || result.pixels.length) {
34+
expect(result.pixels).toEqual(app.pixels);
35+
}
2136
});
2237
});
2338
});

src/compiler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ export const compile: Compiler = src => {
1111

1212
export const runtime: Runtime = async (src, env) => {
1313
const wasm = compile(src);
14+
const memory = new WebAssembly.Memory({ initial: 1 });
1415
const result: any = await WebAssembly.instantiate(wasm, {
15-
env
16+
env: { ...env, memory }
1617
});
1718
return () => {
1819
result.instance.exports.run();
20+
env.display.set(new Uint8Array(memory.buffer, 0, 10000));
1921
};
2022
};

src/emitter.ts

Lines changed: 101 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { unsignedLEB128, signedLEB128, encodeString, ieee754 } from "./encoding";
1+
import {
2+
unsignedLEB128,
3+
signedLEB128,
4+
encodeString,
5+
ieee754
6+
} from "./encoding";
27
import traverse from "./traverse";
38

49
const flatten = (arr: any[]) => [].concat.apply([], arr);
@@ -40,6 +45,7 @@ enum Opcodes {
4045
call = 0x10,
4146
get_local = 0x20,
4247
set_local = 0x21,
48+
i32_store_8 = 0x3a,
4349
f32_const = 0x43,
4450
i32_eqz = 0x45,
4551
f32_eq = 0x5b,
@@ -49,7 +55,8 @@ enum Opcodes {
4955
f32_add = 0x92,
5056
f32_sub = 0x93,
5157
f32_mul = 0x94,
52-
f32_div = 0x95
58+
f32_div = 0x95,
59+
i32_trunc_f32_s = 0xa8
5360
}
5461

5562
const binaryOpcode = {
@@ -83,13 +90,13 @@ const moduleVersion = [0x01, 0x00, 0x00, 0x00];
8390
// https://webassembly.github.io/spec/core/binary/conventions.html#binary-vec
8491
// Vectors are encoded with their length followed by their element sequence
8592
const encodeVector = (data: any[]) => [
86-
unsignedLEB128(data.length),
93+
...unsignedLEB128(data.length),
8794
...flatten(data)
8895
];
8996

9097
// https://webassembly.github.io/spec/core/binary/modules.html#code-section
9198
const encodeLocal = (count: number, type: Valtype) => [
92-
unsignedLEB128(count),
99+
...unsignedLEB128(count),
93100
type
94101
];
95102

@@ -129,49 +136,86 @@ const codeFromAst = (ast: Program) => {
129136
}
130137
});
131138

132-
const emitStatements = (statements: StatementNode[]) =>
139+
const emitStatements = (statements: StatementNode[]) =>
133140
statements.forEach(statement => {
134-
switch (statement.type) {
135-
case "printStatement":
136-
emitExpression(statement.expression);
137-
code.push(Opcodes.call);
138-
code.push(...unsignedLEB128(0));
139-
break;
140-
case "variableDeclaration":
141-
emitExpression(statement.initializer);
142-
code.push(Opcodes.set_local);
143-
code.push(...unsignedLEB128(localIndexForSymbol(statement.name)));
144-
break;
145-
case "variableAssignment":
146-
emitExpression(statement.value);
147-
code.push(Opcodes.set_local);
148-
code.push(...unsignedLEB128(localIndexForSymbol(statement.name)));
149-
break;
150-
case "whileStatement":
151-
// outer block
152-
code.push(Opcodes.block);
153-
code.push(Blocktype.void);
154-
// inner loop
155-
code.push(Opcodes.loop);
156-
code.push(Blocktype.void);
157-
// compute the while expression
158-
emitExpression(statement.expression);
159-
code.push(Opcodes.i32_eqz);
160-
// br_if $label0
161-
code.push(Opcodes.br_if);
162-
code.push(...signedLEB128(1));
163-
// the nested logic
164-
emitStatements(statement.statements);
165-
// br $label1
166-
code.push(Opcodes.br);
167-
code.push(...signedLEB128(0));
168-
// end loop
169-
code.push(Opcodes.end);
170-
// end block
171-
code.push(Opcodes.end);
172-
break;
173-
}
174-
});
141+
switch (statement.type) {
142+
case "printStatement":
143+
emitExpression(statement.expression);
144+
code.push(Opcodes.call);
145+
code.push(...unsignedLEB128(0));
146+
break;
147+
case "variableDeclaration":
148+
emitExpression(statement.initializer);
149+
code.push(Opcodes.set_local);
150+
code.push(...unsignedLEB128(localIndexForSymbol(statement.name)));
151+
break;
152+
case "variableAssignment":
153+
emitExpression(statement.value);
154+
code.push(Opcodes.set_local);
155+
code.push(...unsignedLEB128(localIndexForSymbol(statement.name)));
156+
break;
157+
case "whileStatement":
158+
// outer block
159+
code.push(Opcodes.block);
160+
code.push(Blocktype.void);
161+
// inner loop
162+
code.push(Opcodes.loop);
163+
code.push(Blocktype.void);
164+
// compute the while expression
165+
emitExpression(statement.expression);
166+
code.push(Opcodes.i32_eqz);
167+
// br_if $label0
168+
code.push(Opcodes.br_if);
169+
code.push(...signedLEB128(1));
170+
// the nested logic
171+
emitStatements(statement.statements);
172+
// br $label1
173+
code.push(Opcodes.br);
174+
code.push(...signedLEB128(0));
175+
// end loop
176+
code.push(Opcodes.end);
177+
// end block
178+
code.push(Opcodes.end);
179+
break;
180+
case "setpixelStatement":
181+
// compute and cache the setpixel parameters
182+
emitExpression(statement.x);
183+
code.push(Opcodes.set_local);
184+
code.push(...unsignedLEB128(localIndexForSymbol("x")));
185+
186+
emitExpression(statement.y);
187+
code.push(Opcodes.set_local);
188+
code.push(...unsignedLEB128(localIndexForSymbol("y")));
189+
190+
emitExpression(statement.color);
191+
code.push(Opcodes.set_local);
192+
code.push(...unsignedLEB128(localIndexForSymbol("color")));
193+
194+
// compute the offset (x * 100) + y
195+
code.push(Opcodes.get_local);
196+
code.push(...unsignedLEB128(localIndexForSymbol("y")));
197+
code.push(Opcodes.f32_const);
198+
code.push(...ieee754(100));
199+
code.push(Opcodes.f32_mul);
200+
201+
code.push(Opcodes.get_local);
202+
code.push(...unsignedLEB128(localIndexForSymbol("x")));
203+
code.push(Opcodes.f32_add);
204+
205+
// convert to an integer
206+
code.push(Opcodes.i32_trunc_f32_s);
207+
208+
// fetch the color
209+
code.push(Opcodes.get_local);
210+
code.push(...unsignedLEB128(localIndexForSymbol("color")));
211+
code.push(Opcodes.i32_trunc_f32_s);
212+
213+
// write
214+
code.push(Opcodes.i32_store_8);
215+
code.push(...[0x00, 0x00]); // align and offset
216+
break;
217+
}
218+
});
175219

176220
emitStatements(ast);
177221

@@ -210,9 +254,19 @@ export const emitter: Emitter = (ast: Program) => {
210254
0x01 // type index
211255
];
212256

257+
const memoryImport = [
258+
...encodeString("env"),
259+
...encodeString("memory"),
260+
ExportType.mem,
261+
/* limits https://webassembly.github.io/spec/core/binary/types.html#limits -
262+
indicates a min memory size of one page */
263+
0x00,
264+
0x01
265+
];
266+
213267
const importSection = createSection(
214268
Section.import,
215-
encodeVector([printFunctionImport])
269+
encodeVector([printFunctionImport, memoryImport])
216270
);
217271

218272
// the export section is a vector of exported functions

src/interpreter.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const applyOperator = (operator: string, left: number, right: number) => {
2323
throw Error(`Unknown binary operator ${operator}`);
2424
};
2525

26-
export const runtime: Runtime = async (src, { print }) => () => {
26+
export const runtime: Runtime = async (src, { print, display }) => () => {
2727
const tokens = tokenize(src);
2828
const program = parse(tokens);
2929

@@ -64,6 +64,12 @@ export const runtime: Runtime = async (src, { print }) => () => {
6464
executeStatements(statement.statements);
6565
}
6666
break;
67+
case "setpixelStatement":
68+
const x = evaluateExpression(statement.x);
69+
const y = evaluateExpression(statement.y);
70+
const color = evaluateExpression(statement.color);
71+
display[y * 100 + x] = color;
72+
break;
6773
}
6874
});
6975
};

src/parser.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ export const parse: Parser = tokens => {
109109
};
110110
};
111111

112+
const parseSetPixelStatement: ParserStep<SetPixelStatementNode> = () => {
113+
eatToken("setpixel");
114+
return {
115+
type: "setpixelStatement",
116+
x: parseExpression(),
117+
y: parseExpression(),
118+
color: parseExpression()
119+
};
120+
};
121+
112122
const parseStatement: ParserStep<StatementNode> = () => {
113123
if (currentToken.type === "keyword") {
114124
switch (currentToken.value) {
@@ -118,6 +128,8 @@ export const parse: Parser = tokens => {
118128
return parseVariableDeclarationStatement();
119129
case "while":
120130
return parseWhileStatement();
131+
case "setpixel":
132+
return parseSetPixelStatement();
121133
default:
122134
throw new ParserError(
123135
`Unknown keyword ${currentToken.value}`,

src/tokenizer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const keywords = ["print", "var", "while", "endwhile"];
1+
export const keywords = ["print", "var", "while", "endwhile", "setpixel"];
22
export const operators = ["+", "-", "*", "/", "==", "<", ">", "&&"];
33

44
const escapeRegEx = (text: string) =>

src/types/parser.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ type StatementNode =
1414
| PrintStatementNode
1515
| VariableDeclarationNode
1616
| VariableAssignmentNode
17-
| WhileStatementNode;
17+
| WhileStatementNode
18+
| SetPixelStatementNode;
1819

1920
type Program = StatementNode[];
2021

@@ -57,6 +58,13 @@ interface PrintStatementNode extends ProgramNode {
5758
expression: ExpressionNode;
5859
}
5960

61+
interface SetPixelStatementNode extends ProgramNode {
62+
type: "setpixelStatement";
63+
x: ExpressionNode;
64+
y: ExpressionNode;
65+
color: ExpressionNode;
66+
}
67+
6068
interface WhileStatementNode extends ProgramNode {
6169
type: "whileStatement";
6270
expression: ExpressionNode;

src/types/runtime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface TickFunction {
88

99
interface Environment {
1010
print: PrintFunction;
11+
display: Uint8Array;
1112
}
1213

1314
interface PrintFunction {

0 commit comments

Comments
 (0)