generating text code from blocks, first experimental iteration
pull/3/merge
jmoenig 2013-06-18 18:43:15 +02:00
rodzic a7ebff7c5b
commit acc40a6d2a
6 zmienionych plików z 428 dodań i 5 usunięć

316
blocks.js
Wyświetl plik

@ -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 () {

Wyświetl plik

@ -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)

16
gui.js
Wyświetl plik

@ -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',

Wyświetl plik

@ -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

Wyświetl plik

@ -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 () {

Wyświetl plik

@ -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) {