summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--blocks.js40
-rw-r--r--favicon.icobin0 -> 32988 bytes
-rwxr-xr-xhistory.txt17
-rw-r--r--objects.js56
-rwxr-xr-xsnap.html4
-rw-r--r--store.js4
-rw-r--r--threads.js90
7 files changed, 147 insertions, 64 deletions
diff --git a/blocks.js b/blocks.js
index 82ef601..e56d0ed 100644
--- a/blocks.js
+++ b/blocks.js
@@ -155,7 +155,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/
// Global stuff ////////////////////////////////////////////////////////
-modules.blocks = '2014-November-17';
+modules.blocks = '2014-November-21';
var SyntaxElementMorph;
@@ -395,7 +395,9 @@ 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
+ // 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')) {
@@ -410,19 +412,24 @@ SyntaxElementMorph.prototype.allEmptySlots = function () {
return empty;
};
-SyntaxElementMorph.prototype.allReportBlocks = function () {
- // answer report blocks of all children including myself,
- // but omit those in nested rings (lambdas)
- if (this.selector === 'doReport') {return [this]; }
- var reports = [];
- if (!(this instanceof RingMorph)) {
- this.children.forEach(function (morph) {
- if (morph.allReportBlocks) {
- reports = reports.concat(morph.allReportBlocks());
- }
- });
+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);
+ }
+ });
+ }
}
- return reports;
};
SyntaxElementMorph.prototype.replaceInput = function (oldArg, newArg) {
@@ -3186,9 +3193,10 @@ BlockMorph.prototype.snap = function () {
bottomBlock() - answer the bottom block of my stack
blockSequence() - answer an array of blocks starting with myself
- and the following "lexical awareness" indicator:
+ 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:
@@ -3206,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:
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000..46a1369
--- /dev/null
+++ b/favicon.ico
Binary files differ
diff --git a/history.txt b/history.txt
index 0faac6b..30de1ab 100755
--- a/history.txt
+++ b/history.txt
@@ -2333,3 +2333,20 @@ ______
* 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
+
+1411214
+-------
+* Threads: Fixed #318
+* Objects: Fixed #416
+* Objects: Fixed #372
+* Threads: Fixed #644
+* Store: Fixed #34
+* Threads: Fixed #131
diff --git a/objects.js b/objects.js
index 4be213a..69376c8 100644
--- a/objects.js
+++ b/objects.js
@@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
-modules.objects = '2014-October-08';
+modules.objects = '2014-November-24';
var SpriteMorph;
var StageMorph;
@@ -1674,6 +1674,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 +1981,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',
@@ -2164,8 +2170,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 +2210,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];
}
});
@@ -4858,6 +4864,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 +5119,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',
diff --git a/snap.html b/snap.html
index 904aba8..93b4c73 100755
--- a/snap.html
+++ b/snap.html
@@ -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>
diff --git a/store.js b/store.js
index bc24849..3568809 100644
--- a/store.js
+++ b/store.js
@@ -61,7 +61,7 @@ SyntaxElementMorph, Variable*/
// Global stuff ////////////////////////////////////////////////////////
-modules.store = '2014-November-14';
+modules.store = '2014-November-24';
// 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',
diff --git a/threads.js b/threads.js
index cd83af6..1782984 100644
--- a/threads.js
+++ b/threads.js
@@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/
// Global stuff ////////////////////////////////////////////////////////
-modules.threads = '2014-November-20';
+modules.threads = '2014-November-24';
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();
}
@@ -322,6 +332,9 @@ ThreadManager.prototype.findProcess = function (block) {
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 = {};
@@ -347,6 +360,7 @@ function Process(topBlock, onComplete) {
this.frameCount = 0;
this.exportResult = false;
this.onComplete = onComplete || null;
+ this.procedureCount = 0;
if (topBlock) {
this.homeContext.receiver = topBlock.receiver();
@@ -684,7 +698,7 @@ Process.prototype.doYield = function () {
Process.prototype.exitReporter = function () {
// catch-tag for REPORT and STOP BLOCK primitives
- this.popContext();
+ this.handleError(new Error("missing 'report' statement in reporter"));
};
// Process Exception Handling
@@ -765,12 +779,6 @@ 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);
};
@@ -957,9 +965,11 @@ Process.prototype.fork = function (context, args) {
stage.threads.processes.push(proc);
};
+// Process "return" primitives
+
Process.prototype.doReport = function (value) {
if (this.context.expression.partOfCustomCommand) {
- return this.doStopBlock();
+ return this.doStopCustomBlock();
}
while (this.context && this.context.expression !== 'exitReporter') {
if (this.context.expression === 'doStopWarping') {
@@ -972,6 +982,24 @@ Process.prototype.doReport = function (value) {
};
Process.prototype.doStopBlock = function () {
+ 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.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();
@@ -1010,7 +1038,8 @@ 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(),
@@ -1022,6 +1051,7 @@ Process.prototype.evaluateCustomBlock = function () {
outer;
if (!context) {return null; }
+ this.procedureCount += 1;
outer = new Context();
outer.receiver = this.context.receiver;
outer.variables.parentFrame = outer.receiver ?
@@ -1068,10 +1098,19 @@ Process.prototype.evaluateCustomBlock = function () {
outer.receiver
);
runnable.parentContext = exit;
- } else { // mark all REPORT blocks as being part of a custom command
- runnable.expression.allReportBlocks().forEach(function (rb) {
- rb.partOfCustomCommand = true;
- });
+ this.popContext(); // don't yield when done
+ } 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;
+ }
}
runnable.expression = runnable.expression.blockSequence();
}
@@ -1269,7 +1308,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;
@@ -2750,23 +2789,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
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(
@@ -2791,6 +2832,7 @@ function Context(
this.activeNote = null;
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 () {