Codification (blocks to text) support complete

text code mapping and block header support, both via GUI and
primitives, for built-in blocks and for custom ones.
pull/3/merge
jmoenig 2013-07-04 15:31:05 +02:00
rodzic 8e0f60fdd6
commit b59f7f3e27
8 zmienionych plików z 261 dodań i 59 usunięć

165
blocks.js
Wyświetl plik

@ -155,7 +155,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
modules.blocks = '2013-June-25';
modules.blocks = '2013-July-04';
var SyntaxElementMorph;
var BlockMorph;
@ -1535,10 +1535,10 @@ SyntaxElementMorph.prototype.showBubble = function (value) {
it's not part of Snap's evaluator and not needed for Snap itself
*/
SyntaxElementMorph.prototype.mappedCode = function () {
SyntaxElementMorph.prototype.mappedCode = function (definitions) {
var result = this.evaluate();
if (result instanceof BlockMorph) {
return result.mappedCode();
return result.mappedCode(definitions);
}
return result;
};
@ -1858,6 +1858,10 @@ BlockMorph.prototype.userMenu = function () {
}
if (StageMorph.prototype.enableCodeMapping) {
menu.addLine();
menu.addItem(
'header mapping...',
'mapToHeader'
);
menu.addItem(
'code mapping...',
'mapToCode'
@ -1941,6 +1945,11 @@ BlockMorph.prototype.userMenu = function () {
menu.addLine();
menu.addItem("ringify", 'ringify');
if (StageMorph.prototype.enableCodeMapping) {
menu.addLine();
menu.addItem(
'header mapping...',
'mapToHeader'
);
menu.addItem(
'code mapping...',
'mapToCode'
@ -2182,6 +2191,45 @@ BlockMorph.prototype.showHelp = function () {
it's not part of Snap's evaluator and not needed for Snap itself
*/
BlockMorph.prototype.mapToHeader = function () {
// open a dialog box letting the user map header code via the GUI
var key = this.selector.substr(0, 5) === 'reify' ?
'reify' : this.selector,
block = this.codeDefinitionHeader(),
myself = this,
help,
pic;
block.addShadow(new Point(3, 3));
pic = block.fullImageClassic();
if (this.definition) {
help = 'Enter code that corresponds to the block\'s definition. ' +
'Use the formal parameter\nnames as shown and <body> to ' +
'reference the definition body\'s generated text code.';
} else {
help = 'Enter code that corresponds to the block\'s definition. ' +
'Choose your own\nformal parameter names (ignoring the ones ' +
'shown .';
}
new DialogBoxMorph(
this,
function (code) {
if (key === 'evaluateCustomBlock') {
myself.definition.codeHeader = code;
} else {
StageMorph.prototype.codeHeaders[key] = code;
}
},
this
).promptCode(
'Header mapping',
key === 'evaluateCustomBlock' ? this.definition.codeHeader || ''
: StageMorph.prototype.codeHeaders[key] || '',
this.world(),
pic,
help
);
};
BlockMorph.prototype.mapToCode = function () {
// open a dialog box letting the user map code via the GUI
var key = this.selector.substr(0, 5) === 'reify' ?
@ -2206,10 +2254,26 @@ BlockMorph.prototype.mapToCode = function () {
key === 'evaluateCustomBlock' ? this.definition.codeMapping || ''
: StageMorph.prototype.codeMappings[key] || '',
this.world(),
pic
pic,
'Enter code that corresponds to the block\'s operation ' +
'(usually a single\nfunction invocation). Use <#n> to ' +
'reference actual arguments as shown.'
);
};
BlockMorph.prototype.mapHeader = function (aString, key) {
// primitive for programatically mapping header code
var sel = key || this.selector.substr(0, 5) === 'reify' ?
'reify' : this.selector;
if (aString) {
if (this.definition) { // custom block
this.definition.codeHeader = aString;
} else {
StageMorph.prototype.codeHeaders[sel] = aString;
}
}
};
BlockMorph.prototype.mapCode = function (aString, key) {
// primitive for programatically mapping code
var sel = key || this.selector.substr(0, 5) === 'reify' ?
@ -2223,19 +2287,63 @@ BlockMorph.prototype.mapCode = function (aString, key) {
}
};
BlockMorph.prototype.mappedCode = function () {
BlockMorph.prototype.mappedCode = function (definitions) {
var key = this.selector.substr(0, 5) === 'reify' ?
'reify' : this.selector,
code,
codeLines,
count = 1,
header,
headers,
headerLines,
body,
bodyLines,
defKey = this.definition ? this.definition.spec : key,
defs = definitions || {},
parts = [];
code = key === 'reportGetVar' ? this.blockSpec
: this.definition ? this.definition.codeMapping || ''
: StageMorph.prototype.codeMappings[key] || '';
// map header
if (key !== 'reportGetVar' && !defs.hasOwnProperty(defKey)) {
defs[defKey] = null; // create the property for recursive definitions
if (this.definition) {
header = this.definition.codeHeader || '';
if (header.indexOf('<body') !== -1) { // replace with def mapping
body = '';
if (this.definition.body) {
body = this.definition.body.expression.mappedCode(defs);
}
bodyLines = body.split('\n');
headerLines = header.split('\n');
headerLines.forEach(function (headerLine, idx) {
var prefix = '',
indent;
if (headerLine.trimLeft().indexOf('<body') === 0) {
indent = headerLine.indexOf('<body');
prefix = headerLine.slice(0, indent);
}
headerLines[idx] = headerLine.replace(
new RegExp('<body>'),
bodyLines.join('\n' + prefix)
);
headerLines[idx] = headerLines[idx].replace(
new RegExp('<body>', 'g'),
bodyLines.join('\n')
);
});
header = headerLines.join('\n');
}
defs[defKey] = header;
} else {
defs[defKey] = StageMorph.prototype.codeHeaders[defKey];
}
}
codeLines = code.split('\n');
this.inputs().forEach(function (input) {
parts.push(input.mappedCode().toString());
parts.push(input.mappedCode(defs).toString());
});
parts.forEach(function (part) {
var partLines = part.split('\n'),
@ -2258,11 +2366,46 @@ BlockMorph.prototype.mappedCode = function () {
});
code = codeLines.join('\n');
if (this.nextBlock && this.nextBlock()) { // Command
code += ('\n' + this.nextBlock().mappedCode());
code += ('\n' + this.nextBlock().mappedCode(defs));
}
if (!definitions) { // top-level, add headers
headers = [];
Object.keys(defs).forEach(function (each) {
if (defs[each]) {
headers.push(defs[each]);
}
});
if (headers.length) {
return headers.join('\n\n')
+ '\n\n'
+ code;
}
}
return code;
};
BlockMorph.prototype.codeDefinitionHeader = function () {
var block = this.definition ? new PrototypeHatBlockMorph(this.definition)
: SpriteMorph.prototype.blockForSelector(this.selector),
hat = new HatBlockMorph(),
count = 1;
if (this.definition) {return block; }
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.prototype.codeMappingHeader = function () {
var block = this.definition ? this.definition.blockInstance()
: SpriteMorph.prototype.blockForSelector(this.selector),
@ -5402,11 +5545,11 @@ CSlotMorph.prototype.getSpec = function () {
return '%c';
};
CSlotMorph.prototype.mappedCode = function () {
CSlotMorph.prototype.mappedCode = function (definitions) {
var code = StageMorph.prototype.codeMappings.reify || '<#1>',
codeLines = code.split('\n'),
nested = this.nestedBlock(),
part = nested ? nested.mappedCode() : '',
part = nested ? nested.mappedCode(definitions) : '',
partLines = (part.toString()).split('\n'),
rx = new RegExp('<#1>', 'g');
@ -8541,7 +8684,7 @@ MultiArgMorph.prototype.mapToCode = function (key, label) {
);
};
MultiArgMorph.prototype.mappedCode = function () {
MultiArgMorph.prototype.mappedCode = function (definitions) {
var block = this.parentThatIsA(BlockMorph),
key = '',
code,
@ -8564,7 +8707,7 @@ MultiArgMorph.prototype.mappedCode = function () {
delim = StageMorph.prototype.codeMappings[key + 'delim'] || ' ';
this.inputs().forEach(function (input) {
parts.push(itemCode.replace(/<#1>/g, input.mappedCode()));
parts.push(itemCode.replace(/<#1>/g, input.mappedCode(definitions)));
});
parts.forEach(function (part) {
if (count) {

Wyświetl plik

@ -105,7 +105,7 @@ CommentMorph, localize, CSlotMorph, SpeechBubbleMorph, MorphicPreferences*/
// Global stuff ////////////////////////////////////////////////////////
modules.byob = '2013-June-18';
modules.byob = '2013-July-04';
// Declarations
@ -139,6 +139,7 @@ function CustomBlockDefinition(spec, receiver) {
this.declarations = {}; // {'inputName' : [type, default]}
this.comment = null;
this.codeMapping = null; // experimental, generate text code
this.codeHeader = null; // experimental, generate text code
// don't serialize (not needed for functionality):
this.receiver = receiver || null; // for serialization only (pointer)

58
gui.js
Wyświetl plik

@ -68,7 +68,7 @@ sb, CommentMorph, CommandBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
modules.gui = '2013-July-02';
modules.gui = '2013-July-04';
// Declarations
@ -1681,8 +1681,7 @@ IDE_Morph.prototype.applySavedSettings = function () {
zoom = this.getSetting('zoom'),
language = this.getSetting('language'),
click = this.getSetting('click'),
longform = this.getSetting('longform'),
code = this.getSetting('code');
longform = this.getSetting('longform');
// design
if (design === 'flat') {
@ -1714,11 +1713,6 @@ IDE_Morph.prototype.applySavedSettings = function () {
if (longform) {
InputSlotDialogMorph.prototype.isLaunchingExpanded = true;
}
// code mapping
if (code && !StageMorph.prototype.enableCodeMapping) {
StageMorph.prototype.enableCodeMapping = true;
}
};
IDE_Morph.prototype.saveSetting = function (key, value) {
@ -1763,7 +1757,7 @@ IDE_Morph.prototype.addNewSprite = function () {
this.selectSprite(sprite);
};
IDE_Morph.prototype.paintNewSprite = function() {
IDE_Morph.prototype.paintNewSprite = function () {
var sprite = new SpriteMorph(this.globalVariables),
cos = new Costume(),
myself = this;
@ -1779,7 +1773,7 @@ IDE_Morph.prototype.paintNewSprite = function() {
this.world(),
this,
true,
function() {myself.removeSprite(sprite); },
function () {myself.removeSprite(sprite); },
function () {
sprite.addCostume(cos);
sprite.wearCostume(cos);
@ -2158,26 +2152,7 @@ IDE_Morph.prototype.settingsMenu = function () {
'check for alternative\nGUI design',
false
);
addPreference(
'Code mapping',
function () {
StageMorph.prototype.enableCodeMapping =
!StageMorph.prototype.enableCodeMapping;
if (StageMorph.prototype.enableCodeMapping) {
myself.saveSetting('code', true);
} else {
myself.removeSetting('code');
}
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
menu.addLine(); // everything below this line is stored in the project
addPreference(
'Thread safe scripts',
function () {stage.isThreadSafe = !stage.isThreadSafe; },
@ -2192,6 +2167,20 @@ IDE_Morph.prototype.settingsMenu = function () {
'uncheck for greater speed\nat variable frame rates',
'check for smooth, predictable\nanimations across computers'
);
addPreference(
'Codification support',
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.popup(world, pos);
};
@ -2580,6 +2569,9 @@ IDE_Morph.prototype.newProject = function () {
this.currentSprite = new SpriteMorph(this.globalVariables);
this.sprites = new List([this.currentSprite]);
StageMorph.prototype.hiddenPrimitives = {};
StageMorph.prototype.codeMappings = {};
StageMorph.prototype.codeHeaders = {};
StageMorph.prototype.enableCodeMapping = false;
this.setProjectName('');
this.projectNotes = '';
this.createStage();
@ -5115,7 +5107,7 @@ CostumeIconMorph.prototype.renameCostume = function () {
);
};
CostumeIconMorph.prototype.duplicateCostume = function() {
CostumeIconMorph.prototype.duplicateCostume = function () {
var wardrobe = this.parentThatIsA(WardrobeMorph),
ide = this.parentThatIsA(IDE_Morph),
newcos = this.object.copy(),
@ -5494,11 +5486,11 @@ WardrobeMorph.prototype.removeCostumeAt = function (idx) {
this.updateList();
};
WardrobeMorph.prototype.paintNew = function() {
WardrobeMorph.prototype.paintNew = function () {
var cos = new Costume(newCanvas(), "Untitled"),
ide = this.parentThatIsA(IDE_Morph),
myself = this;
cos.edit(this.world(), ide, true, null, function() {
cos.edit(this.world(), ide, true, null, function () {
myself.sprite.addCostume(cos);
myself.updateList();
if (ide) {

Wyświetl plik

@ -1778,3 +1778,7 @@ ______
------
* Objects: took out "security margin" in Costume's shrinkWrap() method b/c Chrome no longer needs it -> fixed empty costume bug when drawing over the paint editor's bounds
* GUI: Import libraries feature (in the project menu)
130704
------
* Codification (text code mapping and block header support)

Wyświetl plik

@ -123,7 +123,7 @@ PrototypeHatBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
modules.objects = '2013-July-02';
modules.objects = '2013-July-04';
var SpriteMorph;
var StageMorph;
@ -1019,6 +1019,11 @@ SpriteMorph.prototype.initBlocks = function () {
},
// Code mapping - experimental
doMapHeader: { // experimental
type: 'command',
category: 'other',
spec: 'map %cmdRing to header %code'
},
doMapCode: { // experimental
type: 'command',
category: 'other',
@ -1795,6 +1800,7 @@ SpriteMorph.prototype.blockTemplates = function (category) {
blocks.push('=');
if (StageMorph.prototype.enableCodeMapping) {
blocks.push(block('doMapHeader'));
blocks.push(block('doMapCode'));
blocks.push(block('doMapStringCode'));
blocks.push(block('doMapListCode'));
@ -3223,6 +3229,7 @@ StageMorph.prototype.paletteTextColor
StageMorph.prototype.hiddenPrimitives = {};
StageMorph.prototype.codeMappings = {};
StageMorph.prototype.codeHeaders = {};
StageMorph.prototype.enableCodeMapping = false;
// StageMorph instance creation
@ -4047,6 +4054,7 @@ StageMorph.prototype.blockTemplates = function (category) {
blocks.push('=');
if (StageMorph.prototype.enableCodeMapping) {
blocks.push(block('doMapHeader'));
blocks.push(block('doMapCode'));
blocks.push(block('doMapStringCode'));
blocks.push(block('doMapListCode'));

Wyświetl plik

@ -61,7 +61,7 @@ SyntaxElementMorph*/
// Global stuff ////////////////////////////////////////////////////////
modules.store = '2013-June-19';
modules.store = '2013-July-04';
// XML_Serializer ///////////////////////////////////////////////////////
@ -378,6 +378,8 @@ SnapSerializer.prototype.loadProjectModel = function (xmlNode) {
project.stage.setExtent(StageMorph.prototype.dimensions);
project.stage.isThreadSafe =
model.stage.attributes.threadsafe === 'true';
StageMorph.prototype.enableCodeMapping =
model.stage.attributes.codify === 'true';
model.hiddenPrimitives = model.project.childNamed('hidden');
if (model.hiddenPrimitives) {
@ -390,6 +392,13 @@ SnapSerializer.prototype.loadProjectModel = function (xmlNode) {
);
}
model.codeHeaders = model.project.childNamed('headers');
if (model.codeHeaders) {
model.codeHeaders.children.forEach(function (xml) {
StageMorph.prototype.codeHeaders[xml.tag] = xml.contents;
});
}
model.codeMappings = model.project.childNamed('code');
if (model.codeMappings) {
model.codeMappings.children.forEach(function (xml) {
@ -672,7 +681,7 @@ SnapSerializer.prototype.loadCustomBlocks = function (
// private
var myself = this;
element.children.forEach(function (child) {
var definition, names, inputs, code, comment, i;
var definition, names, inputs, header, code, comment, i;
if (child.tag !== 'block-definition') {
return;
}
@ -711,6 +720,11 @@ SnapSerializer.prototype.loadCustomBlocks = function (
});
}
header = child.childNamed('header');
if (header) {
definition.codeHeader = header.contents;
}
code = child.childNamed('code');
if (code) {
definition.codeMapping = code.contents;
@ -1266,20 +1280,20 @@ StageMorph.prototype.toXML = function (serializer) {
thumbdata = null;
}
function codeMappings() {
var code = '';
Object.keys(StageMorph.prototype.codeMappings).forEach(
function code(key) {
var str = '';
Object.keys(StageMorph.prototype[key]).forEach(
function (selector) {
code += (
str += (
'<' + selector + '>' +
XML_Element.prototype.escape(
StageMorph.prototype.codeMappings[selector]
StageMorph.prototype[key][selector]
) +
'</' + selector + '>'
);
}
);
return code;
return str;
}
this.removeAllClones();
@ -1288,6 +1302,7 @@ StageMorph.prototype.toXML = function (serializer) {
'<notes>$</notes>' +
'<thumbnail>$</thumbnail>' +
'<stage name="@" costume="@" tempo="@" threadsafe="@" ' +
'codify="@" ' +
'scheduled="@" ~>' +
'<pentrails>$</pentrails>' +
'<costumes>%</costumes>' +
@ -1297,6 +1312,7 @@ StageMorph.prototype.toXML = function (serializer) {
'<scripts>%</scripts><sprites>%</sprites>' +
'</stage>' +
'<hidden>$</hidden>' +
'<headers>%</headers>' +
'<code>%</code>' +
'<blocks>%</blocks>' +
'<variables>%</variables>' +
@ -1310,6 +1326,7 @@ StageMorph.prototype.toXML = function (serializer) {
this.getCostumeIdx(),
this.getTempo(),
this.isThreadSafe,
this.enableCodeMapping,
StageMorph.prototype.frameRate !== 0,
this.trailsCanvas.toDataURL('image/png'),
serializer.store(this.costumes, this.name + '_cst'),
@ -1322,7 +1339,8 @@ StageMorph.prototype.toXML = function (serializer) {
function (a, b) {return a + ' ' + b; },
''
),
codeMappings(),
code('codeHeaders'),
code('codeMappings'),
serializer.store(this.globalBlocks),
(ide && ide.globalVariables) ?
serializer.store(ide.globalVariables) : ''
@ -1587,6 +1605,7 @@ CustomBlockDefinition.prototype.toXML = function (serializer) {
return serializer.format(
'<block-definition s="@" type="@" category="@">' +
'%' +
'<header>@</header>' +
'<code>@</code>' +
'<inputs>%</inputs>%%' +
'</block-definition>',
@ -1594,6 +1613,7 @@ CustomBlockDefinition.prototype.toXML = function (serializer) {
this.type,
this.category || 'other',
this.comment ? this.comment.toXML(serializer) : '',
this.codeHeader || '',
this.codeMapping || '',
Object.keys(this.declarations).reduce(function (xml, decl) {
return xml + serializer.format(

Wyświetl plik

@ -83,7 +83,7 @@ ArgLabelMorph, localize*/
// Global stuff ////////////////////////////////////////////////////////
modules.threads = '2013-June-18';
modules.threads = '2013-July-04';
var ThreadManager;
var Process;
@ -2291,6 +2291,14 @@ Process.prototype.reportTimer = function () {
blocks - not needed to run or debug Snap
*/
Process.prototype.doMapHeader = function (aContext, aString) {
if (aContext instanceof Context) {
if (aContext.expression instanceof SyntaxElementMorph) {
return aContext.expression.mapHeader(aString || '');
}
}
};
Process.prototype.doMapCode = function (aContext, aString) {
if (aContext instanceof Context) {
if (aContext.expression instanceof SyntaxElementMorph) {

Wyświetl plik

@ -74,7 +74,7 @@ HTMLCanvasElement, fontHeight, SymbolMorph, localize, SpeechBubbleMorph,
ArrowMorph, MenuMorph, isString, isNil, SliderMorph, MorphicPreferences,
ScrollFrameMorph*/
modules.widgets = '2013-June-25';
modules.widgets = '2013-July-04';
var PushButtonMorph;
var ToggleButtonMorph;
@ -1680,16 +1680,33 @@ DialogBoxMorph.prototype.promptCode = function (
title,
defaultString,
world,
pic
pic,
instructions
) {
var frame = new ScrollFrameMorph(),
text = new TextMorph(defaultString || ''),
bdy = new AlignmentMorph('column', this.padding),
size = pic ? Math.max(pic.width, 400) : 400;
this.getInput = function () {
return text.text;
};
function remarkText(string) {
return new TextMorph(
string,
10,
null, // style
false, // bold
null, // italic
null, // alignment
null, // width
null, // font name
MorphicPreferences.isFlat ? null : new Point(1, 1),
new Color(255, 255, 255) // shadowColor
);
}
frame.padding = 6;
frame.setWidth(size);
frame.acceptsDrops = false;
@ -1723,8 +1740,17 @@ DialogBoxMorph.prototype.promptCode = function (
this.key = 'promptCode' + title + defaultString;
}
this.addBody(frame);
bdy.setColor(this.color);
bdy.add(frame);
if (instructions) {
bdy.add(remarkText(instructions));
}
bdy.fixLayout();
this.addBody(bdy);
frame.drawNew();
bdy.drawNew();
this.addButton('ok', 'OK');
this.addButton('cancel', 'Cancel');
this.fixLayout();