Merge remote-tracking branch 'upstream/master'

pull/3/merge
Erik Olsson 2014-12-14 18:48:03 +01:00
commit 57bef2b736
15 zmienionych plików z 1799 dodań i 261 usunięć

Wyświetl plik

@ -155,7 +155,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/
// Global stuff //////////////////////////////////////////////////////// // Global stuff ////////////////////////////////////////////////////////
modules.blocks = '2014-October-01'; modules.blocks = '2014-November-21';
var SyntaxElementMorph; var SyntaxElementMorph;
@ -394,10 +394,10 @@ SyntaxElementMorph.prototype.allInputs = function () {
}; };
SyntaxElementMorph.prototype.allEmptySlots = function () { SyntaxElementMorph.prototype.allEmptySlots = function () {
/* // answer empty input slots of all children excluding myself,
answer empty input slots of all children excluding myself, // but omit those in nested rings (lambdas) and JS-Function primitives.
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 = []; var empty = [];
if (!(this instanceof RingMorph) && if (!(this instanceof RingMorph) &&
(this.selector !== 'reportJSFunction')) { (this.selector !== 'reportJSFunction')) {
@ -412,6 +412,26 @@ SyntaxElementMorph.prototype.allEmptySlots = function () {
return empty; 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) { SyntaxElementMorph.prototype.replaceInput = function (oldArg, newArg) {
var scripts = this.parentThatIsA(ScriptsMorph), var scripts = this.parentThatIsA(ScriptsMorph),
replacement = newArg, replacement = newArg,
@ -3169,9 +3189,14 @@ BlockMorph.prototype.snap = function () {
I inherit from BlockMorph adding the following most important I inherit from BlockMorph adding the following most important
public accessors: public accessors:
nextBlock() - set / get the block attached to my bottom nextBlock() - set / get the block attached to my bottom
bottomBlock() - answer the bottom block of my stack bottomBlock() - answer the bottom block of my stack
blockSequence() - answer an array of blocks starting with myself 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: // CommandBlockMorph inherits from BlockMorph:
@ -3189,6 +3214,8 @@ function CommandBlockMorph() {
CommandBlockMorph.prototype.init = function () { CommandBlockMorph.prototype.init = function () {
CommandBlockMorph.uber.init.call(this); CommandBlockMorph.uber.init.call(this);
this.setExtent(new Point(200, 100)); this.setExtent(new Point(200, 100));
this.partOfCustomCommand = false;
this.exitTag = null;
}; };
// CommandBlockMorph enumerating: // CommandBlockMorph enumerating:

Wyświetl plik

@ -106,7 +106,7 @@ SymbolMorph, isNil*/
// Global stuff //////////////////////////////////////////////////////// // Global stuff ////////////////////////////////////////////////////////
modules.byob = '2014-September-30'; modules.byob = '2014-November-20';
// Declarations // Declarations
@ -688,7 +688,8 @@ CustomCommandBlockMorph.prototype.labelPart = function (spec) {
return CustomCommandBlockMorph.uber.labelPart.call(this, spec); return CustomCommandBlockMorph.uber.labelPart.call(this, spec);
} }
if ((spec[0] === '%') && (spec.length > 1)) { 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 { } else {
part = new BlockLabelFragmentMorph(spec); part = new BlockLabelFragmentMorph(spec);
part.fontSize = this.fontSize; part.fontSize = this.fontSize;

BIN
favicon.ico 100644

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 7.9 KiB

18
gui.js
Wyświetl plik

@ -69,7 +69,7 @@ SpeechBubbleMorph*/
// Global stuff //////////////////////////////////////////////////////// // Global stuff ////////////////////////////////////////////////////////
modules.gui = '2014-October-06'; modules.gui = '2014-December-04';
// Declarations // Declarations
@ -233,10 +233,6 @@ IDE_Morph.prototype.init = function (isAutoFill) {
IDE_Morph.prototype.openIn = function (world) { IDE_Morph.prototype.openIn = function (world) {
var hash, usr, myself = this, urlLanguage = null; var hash, usr, myself = this, urlLanguage = null;
this.buildPanes();
world.add(this);
world.userMenu = this.userMenu;
// get persistent user data, if any // get persistent user data, if any
if (localStorage) { if (localStorage) {
usr = localStorage['-snap-user']; usr = localStorage['-snap-user'];
@ -245,10 +241,17 @@ IDE_Morph.prototype.openIn = function (world) {
if (usr) { if (usr) {
SnapCloud.username = usr.username || null; SnapCloud.username = usr.username || null;
SnapCloud.password = usr.password || 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 // override SnapCloud's user message with Morphic
SnapCloud.message = function (string) { SnapCloud.message = function (string) {
var m = new MenuMorph(null, 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' + 'jens@moenig.org, bh@cs.berkeley.edu\n\n'
+ 'Snap! is developed by the University of California, Berkeley\n' + 'Snap! is developed by the University of California, Berkeley\n'
+ ' with support from the National Science Foundation ' + ' with support from the National Science Foundation, '
+ 'and MioSoft. \n' + 'MioSoft, \n'
+ 'and the Communications Design Group at SAP Labs. \n'
+ 'The design of Snap! is influenced and inspired by Scratch,\n' + 'The design of Snap! is influenced and inspired by Scratch,\n'
+ 'from the Lifelong Kindergarten group at the MIT Media Lab\n\n' + 'from the Lifelong Kindergarten group at the MIT Media Lab\n\n'

Wyświetl plik

@ -2309,3 +2309,88 @@ ______
141008 141008
------ ------
* Objects: fixed #608, #610 * 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 BLOCKs 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

1273
lang-kn.js 100644

Plik diff jest za duży Load Diff

Wyświetl plik

@ -650,11 +650,12 @@ SnapTranslator.dict.pt = {
'Save As...': 'Save As...':
'Guardar este projecto como…', 'Guardar este projecto como…',
'Import...': 'Import...':
'Importar para este projecto…', 'Importar…',
'file menu import hint': 'file menu import hint':
'Importar para este projecto\num projecto exportado,\n' 'Abrir um projecto exportado,\n'
+ 'uma biblioteca de blocos,\n' + 'substitundo o projecto corrente, ou\n'
+ 'um traje ou um som.', + 'importar uma biblioteca de blocos, um\n'
+ 'traje ou um som para o projecto corrente.',
'Export project as plain text...': 'Export project as plain text...':
'Exportar este projecto como texto simples…', 'Exportar este projecto como texto simples…',
'Export project...': 'Export project...':
@ -1616,7 +1617,7 @@ SnapTranslator.dict.pt = {
'unshared.': 'unshared.':
'deixado de partilhar.', 'deixado de partilhar.',
'Unshare': 'Unshare':
'Deixar de Partilhar', 'Não Partilhar',
'password has been changed.': 'password has been changed.':
'a sua palavra-passe foi alterada.', 'a sua palavra-passe foi alterada.',
'SVG costumes are\nnot yet fully supported\nin every browser': '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.', 'Seleccionar um traje da biblioteca de média.',
'edit rotation point only...': 'edit rotation point only...':
'editar apenas ponto de rotação…', '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 // produção de código
'map %cmdRing to %codeKind %code': 'map %cmdRing to %codeKind %code':

Wyświetl plik

@ -61,7 +61,7 @@ PushButtonMorph, SyntaxElementMorph, Color, Point, WatcherMorph,
StringMorph, SpriteMorph, ScrollFrameMorph, CellMorph, ArrowMorph, StringMorph, SpriteMorph, ScrollFrameMorph, CellMorph, ArrowMorph,
MenuMorph, snapEquals, Morph, isNil, localize, MorphicPreferences*/ MenuMorph, snapEquals, Morph, isNil, localize, MorphicPreferences*/
modules.lists = '2014-July-28'; modules.lists = '2014-November-20';
var List; var List;
var ListWatcherMorph; var ListWatcherMorph;
@ -125,6 +125,9 @@ List.prototype.changed = function () {
List.prototype.cons = function (car, cdr) { List.prototype.cons = function (car, cdr) {
var answer = new List(); 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.first = isNil(car) ? null : car;
answer.rest = cdr || null; answer.rest = cdr || null;
answer.isLinked = true; answer.isLinked = true;

Wyświetl plik

@ -42,7 +42,7 @@
/*global modules, contains*/ /*global modules, contains*/
modules.locale = '2014-October-01'; modules.locale = '2014-December-02';
// Global stuff // Global stuff
@ -427,3 +427,15 @@ SnapTranslator.dict.bn = {
'last_changed': 'last_changed':
'2014-07-02' '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'
};

Wyświetl plik

@ -464,9 +464,15 @@
MyMorph.prototype.mouseMove = function(pos) {}; 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 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 Events may be "bubbled" up a morph's owner chain by calling
@ -1035,7 +1041,7 @@
/*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio, /*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio,
FileList, getBlurredShadowSupport*/ FileList, getBlurredShadowSupport*/
var morphicVersion = '2014-September-30'; var morphicVersion = '2014-December-05';
var modules = {}; // keep track of additional loaded modules var modules = {}; // keep track of additional loaded modules
var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug
@ -1925,7 +1931,9 @@ Rectangle.prototype.round = function () {
Rectangle.prototype.spread = function () { Rectangle.prototype.spread = function () {
// round me by applying floor() to my origin and ceil() to my corner // 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) { Rectangle.prototype.amountToTranslateWithin = function (aRect) {
@ -3972,6 +3980,7 @@ PenMorph.prototype.init = function () {
this.size = 1; this.size = 1;
this.wantsRedraw = false; this.wantsRedraw = false;
this.penPoint = 'tip'; // or 'center" this.penPoint = 'tip'; // or 'center"
this.penBounds = null; // rect around the visible arrow shape
HandleMorph.uber.init.call(this); HandleMorph.uber.init.call(this);
this.setExtent(new Point(size, size)); this.setExtent(new Point(size, size));
@ -3994,11 +4003,9 @@ PenMorph.prototype.changed = function () {
// PenMorph display: // PenMorph display:
PenMorph.prototype.drawNew = function (facing) { PenMorph.prototype.drawNew = function (facing) {
/* // my orientation can be overridden with the "facing" parameter to
my orientation can be overridden with the "facing" parameter to // implement Scratch-style rotation styles
implement Scratch-style rotation styles
*/
var context, start, dest, left, right, len, var context, start, dest, left, right, len,
direction = facing || this.heading; direction = facing || this.heading;
@ -4021,6 +4028,15 @@ PenMorph.prototype.drawNew = function (facing) {
right = start.distanceAngle(len * 0.33, direction - 230); 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.fillStyle = this.color.toString();
context.beginPath(); context.beginPath();
@ -4037,7 +4053,6 @@ PenMorph.prototype.drawNew = function (facing) {
context.lineWidth = 1; context.lineWidth = 1;
context.stroke(); context.stroke();
context.fill(); context.fill();
}; };
// PenMorph access: // PenMorph access:
@ -5754,7 +5769,7 @@ SliderMorph.prototype.rangeSize = function () {
}; };
SliderMorph.prototype.ratio = function () { SliderMorph.prototype.ratio = function () {
return this.size / this.rangeSize(); return this.size / (this.rangeSize() + 1);
}; };
SliderMorph.prototype.unitSize = function () { SliderMorph.prototype.unitSize = function () {
@ -9346,11 +9361,11 @@ HandMorph.prototype.init = function (aWorld) {
this.world = aWorld; this.world = aWorld;
this.mouseButton = null; this.mouseButton = null;
this.mouseOverList = []; this.mouseOverList = [];
this.mouseDownMorph = null;
this.morphToGrab = null; this.morphToGrab = null;
this.grabOrigin = null; this.grabOrigin = null;
this.temporaries = []; this.temporaries = [];
this.touchHoldTimeout = null; this.touchHoldTimeout = null;
this.contextMenuEnabled = false;
}; };
HandMorph.prototype.changed = function () { HandMorph.prototype.changed = function () {
@ -9494,9 +9509,10 @@ HandMorph.prototype.drop = function () {
*/ */
HandMorph.prototype.processMouseDown = function (event) { HandMorph.prototype.processMouseDown = function (event) {
var morph, expectedClick, actualClick; var morph, actualClick;
this.destroyTemporaries(); this.destroyTemporaries();
this.contextMenuEnabled = true;
this.morphToGrab = null; this.morphToGrab = null;
if (this.children.length !== 0) { if (this.children.length !== 0) {
this.drop(); this.drop();
@ -9529,15 +9545,9 @@ HandMorph.prototype.processMouseDown = function (event) {
if (event.button === 2 || event.ctrlKey) { if (event.button === 2 || event.ctrlKey) {
this.mouseButton = 'right'; this.mouseButton = 'right';
actualClick = 'mouseDownRight'; actualClick = 'mouseDownRight';
expectedClick = 'mouseClickRight';
} else { } else {
this.mouseButton = 'left'; this.mouseButton = 'left';
actualClick = 'mouseDownLeft'; actualClick = 'mouseDownLeft';
expectedClick = 'mouseClickLeft';
}
this.mouseDownMorph = morph;
while (!this.mouseDownMorph[expectedClick]) {
this.mouseDownMorph = this.mouseDownMorph.parent;
} }
while (!morph[actualClick]) { while (!morph[actualClick]) {
morph = morph.parent; morph = morph.parent;
@ -9596,7 +9606,7 @@ HandMorph.prototype.processMouseUp = function () {
expectedClick = 'mouseClickLeft'; expectedClick = 'mouseClickLeft';
} else { } else {
expectedClick = 'mouseClickRight'; expectedClick = 'mouseClickRight';
if (this.mouseButton) { if (this.mouseButton && this.contextMenuEnabled) {
context = morph; context = morph;
contextMenu = context.contextMenu(); contextMenu = context.contextMenu();
while ((!contextMenu) && while ((!contextMenu) &&
@ -9654,16 +9664,18 @@ HandMorph.prototype.processMouseMove = function (event) {
// mouseOverNew = this.allMorphsAtPointer(); // mouseOverNew = this.allMorphsAtPointer();
mouseOverNew = this.morphAtPointer().allParents(); mouseOverNew = this.morphAtPointer().allParents();
if ((this.children.length === 0) && if (!this.children.length && this.mouseButton) {
(this.mouseButton === 'left')) {
topMorph = this.morphAtPointer(); topMorph = this.morphAtPointer();
morph = topMorph.rootForGrab(); morph = topMorph.rootForGrab();
if (topMorph.mouseMove) { 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 a morph is marked for grabbing, just grab it
if (this.morphToGrab) { if (this.mouseButton === 'left' && this.morphToGrab) {
if (this.morphToGrab.isDraggable) { if (this.morphToGrab.isDraggable) {
morph = this.morphToGrab; morph = this.morphToGrab;
this.grab(morph); this.grab(morph);

Wyświetl plik

@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/
// Global stuff //////////////////////////////////////////////////////// // Global stuff ////////////////////////////////////////////////////////
modules.objects = '2014-October-08'; modules.objects = '2014-December-04';
var SpriteMorph; var SpriteMorph;
var StageMorph; var StageMorph;
@ -1151,6 +1151,13 @@ SpriteMorph.prototype.initBlocks = function () {
category: 'lists', category: 'lists',
spec: 'map %repRing over %l' 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 // Code mapping - experimental
doMapCodeOrHeader: { // experimental doMapCodeOrHeader: { // experimental
@ -1674,6 +1681,20 @@ SpriteMorph.prototype.blockTemplates = function (category) {
return menu; 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') { if (cat === 'motion') {
blocks.push(block('forward')); blocks.push(block('forward'));
@ -1967,15 +1988,7 @@ SpriteMorph.prototype.blockTemplates = function (category) {
function () { function () {
new VariableDialogMorph( new VariableDialogMorph(
null, null,
function (pair) { addVar,
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();
}
},
myself myself
).prompt( ).prompt(
'Variable name', 'Variable name',
@ -2057,6 +2070,8 @@ SpriteMorph.prototype.blockTemplates = function (category) {
blocks.push(txt); blocks.push(txt);
blocks.push('-'); blocks.push('-');
blocks.push(block('reportMap')); blocks.push(block('reportMap'));
blocks.push('-');
blocks.push(block('doForEach'));
} }
///////////////////////////////// /////////////////////////////////
@ -2164,8 +2179,8 @@ SpriteMorph.prototype.freshPalette = function (category) {
var defs = SpriteMorph.prototype.blocks, var defs = SpriteMorph.prototype.blocks,
hiddens = StageMorph.prototype.hiddenPrimitives; hiddens = StageMorph.prototype.hiddenPrimitives;
return Object.keys(hiddens).some(function (any) { return Object.keys(hiddens).some(function (any) {
return defs[any].category === category || return !isNil(defs[any]) && (defs[any].category === category
contains((more[category] || []), any); || contains((more[category] || []), any));
}); });
} }
@ -2204,7 +2219,7 @@ SpriteMorph.prototype.freshPalette = function (category) {
var hiddens = StageMorph.prototype.hiddenPrimitives, var hiddens = StageMorph.prototype.hiddenPrimitives,
defs = SpriteMorph.prototype.blocks; defs = SpriteMorph.prototype.blocks;
Object.keys(hiddens).forEach(function (sel) { Object.keys(hiddens).forEach(function (sel) {
if (defs[sel].category === category) { if (defs[sel] && (defs[sel].category === category)) {
delete StageMorph.prototype.hiddenPrimitives[sel]; delete StageMorph.prototype.hiddenPrimitives[sel];
} }
}); });
@ -3232,8 +3247,11 @@ SpriteMorph.prototype.setCenter = function (aPoint, justMe) {
SpriteMorph.prototype.nestingBounds = function () { SpriteMorph.prototype.nestingBounds = function () {
// same as fullBounds(), except that it uses "parts" instead of children // same as fullBounds(), except that it uses "parts" instead of children
var result; // and special cases the costume-less "arrow" shape's bounding box
result = this.bounds; var result = this.bounds;
if (!this.costume && this.penBounds) {
result = this.penBounds.translateBy(this.position());
}
this.parts.forEach(function (part) { this.parts.forEach(function (part) {
if (part.isVisible) { if (part.isVisible) {
result = result.merge(part.nestingBounds()); 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') { if (cat === 'motion') {
txt = new TextMorph(localize( txt = new TextMorph(localize(
@ -5099,15 +5131,7 @@ StageMorph.prototype.blockTemplates = function (category) {
function () { function () {
new VariableDialogMorph( new VariableDialogMorph(
null, null,
function (pair) { addVar,
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();
}
},
myself myself
).prompt( ).prompt(
'Variable name', 'Variable name',
@ -5183,6 +5207,8 @@ StageMorph.prototype.blockTemplates = function (category) {
blocks.push(txt); blocks.push(txt);
blocks.push('-'); blocks.push('-');
blocks.push(block('reportMap')); 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.dimensions.y * this.scale
); );
this.children.forEach(function (morph) { this.children.forEach(function (morph) {
if (morph !== excludedSprite) { if (morph.isVisible && (morph !== excludedSprite)) {
fb = morph.fullBounds(); fb = morph.fullBounds();
fimg = morph.fullImage(); fimg = morph.fullImage();
if (fimg.width && fimg.height) { if (fimg.width && fimg.height) {

Wyświetl plik

@ -3,7 +3,7 @@
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Snap! Build Your Own Blocks. Beta</title> <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="morphic.js"></script>
<script type="text/javascript" src="widgets.js"></script> <script type="text/javascript" src="widgets.js"></script>
<script type="text/javascript" src="blocks.js"></script> <script type="text/javascript" src="blocks.js"></script>
@ -33,5 +33,5 @@
</head> </head>
<body style="margin: 0;"> <body style="margin: 0;">
<canvas id="world" tabindex="1" style="position: absolute;" /> <canvas id="world" tabindex="1" style="position: absolute;" />
</body> </body>
</html> </html>

Wyświetl plik

@ -61,7 +61,7 @@ SyntaxElementMorph, Variable*/
// Global stuff //////////////////////////////////////////////////////// // Global stuff ////////////////////////////////////////////////////////
modules.store = '2014-October-01'; modules.store = '2014-December-06';
// XML_Serializer /////////////////////////////////////////////////////// // XML_Serializer ///////////////////////////////////////////////////////
@ -261,7 +261,9 @@ SnapSerializer.prototype.watcherLabels = {
yPosition: 'y position', yPosition: 'y position',
direction: 'direction', direction: 'direction',
getScale: 'size', getScale: 'size',
getTempo: 'tempo',
getLastAnswer: 'answer', getLastAnswer: 'answer',
getLastMessage: 'message',
getTimer: 'timer', getTimer: 'timer',
getCostumeIdx: 'costume #', getCostumeIdx: 'costume #',
reportMouseX: 'mouse x', reportMouseX: 'mouse x',
@ -1199,6 +1201,10 @@ SnapSerializer.prototype.loadValue = function (model) {
if (el) { if (el) {
v.outerContext = this.loadValue(el); v.outerContext = this.loadValue(el);
} }
if (v.outerContext && v.receiver &&
!v.outerContext.variables.parentFrame) {
v.outerContext.variables.parentFrame = v.receiver.variables;
}
return v; return v;
case 'costume': case 'costume':
center = new Point(); center = new Point();
@ -1824,9 +1830,8 @@ Context.prototype.toXML = function (serializer) {
return ''; return '';
} }
return serializer.format( return serializer.format(
'<context% ~><inputs>%</inputs><variables>%</variables>' + '<context ~><inputs>%</inputs><variables>%</variables>' +
'%<receiver>%</receiver>%</context>', '%<receiver>%</receiver>%</context>',
this.isLambda ? ' lambda="lambda"' : '',
this.inputs.reduce( this.inputs.reduce(
function (xml, input) { function (xml, input) {
return xml + serializer.format('<input>$</input>', input); return xml + serializer.format('<input>$</input>', input);

Wyświetl plik

@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/
// Global stuff //////////////////////////////////////////////////////// // Global stuff ////////////////////////////////////////////////////////
modules.threads = '2014-October-01'; modules.threads = '2014-December-11';
var ThreadManager; var ThreadManager;
var Process; var Process;
@ -100,8 +100,18 @@ function snapEquals(a, b) {
var x = +a, var x = +a,
y = +b, y = +b,
i,
specials = [true, false, '']; 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 // check for special values before coercing to numbers
if (isNaN(x) || isNaN(y) || if (isNaN(x) || isNaN(y) ||
[a, b].some(function (any) {return contains(specials, any) || [a, b].some(function (any) {return contains(specials, any) ||
@ -110,7 +120,7 @@ function snapEquals(a, b) {
y = b; y = b;
} }
// handle text comparision case-insensitive. // handle text comparison case-insensitive.
if (isString(x) && isString(y)) { if (isString(x) && isString(y)) {
return x.toLowerCase() === y.toLowerCase(); return x.toLowerCase() === y.toLowerCase();
} }
@ -136,7 +146,8 @@ ThreadManager.prototype.toggleProcess = function (block) {
ThreadManager.prototype.startProcess = function ( ThreadManager.prototype.startProcess = function (
block, block,
isThreadSafe, isThreadSafe,
exportResult exportResult,
callback
) { ) {
var active = this.findProcess(block), var active = this.findProcess(block),
top = block.topBlock(), top = block.topBlock(),
@ -149,7 +160,7 @@ ThreadManager.prototype.startProcess = function (
this.removeTerminatedProcesses(); this.removeTerminatedProcesses();
} }
top.addHighlight(); top.addHighlight();
newProc = new Process(block.topBlock()); newProc = new Process(block.topBlock(), callback);
newProc.exportResult = exportResult; newProc.exportResult = exportResult;
this.processes.push(newProc); this.processes.push(newProc);
return newProc; return newProc;
@ -207,11 +218,10 @@ ThreadManager.prototype.resumeAll = function (stage) {
}; };
ThreadManager.prototype.step = function () { ThreadManager.prototype.step = function () {
/* // run each process until it gives up control, skipping processes
run each process until it gives up control, skipping processes // for sprites that are currently picked up, then filter out any
for sprites that are currently picked up, then filter out any // processes that have been terminated
processes that have been terminated
*/
this.processes.forEach(function (proc) { this.processes.forEach(function (proc) {
if (!proc.homeContext.receiver.isPickedUp() && !proc.isDead) { if (!proc.homeContext.receiver.isPickedUp() && !proc.isDead) {
proc.runStep(); proc.runStep();
@ -225,8 +235,9 @@ ThreadManager.prototype.removeTerminatedProcesses = function () {
var remaining = []; var remaining = [];
this.processes.forEach(function (proc) { this.processes.forEach(function (proc) {
if (!proc.isRunning() && !proc.errorFlag && !proc.isDead) { if (!proc.isRunning() && !proc.errorFlag && !proc.isDead) {
proc.topBlock.removeHighlight(); if (proc.topBlock instanceof BlockMorph) {
proc.topBlock.removeHighlight();
}
if (proc.prompter) { if (proc.prompter) {
proc.prompter.destroy(); proc.prompter.destroy();
if (proc.homeContext.receiver.stopTalking) { if (proc.homeContext.receiver.stopTalking) {
@ -235,18 +246,22 @@ ThreadManager.prototype.removeTerminatedProcesses = function () {
} }
if (proc.topBlock instanceof ReporterBlockMorph) { if (proc.topBlock instanceof ReporterBlockMorph) {
if (proc.homeContext.inputs[0] instanceof List) { if (proc.onComplete instanceof Function) {
proc.topBlock.showBubble( proc.onComplete(proc.homeContext.inputs[0]);
new ListWatcherMorph(
proc.homeContext.inputs[0]
),
proc.exportResult
);
} else { } else {
proc.topBlock.showBubble( if (proc.homeContext.inputs[0] instanceof List) {
proc.homeContext.inputs[0], proc.topBlock.showBubble(
proc.exportResult new ListWatcherMorph(
); proc.homeContext.inputs[0]
),
proc.exportResult
);
} else {
proc.topBlock.showBubble(
proc.homeContext.inputs[0],
proc.exportResult
);
}
} }
} }
} else { } else {
@ -295,9 +310,9 @@ ThreadManager.prototype.findProcess = function (block) {
are children are children
receiver object (sprite) to which the process applies, receiver object (sprite) to which the process applies,
cached from the top block cached from the top block
context the Context describing the current state context the Context describing the current state
of this process 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. i.e. its receiver, result etc.
isPaused boolean indicating whether to pause isPaused boolean indicating whether to pause
readyToYield boolean indicating whether to yield control to 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 readyToTerminate boolean indicating whether the stop method has
been called been called
isDead boolean indicating a terminated clone process isDead boolean indicating a terminated clone process
timeout msecs after which to force yield timeout msecs after which to force yield
lastYield msecs when the process last yielded lastYield msecs when the process last yielded
errorFlag boolean indicating whether an error was encountered errorFlag boolean indicating whether an error was encountered
prompter active instance of StagePrompterMorph prompter active instance of StagePrompterMorph
httpRequest active instance of an HttpRequest or null httpRequest active instance of an HttpRequest or null
pauseOffset msecs between the start of an interpolated operation pauseOffset msecs between the start of an interpolated operation
and when the process was paused and when the process was paused
exportResult boolean flag indicating whether a picture of the top exportResult boolean flag indicating whether a picture of the top
block along with the result bubble shoud be exported 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 = {}; Process.prototype = {};
@ -321,7 +341,7 @@ Process.prototype.contructor = Process;
Process.prototype.timeout = 500; // msecs after which to force yield Process.prototype.timeout = 500; // msecs after which to force yield
Process.prototype.isCatchingErrors = true; Process.prototype.isCatchingErrors = true;
function Process(topBlock) { function Process(topBlock, onComplete) {
this.topBlock = topBlock || null; this.topBlock = topBlock || null;
this.readyToYield = false; this.readyToYield = false;
@ -338,6 +358,8 @@ function Process(topBlock) {
this.pauseOffset = null; this.pauseOffset = null;
this.frameCount = 0; this.frameCount = 0;
this.exportResult = false; this.exportResult = false;
this.onComplete = onComplete || null;
this.procedureCount = 0;
if (topBlock) { if (topBlock) {
this.homeContext.receiver = topBlock.receiver(); this.homeContext.receiver = topBlock.receiver();
@ -361,13 +383,13 @@ Process.prototype.isRunning = function () {
// Process entry points // Process entry points
Process.prototype.runStep = function () { Process.prototype.runStep = function () {
/* // a step is an an uninterruptable 'atom', it can consist
a step is an an uninterruptable 'atom', it can consist // of several contexts, even of several blocks
of several contexts, even of several blocks
*/
if (this.isPaused) { // allow pausing in between atomic steps: if (this.isPaused) { // allow pausing in between atomic steps:
return this.pauseStep(); return this.pauseStep();
} }
this.readyToYield = false; this.readyToYield = false;
while (!this.readyToYield while (!this.readyToYield
&& this.context && this.context
@ -435,8 +457,10 @@ Process.prototype.pauseStep = function () {
Process.prototype.evaluateContext = function () { Process.prototype.evaluateContext = function () {
var exp = this.context.expression; var exp = this.context.expression;
this.frameCount += 1; this.frameCount += 1;
if (this.context.tag === 'exit') {
this.expectReport();
}
if (exp instanceof Array) { if (exp instanceof Array) {
return this.evaluateSequence(exp); return this.evaluateSequence(exp);
} }
@ -460,7 +484,7 @@ Process.prototype.evaluateContext = function () {
Process.prototype.evaluateBlock = function (block, argCount) { Process.prototype.evaluateBlock = function (block, argCount) {
// check for special forms // check for special forms
if (contains(['reportOr', 'reportAnd'], block.selector)) { if (contains(['reportOr', 'reportAnd', 'doReport'], block.selector)) {
return this[block.selector](block); 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: Non-Block evaluation
Process.prototype.evaluateMultiSlot = function (multiSlot, argCount) { Process.prototype.evaluateMultiSlot = function (multiSlot, argCount) {
@ -586,7 +639,6 @@ Process.prototype.evaluateInput = function (input) {
) || (input instanceof CSlotMorph && !input.isStatic)) { ) || (input instanceof CSlotMorph && !input.isStatic)) {
// I know, this still needs yet to be done right.... // I know, this still needs yet to be done right....
ans = this.reify(ans, new List()); ans = this.reify(ans, new List());
ans.isImplicitLambda = true;
} }
} }
} }
@ -597,8 +649,6 @@ Process.prototype.evaluateInput = function (input) {
Process.prototype.evaluateSequence = function (arr) { Process.prototype.evaluateSequence = function (arr) {
var pc = this.context.pc, var pc = this.context.pc,
outer = this.context.outerContext, outer = this.context.outerContext,
isLambda = this.context.isLambda,
isImplicitLambda = this.context.isImplicitLambda,
isCustomBlock = this.context.isCustomBlock; isCustomBlock = this.context.isCustomBlock;
if (pc === (arr.length - 1)) { // tail call elimination if (pc === (arr.length - 1)) { // tail call elimination
this.context = new Context( this.context = new Context(
@ -607,8 +657,6 @@ Process.prototype.evaluateSequence = function (arr) {
this.context.outerContext, this.context.outerContext,
this.context.receiver this.context.receiver
); );
this.context.isLambda = isLambda;
this.context.isImplicitLambda = isImplicitLambda;
this.context.isCustomBlock = isCustomBlock; this.context.isCustomBlock = isCustomBlock;
} else { } else {
if (pc >= arr.length) { 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 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. 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 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 the variable bindings) and the isCustomBlock flag along, and are indicated
short comment in the code. But to really revert would take a good measure 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 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 is a version of threads.js dated 120119(2) which basically resembles the
last version before introducing tail call optimization on 120123. 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 Exception Handling
Process.prototype.handleError = function (error, element) { Process.prototype.handleError = function (error, element) {
@ -757,8 +809,8 @@ Process.prototype.reportJSFunction = function (parmNames, body) {
); );
}; };
Process.prototype.doRun = function (context, args, isCustomBlock) { Process.prototype.doRun = function (context, args) {
return this.evaluate(context, args, true, isCustomBlock); return this.evaluate(context, args, true);
}; };
Process.prototype.evaluate = function ( Process.prototype.evaluate = function (
@ -781,8 +833,9 @@ Process.prototype.evaluate = function (
} }
var outer = new Context(null, null, context.outerContext), var outer = new Context(null, null, context.outerContext),
caller = this.context.parentContext,
exit,
runnable, runnable,
extra,
parms = args.asArray(), parms = args.asArray(),
i, i,
value; value;
@ -796,25 +849,13 @@ Process.prototype.evaluate = function (
outer, outer,
context.receiver context.receiver
); );
extra = new Context(runnable, 'doYield'); this.context.parentContext = runnable;
/* if (context.expression instanceof ReporterBlockMorph) {
Note: if the context's expression is a ReporterBlockMorph, // auto-"warp" nested reporters
the extra context gets popped off immediately without taking this.readyToYield = (Date.now() - this.lastYield > this.timeout);
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;
} }
runnable.isLambda = true;
runnable.isImplicitLambda = context.isImplicitLambda;
runnable.isCustomBlock = false;
// assign parameters if any were passed // assign parameters if any were passed
if (parms.length > 0) { if (parms.length > 0) {
@ -849,8 +890,9 @@ Process.prototype.evaluate = function (
} else if (context.emptySlots !== 1) { } else if (context.emptySlots !== 1) {
throw new Error( throw new Error(
'expecting ' + context.emptySlots + ' input(s), ' localize('expecting') + ' ' + context.emptySlots + ' '
+ 'but getting ' + parms.length + localize('input(s), but getting') + ' '
+ parms.length
); );
} }
} }
@ -858,6 +900,23 @@ Process.prototype.evaluate = function (
if (runnable.expression instanceof CommandBlockMorph) { if (runnable.expression instanceof CommandBlockMorph) {
runnable.expression = runnable.expression.blockSequence(); 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' '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), var outer = new Context(null, null, context.outerContext),
runnable = new Context(null, runnable = new Context(null,
@ -879,8 +941,6 @@ Process.prototype.fork = function (context, args) {
stage = this.homeContext.receiver.parentThatIsA(StageMorph), stage = this.homeContext.receiver.parentThatIsA(StageMorph),
proc = new Process(); proc = new Process();
runnable.isLambda = true;
// assign parameters if any were passed // assign parameters if any were passed
if (parms.length > 0) { if (parms.length > 0) {
@ -915,8 +975,9 @@ Process.prototype.fork = function (context, args) {
} else if (context.emptySlots !== 1) { } else if (context.emptySlots !== 1) {
throw new Error( throw new Error(
'expecting ' + context.emptySlots + ' input(s), ' localize('expecting') + ' ' + context.emptySlots + ' '
+ 'but getting ' + parms.length + localize('input(s), but getting') + ' '
+ parms.length
); );
} }
} }
@ -933,68 +994,48 @@ Process.prototype.fork = function (context, args) {
stage.threads.processes.push(proc); stage.threads.processes.push(proc);
}; };
Process.prototype.doReport = function (value, isCSlot) { // Process stopping blocks primitives
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.prototype.doStopBlock = function () { 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.doStopCustomBlock = function () {
// fallback solution for "report" blocks inside
/* // custom command definitions and untagged "stop" blocks
Process.prototype.doRunWithInputList = function (context, args) { while (this.context && !this.context.isCustomBlock) {
// provide an extra selector for the palette if (this.context.expression === 'doStopWarping') {
return this.doRun(context, args); 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 continuations primitives
Process.prototype.doCallCC = function (aContext) { Process.prototype.doCallCC = function (aContext, isReporter) {
this.evaluate(aContext, new List([this.context.continuation()])); this.evaluate(
aContext,
new List([this.context.continuation()]),
!isReporter
);
}; };
Process.prototype.reportCallCC = function (aContext) { Process.prototype.reportCallCC = function (aContext) {
this.doCallCC(aContext); this.doCallCC(aContext, true);
}; };
Process.prototype.runContinuation = function (aContext, args) { Process.prototype.runContinuation = function (aContext, args) {
@ -1012,19 +1053,21 @@ Process.prototype.runContinuation = function (aContext, args) {
// Process custom block primitives // Process custom block primitives
Process.prototype.evaluateCustomBlock = function () { 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, declarations = this.context.expression.definition.declarations,
args = new List(this.context.inputs), args = new List(this.context.inputs),
parms = args.asArray(), parms = args.asArray(),
runnable, runnable,
extra, exit,
i, i,
value, value,
outer; outer;
if (!context) {return null; } if (!context) {return null; }
this.procedureCount += 1;
outer = new Context(); outer = new Context();
outer.receiver = this.context.receiver; // || this.homeContext.receiver; outer.receiver = this.context.receiver;
outer.variables.parentFrame = outer.receiver ? outer.variables.parentFrame = outer.receiver ?
outer.receiver.variables : null; outer.receiver.variables : null;
@ -1032,15 +1075,10 @@ Process.prototype.evaluateCustomBlock = function () {
this.context.parentContext, this.context.parentContext,
context.expression, context.expression,
outer, outer,
outer.receiver, outer.receiver
true // is custom block
); );
extra = new Context(runnable, 'doYield');
this.context.parentContext = extra;
runnable.isLambda = true;
runnable.isCustomBlock = true; runnable.isCustomBlock = true;
this.context.parentContext = runnable;
// passing parameters if any were passed // passing parameters if any were passed
if (parms.length > 0) { if (parms.length > 0) {
@ -1062,9 +1100,43 @@ Process.prototype.evaluateCustomBlock = function () {
} }
} }
if (runnable.expression instanceof CommandBlockMorph) { // tag return target
runnable.expression = runnable.expression.blockSequence(); 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 // Process variables primitives
@ -1148,7 +1220,7 @@ Process.prototype.doShowVar = function (varName) {
if (isGlobal || target.owner) { if (isGlobal || target.owner) {
label = name; label = name;
} else { } else {
label = name + ' (temporary)'; label = name + ' ' + localize('(temporary)');
} }
watcher = new WatcherMorph( watcher = new WatcherMorph(
label, label,
@ -1259,7 +1331,7 @@ Process.prototype.doInsertInList = function (element, index, list) {
return null; return null;
} }
if (this.inputOption(index) === 'any') { if (this.inputOption(index) === 'any') {
idx = this.reportRandom(1, list.length()); idx = this.reportRandom(1, list.length() + 1);
} }
if (this.inputOption(index) === 'last') { if (this.inputOption(index) === 'last') {
idx = list.length() + 1; idx = list.length() + 1;
@ -1308,16 +1380,12 @@ Process.prototype.reportListContainsItem = function (list, element) {
Process.prototype.doIf = function () { Process.prototype.doIf = function () {
var args = this.context.inputs, var args = this.context.inputs,
outer = this.context.outerContext, // for tail call elimination outer = this.context.outerContext, // for tail call elimination
isLambda = this.context.isLambda,
isImplicitLambda = this.context.isImplicitLambda,
isCustomBlock = this.context.isCustomBlock; isCustomBlock = this.context.isCustomBlock;
this.popContext(); this.popContext();
if (args[0]) { if (args[0]) {
if (args[1]) { if (args[1]) {
this.pushContext(args[1].blockSequence(), outer); this.pushContext(args[1].blockSequence(), outer);
this.context.isLambda = isLambda;
this.context.isImplicitLambda = isImplicitLambda;
this.context.isCustomBlock = isCustomBlock; this.context.isCustomBlock = isCustomBlock;
} }
} }
@ -1327,8 +1395,6 @@ Process.prototype.doIf = function () {
Process.prototype.doIfElse = function () { Process.prototype.doIfElse = function () {
var args = this.context.inputs, var args = this.context.inputs,
outer = this.context.outerContext, // for tail call elimination outer = this.context.outerContext, // for tail call elimination
isLambda = this.context.isLambda,
isImplicitLambda = this.context.isImplicitLambda,
isCustomBlock = this.context.isCustomBlock; isCustomBlock = this.context.isCustomBlock;
this.popContext(); this.popContext();
@ -1344,8 +1410,6 @@ Process.prototype.doIfElse = function () {
} }
} }
if (this.context) { if (this.context) {
this.context.isLambda = isLambda;
this.context.isImplicitLambda = isImplicitLambda;
this.context.isCustomBlock = isCustomBlock; this.context.isCustomBlock = isCustomBlock;
} }
@ -1420,8 +1484,6 @@ Process.prototype.doStopOthers = function (choice) {
Process.prototype.doWarp = function (body) { Process.prototype.doWarp = function (body) {
// execute my contents block atomically (more or less) // execute my contents block atomically (more or less)
var outer = this.context.outerContext, // for tail call elimination var outer = this.context.outerContext, // for tail call elimination
isLambda = this.context.isLambda,
isImplicitLambda = this.context.isImplicitLambda,
isCustomBlock = this.context.isCustomBlock, isCustomBlock = this.context.isCustomBlock,
stage; stage;
@ -1438,13 +1500,8 @@ Process.prototype.doWarp = function (body) {
stage.fps = 0; // variable frame rate stage.fps = 0; // variable frame rate
} }
} }
this.pushContext('doYield'); this.pushContext('doYield');
this.context.isLambda = isLambda;
this.context.isImplicitLambda = isImplicitLambda;
this.context.isCustomBlock = isCustomBlock; this.context.isCustomBlock = isCustomBlock;
if (!this.isAtomic) { if (!this.isAtomic) {
this.pushContext('doStopWarping'); this.pushContext('doStopWarping');
} }
@ -1523,28 +1580,19 @@ Process.prototype.doForever = function (body) {
Process.prototype.doRepeat = function (counter, body) { Process.prototype.doRepeat = function (counter, body) {
var block = this.context.expression, var block = this.context.expression,
outer = this.context.outerContext, // for tail call elimination outer = this.context.outerContext, // for tail call elimination
isLambda = this.context.isLambda,
isImplicitLambda = this.context.isImplicitLambda,
isCustomBlock = this.context.isCustomBlock; isCustomBlock = this.context.isCustomBlock;
if (counter < 1) { // was '=== 0', which caused infinite loops on non-ints if (counter < 1) { // was '=== 0', which caused infinite loops on non-ints
return null; return null;
} }
this.popContext(); this.popContext();
this.pushContext(block, outer); this.pushContext(block, outer);
this.context.isLambda = isLambda;
this.context.isImplicitLambda = isImplicitLambda;
this.context.isCustomBlock = isCustomBlock; this.context.isCustomBlock = isCustomBlock;
this.context.addInput(counter - 1); this.context.addInput(counter - 1);
this.pushContext('doYield'); this.pushContext('doYield');
if (body) { if (body) {
this.pushContext(body.blockSequence()); this.pushContext(body.blockSequence());
} }
this.pushContext(); 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 interpolated primitives
Process.prototype.doWait = function (secs) { Process.prototype.doWait = function (secs) {
@ -2134,15 +2203,17 @@ Process.prototype.reportTextSplit = function (string, delimiter) {
str, str,
del; del;
if (!contains(types, strType)) { 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)) { 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(); str = (string || '').toString();
switch (this.inputOption(delimiter)) { switch (this.inputOption(delimiter)) {
case 'line': 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; break;
case 'tab': case 'tab':
del = '\t'; del = '\t';
@ -2151,7 +2222,9 @@ Process.prototype.reportTextSplit = function (string, delimiter) {
del = '\r'; del = '\r';
break; break;
case 'whitespace': case 'whitespace':
return new List(str.trim().split(/[\t\r\n ]+/)); str = str.trim();
del = /\s+/;
break;
case 'letter': case 'letter':
del = ''; del = '';
break; break;
@ -2316,6 +2389,7 @@ Process.prototype.objectTouchingObject = function (thisObj, name) {
var myself = this, var myself = this,
those, those,
stage, stage,
box,
mouse; mouse;
if (this.inputOption(name) === 'mouse-pointer') { if (this.inputOption(name) === 'mouse-pointer') {
@ -2327,9 +2401,14 @@ Process.prototype.objectTouchingObject = function (thisObj, name) {
} else { } else {
stage = thisObj.parentThatIsA(StageMorph); stage = thisObj.parentThatIsA(StageMorph);
if (stage) { if (stage) {
if (this.inputOption(name) === 'edge' && if (this.inputOption(name) === 'edge') {
!stage.bounds.containsRectangle(thisObj.bounds)) { box = thisObj.bounds;
return true; if (!thisObj.costume && thisObj.penBounds) {
box = thisObj.penBounds.translateBy(thisObj.position());
}
if (!stage.bounds.containsRectangle(box)) {
return true;
}
} }
if (this.inputOption(name) === 'pen trails' && if (this.inputOption(name) === 'pen trails' &&
thisObj.isTouching(stage.penTrailsMorph())) { thisObj.isTouching(stage.penTrailsMorph())) {
@ -2760,25 +2839,25 @@ Process.prototype.reportFrameCount = function () {
structure: structure:
parentContext the Context to return to when this one has parentContext the Context to return to when this one has
been evaluated. been evaluated.
outerContext the Context holding my lexical scope 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' null or a String denoting a selector, e.g. 'doYield'
receiver the object to which the expression applies, if any receiver the object to which the expression applies, if any
variables the current VariableFrame, if any variables the current VariableFrame, if any
inputs an array of input values computed so far inputs an array of input values computed so far
(if expression is a BlockMorph) (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) (if expression is an array)
startTime time when the context was first evaluated startTime time when the context was first evaluated
startValue initial value for interpolated operations startValue initial value for interpolated operations
activeAudio audio buffer for interpolated operations, don't persist activeAudio audio buffer for interpolated operations, don't persist
activeNote audio oscillator for interpolated ops, 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 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( function Context(
@ -2801,22 +2880,19 @@ function Context(
this.startTime = null; this.startTime = null;
this.activeAudio = null; this.activeAudio = null;
this.activeNote = 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.isCustomBlock = false; // marks the end of a custom block's stack
this.emptySlots = 0; // used for block reification this.emptySlots = 0; // used for block reification
this.tag = null; // lexical catch-tag for custom blocks
} }
Context.prototype.toString = function () { Context.prototype.toString = function () {
var pref = this.isLambda ? '\u03BB-' : '', var expr = this.expression;
expr = this.expression;
if (expr instanceof Array) { if (expr instanceof Array) {
if (expr.length > 0) { if (expr.length > 0) {
expr = '[' + expr[0] + ']'; expr = '[' + expr[0] + ']';
} }
} }
return pref + 'Context >> ' + expr + ' ' + this.variables; return 'Context >> ' + expr + ' ' + this.variables;
}; };
Context.prototype.image = function () { Context.prototype.image = function () {
@ -2870,9 +2946,10 @@ Context.prototype.continuation = function () {
} else if (this.parentContext) { } else if (this.parentContext) {
cont = this.parentContext; cont = this.parentContext;
} else { } else {
return new Context(null, 'doStop'); return new Context(null, 'doYield');
} }
cont = cont.copyForContinuation(); cont = cont.copyForContinuation();
cont.tag = null;
cont.isContinuation = true; cont.isContinuation = true;
return cont; return cont;
}; };
@ -3003,9 +3080,9 @@ VariableFrame.prototype.find = function (name) {
var frame = this.silentFind(name); var frame = this.silentFind(name);
if (frame) {return frame; } if (frame) {return frame; }
throw new Error( throw new Error(
'a variable of name \'' localize('a variable of name \'')
+ 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 ''; return '';
} }
throw new Error( throw new Error(
'a variable of name \'' localize('a variable of name \'')
+ 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