Skip to content

Commit

Permalink
implemented setpixel
Browse files Browse the repository at this point in the history
  • Loading branch information
ColinEberhardt committed Apr 24, 2019
1 parent f0ceb9f commit cb4c069
Show file tree
Hide file tree
Showing 13 changed files with 357 additions and 66 deletions.
6 changes: 6 additions & 0 deletions __tests__/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ const apps = [
name: "while statements",
input: "var f = 0 while (f < 5) f = (f + 1) print f endwhile",
output: [1, 2, 3, 4, 5]
},
{
name: "setpixel statements",
input: "setpixel 1 2 3",
output: [] as any[],
pixels: [[201, 3]]
}
];

Expand Down
17 changes: 15 additions & 2 deletions __tests__/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,25 @@ import apps from "./apps";

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

try {
const tick = await runtime(code, {
print: d => output.push(d)
print: d => output.push(d),
display
});
tick();

// find any pixels that have been written to
display.forEach((value, index) => {
if (value !== 0) {
pixels.push([index, value]);
}
});

done();
return { output };
return { output, pixels };
} catch (e) {
console.error(e);
done.fail();
Expand All @@ -23,6 +33,9 @@ describe("compiler", () => {
test(app.name, async done => {
const result = await executeCode(app.input, done);
expect(result.output).toEqual(app.output);
if (app.pixels || result.pixels.length) {
expect(result.pixels).toEqual(app.pixels);
}
});
});
});
21 changes: 18 additions & 3 deletions __tests__/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,35 @@ import apps from "./apps";
// execute the app, recording print statements and pixel writes
const executeCode = async (code: string) => {
const output: any[] = [];

const pixels: any[] = [];
const display = new Uint8Array(10000);

const tick = await runtime(code, {
print: d => output.push(d)
print: d => output.push(d),
display
});
tick();

return { output };
// find any pixels that have been written to
display.forEach((value, index) => {
if (value !== 0) {
pixels.push([index, value]);
}
});

return {
output, pixels
};
};

describe("interpreter", () => {
apps.forEach(app => {
test(app.name, async () => {
const result = await executeCode(app.input);
expect(result.output).toEqual(app.output);
if (app.pixels || result.pixels.length) {
expect(result.pixels).toEqual(app.pixels);
}
});
});
});
4 changes: 3 additions & 1 deletion src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ export const compile: Compiler = src => {

export const runtime: Runtime = async (src, env) => {
const wasm = compile(src);
const memory = new WebAssembly.Memory({ initial: 1 });
const result: any = await WebAssembly.instantiate(wasm, {
env
env: { ...env, memory }
});
return () => {
result.instance.exports.run();
env.display.set(new Uint8Array(memory.buffer, 0, 10000));
};
};
148 changes: 101 additions & 47 deletions src/emitter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { unsignedLEB128, signedLEB128, encodeString, ieee754 } from "./encoding";
import {
unsignedLEB128,
signedLEB128,
encodeString,
ieee754
} from "./encoding";
import traverse from "./traverse";

const flatten = (arr: any[]) => [].concat.apply([], arr);
Expand Down Expand Up @@ -40,6 +45,7 @@ enum Opcodes {
call = 0x10,
get_local = 0x20,
set_local = 0x21,
i32_store_8 = 0x3a,
f32_const = 0x43,
i32_eqz = 0x45,
f32_eq = 0x5b,
Expand All @@ -49,7 +55,8 @@ enum Opcodes {
f32_add = 0x92,
f32_sub = 0x93,
f32_mul = 0x94,
f32_div = 0x95
f32_div = 0x95,
i32_trunc_f32_s = 0xa8
}

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

// https://webassembly.github.io/spec/core/binary/modules.html#code-section
const encodeLocal = (count: number, type: Valtype) => [
unsignedLEB128(count),
...unsignedLEB128(count),
type
];

Expand Down Expand Up @@ -129,49 +136,86 @@ const codeFromAst = (ast: Program) => {
}
});

const emitStatements = (statements: StatementNode[]) =>
const emitStatements = (statements: StatementNode[]) =>
statements.forEach(statement => {
switch (statement.type) {
case "printStatement":
emitExpression(statement.expression);
code.push(Opcodes.call);
code.push(...unsignedLEB128(0));
break;
case "variableDeclaration":
emitExpression(statement.initializer);
code.push(Opcodes.set_local);
code.push(...unsignedLEB128(localIndexForSymbol(statement.name)));
break;
case "variableAssignment":
emitExpression(statement.value);
code.push(Opcodes.set_local);
code.push(...unsignedLEB128(localIndexForSymbol(statement.name)));
break;
case "whileStatement":
// outer block
code.push(Opcodes.block);
code.push(Blocktype.void);
// inner loop
code.push(Opcodes.loop);
code.push(Blocktype.void);
// compute the while expression
emitExpression(statement.expression);
code.push(Opcodes.i32_eqz);
// br_if $label0
code.push(Opcodes.br_if);
code.push(...signedLEB128(1));
// the nested logic
emitStatements(statement.statements);
// br $label1
code.push(Opcodes.br);
code.push(...signedLEB128(0));
// end loop
code.push(Opcodes.end);
// end block
code.push(Opcodes.end);
break;
}
});
switch (statement.type) {
case "printStatement":
emitExpression(statement.expression);
code.push(Opcodes.call);
code.push(...unsignedLEB128(0));
break;
case "variableDeclaration":
emitExpression(statement.initializer);
code.push(Opcodes.set_local);
code.push(...unsignedLEB128(localIndexForSymbol(statement.name)));
break;
case "variableAssignment":
emitExpression(statement.value);
code.push(Opcodes.set_local);
code.push(...unsignedLEB128(localIndexForSymbol(statement.name)));
break;
case "whileStatement":
// outer block
code.push(Opcodes.block);
code.push(Blocktype.void);
// inner loop
code.push(Opcodes.loop);
code.push(Blocktype.void);
// compute the while expression
emitExpression(statement.expression);
code.push(Opcodes.i32_eqz);
// br_if $label0
code.push(Opcodes.br_if);
code.push(...signedLEB128(1));
// the nested logic
emitStatements(statement.statements);
// br $label1
code.push(Opcodes.br);
code.push(...signedLEB128(0));
// end loop
code.push(Opcodes.end);
// end block
code.push(Opcodes.end);
break;
case "setpixelStatement":
// compute and cache the setpixel parameters
emitExpression(statement.x);
code.push(Opcodes.set_local);
code.push(...unsignedLEB128(localIndexForSymbol("x")));

emitExpression(statement.y);
code.push(Opcodes.set_local);
code.push(...unsignedLEB128(localIndexForSymbol("y")));

emitExpression(statement.color);
code.push(Opcodes.set_local);
code.push(...unsignedLEB128(localIndexForSymbol("color")));

// compute the offset (x * 100) + y
code.push(Opcodes.get_local);
code.push(...unsignedLEB128(localIndexForSymbol("y")));
code.push(Opcodes.f32_const);
code.push(...ieee754(100));
code.push(Opcodes.f32_mul);

code.push(Opcodes.get_local);
code.push(...unsignedLEB128(localIndexForSymbol("x")));
code.push(Opcodes.f32_add);

// convert to an integer
code.push(Opcodes.i32_trunc_f32_s);

// fetch the color
code.push(Opcodes.get_local);
code.push(...unsignedLEB128(localIndexForSymbol("color")));
code.push(Opcodes.i32_trunc_f32_s);

// write
code.push(Opcodes.i32_store_8);
code.push(...[0x00, 0x00]); // align and offset
break;
}
});

emitStatements(ast);

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

const memoryImport = [
...encodeString("env"),
...encodeString("memory"),
ExportType.mem,
/* limits https://webassembly.github.io/spec/core/binary/types.html#limits -
indicates a min memory size of one page */
0x00,
0x01
];

const importSection = createSection(
Section.import,
encodeVector([printFunctionImport])
encodeVector([printFunctionImport, memoryImport])
);

// the export section is a vector of exported functions
Expand Down
8 changes: 7 additions & 1 deletion src/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const applyOperator = (operator: string, left: number, right: number) => {
throw Error(`Unknown binary operator ${operator}`);
};

export const runtime: Runtime = async (src, { print }) => () => {
export const runtime: Runtime = async (src, { print, display }) => () => {
const tokens = tokenize(src);
const program = parse(tokens);

Expand Down Expand Up @@ -64,6 +64,12 @@ export const runtime: Runtime = async (src, { print }) => () => {
executeStatements(statement.statements);
}
break;
case "setpixelStatement":
const x = evaluateExpression(statement.x);
const y = evaluateExpression(statement.y);
const color = evaluateExpression(statement.color);
display[y * 100 + x] = color;
break;
}
});
};
Expand Down
12 changes: 12 additions & 0 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ export const parse: Parser = tokens => {
};
};

const parseSetPixelStatement: ParserStep<SetPixelStatementNode> = () => {
eatToken("setpixel");
return {
type: "setpixelStatement",
x: parseExpression(),
y: parseExpression(),
color: parseExpression()
};
};

const parseStatement: ParserStep<StatementNode> = () => {
if (currentToken.type === "keyword") {
switch (currentToken.value) {
Expand All @@ -118,6 +128,8 @@ export const parse: Parser = tokens => {
return parseVariableDeclarationStatement();
case "while":
return parseWhileStatement();
case "setpixel":
return parseSetPixelStatement();
default:
throw new ParserError(
`Unknown keyword ${currentToken.value}`,
Expand Down
2 changes: 1 addition & 1 deletion src/tokenizer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const keywords = ["print", "var", "while", "endwhile"];
export const keywords = ["print", "var", "while", "endwhile", "setpixel"];
export const operators = ["+", "-", "*", "/", "==", "<", ">", "&&"];

const escapeRegEx = (text: string) =>
Expand Down
10 changes: 9 additions & 1 deletion src/types/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ type StatementNode =
| PrintStatementNode
| VariableDeclarationNode
| VariableAssignmentNode
| WhileStatementNode;
| WhileStatementNode
| SetPixelStatementNode;

type Program = StatementNode[];

Expand Down Expand Up @@ -57,6 +58,13 @@ interface PrintStatementNode extends ProgramNode {
expression: ExpressionNode;
}

interface SetPixelStatementNode extends ProgramNode {
type: "setpixelStatement";
x: ExpressionNode;
y: ExpressionNode;
color: ExpressionNode;
}

interface WhileStatementNode extends ProgramNode {
type: "whileStatement";
expression: ExpressionNode;
Expand Down
1 change: 1 addition & 0 deletions src/types/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface TickFunction {

interface Environment {
print: PrintFunction;
display: Uint8Array;
}

interface PrintFunction {
Expand Down
Loading

0 comments on commit cb4c069

Please sign in to comment.