uploading because copy+paste doesn't work

snap8
xBZZZZ 2022-07-16 20:18:07 +03:00 zatwierdzone przez GitHub
rodzic 864a3b62a0
commit e86b65bb41
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
1 zmienionych plików z 158 dodań i 106 usunięć

Wyświetl plik

@ -7406,7 +7406,7 @@ Process.prototype.setVarNamed = function (name, value) {
Process.prototype.incrementVarNamed = function (name, delta) { Process.prototype.incrementVarNamed = function (name, delta) {
// private - special form for compiled expressions // private - special form for compiled expressions
this.setVarNamed(name, this.getVarNamed(name) + (+delta)); this.setVarNamed(name, this.getVarNamed(name) - (-delta));
}; };
// Process: Atomic HOFs using experimental JIT-compilation // Process: Atomic HOFs using experimental JIT-compilation
@ -8213,101 +8213,154 @@ VariableFrame.prototype.allNames = function (upTo, includeHidden) {
return answer; return answer;
}; };
// JSCompiler ///////////////////////////////////////////////////////////////// // JSCompiler ////////////////////////////////////////////////////////////////
/* /*
Compile simple, side-effect free Reporters *** don't use same JSCompiler object multiple times ***
Compile simple reporters
with either only explicit formal parameters or a specified number of with either only explicit formal parameters or a specified number of
implicit formal parameters mapped to empty input slots implicit formal parameters mapped to empty input slots
*** highly experimental and heavily under construction *** *** highly experimental and heavily under construction ***
*/ */
function JSCompiler(aProcess) { function JSCompiler(aProcess, outerScope) {
this.process = aProcess; this.process = aProcess;
this.source = null; // a context this.source = null; // a context
this.gensyms = new Map(); // temp dictionary for parameter substitutions this.paramCount = 0;
this.implicitParams = null; this.params = 0;
this.paramCount = null; this.gensymArgIndexes = new Map();
this.scriptVarCounter = null; this.scope = new Map();
if (outerScope == null) {
this.scope.depth = 0;
this.scope.outerScope = null;
return;
}
this.scope.depth = 1 + outerScope.depth;
this.scope.outerScope = outerScope;
} }
JSCompiler.prototype.toString = function () { JSCompiler.prototype.toString = () => 'a JSCompiler';
return 'a JSCompiler';
JSCompiler.prototype.gensymForVar = function (varName, argIndex) {
// argIndex -1 for script variables
var gensym = this.getGensym(varName), oldArgIndex;
if (gensym == null) {
gensym = '_' + this.scope.depth + '_' + this.scope.size;
this.scope.set(varName, gensym);
this.gensymArgIndexes.set(gensym, argIndex);
return gensym;
}
oldArgIndex = this.gensymArgIndexes.get(gensym);
if (oldArgIndex == null || oldArgIndex < argIndex) {
this.gensymArgIndexes.set(gensym, argIndex);
}
return gensym;
}; };
JSCompiler.prototype.compileFunction = function (aContext, implicitParamCount) { JSCompiler.prototype.getGensym = function (varName) {
var scope = this.scope, gensym;
while (null == (gensym = scope.get(varName)) &&
null != (scope = scope.outerScope));
return gensym;
};
JSCompiler.prototype.functionHead = function () {
var str1 = 'var ', str2 = '';
this.gensymArgIndexes.forEach((argIndex, gensym) => {
if (argIndex === -1) {
str1 += gensym + '=0,';
return;
}
str2 += ',' + argIndex + ':' + gensym;
});
str1 += 'proc=params.pop();\n';
if (this.params) {
str1 += 'while(' + this.params + '>params.length)params.push(0);\n';
}
if (str2) {
str1 += 'var{' + str2.substring(1) + '}=params;\n';
}
return str1;
};
JSCompiler.prototype.compileFunction = function () {
return window.eval(this.compileFunctionBody.apply(this, arguments));
};
JSCompiler.prototype.findEmptySlot = function findEmptySlot(m) {
if (m.isEmptySlot != null && m.isEmptySlot()) {
return true;
}
if (m instanceof RingMorph) {
// don't look in rings (they are not current scope)
return false;
}
m = m.children;
var i = m.length;
while (i) {
if (findEmptySlot(m[--i])) {
return true;
}
}
return false;
};
JSCompiler.prototype.compileFunctionBody = function (aContext,
implicitParamCount) {
var block = aContext.expression, var block = aContext.expression,
parameters = aContext.inputs, parameters = aContext.inputs,
hasEmptySlots = false, hasEmptySlots,
plength = 0, plength = 0,
code; code;
this.source = aContext; if (block instanceof Array) {
if (isNil(implicitParamCount) || implicitParamCount === '') { throw new Error('can\'t compile empty ring');
}
this.source = aContext;
if (implicitParamCount === '' || isNil(implicitParamCount)) {
this.implicitParams = 1; this.implicitParams = 1;
} else { } else {
this.implicitParams = Math.floor(implicitParamCount); this.implicitParams = Math.floor(implicitParamCount);
if (this.implicitParams < 0) { if (!(this.implicitParams > 0 && this.implicitParams < 128)) {
// use 1 if implicitParamCount doesn't make sense // use 1 if implicitParamCount doesn't make sense
this.implicitParams = 1; this.implicitParams = 1;
} }
} }
// scan for empty input slots // scan for empty input slots
hasEmptySlots = !isNil(detect( hasEmptySlots = this.findEmptySlot(block);
block.allChildren(),
morph => morph.isEmptySlot && morph.isEmptySlot()
));
// translate formal parameters into gensyms // translate formal parameters into gensyms
this.gensyms.clear();
this.paramCount = 0;
if (parameters.length) { if (parameters.length) {
// test for conflicts // test for conflicts
if (hasEmptySlots) { if (hasEmptySlots) {
throw new Error( throw new Error(
'compiling does not yet support\n' + 'compiling does not yet support\n' +
'mixing explicit formal parameters\n' + 'mixing explicit formal parameters\n' +
'with empty input slots' 'with empty input slots'
); );
} }
// map explicit formal parameters // map explicit formal parameters
parameters.forEach((pName, idx) => { this.params = parameters.length;
this.gensyms.set(pName, 'p[' + idx + ']'); parameters.forEach(this.gensymForVar, this);
});
plength = parameters.length;
} else if (hasEmptySlots) { } else if (hasEmptySlots) {
plength = this.implicitParams; this.params = this.implicitParams;
} }
// compile using gensyms // compile using gensyms
this.scriptVarCounter = 0;
code = 'proc=p.pop();\n';
if (plength) {
// fill missing parameters with empty string
code += 'while(' + plength + '>p.length)p.push("");\n';
}
if (block instanceof CommandBlockMorph) { if (block instanceof CommandBlockMorph) {
code += this.compileSequence(block) + 'return ""'; code = this.compileSequence(block) + 'return "";\n';
} else { } else {
code += 'return ' + this.compileExpression(block); code = 'return ' + this.compileExpression(block) + ';\n';
} }
block = 'var '; return '(function func(...params){\n' + this.functionHead() + code + '})';
this.gensyms.forEach(value => {
if (value.charAt(0) === 's') {
// declare script variable
block += value + '=0,';
}
});
return Function('...p', block + code);
}; };
JSCompiler.prototype.compileExpression = function (block) { JSCompiler.prototype.compileExpression = function (block) {
var selector = block.selector, var selector = block.selector,
inputs = block.inputs(), inputs = block.inputs(),
target, target,
rcvr,
args; args;
// first check for special forms and infix operators // first check for special forms and infix operators
@ -8329,41 +8382,30 @@ JSCompiler.prototype.compileExpression = function (block) {
'compiling does not yet support\n' + 'compiling does not yet support\n' +
'custom blocks' 'custom blocks'
); );
// special evaluation primitives // special evaluation primitives
case 'doRun': case 'doRun':
case 'evaluate': case 'evaluate':
return 'invoke(' + return 'invoke(' + this.compileInput(inputs[0]) + ',' +
this.compileInput(inputs[0]) +
', ' +
this.compileInput(inputs[1]) + this.compileInput(inputs[1]) +
')'; ',proc.blockReceiver(),null,null,null,proc,null)';
// special command forms // special command forms
case 'doDeclareVariables': case 'doDeclareVariables':
block = ''; block = '';
inputs[0].inputs().forEach(x => {
inputs[0].inputs().forEach(({children: {0: {blockSpec: name}}}) => { block += this.gensymForVar(x.children[0].blockSpec, -1) + '=';
var gensym = this.gensyms.get(name);
if (gensym) {
// we already have that script variable, just set it to 0
block += gensym + '=';
return;
}
gensym = 's' + this.scriptVarCounter++;
block += gensym + '=';
this.gensyms.set(name, gensym);
}); });
return block + '0'; return block + '0';
case 'reportGetVar': case 'reportGetVar':
return this.gensyms.get(block.blockSpec) || ('proc.getVarNamed("' + target = this.getGensym(block = block.blockSpec);
this.escape(block.blockSpec) + if (target == null) {
'")'); // redirect var to process
return 'proc.getVarNamed("' + this.escape(block) + '")';
}
return target;
case 'doSetVar': case 'doSetVar':
if (inputs[0] instanceof ArgMorph) { if (inputs[0] instanceof ArgMorph) {
target = this.gensyms.get(inputs[0].evaluate()); target = this.getGensym(inputs[0].evaluate());
if (target) { if (target != null) {
// setting gensym (script or argument) variable
return target + ' = ' + this.compileInput(inputs[1]); return target + ' = ' + this.compileInput(inputs[1]);
} }
} }
@ -8375,11 +8417,9 @@ JSCompiler.prototype.compileExpression = function (block) {
')'; ')';
case 'doChangeVar': case 'doChangeVar':
if (inputs[0] instanceof ArgMorph) { if (inputs[0] instanceof ArgMorph) {
target = this.gensyms.get(inputs[0].evaluate()); target = this.getGensym(inputs[0].evaluate());
if (target) { if (target != null) {
return '{const d=' + this.compileInput(inputs[1]) + return target + ' -=- ' + this.compileInput(inputs[1]);
',v=parseFloat(' + target + ');' +
target + '=isNaN(v)?d:v+parseFloat(d)}';
} }
} }
// redirect var to process // redirect var to process
@ -8404,20 +8444,20 @@ JSCompiler.prototype.compileExpression = function (block) {
'} else {\n' + '} else {\n' +
this.compileSequence(inputs[2].evaluate()) + this.compileSequence(inputs[2].evaluate()) +
'}'; '}';
case 'doWarp':
// synchronous javascript is already like warp
return this.compileSequence(inputs[0].evaluate());
case 'reportBoolean': case 'reportBoolean':
case 'reportNewList': case 'reportNewList':
return this.compileInput(inputs[0]); return this.compileInput(inputs[0]);
case 'reportThisContext':
return 'func';
default: default:
target = this.process[selector] ? this.process target = this.process[selector] ? this.process
: (this.source.receiver || this.process.receiver); : (this.source.receiver || this.process.receiver);
rcvr = target.constructor.name + '.prototype';
args = this.compileInputs(inputs); args = this.compileInputs(inputs);
if (isSnapObject(target)) { if (isSnapObject(target)) {
if (rcvr === 'SpriteMorph.prototype') { return 'proc.blockReceiver().' + selector + '(' + args + ')';
// fix for blocks like (x position)
rcvr = 'proc.blockReceiver()';
}
return rcvr + '.' + selector + '(' + args + ')';
} else { } else {
return 'proc.' + selector + '(' + args + ')'; return 'proc.' + selector + '(' + args + ')';
} }
@ -8425,10 +8465,12 @@ JSCompiler.prototype.compileExpression = function (block) {
}; };
JSCompiler.prototype.compileSequence = function (commandBlock) { JSCompiler.prototype.compileSequence = function (commandBlock) {
var body = ''; if (commandBlock == null) return '';
commandBlock.blockSequence().forEach(block => { commandBlock = commandBlock.blockSequence();
body += this.compileExpression(block) + ';\n'; var l = commandBlock.length, i = 0, body = '';
}); while (l > i) {
body += this.compileExpression(commandBlock[i++]) + ';\n';
}
return body; return body;
}; };
@ -8454,22 +8496,32 @@ JSCompiler.prototype.compileInput = function (inp) {
if (inp.isEmptySlot && inp.isEmptySlot()) { if (inp.isEmptySlot && inp.isEmptySlot()) {
// implicit formal parameter // implicit formal parameter
if (this.implicitParams > 1) { if (this.implicitParams > 1) {
if (this.paramCount < this.implicitParams) { if (this.paramCount < this.implicitParams) {
this.paramCount += 1; return 'params[' + this.paramCount++ + ']';
return 'p[' + (this.paramCount - 1) + ']'; }
}
throw new Error( throw new Error(
localize('expecting') + ' ' + this.implicitParams + ' ' localize('expecting') + ' ' + this.implicitParams + ' '
+ localize('input(s), but getting') + ' ' + localize('input(s), but getting') + ' '
+ this.paramCount + this.paramCount
); );
} }
return 'p[0]'; return 'params[0]';
} else if (inp instanceof MultiArgMorph) { }
if (inp instanceof RingMorph) {
inp = inp.children;
return new JSCompiler(this.process,this.scope).compileFunctionBody({
'expression': inp[0].children[0],
'inputs': inp[1].inputs().map(x => x.children[0].blockSpec),
'receiver': this.source.receiver
}, '');
}
if (inp instanceof MultiArgMorph) {
return 'new List([' + this.compileInputs(inp.inputs()) + '])'; return 'new List([' + this.compileInputs(inp.inputs()) + '])';
} else if (inp instanceof ArgLabelMorph) { }
return this.compileInput(inp.argMorph()); if (inp instanceof ArgLabelMorph) {
} else if (inp instanceof ArgMorph) { return this.compileInput(inp.argMorph());
}
if (inp instanceof ArgMorph) {
// literal - evaluate inline // literal - evaluate inline
value = inp.evaluate(); value = inp.evaluate();
type = this.process.reportTypeOf(value); type = this.process.reportTypeOf(value);
@ -8492,18 +8544,18 @@ JSCompiler.prototype.compileInput = function (inp) {
type type
); );
} }
} else if (inp instanceof BlockMorph) {
return this.compileExpression(inp);
} else {
throw new Error(
'compiling does not yet support\n' +
'input slots of type\n' +
inp.constructor.name
);
} }
if (inp instanceof BlockMorph) {
return this.compileExpression(inp);
}
throw new Error(
'compiling does not yet support\n' +
'input slots of type\n' +
inp.constructor.name
);
}; };
JSCompiler.prototype.escape = function(string) { JSCompiler.prototype.escape = string => {
// make sure string is a string // make sure string is a string
string += ''; string += '';
var len = string.length, i = 0, char, escaped = '', safe_chars = var len = string.length, i = 0, char, escaped = '', safe_chars =