summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjmoenig <jens@moenig.org>2013-06-18 18:43:15 +0200
committerjmoenig <jens@moenig.org>2013-06-18 18:43:15 +0200
commitacc40a6d2a06ab1fcfbd488030e091bd9215a1b2 (patch)
tree57374704c2b908df2c1d8721f8a055a0f1378d1e
parenta7ebff7c5bb4a79b3a64441b50957e523fe686e0 (diff)
downloadsnap-byow-acc40a6d2a06ab1fcfbd488030e091bd9215a1b2.tar.gz
snap-byow-acc40a6d2a06ab1fcfbd488030e091bd9215a1b2.zip
Code mapping
generating text code from blocks, first experimental iteration
-rw-r--r--blocks.js316
-rw-r--r--byob.js3
-rw-r--r--gui.js16
-rwxr-xr-xhistory.txt4
-rw-r--r--objects.js45
-rw-r--r--threads.js49
6 files changed, 428 insertions, 5 deletions
diff --git a/blocks.js b/blocks.js
index 4f1f093..1a325a8 100644
--- a/blocks.js
+++ b/blocks.js
@@ -153,7 +153,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
-modules.blocks = '2013-May-16';
+modules.blocks = '2013-June-18';
var SyntaxElementMorph;
var BlockMorph;
@@ -1061,6 +1061,34 @@ SyntaxElementMorph.prototype.labelPart = function (spec) {
part = new ReporterSlotMorph(true);
break;
+ // code mapping (experimental)
+
+ case '%codeListPart':
+ part = new InputSlotMorph(
+ null, // text
+ false, // numeric?
+ {
+ 'list' : ['list'],
+ 'item' : ['item'],
+ 'delimiter' : ['delimiter']
+ },
+ true // read-only
+ );
+ break;
+ case '%codeListKind':
+ part = new InputSlotMorph(
+ null, // text
+ false, // numeric?
+ {
+ 'collection' : ['collection'],
+ 'variables' : ['variables'],
+ 'parameters' : ['parameters']
+ },
+ true // read-only
+ );
+ break;
+
+
// symbols:
case '%turtle':
@@ -1479,6 +1507,22 @@ SyntaxElementMorph.prototype.showBubble = function (value) {
);
};
+// SyntaxElementMorph code mapping
+
+/*
+ code mapping lets you use blocks to generate arbitrary text-based
+ source code that can be exported and compiled / embedded elsewhere,
+ it's not part of Snap's evaluator and not needed for Snap itself
+*/
+
+SyntaxElementMorph.prototype.mappedCode = function () {
+ var result = this.evaluate();
+ if (result instanceof BlockMorph) {
+ return result.mappedCode();
+ }
+ return result;
+};
+
// SyntaxElementMorph layout update optimization
SyntaxElementMorph.prototype.startLayout = function () {
@@ -1790,6 +1834,13 @@ BlockMorph.prototype.userMenu = function () {
'hidePrimitive'
);
}
+ if (StageMorph.prototype.enableCodeMapping) {
+ menu.addLine();
+ menu.addItem(
+ 'code mapping...',
+ 'mapToCode'
+ );
+ }
return menu;
}
menu.addLine();
@@ -1867,6 +1918,12 @@ BlockMorph.prototype.userMenu = function () {
}
menu.addLine();
menu.addItem("ringify", 'ringify');
+ if (StageMorph.prototype.enableCodeMapping) {
+ menu.addItem(
+ 'code mapping...',
+ 'mapToCode'
+ );
+ }
return menu;
};
@@ -2095,6 +2152,99 @@ BlockMorph.prototype.showHelp = function () {
}
};
+// BlockMorph code mapping
+
+/*
+ code mapping lets you use blocks to generate arbitrary text-based
+ source code that can be exported and compiled / embedded elsewhere,
+ it's not part of Snap's evaluator and not needed for Snap itself
+*/
+
+BlockMorph.prototype.mapToCode = function () {
+ // open a dialog box letting the user map code via the GUI
+ var key = this.selector.substr(0, 5) === 'reify' ?
+ 'reify' : this.selector,
+ block = this.codeMappingHeader(),
+ myself = this,
+ pic;
+ block.addShadow(new Point(3, 3));
+ pic = block.fullImageClassic();
+ new DialogBoxMorph(
+ this,
+ function (code) {
+ if (key === 'evaluateCustomBlock') {
+ myself.definition.codeMapping = code;
+ } else {
+ StageMorph.prototype.codeMappings[key] = code;
+ }
+ },
+ this
+ ).prompt(
+ 'Code mapping',
+ key === 'evaluateCustomBlock' ? this.definition.codeMapping || ''
+ : StageMorph.prototype.codeMappings[key] || '',
+ this.world(),
+ pic
+ );
+};
+
+BlockMorph.prototype.mapCode = function (aString, key) {
+ // primitive for programatically mapping code
+ var sel = key || this.selector.substr(0, 5) === 'reify' ?
+ 'reify' : this.selector;
+ if (aString) {
+ if (this.definition) { // custom block
+ this.definition.codeMapping = aString;
+ } else {
+ StageMorph.prototype.codeMappings[sel] = aString;
+ }
+ }
+};
+
+BlockMorph.prototype.mappedCode = function () {
+ var key = this.selector.substr(0, 5) === 'reify' ?
+ 'reify' : this.selector,
+ code,
+ count = 1,
+ parts = [];
+ code = key === 'reportGetVar' ? this.blockSpec
+ : this.definition ? this.definition.codeMapping || ''
+ : StageMorph.prototype.codeMappings[key] || '';
+ this.inputs().forEach(function (input) {
+ parts.push(input.mappedCode());
+ });
+ parts.forEach(function (part) {
+ var rx = new RegExp('<#' + count + '>', 'g');
+ code = code.replace(rx, part);
+ count += 1;
+ });
+ if (this.nextBlock && this.nextBlock()) { // Command
+ code += this.nextBlock().mappedCode();
+ }
+ return code;
+};
+
+BlockMorph.prototype.codeMappingHeader = function () {
+ var block = this.definition ? this.definition.blockInstance()
+ : SpriteMorph.prototype.blockForSelector(this.selector),
+ hat = new HatBlockMorph(),
+ count = 1;
+
+ block.inputs().forEach(function (input) {
+ var part = new TemplateSlotMorph('<#' + count + '>');
+ block.silentReplaceInput(input, part);
+ count += 1;
+ });
+ block.isPrototype = true;
+ hat.setCategory("control");
+ hat.setSpec('%s');
+ hat.silentReplaceInput(hat.inputs()[0], block);
+ if (this.category === 'control') {
+ hat.alternateBlockColor();
+ }
+ return hat;
+};
+
// BlockMorph drawing
BlockMorph.prototype.eraseHoles = function (context) {
@@ -5187,6 +5337,13 @@ CSlotMorph.prototype.getSpec = function () {
return '%c';
};
+CSlotMorph.prototype.mappedCode = function () {
+ var code = StageMorph.prototype.codeMappings.reify || '',
+ part = this.nestedBlock(),
+ nestedCode = part ? part.mappedCode() : '';
+ return code.replace(/<#1>/g, nestedCode);
+};
+
// CSlotMorph layout:
CSlotMorph.prototype.fixLayout = function () {
@@ -6113,6 +6270,60 @@ InputSlotMorph.prototype.reactToSliderEdit = function () {
}
};
+// InputSlotMorph menu:
+
+InputSlotMorph.prototype.userMenu = function () {
+ var menu = new MenuMorph(this);
+ if (!StageMorph.prototype.enableCodeMapping || this.isNumeric) {
+ return this.parent.userMenu();
+ }
+ menu.addItem(
+ 'code string mapping...',
+ 'mapToCode'
+ );
+ return menu;
+};
+
+// InputSlotMorph code mapping
+
+/*
+ code mapping lets you use blocks to generate arbitrary text-based
+ source code that can be exported and compiled / embedded elsewhere,
+ it's not part of Snap's evaluator and not needed for Snap itself
+*/
+
+InputSlotMorph.prototype.mapToCode = function () {
+ // private - open a dialog box letting the user map code via the GUI
+ new DialogBoxMorph(
+ this,
+ function (code) {
+ StageMorph.prototype.codeMappings.string = code;
+ },
+ this
+ ).prompt(
+ 'Code mapping - String <#1>',
+ StageMorph.prototype.codeMappings.string || '',
+ this.world()
+ );
+};
+
+InputSlotMorph.prototype.mappedCode = function () {
+ var code = StageMorph.prototype.codeMappings.string || '<#1>',
+ block = this.parentThatIsA(BlockMorph),
+ val = this.evaluate();
+
+ if (this.isNumeric) {return val; }
+ if (!isNaN(parseFloat(val))) {return val; }
+ if (!isString(val)) {return val; }
+ if (block && contains(
+ ['doSetVar', 'doChangeVar', 'doShowVar', 'doHideVar'],
+ block.selector
+ )) {
+ return val;
+ }
+ return code.replace(/<#1>/g, val);
+};
+
// InputSlotMorph evaluating:
InputSlotMorph.prototype.evaluate = function () {
@@ -8089,6 +8300,109 @@ MultiArgMorph.prototype.mouseClickLeft = function (pos) {
this.endLayout();
};
+// MultiArgMorph menu:
+
+MultiArgMorph.prototype.userMenu = function () {
+ var menu = new MenuMorph(this),
+ block = this.parentThatIsA(BlockMorph),
+ key = '',
+ myself = this;
+ if (!StageMorph.prototype.enableCodeMapping) {
+ return this.parent.userMenu();
+ }
+ if (block) {
+ if (block instanceof RingMorph) {
+ key = 'parms_';
+ } else if (block.selector === 'doDeclareVariables') {
+ key = 'tempvars_';
+ }
+ }
+ menu.addItem(
+ 'code list mapping...',
+ function () {myself.mapCodeList(key); }
+ );
+ menu.addItem(
+ 'code item mapping...',
+ function () {myself.mapCodeItem(key); }
+ );
+ menu.addItem(
+ 'code delimiter mapping...',
+ function () {myself.mapCodeDelimiter(key); }
+ );
+ return menu;
+};
+
+// MultiArgMorph code mapping
+
+/*
+ code mapping lets you use blocks to generate arbitrary text-based
+ source code that can be exported and compiled / embedded elsewhere,
+ it's not part of Snap's evaluator and not needed for Snap itself
+*/
+
+MultiArgMorph.prototype.mapCodeDelimiter = function (key) {
+ this.mapToCode(key + 'delim', 'list item delimiter');
+};
+
+MultiArgMorph.prototype.mapCodeList = function (key) {
+ this.mapToCode(key + 'list', 'list contents <#1>');
+};
+
+MultiArgMorph.prototype.mapCodeItem = function (key) {
+ this.mapToCode(key + 'item', 'list item <#1>');
+};
+
+MultiArgMorph.prototype.mapToCode = function (key, label) {
+ // private - open a dialog box letting the user map code via the GUI
+ new DialogBoxMorph(
+ this,
+ function (code) {
+ StageMorph.prototype.codeMappings[key] = code;
+ },
+ this
+ ).prompt(
+ 'Code mapping - ' + label,
+ StageMorph.prototype.codeMappings[key] || '',
+ this.world()
+ );
+};
+
+MultiArgMorph.prototype.mappedCode = function () {
+ var block = this.parentThatIsA(BlockMorph),
+ key = '',
+ code,
+ items = '',
+ itemCode,
+ delim,
+ count = 0,
+ parts = [];
+
+ if (block) {
+ if (block instanceof RingMorph) {
+ key = 'parms_';
+ } else if (block.selector === 'doDeclareVariables') {
+ key = 'tempvars_';
+ }
+ }
+
+ code = StageMorph.prototype.codeMappings[key + 'list'] || '<#1>';
+ itemCode = StageMorph.prototype.codeMappings[key + 'item'] || '<#1>';
+ delim = StageMorph.prototype.codeMappings[key + 'delim'] || ' ';
+
+ this.inputs().forEach(function (input) {
+ parts.push(itemCode.replace(/<#1>/g, input.mappedCode()));
+ });
+ parts.forEach(function (part) {
+ if (count) {
+ items += delim;
+ }
+ items += part;
+ count += 1;
+ });
+ code = code.replace(/<#1>/g, items);
+ return code;
+};
+
// MultiArgMorph arity evaluating:
MultiArgMorph.prototype.evaluate = function () {
diff --git a/byob.js b/byob.js
index b9797e3..72715ad 100644
--- a/byob.js
+++ b/byob.js
@@ -105,7 +105,7 @@ CommentMorph, localize, CSlotMorph, SpeechBubbleMorph, MorphicPreferences*/
// Global stuff ////////////////////////////////////////////////////////
-modules.byob = '2013-June-06';
+modules.byob = '2013-June-18';
// Declarations
@@ -138,6 +138,7 @@ function CustomBlockDefinition(spec, receiver) {
this.spec = spec || '';
this.declarations = {}; // {'inputName' : [type, default]}
this.comment = null;
+ this.codeMapping = null; // experimental, generate text code
// don't serialize (not needed for functionality):
this.receiver = receiver || null; // for serialization only (pointer)
diff --git a/gui.js b/gui.js
index f8c688d..df6c5e7 100644
--- a/gui.js
+++ b/gui.js
@@ -68,7 +68,7 @@ sb, CommentMorph, CommandBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
-modules.gui = '2013-May-17';
+modules.gui = '2013-June-18';
// Declarations
@@ -2148,6 +2148,20 @@ IDE_Morph.prototype.settingsMenu = function () {
'check for alternative\nGUI design',
false
);
+ addPreference(
+ 'Code mapping',
+ function () {
+ StageMorph.prototype.enableCodeMapping =
+ !StageMorph.prototype.enableCodeMapping;
+ myself.currentSprite.blocksCache.variables = null;
+ myself.currentSprite.paletteCache.variables = null;
+ myself.refreshPalette();
+ },
+ StageMorph.prototype.enableCodeMapping,
+ 'uncheck to disable\nblock to text mapping features',
+ 'check for block\nto text mapping features',
+ false
+ );
menu.addLine(); // everything below this line is made persistent
addPreference(
'Thread safe scripts',
diff --git a/history.txt b/history.txt
index 10b26c9..5e1d9c6 100755
--- a/history.txt
+++ b/history.txt
@@ -1733,3 +1733,7 @@ ______
* BYOB: Newly created custom reporters now have an initial default REPORT block as definition body
* Morphic: focus World canvas on mouse down (otherwise prevent default)
+
+130618
+------
+* Code mapping (generating textual code from blocks), first iteration
diff --git a/objects.js b/objects.js
index 59a9d49..cdddb45 100644
--- a/objects.js
+++ b/objects.js
@@ -123,7 +123,7 @@ PrototypeHatBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
-modules.objects = '2013-June-05';
+modules.objects = '2013-June-18';
var SpriteMorph;
var StageMorph;
@@ -1016,6 +1016,29 @@ SpriteMorph.prototype.initBlocks = function () {
category: 'lists',
spec: 'replace item %idx of %l with %s',
defaults: [1, null, localize('thing')]
+ },
+
+ // Code mapping - experimental
+ doMapCode: { // experimental
+ type: 'command',
+ category: 'other',
+ spec: 'map %cmdRing to code %s'
+ },
+ doMapStringCode: { // experimental
+ type: 'command',
+ category: 'other',
+ spec: 'map String to code %s',
+ defaults: ['<#1>']
+ },
+ doMapListCode: { // experimental
+ type: 'command',
+ category: 'other',
+ spec: 'map %codeListPart of %codeListKind to code %s'
+ },
+ reportMappedCode: { // experimental
+ type: 'reporter',
+ category: 'other',
+ spec: 'code of %cmdRing'
}
};
};
@@ -1771,6 +1794,15 @@ SpriteMorph.prototype.blockTemplates = function (category) {
blocks.push('=');
+ if (StageMorph.prototype.enableCodeMapping) {
+ blocks.push(block('doMapCode'));
+ blocks.push(block('doMapStringCode'));
+ blocks.push(block('doMapListCode'));
+ blocks.push('-');
+ blocks.push(block('reportMappedCode'));
+ blocks.push('=');
+ }
+
button = new PushButtonMorph(
null,
function () {
@@ -3189,6 +3221,8 @@ StageMorph.prototype.paletteTextColor
= SpriteMorph.prototype.paletteTextColor;
StageMorph.prototype.hiddenPrimitives = {};
+StageMorph.prototype.codeMappings = {};
+StageMorph.prototype.enableCodeMapping = false;
// StageMorph instance creation
@@ -4011,6 +4045,15 @@ StageMorph.prototype.blockTemplates = function (category) {
blocks.push('=');
+ if (StageMorph.prototype.enableCodeMapping) {
+ blocks.push(block('doMapCode'));
+ blocks.push(block('doMapStringCode'));
+ blocks.push(block('doMapListCode'));
+ blocks.push('-');
+ blocks.push(block('reportMappedCode'));
+ blocks.push('=');
+ }
+
button = new PushButtonMorph(
null,
function () {
diff --git a/threads.js b/threads.js
index 2a1802c..d98d2b5 100644
--- a/threads.js
+++ b/threads.js
@@ -83,7 +83,7 @@ ArgLabelMorph, localize*/
// Global stuff ////////////////////////////////////////////////////////
-modules.threads = '2013-May-14';
+modules.threads = '2013-June-18';
var ThreadManager;
var Process;
@@ -2284,6 +2284,53 @@ Process.prototype.reportTimer = function () {
return 0;
};
+// Process code mapping
+
+/*
+ for generating textual source code using
+ blocks - not needed to run or debug Snap
+*/
+
+Process.prototype.doMapCode = function (aContext, aString) {
+ if (aContext instanceof Context) {
+ if (aContext.expression instanceof SyntaxElementMorph) {
+ return aContext.expression.mapCode(aString || '');
+ }
+ }
+};
+
+Process.prototype.doMapStringCode = function (aString) {
+ StageMorph.prototype.codeMappings.string = aString || '<#1>';
+};
+
+Process.prototype.doMapListCode = function (part, kind, aString) {
+ var key1 = '',
+ key2 = 'delim';
+
+ if (this.inputOption(kind) === 'parameters') {
+ key1 = 'parms_';
+ } else if (this.inputOption(kind) === 'variables') {
+ key1 = 'tempvars_';
+ }
+
+ if (this.inputOption(part) === 'list') {
+ key2 = 'list';
+ } else if (this.inputOption(part) === 'item') {
+ key2 = 'item';
+ }
+
+ StageMorph.prototype.codeMappings[key1 + key2] = aString || '';
+};
+
+Process.prototype.reportMappedCode = function (aContext) {
+ if (aContext instanceof Context) {
+ if (aContext.expression instanceof SyntaxElementMorph) {
+ return aContext.expression.mappedCode();
+ }
+ }
+ return '';
+};
+
// Process music primitives
Process.prototype.doRest = function (beats) {