kopia lustrzana https://github.com/backface/turtlestitch
Merge remote-tracking branch 'upstream/master'
commit
57bef2b736
43
blocks.js
43
blocks.js
|
@ -155,7 +155,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.blocks = '2014-October-01';
|
||||
modules.blocks = '2014-November-21';
|
||||
|
||||
|
||||
var SyntaxElementMorph;
|
||||
|
@ -394,10 +394,10 @@ SyntaxElementMorph.prototype.allInputs = function () {
|
|||
};
|
||||
|
||||
SyntaxElementMorph.prototype.allEmptySlots = function () {
|
||||
/*
|
||||
answer empty input slots of all children excluding myself,
|
||||
but omit those in nested rings (lambdas) and JS-Function primitives
|
||||
*/
|
||||
// answer empty input slots of all children excluding myself,
|
||||
// but omit those in nested rings (lambdas) and JS-Function primitives.
|
||||
// Used by the evaluator when binding implicit formal parameters
|
||||
// to empty input slots
|
||||
var empty = [];
|
||||
if (!(this instanceof RingMorph) &&
|
||||
(this.selector !== 'reportJSFunction')) {
|
||||
|
@ -412,6 +412,26 @@ SyntaxElementMorph.prototype.allEmptySlots = function () {
|
|||
return empty;
|
||||
};
|
||||
|
||||
SyntaxElementMorph.prototype.tagExitBlocks = function (stopTag, isCommand) {
|
||||
// tag 'report' and 'stop this block' blocks of all children including
|
||||
// myself, with either a stopTag (for "stop" blocks) or an indicator of
|
||||
// being inside a command block definition, but omit those in nested
|
||||
// rings (lambdas. Used by the evaluator when entering a procedure
|
||||
if (this.selector === 'doReport') {
|
||||
this.partOfCustomCommand = isCommand;
|
||||
} else if (this.selector === 'doStopThis') {
|
||||
this.exitTag = stopTag;
|
||||
} else {
|
||||
if (!(this instanceof RingMorph)) {
|
||||
this.children.forEach(function (morph) {
|
||||
if (morph.tagExitBlocks) {
|
||||
morph.tagExitBlocks(stopTag, isCommand);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SyntaxElementMorph.prototype.replaceInput = function (oldArg, newArg) {
|
||||
var scripts = this.parentThatIsA(ScriptsMorph),
|
||||
replacement = newArg,
|
||||
|
@ -3169,9 +3189,14 @@ BlockMorph.prototype.snap = function () {
|
|||
I inherit from BlockMorph adding the following most important
|
||||
public accessors:
|
||||
|
||||
nextBlock() - set / get the block attached to my bottom
|
||||
bottomBlock() - answer the bottom block of my stack
|
||||
blockSequence() - answer an array of blocks starting with myself
|
||||
nextBlock() - set / get the block attached to my bottom
|
||||
bottomBlock() - answer the bottom block of my stack
|
||||
blockSequence() - answer an array of blocks starting with myself
|
||||
|
||||
and the following "lexical awareness" indicators:
|
||||
|
||||
partOfCustomCommand - temporary bool set by the evaluator
|
||||
exitTag - temporary string or number set by the evaluator
|
||||
*/
|
||||
|
||||
// CommandBlockMorph inherits from BlockMorph:
|
||||
|
@ -3189,6 +3214,8 @@ function CommandBlockMorph() {
|
|||
CommandBlockMorph.prototype.init = function () {
|
||||
CommandBlockMorph.uber.init.call(this);
|
||||
this.setExtent(new Point(200, 100));
|
||||
this.partOfCustomCommand = false;
|
||||
this.exitTag = null;
|
||||
};
|
||||
|
||||
// CommandBlockMorph enumerating:
|
||||
|
|
5
byob.js
5
byob.js
|
@ -106,7 +106,7 @@ SymbolMorph, isNil*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.byob = '2014-September-30';
|
||||
modules.byob = '2014-November-20';
|
||||
|
||||
// Declarations
|
||||
|
||||
|
@ -688,7 +688,8 @@ CustomCommandBlockMorph.prototype.labelPart = function (spec) {
|
|||
return CustomCommandBlockMorph.uber.labelPart.call(this, spec);
|
||||
}
|
||||
if ((spec[0] === '%') && (spec.length > 1)) {
|
||||
part = new BlockInputFragmentMorph(spec.slice(1));
|
||||
// part = new BlockInputFragmentMorph(spec.slice(1));
|
||||
part = new BlockInputFragmentMorph(spec.replace(/%/g, ''));
|
||||
} else {
|
||||
part = new BlockLabelFragmentMorph(spec);
|
||||
part.fontSize = this.fontSize;
|
||||
|
|
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 7.9 KiB |
18
gui.js
18
gui.js
|
@ -69,7 +69,7 @@ SpeechBubbleMorph*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.gui = '2014-October-06';
|
||||
modules.gui = '2014-December-04';
|
||||
|
||||
// Declarations
|
||||
|
||||
|
@ -233,10 +233,6 @@ IDE_Morph.prototype.init = function (isAutoFill) {
|
|||
IDE_Morph.prototype.openIn = function (world) {
|
||||
var hash, usr, myself = this, urlLanguage = null;
|
||||
|
||||
this.buildPanes();
|
||||
world.add(this);
|
||||
world.userMenu = this.userMenu;
|
||||
|
||||
// get persistent user data, if any
|
||||
if (localStorage) {
|
||||
usr = localStorage['-snap-user'];
|
||||
|
@ -245,10 +241,17 @@ IDE_Morph.prototype.openIn = function (world) {
|
|||
if (usr) {
|
||||
SnapCloud.username = usr.username || null;
|
||||
SnapCloud.password = usr.password || null;
|
||||
if (SnapCloud.username) {
|
||||
this.source = 'cloud';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.buildPanes();
|
||||
world.add(this);
|
||||
world.userMenu = this.userMenu;
|
||||
|
||||
// override SnapCloud's user message with Morphic
|
||||
SnapCloud.message = function (string) {
|
||||
var m = new MenuMorph(null, string),
|
||||
|
@ -2511,8 +2514,9 @@ IDE_Morph.prototype.aboutSnap = function () {
|
|||
+ 'jens@moenig.org, bh@cs.berkeley.edu\n\n'
|
||||
|
||||
+ 'Snap! is developed by the University of California, Berkeley\n'
|
||||
+ ' with support from the National Science Foundation '
|
||||
+ 'and MioSoft. \n'
|
||||
+ ' with support from the National Science Foundation, '
|
||||
+ 'MioSoft, \n'
|
||||
+ 'and the Communications Design Group at SAP Labs. \n'
|
||||
|
||||
+ 'The design of Snap! is influenced and inspired by Scratch,\n'
|
||||
+ 'from the Lifelong Kindergarten group at the MIT Media Lab\n\n'
|
||||
|
|
85
history.txt
85
history.txt
|
@ -2309,3 +2309,88 @@ ______
|
|||
141008
|
||||
------
|
||||
* Objects: fixed #608, #610
|
||||
|
||||
141106
|
||||
------
|
||||
* Morphic: Enable mouseMove events with right button pressed
|
||||
|
||||
141114
|
||||
------
|
||||
* Threads, Store: Fix reporting out of nested custom C-shaped blocks
|
||||
|
||||
141117
|
||||
------
|
||||
* Threads, Blocks: Treat REPORT blocks inside custom command definitions as STOP THIS BLOCK / IGNORE INPUTS
|
||||
|
||||
141120
|
||||
------
|
||||
* Lists: Fixed #642 avoid “freezing” when calling CONS on non-list/null
|
||||
* Threads: Fixed #364 avoid “freezing” when calling LAUNCH on empty ring
|
||||
* Threads: Added optional “onComplete” callback to Process, thanks, @bromagosa!
|
||||
* GUI: Set Default Save location to Cloud on load, thanks, @cycomachead!
|
||||
* GUI: Updated the “About” Dialog with a mention of support from CDG (SAP Labs)
|
||||
* BYOB: Percent sign fix for block labels, thanks, @natashasandy!
|
||||
* Threads: fix ‘line’ option in ‘split’ block for Windows files, thanks, @brianharvey!
|
||||
* Morphic: fix slider range 1, thanks, @tonychenr !
|
||||
* translation update, thanks, Manuel!
|
||||
|
||||
141121
|
||||
------
|
||||
* Threads, Blocks: Fix STOP THIS BLOCK’s lexical awareness
|
||||
|
||||
1411213
|
||||
-------
|
||||
* Threads: Fix “stop this block” primitive for tail-call-elimination
|
||||
|
||||
1411224
|
||||
-------
|
||||
* Threads: Fixed #318
|
||||
* Objects: Fixed #416
|
||||
* Objects: Fixed #372
|
||||
* Threads: Fixed #644
|
||||
* Store: Fixed #34
|
||||
* Threads: Fixed #131
|
||||
* snap.html, favicon.ico: new Favicon, thanks, Michael!
|
||||
* Threads: improved whitespace detection for “split” primitive, thanks, Michael!
|
||||
* Threads: tail-call-elimination for reporters experiment (commented out, under construction)
|
||||
|
||||
1411225
|
||||
-------
|
||||
* Threads: Evaluator optimizations (reducing the stack size for reporters)
|
||||
* Threads: Full TCO (tail-call-elimination), now Snap! *is* Scheme :-)
|
||||
|
||||
1411225
|
||||
-------
|
||||
* Threads: Fixed #656
|
||||
|
||||
141201
|
||||
------
|
||||
* Objects: Hide hidden elements in the project thumbnail
|
||||
* GUI: Point project dialog to cloud if already signed in, thanks, Michael!
|
||||
* favicon: Transparent background, thanks, Michael!
|
||||
|
||||
141202
|
||||
------
|
||||
* New Kannada translation. Yay!! Thanks, Vinayakumar R!!
|
||||
|
||||
141203
|
||||
------
|
||||
* Morphic: Cache actual bounding box of the Pen arrow shape
|
||||
* Threads, Objects: Improve edge-collision detection of default sprite “arrow” shape
|
||||
|
||||
141204
|
||||
------
|
||||
* Threads, Objects: Experimental “ForEach” primitive (hidden in dev mode)
|
||||
* GUI: Another attempt at pointing the project dialog to the cloud if signed in
|
||||
|
||||
141205
|
||||
------
|
||||
* Morphic: Avoid auto-scaling artefacts in Safari on retina displays (resulting in “traces” when dragging items)
|
||||
|
||||
141206
|
||||
------
|
||||
* Store: Fixed #668
|
||||
|
||||
141211
|
||||
------
|
||||
* Threads: yield after each cycle in the experimental “forEach” primitive
|
||||
|
|
Plik diff jest za duży
Load Diff
23
lang-pt.js
23
lang-pt.js
|
@ -650,11 +650,12 @@ SnapTranslator.dict.pt = {
|
|||
'Save As...':
|
||||
'Guardar este projecto como…',
|
||||
'Import...':
|
||||
'Importar para este projecto…',
|
||||
'Importar…',
|
||||
'file menu import hint':
|
||||
'Importar para este projecto\num projecto exportado,\n'
|
||||
+ 'uma biblioteca de blocos,\n'
|
||||
+ 'um traje ou um som.',
|
||||
'Abrir um projecto exportado,\n'
|
||||
+ 'substitundo o projecto corrente, ou\n'
|
||||
+ 'importar uma biblioteca de blocos, um\n'
|
||||
+ 'traje ou um som para o projecto corrente.',
|
||||
'Export project as plain text...':
|
||||
'Exportar este projecto como texto simples…',
|
||||
'Export project...':
|
||||
|
@ -1616,7 +1617,7 @@ SnapTranslator.dict.pt = {
|
|||
'unshared.':
|
||||
'deixado de partilhar.',
|
||||
'Unshare':
|
||||
'Deixar de Partilhar',
|
||||
'Não Partilhar',
|
||||
'password has been changed.':
|
||||
'a sua palavra-passe foi alterada.',
|
||||
'SVG costumes are\nnot yet fully supported\nin every browser':
|
||||
|
@ -1641,6 +1642,18 @@ SnapTranslator.dict.pt = {
|
|||
'Seleccionar um traje da biblioteca de média.',
|
||||
'edit rotation point only...':
|
||||
'editar apenas ponto de rotação…',
|
||||
'Export Project As...':
|
||||
'Exportar Projecto Como…',
|
||||
'a variable of name \'':
|
||||
'não existe uma variável «',
|
||||
'\'\ndoes not exist in this context':
|
||||
'»\nneste contexto',
|
||||
'(temporary)':
|
||||
'(temporária)',
|
||||
'expecting':
|
||||
'esperavam-se',
|
||||
'input(s), but getting':
|
||||
'argumento(s), mas foram passados',
|
||||
|
||||
// produção de código
|
||||
'map %cmdRing to %codeKind %code':
|
||||
|
|
5
lists.js
5
lists.js
|
@ -61,7 +61,7 @@ PushButtonMorph, SyntaxElementMorph, Color, Point, WatcherMorph,
|
|||
StringMorph, SpriteMorph, ScrollFrameMorph, CellMorph, ArrowMorph,
|
||||
MenuMorph, snapEquals, Morph, isNil, localize, MorphicPreferences*/
|
||||
|
||||
modules.lists = '2014-July-28';
|
||||
modules.lists = '2014-November-20';
|
||||
|
||||
var List;
|
||||
var ListWatcherMorph;
|
||||
|
@ -125,6 +125,9 @@ List.prototype.changed = function () {
|
|||
|
||||
List.prototype.cons = function (car, cdr) {
|
||||
var answer = new List();
|
||||
if (!(cdr instanceof List || isNil(cdr))) {
|
||||
throw new Error("cdr isn't a list: " + cdr);
|
||||
}
|
||||
answer.first = isNil(car) ? null : car;
|
||||
answer.rest = cdr || null;
|
||||
answer.isLinked = true;
|
||||
|
|
14
locale.js
14
locale.js
|
@ -42,7 +42,7 @@
|
|||
|
||||
/*global modules, contains*/
|
||||
|
||||
modules.locale = '2014-October-01';
|
||||
modules.locale = '2014-December-02';
|
||||
|
||||
// Global stuff
|
||||
|
||||
|
@ -427,3 +427,15 @@ SnapTranslator.dict.bn = {
|
|||
'last_changed':
|
||||
'2014-07-02'
|
||||
};
|
||||
|
||||
SnapTranslator.dict.kn = {
|
||||
// translations meta information
|
||||
'language_name':
|
||||
'\u0C95\u0CA8\u0CCD\u0CA8\u0CA1',
|
||||
'language_translator':
|
||||
'Vinayakumar R',
|
||||
'translator_e-mail':
|
||||
'vnkmr7620@gmail.com',
|
||||
'last_changed':
|
||||
'2014-12-02'
|
||||
};
|
||||
|
|
58
morphic.js
58
morphic.js
|
@ -464,9 +464,15 @@
|
|||
|
||||
MyMorph.prototype.mouseMove = function(pos) {};
|
||||
|
||||
The only optional parameter of such a method is a Point object
|
||||
All of these methods have as optional parameter a Point object
|
||||
indicating the current position of the Hand inside the World's
|
||||
coordinate system.
|
||||
coordinate system. The
|
||||
|
||||
mouseMove(pos, button)
|
||||
|
||||
event method has an additional optional parameter indicating the
|
||||
currently pressed mouse button, which is either 'left' or 'right'.
|
||||
You can use this to let users interact with 3D environments.
|
||||
|
||||
Events may be "bubbled" up a morph's owner chain by calling
|
||||
|
||||
|
@ -1035,7 +1041,7 @@
|
|||
/*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio,
|
||||
FileList, getBlurredShadowSupport*/
|
||||
|
||||
var morphicVersion = '2014-September-30';
|
||||
var morphicVersion = '2014-December-05';
|
||||
var modules = {}; // keep track of additional loaded modules
|
||||
var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug
|
||||
|
||||
|
@ -1925,7 +1931,9 @@ Rectangle.prototype.round = function () {
|
|||
|
||||
Rectangle.prototype.spread = function () {
|
||||
// round me by applying floor() to my origin and ceil() to my corner
|
||||
return this.origin.floor().corner(this.corner.ceil());
|
||||
// expand by 1 to be on the safe side, this eliminates rounding
|
||||
// artefacts caused by Safari's auto-scaling on retina displays
|
||||
return this.origin.floor().corner(this.corner.ceil()).expandBy(1);
|
||||
};
|
||||
|
||||
Rectangle.prototype.amountToTranslateWithin = function (aRect) {
|
||||
|
@ -3972,6 +3980,7 @@ PenMorph.prototype.init = function () {
|
|||
this.size = 1;
|
||||
this.wantsRedraw = false;
|
||||
this.penPoint = 'tip'; // or 'center"
|
||||
this.penBounds = null; // rect around the visible arrow shape
|
||||
|
||||
HandleMorph.uber.init.call(this);
|
||||
this.setExtent(new Point(size, size));
|
||||
|
@ -3994,11 +4003,9 @@ PenMorph.prototype.changed = function () {
|
|||
// PenMorph display:
|
||||
|
||||
PenMorph.prototype.drawNew = function (facing) {
|
||||
/*
|
||||
my orientation can be overridden with the "facing" parameter to
|
||||
implement Scratch-style rotation styles
|
||||
// my orientation can be overridden with the "facing" parameter to
|
||||
// implement Scratch-style rotation styles
|
||||
|
||||
*/
|
||||
var context, start, dest, left, right, len,
|
||||
direction = facing || this.heading;
|
||||
|
||||
|
@ -4021,6 +4028,15 @@ PenMorph.prototype.drawNew = function (facing) {
|
|||
right = start.distanceAngle(len * 0.33, direction - 230);
|
||||
}
|
||||
|
||||
// cache penBounds
|
||||
this.penBounds = new Rectangle(
|
||||
Math.min(start.x, dest.x, left.x, right.x),
|
||||
Math.min(start.y, dest.y, left.y, right.y),
|
||||
Math.max(start.x, dest.x, left.x, right.x),
|
||||
Math.max(start.y, dest.y, left.y, right.y)
|
||||
);
|
||||
|
||||
// draw arrow shape
|
||||
context.fillStyle = this.color.toString();
|
||||
context.beginPath();
|
||||
|
||||
|
@ -4037,7 +4053,6 @@ PenMorph.prototype.drawNew = function (facing) {
|
|||
context.lineWidth = 1;
|
||||
context.stroke();
|
||||
context.fill();
|
||||
|
||||
};
|
||||
|
||||
// PenMorph access:
|
||||
|
@ -5754,7 +5769,7 @@ SliderMorph.prototype.rangeSize = function () {
|
|||
};
|
||||
|
||||
SliderMorph.prototype.ratio = function () {
|
||||
return this.size / this.rangeSize();
|
||||
return this.size / (this.rangeSize() + 1);
|
||||
};
|
||||
|
||||
SliderMorph.prototype.unitSize = function () {
|
||||
|
@ -9346,11 +9361,11 @@ HandMorph.prototype.init = function (aWorld) {
|
|||
this.world = aWorld;
|
||||
this.mouseButton = null;
|
||||
this.mouseOverList = [];
|
||||
this.mouseDownMorph = null;
|
||||
this.morphToGrab = null;
|
||||
this.grabOrigin = null;
|
||||
this.temporaries = [];
|
||||
this.touchHoldTimeout = null;
|
||||
this.contextMenuEnabled = false;
|
||||
};
|
||||
|
||||
HandMorph.prototype.changed = function () {
|
||||
|
@ -9494,9 +9509,10 @@ HandMorph.prototype.drop = function () {
|
|||
*/
|
||||
|
||||
HandMorph.prototype.processMouseDown = function (event) {
|
||||
var morph, expectedClick, actualClick;
|
||||
var morph, actualClick;
|
||||
|
||||
this.destroyTemporaries();
|
||||
this.contextMenuEnabled = true;
|
||||
this.morphToGrab = null;
|
||||
if (this.children.length !== 0) {
|
||||
this.drop();
|
||||
|
@ -9529,15 +9545,9 @@ HandMorph.prototype.processMouseDown = function (event) {
|
|||
if (event.button === 2 || event.ctrlKey) {
|
||||
this.mouseButton = 'right';
|
||||
actualClick = 'mouseDownRight';
|
||||
expectedClick = 'mouseClickRight';
|
||||
} else {
|
||||
this.mouseButton = 'left';
|
||||
actualClick = 'mouseDownLeft';
|
||||
expectedClick = 'mouseClickLeft';
|
||||
}
|
||||
this.mouseDownMorph = morph;
|
||||
while (!this.mouseDownMorph[expectedClick]) {
|
||||
this.mouseDownMorph = this.mouseDownMorph.parent;
|
||||
}
|
||||
while (!morph[actualClick]) {
|
||||
morph = morph.parent;
|
||||
|
@ -9596,7 +9606,7 @@ HandMorph.prototype.processMouseUp = function () {
|
|||
expectedClick = 'mouseClickLeft';
|
||||
} else {
|
||||
expectedClick = 'mouseClickRight';
|
||||
if (this.mouseButton) {
|
||||
if (this.mouseButton && this.contextMenuEnabled) {
|
||||
context = morph;
|
||||
contextMenu = context.contextMenu();
|
||||
while ((!contextMenu) &&
|
||||
|
@ -9654,16 +9664,18 @@ HandMorph.prototype.processMouseMove = function (event) {
|
|||
// mouseOverNew = this.allMorphsAtPointer();
|
||||
mouseOverNew = this.morphAtPointer().allParents();
|
||||
|
||||
if ((this.children.length === 0) &&
|
||||
(this.mouseButton === 'left')) {
|
||||
if (!this.children.length && this.mouseButton) {
|
||||
topMorph = this.morphAtPointer();
|
||||
morph = topMorph.rootForGrab();
|
||||
if (topMorph.mouseMove) {
|
||||
topMorph.mouseMove(pos);
|
||||
topMorph.mouseMove(pos, this.mouseButton);
|
||||
if (this.mouseButton === 'right') {
|
||||
this.contextMenuEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// if a morph is marked for grabbing, just grab it
|
||||
if (this.morphToGrab) {
|
||||
if (this.mouseButton === 'left' && this.morphToGrab) {
|
||||
if (this.morphToGrab.isDraggable) {
|
||||
morph = this.morphToGrab;
|
||||
this.grab(morph);
|
||||
|
|
76
objects.js
76
objects.js
|
@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.objects = '2014-October-08';
|
||||
modules.objects = '2014-December-04';
|
||||
|
||||
var SpriteMorph;
|
||||
var StageMorph;
|
||||
|
@ -1151,6 +1151,13 @@ SpriteMorph.prototype.initBlocks = function () {
|
|||
category: 'lists',
|
||||
spec: 'map %repRing over %l'
|
||||
},
|
||||
doForEach: {
|
||||
dev: true,
|
||||
type: 'command',
|
||||
category: 'lists',
|
||||
spec: 'for %upvar in %l %cs',
|
||||
defaults: [localize('each item')]
|
||||
},
|
||||
|
||||
// Code mapping - experimental
|
||||
doMapCodeOrHeader: { // experimental
|
||||
|
@ -1674,6 +1681,20 @@ SpriteMorph.prototype.blockTemplates = function (category) {
|
|||
return menu;
|
||||
}
|
||||
|
||||
function addVar(pair) {
|
||||
if (pair) {
|
||||
if (myself.variables.silentFind(pair[0])) {
|
||||
myself.inform('that name is already in use');
|
||||
} else {
|
||||
myself.addVariable(pair[0], pair[1]);
|
||||
myself.toggleVariableWatcher(pair[0], pair[1]);
|
||||
myself.blocksCache[cat] = null;
|
||||
myself.paletteCache[cat] = null;
|
||||
myself.parentThatIsA(IDE_Morph).refreshPalette();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cat === 'motion') {
|
||||
|
||||
blocks.push(block('forward'));
|
||||
|
@ -1967,15 +1988,7 @@ SpriteMorph.prototype.blockTemplates = function (category) {
|
|||
function () {
|
||||
new VariableDialogMorph(
|
||||
null,
|
||||
function (pair) {
|
||||
if (pair && !myself.variables.silentFind(pair[0])) {
|
||||
myself.addVariable(pair[0], pair[1]);
|
||||
myself.toggleVariableWatcher(pair[0], pair[1]);
|
||||
myself.blocksCache[cat] = null;
|
||||
myself.paletteCache[cat] = null;
|
||||
myself.parentThatIsA(IDE_Morph).refreshPalette();
|
||||
}
|
||||
},
|
||||
addVar,
|
||||
myself
|
||||
).prompt(
|
||||
'Variable name',
|
||||
|
@ -2057,6 +2070,8 @@ SpriteMorph.prototype.blockTemplates = function (category) {
|
|||
blocks.push(txt);
|
||||
blocks.push('-');
|
||||
blocks.push(block('reportMap'));
|
||||
blocks.push('-');
|
||||
blocks.push(block('doForEach'));
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
|
@ -2164,8 +2179,8 @@ SpriteMorph.prototype.freshPalette = function (category) {
|
|||
var defs = SpriteMorph.prototype.blocks,
|
||||
hiddens = StageMorph.prototype.hiddenPrimitives;
|
||||
return Object.keys(hiddens).some(function (any) {
|
||||
return defs[any].category === category ||
|
||||
contains((more[category] || []), any);
|
||||
return !isNil(defs[any]) && (defs[any].category === category
|
||||
|| contains((more[category] || []), any));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2204,7 +2219,7 @@ SpriteMorph.prototype.freshPalette = function (category) {
|
|||
var hiddens = StageMorph.prototype.hiddenPrimitives,
|
||||
defs = SpriteMorph.prototype.blocks;
|
||||
Object.keys(hiddens).forEach(function (sel) {
|
||||
if (defs[sel].category === category) {
|
||||
if (defs[sel] && (defs[sel].category === category)) {
|
||||
delete StageMorph.prototype.hiddenPrimitives[sel];
|
||||
}
|
||||
});
|
||||
|
@ -3232,8 +3247,11 @@ SpriteMorph.prototype.setCenter = function (aPoint, justMe) {
|
|||
|
||||
SpriteMorph.prototype.nestingBounds = function () {
|
||||
// same as fullBounds(), except that it uses "parts" instead of children
|
||||
var result;
|
||||
result = this.bounds;
|
||||
// and special cases the costume-less "arrow" shape's bounding box
|
||||
var result = this.bounds;
|
||||
if (!this.costume && this.penBounds) {
|
||||
result = this.penBounds.translateBy(this.position());
|
||||
}
|
||||
this.parts.forEach(function (part) {
|
||||
if (part.isVisible) {
|
||||
result = result.merge(part.nestingBounds());
|
||||
|
@ -4858,6 +4876,20 @@ StageMorph.prototype.blockTemplates = function (category) {
|
|||
);
|
||||
}
|
||||
|
||||
function addVar(pair) {
|
||||
if (pair) {
|
||||
if (myself.variables.silentFind(pair[0])) {
|
||||
myself.inform('that name is already in use');
|
||||
} else {
|
||||
myself.addVariable(pair[0], pair[1]);
|
||||
myself.toggleVariableWatcher(pair[0], pair[1]);
|
||||
myself.blocksCache[cat] = null;
|
||||
myself.paletteCache[cat] = null;
|
||||
myself.parentThatIsA(IDE_Morph).refreshPalette();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cat === 'motion') {
|
||||
|
||||
txt = new TextMorph(localize(
|
||||
|
@ -5099,15 +5131,7 @@ StageMorph.prototype.blockTemplates = function (category) {
|
|||
function () {
|
||||
new VariableDialogMorph(
|
||||
null,
|
||||
function (pair) {
|
||||
if (pair && !myself.variables.silentFind(pair[0])) {
|
||||
myself.addVariable(pair[0], pair[1]);
|
||||
myself.toggleVariableWatcher(pair[0], pair[1]);
|
||||
myself.blocksCache[cat] = null;
|
||||
myself.paletteCache[cat] = null;
|
||||
myself.parentThatIsA(IDE_Morph).refreshPalette();
|
||||
}
|
||||
},
|
||||
addVar,
|
||||
myself
|
||||
).prompt(
|
||||
'Variable name',
|
||||
|
@ -5183,6 +5207,8 @@ StageMorph.prototype.blockTemplates = function (category) {
|
|||
blocks.push(txt);
|
||||
blocks.push('-');
|
||||
blocks.push(block('reportMap'));
|
||||
blocks.push('-');
|
||||
blocks.push(block('doForEach'));
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
|
@ -5321,7 +5347,7 @@ StageMorph.prototype.thumbnail = function (extentPoint, excludedSprite) {
|
|||
this.dimensions.y * this.scale
|
||||
);
|
||||
this.children.forEach(function (morph) {
|
||||
if (morph !== excludedSprite) {
|
||||
if (morph.isVisible && (morph !== excludedSprite)) {
|
||||
fb = morph.fullBounds();
|
||||
fimg = morph.fullImage();
|
||||
if (fimg.width && fimg.height) {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Snap! Build Your Own Blocks. Beta</title>
|
||||
<link rel="shortcut icon" href="http://snap.berkeley.edu/fav3.gif" type="image/gif">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<script type="text/javascript" src="morphic.js"></script>
|
||||
<script type="text/javascript" src="widgets.js"></script>
|
||||
<script type="text/javascript" src="blocks.js"></script>
|
||||
|
@ -33,5 +33,5 @@
|
|||
</head>
|
||||
<body style="margin: 0;">
|
||||
<canvas id="world" tabindex="1" style="position: absolute;" />
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
|
11
store.js
11
store.js
|
@ -61,7 +61,7 @@ SyntaxElementMorph, Variable*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.store = '2014-October-01';
|
||||
modules.store = '2014-December-06';
|
||||
|
||||
|
||||
// XML_Serializer ///////////////////////////////////////////////////////
|
||||
|
@ -261,7 +261,9 @@ SnapSerializer.prototype.watcherLabels = {
|
|||
yPosition: 'y position',
|
||||
direction: 'direction',
|
||||
getScale: 'size',
|
||||
getTempo: 'tempo',
|
||||
getLastAnswer: 'answer',
|
||||
getLastMessage: 'message',
|
||||
getTimer: 'timer',
|
||||
getCostumeIdx: 'costume #',
|
||||
reportMouseX: 'mouse x',
|
||||
|
@ -1199,6 +1201,10 @@ SnapSerializer.prototype.loadValue = function (model) {
|
|||
if (el) {
|
||||
v.outerContext = this.loadValue(el);
|
||||
}
|
||||
if (v.outerContext && v.receiver &&
|
||||
!v.outerContext.variables.parentFrame) {
|
||||
v.outerContext.variables.parentFrame = v.receiver.variables;
|
||||
}
|
||||
return v;
|
||||
case 'costume':
|
||||
center = new Point();
|
||||
|
@ -1824,9 +1830,8 @@ Context.prototype.toXML = function (serializer) {
|
|||
return '';
|
||||
}
|
||||
return serializer.format(
|
||||
'<context% ~><inputs>%</inputs><variables>%</variables>' +
|
||||
'<context ~><inputs>%</inputs><variables>%</variables>' +
|
||||
'%<receiver>%</receiver>%</context>',
|
||||
this.isLambda ? ' lambda="lambda"' : '',
|
||||
this.inputs.reduce(
|
||||
function (xml, input) {
|
||||
return xml + serializer.format('<input>$</input>', input);
|
||||
|
|
443
threads.js
443
threads.js
|
@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.threads = '2014-October-01';
|
||||
modules.threads = '2014-December-11';
|
||||
|
||||
var ThreadManager;
|
||||
var Process;
|
||||
|
@ -100,8 +100,18 @@ function snapEquals(a, b) {
|
|||
|
||||
var x = +a,
|
||||
y = +b,
|
||||
i,
|
||||
specials = [true, false, ''];
|
||||
|
||||
// "zum Schneckengang verdorben, was Adlerflug geworden wäre"
|
||||
// collecting edge-cases that somebody complained about
|
||||
// on Github. Folks, take it easy and keep it fun, okay?
|
||||
// Shit like this is patently ugly and slows Snap down. Tnx!
|
||||
for (i = 9; i <= 13; i += 1) {
|
||||
specials.push(String.fromCharCode(i));
|
||||
}
|
||||
specials.push(String.fromCharCode(160));
|
||||
|
||||
// check for special values before coercing to numbers
|
||||
if (isNaN(x) || isNaN(y) ||
|
||||
[a, b].some(function (any) {return contains(specials, any) ||
|
||||
|
@ -110,7 +120,7 @@ function snapEquals(a, b) {
|
|||
y = b;
|
||||
}
|
||||
|
||||
// handle text comparision case-insensitive.
|
||||
// handle text comparison case-insensitive.
|
||||
if (isString(x) && isString(y)) {
|
||||
return x.toLowerCase() === y.toLowerCase();
|
||||
}
|
||||
|
@ -136,7 +146,8 @@ ThreadManager.prototype.toggleProcess = function (block) {
|
|||
ThreadManager.prototype.startProcess = function (
|
||||
block,
|
||||
isThreadSafe,
|
||||
exportResult
|
||||
exportResult,
|
||||
callback
|
||||
) {
|
||||
var active = this.findProcess(block),
|
||||
top = block.topBlock(),
|
||||
|
@ -149,7 +160,7 @@ ThreadManager.prototype.startProcess = function (
|
|||
this.removeTerminatedProcesses();
|
||||
}
|
||||
top.addHighlight();
|
||||
newProc = new Process(block.topBlock());
|
||||
newProc = new Process(block.topBlock(), callback);
|
||||
newProc.exportResult = exportResult;
|
||||
this.processes.push(newProc);
|
||||
return newProc;
|
||||
|
@ -207,11 +218,10 @@ ThreadManager.prototype.resumeAll = function (stage) {
|
|||
};
|
||||
|
||||
ThreadManager.prototype.step = function () {
|
||||
/*
|
||||
run each process until it gives up control, skipping processes
|
||||
for sprites that are currently picked up, then filter out any
|
||||
processes that have been terminated
|
||||
*/
|
||||
// run each process until it gives up control, skipping processes
|
||||
// for sprites that are currently picked up, then filter out any
|
||||
// processes that have been terminated
|
||||
|
||||
this.processes.forEach(function (proc) {
|
||||
if (!proc.homeContext.receiver.isPickedUp() && !proc.isDead) {
|
||||
proc.runStep();
|
||||
|
@ -225,8 +235,9 @@ ThreadManager.prototype.removeTerminatedProcesses = function () {
|
|||
var remaining = [];
|
||||
this.processes.forEach(function (proc) {
|
||||
if (!proc.isRunning() && !proc.errorFlag && !proc.isDead) {
|
||||
proc.topBlock.removeHighlight();
|
||||
|
||||
if (proc.topBlock instanceof BlockMorph) {
|
||||
proc.topBlock.removeHighlight();
|
||||
}
|
||||
if (proc.prompter) {
|
||||
proc.prompter.destroy();
|
||||
if (proc.homeContext.receiver.stopTalking) {
|
||||
|
@ -235,18 +246,22 @@ ThreadManager.prototype.removeTerminatedProcesses = function () {
|
|||
}
|
||||
|
||||
if (proc.topBlock instanceof ReporterBlockMorph) {
|
||||
if (proc.homeContext.inputs[0] instanceof List) {
|
||||
proc.topBlock.showBubble(
|
||||
new ListWatcherMorph(
|
||||
proc.homeContext.inputs[0]
|
||||
),
|
||||
proc.exportResult
|
||||
);
|
||||
if (proc.onComplete instanceof Function) {
|
||||
proc.onComplete(proc.homeContext.inputs[0]);
|
||||
} else {
|
||||
proc.topBlock.showBubble(
|
||||
proc.homeContext.inputs[0],
|
||||
proc.exportResult
|
||||
);
|
||||
if (proc.homeContext.inputs[0] instanceof List) {
|
||||
proc.topBlock.showBubble(
|
||||
new ListWatcherMorph(
|
||||
proc.homeContext.inputs[0]
|
||||
),
|
||||
proc.exportResult
|
||||
);
|
||||
} else {
|
||||
proc.topBlock.showBubble(
|
||||
proc.homeContext.inputs[0],
|
||||
proc.exportResult
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -295,9 +310,9 @@ ThreadManager.prototype.findProcess = function (block) {
|
|||
are children
|
||||
receiver object (sprite) to which the process applies,
|
||||
cached from the top block
|
||||
context the Context describing the current state
|
||||
context the Context describing the current state
|
||||
of this process
|
||||
homeContext stores information relevant to the whole process,
|
||||
homeContext stores information relevant to the whole process,
|
||||
i.e. its receiver, result etc.
|
||||
isPaused boolean indicating whether to pause
|
||||
readyToYield boolean indicating whether to yield control to
|
||||
|
@ -305,15 +320,20 @@ ThreadManager.prototype.findProcess = function (block) {
|
|||
readyToTerminate boolean indicating whether the stop method has
|
||||
been called
|
||||
isDead boolean indicating a terminated clone process
|
||||
timeout msecs after which to force yield
|
||||
lastYield msecs when the process last yielded
|
||||
errorFlag boolean indicating whether an error was encountered
|
||||
timeout msecs after which to force yield
|
||||
lastYield msecs when the process last yielded
|
||||
errorFlag boolean indicating whether an error was encountered
|
||||
prompter active instance of StagePrompterMorph
|
||||
httpRequest active instance of an HttpRequest or null
|
||||
pauseOffset msecs between the start of an interpolated operation
|
||||
and when the process was paused
|
||||
exportResult boolean flag indicating whether a picture of the top
|
||||
block along with the result bubble shoud be exported
|
||||
onComplete an optional callback function to be executed when
|
||||
the process is done
|
||||
procedureCount number counting procedure call entries,
|
||||
used to tag custom block calls, so "stop block"
|
||||
invocations can catch them
|
||||
*/
|
||||
|
||||
Process.prototype = {};
|
||||
|
@ -321,7 +341,7 @@ Process.prototype.contructor = Process;
|
|||
Process.prototype.timeout = 500; // msecs after which to force yield
|
||||
Process.prototype.isCatchingErrors = true;
|
||||
|
||||
function Process(topBlock) {
|
||||
function Process(topBlock, onComplete) {
|
||||
this.topBlock = topBlock || null;
|
||||
|
||||
this.readyToYield = false;
|
||||
|
@ -338,6 +358,8 @@ function Process(topBlock) {
|
|||
this.pauseOffset = null;
|
||||
this.frameCount = 0;
|
||||
this.exportResult = false;
|
||||
this.onComplete = onComplete || null;
|
||||
this.procedureCount = 0;
|
||||
|
||||
if (topBlock) {
|
||||
this.homeContext.receiver = topBlock.receiver();
|
||||
|
@ -361,13 +383,13 @@ Process.prototype.isRunning = function () {
|
|||
// Process entry points
|
||||
|
||||
Process.prototype.runStep = function () {
|
||||
/*
|
||||
a step is an an uninterruptable 'atom', it can consist
|
||||
of several contexts, even of several blocks
|
||||
*/
|
||||
// a step is an an uninterruptable 'atom', it can consist
|
||||
// of several contexts, even of several blocks
|
||||
|
||||
if (this.isPaused) { // allow pausing in between atomic steps:
|
||||
return this.pauseStep();
|
||||
}
|
||||
|
||||
this.readyToYield = false;
|
||||
while (!this.readyToYield
|
||||
&& this.context
|
||||
|
@ -435,8 +457,10 @@ Process.prototype.pauseStep = function () {
|
|||
|
||||
Process.prototype.evaluateContext = function () {
|
||||
var exp = this.context.expression;
|
||||
|
||||
this.frameCount += 1;
|
||||
if (this.context.tag === 'exit') {
|
||||
this.expectReport();
|
||||
}
|
||||
if (exp instanceof Array) {
|
||||
return this.evaluateSequence(exp);
|
||||
}
|
||||
|
@ -460,7 +484,7 @@ Process.prototype.evaluateContext = function () {
|
|||
|
||||
Process.prototype.evaluateBlock = function (block, argCount) {
|
||||
// check for special forms
|
||||
if (contains(['reportOr', 'reportAnd'], block.selector)) {
|
||||
if (contains(['reportOr', 'reportAnd', 'doReport'], block.selector)) {
|
||||
return this[block.selector](block);
|
||||
}
|
||||
|
||||
|
@ -526,6 +550,35 @@ Process.prototype.reportAnd = function (block) {
|
|||
}
|
||||
};
|
||||
|
||||
Process.prototype.doReport = function (block) {
|
||||
var outer = this.context.outerContext;
|
||||
if (this.context.expression.partOfCustomCommand) {
|
||||
this.doStopCustomBlock();
|
||||
this.popContext();
|
||||
} else {
|
||||
while (this.context && this.context.tag !== 'exit') {
|
||||
if (this.context.expression === 'doStopWarping') {
|
||||
this.doStopWarping();
|
||||
} else {
|
||||
this.popContext();
|
||||
}
|
||||
}
|
||||
if (this.context) {
|
||||
if (this.context.expression === 'expectReport') {
|
||||
// pop off inserted top-level exit context
|
||||
this.popContext();
|
||||
} else {
|
||||
// un-tag and preserve original caller
|
||||
this.context.tag = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// in any case evaluate (and ignore)
|
||||
// the input, because it could be
|
||||
// and HTTP Request for a hardware extension
|
||||
this.pushContext(block.inputs()[0], outer);
|
||||
};
|
||||
|
||||
// Process: Non-Block evaluation
|
||||
|
||||
Process.prototype.evaluateMultiSlot = function (multiSlot, argCount) {
|
||||
|
@ -586,7 +639,6 @@ Process.prototype.evaluateInput = function (input) {
|
|||
) || (input instanceof CSlotMorph && !input.isStatic)) {
|
||||
// I know, this still needs yet to be done right....
|
||||
ans = this.reify(ans, new List());
|
||||
ans.isImplicitLambda = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -597,8 +649,6 @@ Process.prototype.evaluateInput = function (input) {
|
|||
Process.prototype.evaluateSequence = function (arr) {
|
||||
var pc = this.context.pc,
|
||||
outer = this.context.outerContext,
|
||||
isLambda = this.context.isLambda,
|
||||
isImplicitLambda = this.context.isImplicitLambda,
|
||||
isCustomBlock = this.context.isCustomBlock;
|
||||
if (pc === (arr.length - 1)) { // tail call elimination
|
||||
this.context = new Context(
|
||||
|
@ -607,8 +657,6 @@ Process.prototype.evaluateSequence = function (arr) {
|
|||
this.context.outerContext,
|
||||
this.context.receiver
|
||||
);
|
||||
this.context.isLambda = isLambda;
|
||||
this.context.isImplicitLambda = isImplicitLambda;
|
||||
this.context.isCustomBlock = isCustomBlock;
|
||||
} else {
|
||||
if (pc >= arr.length) {
|
||||
|
@ -626,8 +674,8 @@ Process.prototype.evaluateSequence = function (arr) {
|
|||
Caution: we cannot just revert to this version of the method, because to make
|
||||
tail call elimination work many tweaks had to be done to various primitives.
|
||||
For the most part these tweaks are about schlepping the outer context (for
|
||||
the variable bindings) and the isLambda flag along, and are indicated by a
|
||||
short comment in the code. But to really revert would take a good measure
|
||||
the variable bindings) and the isCustomBlock flag along, and are indicated
|
||||
by a short comment in the code. But to really revert would take a good measure
|
||||
of trial and error as well as debugging. In the developers file archive there
|
||||
is a version of threads.js dated 120119(2) which basically resembles the
|
||||
last version before introducing tail call optimization on 120123.
|
||||
|
@ -679,6 +727,10 @@ Process.prototype.doYield = function () {
|
|||
}
|
||||
};
|
||||
|
||||
Process.prototype.expectReport = function () {
|
||||
this.handleError(new Error("reporter didn't report"));
|
||||
};
|
||||
|
||||
// Process Exception Handling
|
||||
|
||||
Process.prototype.handleError = function (error, element) {
|
||||
|
@ -757,8 +809,8 @@ Process.prototype.reportJSFunction = function (parmNames, body) {
|
|||
);
|
||||
};
|
||||
|
||||
Process.prototype.doRun = function (context, args, isCustomBlock) {
|
||||
return this.evaluate(context, args, true, isCustomBlock);
|
||||
Process.prototype.doRun = function (context, args) {
|
||||
return this.evaluate(context, args, true);
|
||||
};
|
||||
|
||||
Process.prototype.evaluate = function (
|
||||
|
@ -781,8 +833,9 @@ Process.prototype.evaluate = function (
|
|||
}
|
||||
|
||||
var outer = new Context(null, null, context.outerContext),
|
||||
caller = this.context.parentContext,
|
||||
exit,
|
||||
runnable,
|
||||
extra,
|
||||
parms = args.asArray(),
|
||||
i,
|
||||
value;
|
||||
|
@ -796,25 +849,13 @@ Process.prototype.evaluate = function (
|
|||
outer,
|
||||
context.receiver
|
||||
);
|
||||
extra = new Context(runnable, 'doYield');
|
||||
this.context.parentContext = runnable;
|
||||
|
||||
/*
|
||||
Note: if the context's expression is a ReporterBlockMorph,
|
||||
the extra context gets popped off immediately without taking
|
||||
effect (i.e. it doesn't yield within evaluating a stack of
|
||||
nested reporters)
|
||||
*/
|
||||
|
||||
if (isCommand || (context.expression instanceof ReporterBlockMorph)) {
|
||||
this.context.parentContext = extra;
|
||||
} else {
|
||||
this.context.parentContext = runnable;
|
||||
if (context.expression instanceof ReporterBlockMorph) {
|
||||
// auto-"warp" nested reporters
|
||||
this.readyToYield = (Date.now() - this.lastYield > this.timeout);
|
||||
}
|
||||
|
||||
runnable.isLambda = true;
|
||||
runnable.isImplicitLambda = context.isImplicitLambda;
|
||||
runnable.isCustomBlock = false;
|
||||
|
||||
// assign parameters if any were passed
|
||||
if (parms.length > 0) {
|
||||
|
||||
|
@ -849,8 +890,9 @@ Process.prototype.evaluate = function (
|
|||
|
||||
} else if (context.emptySlots !== 1) {
|
||||
throw new Error(
|
||||
'expecting ' + context.emptySlots + ' input(s), '
|
||||
+ 'but getting ' + parms.length
|
||||
localize('expecting') + ' ' + context.emptySlots + ' '
|
||||
+ localize('input(s), but getting') + ' '
|
||||
+ parms.length
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -858,6 +900,23 @@ Process.prototype.evaluate = function (
|
|||
|
||||
if (runnable.expression instanceof CommandBlockMorph) {
|
||||
runnable.expression = runnable.expression.blockSequence();
|
||||
if (!isCommand) {
|
||||
if (caller) {
|
||||
// tag caller, so "report" can catch it later
|
||||
caller.tag = 'exit';
|
||||
} else {
|
||||
// top-level context, insert a tagged exit context
|
||||
// which "report" can catch later
|
||||
exit = new Context(
|
||||
runnable.parentContext,
|
||||
'expectReport',
|
||||
outer,
|
||||
outer.receiver
|
||||
);
|
||||
exit.tag = 'exit';
|
||||
runnable.parentContext = exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -867,6 +926,9 @@ Process.prototype.fork = function (context, args) {
|
|||
'continuations cannot be forked'
|
||||
);
|
||||
}
|
||||
if (!(context instanceof Context)) {
|
||||
throw new Error('expecting a ring but getting ' + context);
|
||||
}
|
||||
|
||||
var outer = new Context(null, null, context.outerContext),
|
||||
runnable = new Context(null,
|
||||
|
@ -879,8 +941,6 @@ Process.prototype.fork = function (context, args) {
|
|||
stage = this.homeContext.receiver.parentThatIsA(StageMorph),
|
||||
proc = new Process();
|
||||
|
||||
runnable.isLambda = true;
|
||||
|
||||
// assign parameters if any were passed
|
||||
if (parms.length > 0) {
|
||||
|
||||
|
@ -915,8 +975,9 @@ Process.prototype.fork = function (context, args) {
|
|||
|
||||
} else if (context.emptySlots !== 1) {
|
||||
throw new Error(
|
||||
'expecting ' + context.emptySlots + ' input(s), '
|
||||
+ 'but getting ' + parms.length
|
||||
localize('expecting') + ' ' + context.emptySlots + ' '
|
||||
+ localize('input(s), but getting') + ' '
|
||||
+ parms.length
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -933,68 +994,48 @@ Process.prototype.fork = function (context, args) {
|
|||
stage.threads.processes.push(proc);
|
||||
};
|
||||
|
||||
Process.prototype.doReport = function (value, isCSlot) {
|
||||
while (this.context && !this.context.isLambda) {
|
||||
if (this.context.expression === 'doStopWarping') {
|
||||
this.doStopWarping();
|
||||
} else {
|
||||
this.popContext();
|
||||
}
|
||||
}
|
||||
if (this.context && this.context.isImplicitLambda) {
|
||||
if (this.context.expression === 'doStopWarping') {
|
||||
this.doStopWarping();
|
||||
} else {
|
||||
this.popContext();
|
||||
}
|
||||
return this.doReport(value, true);
|
||||
}
|
||||
if (this.context && this.context.isCustomBlock) {
|
||||
// now I'm back at the custom block sequence.
|
||||
// advance my pc to my expression's length
|
||||
this.context.pc = this.context.expression.length - 1;
|
||||
}
|
||||
if (isCSlot) {
|
||||
if (this.context &&
|
||||
this.context.parentContext &&
|
||||
this.context.parentContext.expression instanceof Array) {
|
||||
this.popContext();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
// Process stopping blocks primitives
|
||||
|
||||
Process.prototype.doStopBlock = function () {
|
||||
this.doReport();
|
||||
var target = this.context.expression.exitTag;
|
||||
if (isNil(target)) {
|
||||
return this.doStopCustomBlock();
|
||||
}
|
||||
while (this.context &&
|
||||
(isNil(this.context.tag) || (this.context.tag > target))) {
|
||||
if (this.context.expression === 'doStopWarping') {
|
||||
this.doStopWarping();
|
||||
} else {
|
||||
this.popContext();
|
||||
}
|
||||
}
|
||||
this.pushContext();
|
||||
};
|
||||
|
||||
// Process evaluation variants, commented out for now (redundant)
|
||||
|
||||
/*
|
||||
Process.prototype.doRunWithInputList = function (context, args) {
|
||||
// provide an extra selector for the palette
|
||||
return this.doRun(context, args);
|
||||
Process.prototype.doStopCustomBlock = function () {
|
||||
// fallback solution for "report" blocks inside
|
||||
// custom command definitions and untagged "stop" blocks
|
||||
while (this.context && !this.context.isCustomBlock) {
|
||||
if (this.context.expression === 'doStopWarping') {
|
||||
this.doStopWarping();
|
||||
} else {
|
||||
this.popContext();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Process.prototype.evaluateWithInputList = function (context, args) {
|
||||
// provide an extra selector for the palette
|
||||
return this.evaluate(context, args);
|
||||
};
|
||||
|
||||
Process.prototype.forkWithInputList = function (context, args) {
|
||||
// provide an extra selector for the palette
|
||||
return this.fork(context, args);
|
||||
};
|
||||
*/
|
||||
|
||||
// Process continuations primitives
|
||||
|
||||
Process.prototype.doCallCC = function (aContext) {
|
||||
this.evaluate(aContext, new List([this.context.continuation()]));
|
||||
Process.prototype.doCallCC = function (aContext, isReporter) {
|
||||
this.evaluate(
|
||||
aContext,
|
||||
new List([this.context.continuation()]),
|
||||
!isReporter
|
||||
);
|
||||
};
|
||||
|
||||
Process.prototype.reportCallCC = function (aContext) {
|
||||
this.doCallCC(aContext);
|
||||
this.doCallCC(aContext, true);
|
||||
};
|
||||
|
||||
Process.prototype.runContinuation = function (aContext, args) {
|
||||
|
@ -1012,19 +1053,21 @@ Process.prototype.runContinuation = function (aContext, args) {
|
|||
// Process custom block primitives
|
||||
|
||||
Process.prototype.evaluateCustomBlock = function () {
|
||||
var context = this.context.expression.definition.body,
|
||||
var caller = this.context.parentContext,
|
||||
context = this.context.expression.definition.body,
|
||||
declarations = this.context.expression.definition.declarations,
|
||||
args = new List(this.context.inputs),
|
||||
parms = args.asArray(),
|
||||
runnable,
|
||||
extra,
|
||||
exit,
|
||||
i,
|
||||
value,
|
||||
outer;
|
||||
|
||||
if (!context) {return null; }
|
||||
this.procedureCount += 1;
|
||||
outer = new Context();
|
||||
outer.receiver = this.context.receiver; // || this.homeContext.receiver;
|
||||
outer.receiver = this.context.receiver;
|
||||
outer.variables.parentFrame = outer.receiver ?
|
||||
outer.receiver.variables : null;
|
||||
|
||||
|
@ -1032,15 +1075,10 @@ Process.prototype.evaluateCustomBlock = function () {
|
|||
this.context.parentContext,
|
||||
context.expression,
|
||||
outer,
|
||||
outer.receiver,
|
||||
true // is custom block
|
||||
outer.receiver
|
||||
);
|
||||
extra = new Context(runnable, 'doYield');
|
||||
|
||||
this.context.parentContext = extra;
|
||||
|
||||
runnable.isLambda = true;
|
||||
runnable.isCustomBlock = true;
|
||||
this.context.parentContext = runnable;
|
||||
|
||||
// passing parameters if any were passed
|
||||
if (parms.length > 0) {
|
||||
|
@ -1062,9 +1100,43 @@ Process.prototype.evaluateCustomBlock = function () {
|
|||
}
|
||||
}
|
||||
|
||||
if (runnable.expression instanceof CommandBlockMorph) {
|
||||
runnable.expression = runnable.expression.blockSequence();
|
||||
// tag return target
|
||||
if (this.context.expression.definition.type !== 'command') {
|
||||
if (caller) {
|
||||
// tag caller, so "report" can catch it later
|
||||
caller.tag = 'exit';
|
||||
} else {
|
||||
// top-level context, insert a tagged exit context
|
||||
// which "report" can catch later
|
||||
exit = new Context(
|
||||
runnable.parentContext,
|
||||
'expectReport',
|
||||
outer,
|
||||
outer.receiver
|
||||
);
|
||||
exit.tag = 'exit';
|
||||
runnable.parentContext = exit;
|
||||
}
|
||||
// auto-"warp" nested reporters
|
||||
this.readyToYield = (Date.now() - this.lastYield > this.timeout);
|
||||
} else {
|
||||
// tag all "stop this block" blocks with the current
|
||||
// procedureCount as exitTag, and mark all "report" blocks
|
||||
// as being inside a custom command definition
|
||||
runnable.expression.tagExitBlocks(this.procedureCount, true);
|
||||
|
||||
// tag the caller with the current procedure count, so
|
||||
// "stop this block" blocks can catch it, but only
|
||||
// if the caller hasn't been tagged already
|
||||
if (caller && !caller.tag) {
|
||||
caller.tag = this.procedureCount;
|
||||
}
|
||||
// yield commands unless explicitly "warped"
|
||||
if (!this.isAtomic) {
|
||||
this.readyToYield = true;
|
||||
}
|
||||
}
|
||||
runnable.expression = runnable.expression.blockSequence();
|
||||
};
|
||||
|
||||
// Process variables primitives
|
||||
|
@ -1148,7 +1220,7 @@ Process.prototype.doShowVar = function (varName) {
|
|||
if (isGlobal || target.owner) {
|
||||
label = name;
|
||||
} else {
|
||||
label = name + ' (temporary)';
|
||||
label = name + ' ' + localize('(temporary)');
|
||||
}
|
||||
watcher = new WatcherMorph(
|
||||
label,
|
||||
|
@ -1259,7 +1331,7 @@ Process.prototype.doInsertInList = function (element, index, list) {
|
|||
return null;
|
||||
}
|
||||
if (this.inputOption(index) === 'any') {
|
||||
idx = this.reportRandom(1, list.length());
|
||||
idx = this.reportRandom(1, list.length() + 1);
|
||||
}
|
||||
if (this.inputOption(index) === 'last') {
|
||||
idx = list.length() + 1;
|
||||
|
@ -1308,16 +1380,12 @@ Process.prototype.reportListContainsItem = function (list, element) {
|
|||
Process.prototype.doIf = function () {
|
||||
var args = this.context.inputs,
|
||||
outer = this.context.outerContext, // for tail call elimination
|
||||
isLambda = this.context.isLambda,
|
||||
isImplicitLambda = this.context.isImplicitLambda,
|
||||
isCustomBlock = this.context.isCustomBlock;
|
||||
|
||||
this.popContext();
|
||||
if (args[0]) {
|
||||
if (args[1]) {
|
||||
this.pushContext(args[1].blockSequence(), outer);
|
||||
this.context.isLambda = isLambda;
|
||||
this.context.isImplicitLambda = isImplicitLambda;
|
||||
this.context.isCustomBlock = isCustomBlock;
|
||||
}
|
||||
}
|
||||
|
@ -1327,8 +1395,6 @@ Process.prototype.doIf = function () {
|
|||
Process.prototype.doIfElse = function () {
|
||||
var args = this.context.inputs,
|
||||
outer = this.context.outerContext, // for tail call elimination
|
||||
isLambda = this.context.isLambda,
|
||||
isImplicitLambda = this.context.isImplicitLambda,
|
||||
isCustomBlock = this.context.isCustomBlock;
|
||||
|
||||
this.popContext();
|
||||
|
@ -1344,8 +1410,6 @@ Process.prototype.doIfElse = function () {
|
|||
}
|
||||
}
|
||||
if (this.context) {
|
||||
this.context.isLambda = isLambda;
|
||||
this.context.isImplicitLambda = isImplicitLambda;
|
||||
this.context.isCustomBlock = isCustomBlock;
|
||||
}
|
||||
|
||||
|
@ -1420,8 +1484,6 @@ Process.prototype.doStopOthers = function (choice) {
|
|||
Process.prototype.doWarp = function (body) {
|
||||
// execute my contents block atomically (more or less)
|
||||
var outer = this.context.outerContext, // for tail call elimination
|
||||
isLambda = this.context.isLambda,
|
||||
isImplicitLambda = this.context.isImplicitLambda,
|
||||
isCustomBlock = this.context.isCustomBlock,
|
||||
stage;
|
||||
|
||||
|
@ -1438,13 +1500,8 @@ Process.prototype.doWarp = function (body) {
|
|||
stage.fps = 0; // variable frame rate
|
||||
}
|
||||
}
|
||||
|
||||
this.pushContext('doYield');
|
||||
|
||||
this.context.isLambda = isLambda;
|
||||
this.context.isImplicitLambda = isImplicitLambda;
|
||||
this.context.isCustomBlock = isCustomBlock;
|
||||
|
||||
if (!this.isAtomic) {
|
||||
this.pushContext('doStopWarping');
|
||||
}
|
||||
|
@ -1523,28 +1580,19 @@ Process.prototype.doForever = function (body) {
|
|||
Process.prototype.doRepeat = function (counter, body) {
|
||||
var block = this.context.expression,
|
||||
outer = this.context.outerContext, // for tail call elimination
|
||||
isLambda = this.context.isLambda,
|
||||
isImplicitLambda = this.context.isImplicitLambda,
|
||||
isCustomBlock = this.context.isCustomBlock;
|
||||
|
||||
if (counter < 1) { // was '=== 0', which caused infinite loops on non-ints
|
||||
return null;
|
||||
}
|
||||
this.popContext();
|
||||
|
||||
this.pushContext(block, outer);
|
||||
|
||||
this.context.isLambda = isLambda;
|
||||
this.context.isImplicitLambda = isImplicitLambda;
|
||||
this.context.isCustomBlock = isCustomBlock;
|
||||
|
||||
this.context.addInput(counter - 1);
|
||||
|
||||
this.pushContext('doYield');
|
||||
if (body) {
|
||||
this.pushContext(body.blockSequence());
|
||||
}
|
||||
|
||||
this.pushContext();
|
||||
};
|
||||
|
||||
|
@ -1631,6 +1679,27 @@ Process.prototype.reportMap = function (reporter, list) {
|
|||
}
|
||||
};
|
||||
|
||||
Process.prototype.doForEach = function (upvar, list, script) {
|
||||
// perform a script for each element of a list, assigning the
|
||||
// current iteration's element to a variable with the name
|
||||
// specified in the "upvar" parameter, so it can be referenced
|
||||
// within the script. Uses the context's - unused - fourth
|
||||
// element as temporary storage for the current list index
|
||||
|
||||
if (isNil(this.context.inputs[3])) {this.context.inputs[3] = 1; }
|
||||
var index = this.context.inputs[3];
|
||||
this.context.outerContext.variables.addVar(upvar);
|
||||
this.context.outerContext.variables.setVar(
|
||||
upvar,
|
||||
list.at(index)
|
||||
);
|
||||
if (index > list.length()) {return; }
|
||||
this.context.inputs[3] += 1;
|
||||
this.pushContext('doYield');
|
||||
this.pushContext();
|
||||
this.evaluate(script, new List(), true);
|
||||
};
|
||||
|
||||
// Process interpolated primitives
|
||||
|
||||
Process.prototype.doWait = function (secs) {
|
||||
|
@ -2134,15 +2203,17 @@ Process.prototype.reportTextSplit = function (string, delimiter) {
|
|||
str,
|
||||
del;
|
||||
if (!contains(types, strType)) {
|
||||
throw new Error('expecting a text instad of a ' + strType);
|
||||
throw new Error('expecting text instead of a ' + strType);
|
||||
}
|
||||
if (!contains(types, delType)) {
|
||||
throw new Error('expecting a text delimiter instad of a ' + delType);
|
||||
throw new Error('expecting a text delimiter instead of a ' + delType);
|
||||
}
|
||||
str = (string || '').toString();
|
||||
switch (this.inputOption(delimiter)) {
|
||||
case 'line':
|
||||
del = '\n';
|
||||
// Unicode Compliant Line Splitting (Platform independent)
|
||||
// http://www.unicode.org/reports/tr18/#Line_Boundaries
|
||||
del = /\r\n|[\n\v\f\r\x85\u2028\u2029]/;
|
||||
break;
|
||||
case 'tab':
|
||||
del = '\t';
|
||||
|
@ -2151,7 +2222,9 @@ Process.prototype.reportTextSplit = function (string, delimiter) {
|
|||
del = '\r';
|
||||
break;
|
||||
case 'whitespace':
|
||||
return new List(str.trim().split(/[\t\r\n ]+/));
|
||||
str = str.trim();
|
||||
del = /\s+/;
|
||||
break;
|
||||
case 'letter':
|
||||
del = '';
|
||||
break;
|
||||
|
@ -2316,6 +2389,7 @@ Process.prototype.objectTouchingObject = function (thisObj, name) {
|
|||
var myself = this,
|
||||
those,
|
||||
stage,
|
||||
box,
|
||||
mouse;
|
||||
|
||||
if (this.inputOption(name) === 'mouse-pointer') {
|
||||
|
@ -2327,9 +2401,14 @@ Process.prototype.objectTouchingObject = function (thisObj, name) {
|
|||
} else {
|
||||
stage = thisObj.parentThatIsA(StageMorph);
|
||||
if (stage) {
|
||||
if (this.inputOption(name) === 'edge' &&
|
||||
!stage.bounds.containsRectangle(thisObj.bounds)) {
|
||||
return true;
|
||||
if (this.inputOption(name) === 'edge') {
|
||||
box = thisObj.bounds;
|
||||
if (!thisObj.costume && thisObj.penBounds) {
|
||||
box = thisObj.penBounds.translateBy(thisObj.position());
|
||||
}
|
||||
if (!stage.bounds.containsRectangle(box)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (this.inputOption(name) === 'pen trails' &&
|
||||
thisObj.isTouching(stage.penTrailsMorph())) {
|
||||
|
@ -2760,25 +2839,25 @@ Process.prototype.reportFrameCount = function () {
|
|||
|
||||
structure:
|
||||
|
||||
parentContext the Context to return to when this one has
|
||||
parentContext the Context to return to when this one has
|
||||
been evaluated.
|
||||
outerContext the Context holding my lexical scope
|
||||
expression SyntaxElementMorph, an array of blocks to evaluate,
|
||||
expression SyntaxElementMorph, an array of blocks to evaluate,
|
||||
null or a String denoting a selector, e.g. 'doYield'
|
||||
receiver the object to which the expression applies, if any
|
||||
variables the current VariableFrame, if any
|
||||
inputs an array of input values computed so far
|
||||
variables the current VariableFrame, if any
|
||||
inputs an array of input values computed so far
|
||||
(if expression is a BlockMorph)
|
||||
pc the index of the next block to evaluate
|
||||
pc the index of the next block to evaluate
|
||||
(if expression is an array)
|
||||
startTime time when the context was first evaluated
|
||||
startValue initial value for interpolated operations
|
||||
startTime time when the context was first evaluated
|
||||
startValue initial value for interpolated operations
|
||||
activeAudio audio buffer for interpolated operations, don't persist
|
||||
activeNote audio oscillator for interpolated ops, don't persist
|
||||
isLambda marker for return ops
|
||||
isImplicitLambda marker for return ops
|
||||
isCustomBlock marker for return ops
|
||||
emptySlots caches the number of empty slots for reification
|
||||
emptySlots caches the number of empty slots for reification
|
||||
tag string or number to optionally identify the Context,
|
||||
as a "return" target (for the "stop block" primitive)
|
||||
*/
|
||||
|
||||
function Context(
|
||||
|
@ -2801,22 +2880,19 @@ function Context(
|
|||
this.startTime = null;
|
||||
this.activeAudio = null;
|
||||
this.activeNote = null;
|
||||
this.isLambda = false; // marks the end of a lambda
|
||||
this.isImplicitLambda = false; // marks the end of a C-shaped slot
|
||||
this.isCustomBlock = false; // marks the end of a custom block's stack
|
||||
this.emptySlots = 0; // used for block reification
|
||||
this.tag = null; // lexical catch-tag for custom blocks
|
||||
}
|
||||
|
||||
Context.prototype.toString = function () {
|
||||
var pref = this.isLambda ? '\u03BB-' : '',
|
||||
expr = this.expression;
|
||||
|
||||
var expr = this.expression;
|
||||
if (expr instanceof Array) {
|
||||
if (expr.length > 0) {
|
||||
expr = '[' + expr[0] + ']';
|
||||
}
|
||||
}
|
||||
return pref + 'Context >> ' + expr + ' ' + this.variables;
|
||||
return 'Context >> ' + expr + ' ' + this.variables;
|
||||
};
|
||||
|
||||
Context.prototype.image = function () {
|
||||
|
@ -2870,9 +2946,10 @@ Context.prototype.continuation = function () {
|
|||
} else if (this.parentContext) {
|
||||
cont = this.parentContext;
|
||||
} else {
|
||||
return new Context(null, 'doStop');
|
||||
return new Context(null, 'doYield');
|
||||
}
|
||||
cont = cont.copyForContinuation();
|
||||
cont.tag = null;
|
||||
cont.isContinuation = true;
|
||||
return cont;
|
||||
};
|
||||
|
@ -3003,9 +3080,9 @@ VariableFrame.prototype.find = function (name) {
|
|||
var frame = this.silentFind(name);
|
||||
if (frame) {return frame; }
|
||||
throw new Error(
|
||||
'a variable of name \''
|
||||
localize('a variable of name \'')
|
||||
+ name
|
||||
+ '\'\ndoes not exist in this context'
|
||||
+ localize('\'\ndoes not exist in this context')
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -3070,9 +3147,9 @@ VariableFrame.prototype.getVar = function (name) {
|
|||
return '';
|
||||
}
|
||||
throw new Error(
|
||||
'a variable of name \''
|
||||
localize('a variable of name \'')
|
||||
+ name
|
||||
+ '\'\ndoes not exist in this context'
|
||||
+ localize('\'\ndoes not exist in this context')
|
||||
);
|
||||
};
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
Ładowanie…
Reference in New Issue