Skip to content

Commit f3d6049

Browse files
committed
Add new test tw_type_assertions
1 parent 39de860 commit f3d6049

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

test/fixtures/tw-type-assertions.sb3

3.82 KB
Binary file not shown.
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const { test } = require('tap');
4+
const VM = require('../../src/virtual-machine');
5+
const BlockType = require('../../src/extension-support/block-type');
6+
const ArgumentType = require('../../src/extension-support/argument-type');
7+
const IRGenerator = require('../../src/compiler/irgen');
8+
const { IROptimizer } = require('../../src/compiler/iroptimizer');
9+
const { StackOpcode, InputType, InputOpcode } = require('../../src/compiler/enums');
10+
const { IntermediateStack } = require('../../src/compiler/intermediate');
11+
12+
const fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'tw-type-assertions.sb3'));
13+
14+
test("type assertions", async t => {
15+
const vm = new VM();
16+
vm.setCompilerOptions({ enabled: true });
17+
18+
class TestExtension {
19+
getInfo() {
20+
return {
21+
id: 'typeassert',
22+
name: 'Type Assertions',
23+
blocks: [
24+
{
25+
opcode: 'assert',
26+
blockType: BlockType.COMMAND,
27+
text: 'assert [VALUE] is [ADVERB] [NOUN]',
28+
arguments: {
29+
VALUE: {
30+
type: ArgumentType.STRING
31+
},
32+
ADVERB: {
33+
type: ArgumentType.STRING,
34+
menu: "ADVERB_MENU"
35+
},
36+
NOUN: {
37+
type: ArgumentType.STRING,
38+
menu: "NOUN_MENU"
39+
}
40+
}
41+
},
42+
{
43+
opcode: 'region',
44+
blockType: BlockType.CONDITIONAL,
45+
text: 'region [NAME]',
46+
arguments: {
47+
NAME: {
48+
type: ArgumentType.STRING
49+
},
50+
}
51+
}
52+
],
53+
menus: {
54+
ADVERB_MENU: {
55+
acceptReporters: false,
56+
items: ['never', 'always', 'sometimes', 'exactly']
57+
},
58+
NOUN_MENU: {
59+
acceptReporters: false,
60+
items: ['zero', 'infinity', 'NaN', 'a number', 'a string', 'number interpretable', 'anything']
61+
}
62+
}
63+
};
64+
}
65+
assert() { }
66+
region() { return true; }
67+
}
68+
69+
vm.extensionManager.addBuiltinExtension('typeassert', TestExtension);
70+
71+
vm.on('COMPILE_ERROR', () => {
72+
t.fail('Compile error');
73+
});
74+
75+
await vm.loadProject(fixture);
76+
77+
const thread = vm.runtime.startHats("event_whenflagclicked")[0];
78+
79+
function* enumerateAssertions(blocks, region) {
80+
for (const block of blocks) {
81+
if (block.opcode === StackOpcode.COMPATIBILITY_LAYER) {
82+
switch (block.inputs.opcode) {
83+
case "typeassert_assert":
84+
yield { block, region };
85+
break;
86+
case "typeassert_region":
87+
const newRegionNameInput = block.inputs.inputs.NAME;
88+
if (newRegionNameInput.opcode !== InputOpcode.CONSTANT)
89+
throw new Error("Region block inputs must be a constant.");
90+
yield* enumerateAssertions(block.inputs.substacks[0].blocks, newRegionNameInput.inputs.value);
91+
break;
92+
}
93+
} else {
94+
for (const inputName in block.inputs) {
95+
const input = block.inputs[inputName];
96+
if (input instanceof IntermediateStack)
97+
yield* enumerateAssertions(input.blocks);
98+
}
99+
}
100+
}
101+
};
102+
103+
const irGenerator = new IRGenerator(thread);
104+
const ir = irGenerator.generate();
105+
106+
for (const { block } of enumerateAssertions(ir.entry.stack.blocks)) {
107+
block.ignoreState = true;
108+
}
109+
110+
const irOptimizer = new IROptimizer(ir);
111+
irOptimizer.optimize();
112+
113+
for (const { block, region } of enumerateAssertions(ir.entry.stack.blocks, "[no region]")) {
114+
const valueInput = block.inputs.inputs.VALUE;
115+
const adverb = block.inputs.fields.ADVERB;
116+
const noun = block.inputs.fields.NOUN;
117+
118+
let nounType;
119+
120+
switch (noun) {
121+
case "zero":
122+
nounType = InputType.NUMBER_ZERO;
123+
break;
124+
case "infinity":
125+
nounType = InputType.NUMBER_POS_INF;
126+
break;
127+
case "NaN":
128+
nounType = InputType.NUMBER_NAN;
129+
break;
130+
case "a number":
131+
nounType = InputType.NUMBER;
132+
break;
133+
case "a string":
134+
nounType = InputType.STRING;
135+
break;
136+
case "number interpretable":
137+
nounType = InputType.NUMBER_INTERPRETABLE;
138+
break;
139+
case "anything":
140+
nounType = InputType.ANY;
141+
break;
142+
default: throw new Error(`$Invalid noun menu option ${noun}`);
143+
}
144+
145+
const message = `(${region}) assert ${valueInput.opcode} (type ${valueInput.type}) is ${adverb} ${noun}`;
146+
147+
switch (adverb) {
148+
case "never":
149+
t.ok(!valueInput.isSometimesType(nounType), message);
150+
break;
151+
case "always":
152+
t.ok(valueInput.isAlwaysType(nounType), message);
153+
break;
154+
case "sometimes":
155+
t.ok(valueInput.isSometimesType(nounType), message);
156+
break;
157+
case "exactly":
158+
t.equal(valueInput.type, nounType, message);
159+
break;
160+
default: throw new Error(`$Invalid adverb menu option ${adverb}`);
161+
}
162+
}
163+
164+
t.end();
165+
});

0 commit comments

Comments
 (0)