Skip to content

Commit cf9a23e

Browse files
committed
Start on compatibility layer for old compiler
Renamed the new compiler's exports so that the old compiler's exports can now return compatibility layer stubs instead. It seems like getting most extensions to be functional is feasible, though there will be edge cases that can't be handled and performance may be degraded.
1 parent 98df18a commit cf9a23e

File tree

5 files changed

+257
-3
lines changed

5 files changed

+257
-3
lines changed

src/compiler/enums.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ const StackOpcode = {
103103
DEBUGGER: 'tw.debugger',
104104
VISUAL_REPORT: 'visualReport',
105105
COMPATIBILITY_LAYER: 'compat',
106+
OLD_COMPILER_COMPATIBILITY_LAYER: 'oldCompiler',
106107

107108
HAT_EDGE: 'hat.edge',
108109
HAT_PREDICATE: 'hat.predicate',
@@ -201,6 +202,7 @@ const InputOpcode = {
201202
CAST_COLOR: 'cast.toColor',
202203

203204
COMPATIBILITY_LAYER: 'compat',
205+
OLD_COMPILER_COMPATIBILITY_LAYER: 'oldCompiler',
204206

205207
LOOKS_BACKDROP_NUMBER: 'looks.backdropNumber',
206208
LOOKS_BACKDROP_NAME: 'looks.backdropName',

src/compiler/irgen.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const {
1414
IntermediateScript,
1515
IntermediateRepresentation
1616
} = require('./intermediate');
17+
const oldCompilerCompatiblity = require('./old-compiler-compatibility.js');
1718

1819
/**
1920
* @fileoverview Generate intermediate representations from Scratch blocks.
@@ -96,6 +97,12 @@ class ScriptTreeGenerator {
9697
}
9798
}
9899
}
100+
101+
this.oldCompilerStub = (
102+
oldCompilerCompatiblity.enabled ?
103+
new oldCompilerCompatiblity.ScriptTreeGeneratorStub(this) :
104+
null
105+
);
99106
}
100107

101108
setProcedureVariant (procedureVariant) {
@@ -199,6 +206,13 @@ class ScriptTreeGenerator {
199206
* @returns {IntermediateInput} Compiled input node for this input.
200207
*/
201208
descendInput (block, preserveStrings = false) {
209+
if (this.oldCompilerStub) {
210+
const oldCompilerResult = this.oldCompilerStub.descendInputFromNewCompiler(block);
211+
if (oldCompilerResult) {
212+
return oldCompilerResult;
213+
}
214+
}
215+
202216
switch (block.opcode) {
203217
case 'colour_picker':
204218
return this.createConstantInput(block.fields.COLOUR.value, true);

src/compiler/jsgen.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const VariablePool = require('./variable-pool');
66
const jsexecute = require('./jsexecute');
77
const environment = require('./environment');
88
const {StackOpcode, InputOpcode, InputType} = require('./enums.js');
9+
const oldCompilerCompatibility = require('./old-compiler-compatibility.js');
910

1011
// These imports are used by jsdoc comments but eslint doesn't know that
1112
/* eslint-disable no-unused-vars */
@@ -124,6 +125,8 @@ class JSGenerator {
124125
this.isInHat = false;
125126

126127
this.debug = this.target.runtime.debug;
128+
129+
this.oldCompilerStub = new oldCompilerCompatibility.JSGeneratorStub(this);
127130
}
128131

129132
/**
@@ -198,6 +201,9 @@ class JSGenerator {
198201
// Compatibility layer inputs never use flags.
199202
return `(${this.generateCompatibilityLayerCall(node, false)})`;
200203

204+
case InputOpcode.OLD_COMPILER_COMPATIBILITY_LAYER:
205+
return this.oldCompilerStub.descendInputFromNewCompiler(block);
206+
201207
case InputOpcode.CONSTANT:
202208
if (block.isAlwaysType(InputType.NUMBER)) {
203209
if (typeof node.value !== 'number') throw new Error(`JS: '${block.type}' type constant had ${typeof node.value} type value. Expected number.`);
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
const {InputOpcode, InputType} = require('./enums');
2+
const {IntermediateInput} = require('./intermediate');
3+
4+
class IRGeneratorStub {
5+
}
6+
7+
class ScriptTreeGeneratorStub {
8+
constructor (real) {
9+
/**
10+
* The real script generator.
11+
* @type {import("./irgen").ScriptTreeGenerator}
12+
*/
13+
this.real = real;
14+
15+
this.fakeThis = {
16+
descendInputOfBlock: this.descendInputOfBlockFromOldCompiler.bind(this)
17+
};
18+
}
19+
20+
/**
21+
* Intended for extensions to override.
22+
* Always call from `fakeThis` context.
23+
* @param {{opcode: string}} block VM block
24+
* @returns {{kind: string}} Node object from old compiler.
25+
*/
26+
descendInput (block) { // eslint-disable-line no-unused-vars
27+
return null;
28+
}
29+
30+
/**
31+
* Intended for extensions to override.
32+
* Always call from `fakeThis` context.
33+
* @param {{opcode: string}} block VM block
34+
* @returns {{kind: string}} Node object from old compiler.
35+
*/
36+
descendStackedBlock (block) { // eslint-disable-line no-unused-vars
37+
return null;
38+
}
39+
40+
/**
41+
* Part of old compiler's public API.
42+
* @param parentBlock Parent VM block.
43+
* @param {string} inputName Name of input.
44+
*/
45+
descendInputOfBlockFromOldCompiler (parentBlock, inputName) {
46+
const node = this.real.descendInputOfBlock(parentBlock, inputName, true);
47+
return node;
48+
}
49+
50+
/**
51+
* For internal use by new compiler only.
52+
* @param block VM block
53+
* @returns {IntermediateInput|null}
54+
*/
55+
descendInputFromNewCompiler (block) {
56+
const node = this.descendInput.call(this.fakeThis, block);
57+
if (node) {
58+
return new IntermediateInput(InputOpcode.OLD_COMPILER_COMPATIBILITY_LAYER, InputType.ANY, {
59+
block: block,
60+
oldNode: node
61+
}, true);
62+
}
63+
return null;
64+
}
65+
}
66+
67+
const TYPE_NUMBER = 1;
68+
const TYPE_STRING = 2;
69+
const TYPE_BOOLEAN = 3;
70+
const TYPE_UNKNOWN = 4;
71+
const TYPE_NUMBER_NAN = 5;
72+
73+
/**
74+
* Part of the old compiler's public API.
75+
*/
76+
class TypedInput {
77+
/**
78+
* @param {string} source JavaScript
79+
* @param {number|IntermediateInput} typeOrIntermediate
80+
*/
81+
constructor (source, typeOrIntermediate) {
82+
/**
83+
* JavaScript.
84+
* @type {string}
85+
*/
86+
this.source = source;
87+
88+
if (typeOrIntermediate instanceof IntermediateInput) {
89+
/**
90+
* @type {IntermediateInput}
91+
*/
92+
this.intermediate = typeOrIntermediate;
93+
} else {
94+
this.intermediate = null;
95+
this.type = typeOrIntermediate;
96+
}
97+
}
98+
99+
asNumber () {
100+
throw new Error('TODO asNumber');
101+
}
102+
103+
asNumberOrNaN () {
104+
throw new Error('TODO asNumberOrNaN');
105+
}
106+
107+
asString () {
108+
throw new Error('TODO asString');
109+
}
110+
111+
asBoolean () {
112+
throw new Error('TODO asBoolean');
113+
}
114+
115+
asColor () {
116+
throw new Error('TODO asColor');
117+
}
118+
119+
asUnknown () {
120+
return this.source;
121+
}
122+
123+
asSafe () {
124+
return this.source;
125+
}
126+
127+
isAlwaysNumber () {
128+
// TODO
129+
return false;
130+
}
131+
132+
isAlwaysNumberOrNaN () {
133+
// TODO
134+
return false;
135+
}
136+
137+
isNeverNumber () {
138+
// TODO
139+
return false;
140+
}
141+
}
142+
143+
class JSGeneratorStub {
144+
constructor (real) {
145+
/**
146+
* For internal use by new compiler only.
147+
* @type {import("./jsgen")}
148+
*/
149+
this.real = real;
150+
151+
this._fakeThis = {
152+
descendInput: this.descendInputFromOldCompiler.bind(this)
153+
};
154+
}
155+
156+
/**
157+
* Intended for extensions to override.
158+
* Always call from `fakeThis` context.
159+
* @param {{kind: string}} node Old compiler AST node.
160+
* @returns {TypedInput} Old compiler TypedInput.
161+
*/
162+
descendInput (node) {
163+
throw new Error(`Unknown input: ${node.kind}`);
164+
}
165+
166+
/**
167+
* Part of old compiler's public API.
168+
* @param {IntermediateInput} intermediate
169+
* @returns {TypedInput}
170+
*/
171+
descendInputFromOldCompiler (intermediate) {
172+
const js = this.real.descendInput(intermediate);
173+
return new TypedInput(js, intermediate);
174+
}
175+
176+
/**
177+
* @param {IntermediateInput} intermediate
178+
* @returns {string} JavaScript
179+
*/
180+
descendInputFromNewCompiler (intermediate) {
181+
const oldNode = intermediate.inputs.oldNode;
182+
const typedInput = this.descendInput.call(this._fakeThis, oldNode);
183+
return typedInput.asSafe();
184+
}
185+
}
186+
187+
/**
188+
* Part of old compiler's public API.
189+
*/
190+
JSGeneratorStub.unstable_exports = {
191+
TYPE_NUMBER,
192+
TYPE_STRING,
193+
TYPE_BOOLEAN,
194+
TYPE_UNKNOWN,
195+
TYPE_NUMBER_NAN,
196+
// factoryNameVariablePool,
197+
// functionNameVariablePool,
198+
// generatorNameVariablePool,
199+
// VariablePool,
200+
// PEN_EXT,
201+
// PEN_STATE,
202+
TypedInput
203+
// ConstantInput,
204+
// VariableInput,
205+
// Frame,
206+
// sanitize
207+
};
208+
209+
const oldCompilerCompatibility = {
210+
enabled: false,
211+
IRGeneratorStub,
212+
ScriptTreeGeneratorStub,
213+
TypedInput,
214+
JSGeneratorStub
215+
};
216+
217+
module.exports = oldCompilerCompatibility;

src/virtual-machine.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,9 @@ class VirtualMachine extends EventEmitter {
223223
JSZip,
224224
Variable,
225225

226-
i_will_not_ask_for_help_when_these_break: () => {
226+
these_broke_before_and_will_break_again: () => {
227227
console.warn('You are using unsupported APIs. WHEN your code breaks, do not expect help.');
228-
return ({
228+
return {
229229
JSGenerator: require('./compiler/jsgen.js'),
230230
IRGenerator: require('./compiler/irgen.js').IRGenerator,
231231
ScriptTreeGenerator: require('./compiler/irgen.js').ScriptTreeGenerator,
@@ -239,7 +239,22 @@ class VirtualMachine extends EventEmitter {
239239
InputType: require('./compiler/enums.js').InputType,
240240
Thread: require('./engine/thread.js'),
241241
execute: require('./engine/execute.js')
242-
});
242+
};
243+
},
244+
245+
i_will_not_ask_for_help_when_these_break: () => {
246+
this.emit('LEGACY_EXTENSION_API', 'i_will_not_ask_for_help_when_these_break');
247+
248+
const oldCompilerCompatibility = require('./compiler/old-compiler-compatibility.js');
249+
oldCompilerCompatibility.enabled = true;
250+
251+
return {
252+
IRGenerator: oldCompilerCompatibility.IRGeneratorStub,
253+
ScriptTreeGenerator: oldCompilerCompatibility.ScriptTreeGeneratorStub,
254+
JSGenerator: oldCompilerCompatibility.JSGeneratorStub,
255+
Thread: require('./engine/thread.js'),
256+
execute: require('./engine/execute.js')
257+
};
243258
}
244259
};
245260
}

0 commit comments

Comments
 (0)