Skip to content

Commit fe65b89

Browse files
Use context.features and integrate Blockly/Scratch code into Codecast (for Micro:bit in Blockly)
1 parent 610c07c commit fe65b89

File tree

13 files changed

+503
-272
lines changed

13 files changed

+503
-272
lines changed

frontend/stepper/c/unix_runner.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export default class UnixRunner extends AbstractRunner {
8282
return param in PARAM_TYPE_CONVERSION ? PARAM_TYPE_CONVERSION[param] : 'int';
8383
}).join(', ');
8484
// @ts-ignore
85-
headers[code] = `${block.returnType in RETURN_TYPE_CONVERSION ? RETURN_TYPE_CONVERSION[block.returnType] : 'void'} ${code}(${argsSection});`;
85+
headers[code] = `${block.yieldsValue in RETURN_TYPE_CONVERSION ? RETURN_TYPE_CONVERSION[block.yieldsValue] : 'void'} ${code}(${argsSection});`;
8686
}
8787

8888
this.functionHeaders[generatorName + '.h'] = Object.values(headers).join("\n");
@@ -110,9 +110,9 @@ export default class UnixRunner extends AbstractRunner {
110110
const executorPromise = self.quickAlgoCallsExecutor(block.generatorName, block.name, formattedArgs, res => result = res);
111111
yield ['promise', executorPromise];
112112

113-
if ('int' === block.returnType) {
113+
if ('int' === block.yieldsValue) {
114114
yield ['result', new C.IntegralValue(C.builtinTypes['int'], result)];
115-
} else if (block.returnType) {
115+
} else if (block.yieldsValue) {
116116
yield ['result', result];
117117
}
118118
}
Lines changed: 104 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import {addExtraBlocks} from './extra_blocks';
44
import {getStandardBlocklyBlocks} from './standard_blockly_blocks';
55
import {getStandardScratchBlocks} from './standard_scratch_blocks';
6+
import {Block, BlockType} from '../../task/blocks/block_types';
67

78
let blocklySets = {
89
allDefault: {
@@ -67,7 +68,7 @@ function addInSet(l, val) {
6768
}
6869
}
6970

70-
class BlocklyInterface {
71+
export class BlocklyHelper {
7172
private subTask: any;
7273
private scratchMode: boolean;
7374
private maxBlocks: number;
@@ -92,6 +93,7 @@ class BlocklyInterface {
9293
private quickAlgoInterface: any;
9394
private highlightedBlocks: any[];
9495
private includeBlocks: any;
96+
private availableBlocks: Block[];
9597
private mainContext: any;
9698
private placeholderBlocks: boolean;
9799
private strings: any;
@@ -100,6 +102,7 @@ class BlocklyInterface {
100102
private blockCounts: any;
101103
private dragJustTerminated: boolean;
102104
private prevWidth: number;
105+
private availableBlocksInfo: Record<string, Record<string, Record<string, Block>>> = {};
103106

104107
constructor(maxBlocks, subTask) {
105108
this.subTask = subTask;
@@ -147,7 +150,7 @@ class BlocklyInterface {
147150

148151
loadContext(mainContext) {
149152
this.mainContext = mainContext;
150-
this.createGeneratorsAndBlocks();
153+
// this.createGeneratorsAndBlocks();
151154
}
152155

153156
load(locale, display, nbTestCases, options) {
@@ -327,6 +330,11 @@ class BlocklyInterface {
327330
this.includeBlocks = includeBlocks;
328331
}
329332

333+
setAvailableBlocks(availableBlocks: Block[]) {
334+
this.availableBlocks = availableBlocks;
335+
this.createGeneratorsAndBlocksForAvailableBlocks();
336+
}
337+
330338
getEmptyContent() {
331339
if (this.scratchMode) {
332340
return '<xml><block type="robot_start" deletable="false" movable="false" x="10" y="20"></block></xml>';
@@ -402,7 +410,8 @@ class BlocklyInterface {
402410

403411
this.programs[this.codeId].blockly = window.Blockly.Xml.domToText(xml);
404412
this.programs[this.codeId].blocklyJS = this.getCode("javascript");
405-
//this.programs[this.codeId].blocklyPython = this.getCode("python");
413+
this.programs[this.codeId].blocklyPython = this.getCode("python");
414+
console.log('python', this.programs[this.codeId].blocklyPython)
406415
}
407416
}
408417

@@ -428,6 +437,7 @@ class BlocklyInterface {
428437
window.jQuery("#program").val(this.programs[this.codeId].javascript);
429438
}
430439

440+
// Used by some Quickalgo libraries
431441
updateSize(force) {
432442
let panelWidth = 500;
433443
if (this.languages[this.codeId] == "blockly") {
@@ -845,10 +855,10 @@ class BlocklyInterface {
845855
let blockParams = blockInfo.params;
846856

847857
for (let language in {JavaScript: null, Python: null}) {
848-
if (typeof blockInfo.codeGenerators[language] == "undefined") {
849-
// Prevent the function name to be used as a variable
850-
window.Blockly[language].addReservedWords(code);
858+
// Prevent the function name to be used as a variable
859+
window.Blockly[language].addReservedWords(code);
851860

861+
if (typeof blockInfo.codeGenerators[language] == "undefined") {
852862
function setCodeGeneratorForLanguage(language) {
853863
blockInfo.codeGenerators[language] = function (block) {
854864
let params = "";
@@ -1063,6 +1073,36 @@ class BlocklyInterface {
10631073
//this.createBlock(label, generator.labelFr, generator.type, generator.nbParams);
10641074
}
10651075
}
1076+
1077+
}
1078+
1079+
createGeneratorsAndBlocksForAvailableBlocks() {
1080+
console.log('av blocks', this.availableBlocks);
1081+
for (let block of this.availableBlocks.filter(block => block.type === BlockType.Function)) {
1082+
const {generatorName, category, name} = block;
1083+
1084+
this.availableBlocksInfo[generatorName] ??= {};
1085+
this.availableBlocksInfo[generatorName][category] ??= {};
1086+
this.availableBlocksInfo[generatorName][category][name] = {
1087+
...block,
1088+
};
1089+
1090+
const blockInfo = this.availableBlocksInfo[generatorName][category][name];
1091+
1092+
/* TODO: Allow library writers to provide their own JS/Python code instead of just a handler */
1093+
this.completeBlockHandler(blockInfo, generatorName, this.mainContext);
1094+
this.completeBlockJson(blockInfo, generatorName, category, this.mainContext); /* category.category is category name */
1095+
this.completeBlockXml(blockInfo);
1096+
this.completeCodeGenerators(blockInfo, generatorName);
1097+
this.applyCodeGenerators(blockInfo);
1098+
this.createBlock(blockInfo);
1099+
this.applyBlockOptions(blockInfo);
1100+
}
1101+
1102+
// TODO: code generators for customClasses and customClassInstances
1103+
for (let block of this.availableBlocks.filter(block => block.type === BlockType.ClassFunction)) {
1104+
console.log('create code generator', block);
1105+
}
10661106
}
10671107

10681108
getBlocklyLibCode(generators) {
@@ -1163,6 +1203,20 @@ class BlocklyInterface {
11631203
return null;
11641204
}
11651205

1206+
getBlockFromCustomBlocks(generatorName: string, category: string, name: string) {
1207+
if (!(generatorName in this.availableBlocksInfo)) {
1208+
throw new Error(`Generator not found: ${generatorName}`);
1209+
}
1210+
if (!(category in this.availableBlocksInfo[generatorName])) {
1211+
throw new Error(`Category not found in generator ${generatorName}: ${category}`);
1212+
}
1213+
if (!(name in this.availableBlocksInfo[generatorName][category])) {
1214+
throw new Error(`Block not found in generator ${generatorName} and category ${category}: ${name}`);
1215+
}
1216+
1217+
return this.availableBlocksInfo[generatorName][category][name];
1218+
}
1219+
11661220

11671221
addBlocksAndCategories(blockNames, blocksDefinition, categoriesInfos) {
11681222
let colours = this.getDefaultColours();
@@ -1208,45 +1262,55 @@ class BlocklyInterface {
12081262
this.addBlocksAllowed(['math_number', 'text']);
12091263
}
12101264

1265+
console.log('available', this.availableBlocks);
12111266

12121267
// *** Blocks from the lib
1213-
if (this.includeBlocks.generatedBlocks && 'wholeCategories' in this.includeBlocks.generatedBlocks) {
1214-
for (let blockType in this.includeBlocks.generatedBlocks.wholeCategories) {
1215-
let categories = this.includeBlocks.generatedBlocks.wholeCategories[blockType];
1216-
for (let i = 0; i < categories.length; i++) {
1217-
let category = categories[i];
1218-
if (blockType in this.mainContext.customBlocks && category in this.mainContext.customBlocks[blockType]) {
1219-
let contextBlocks = this.mainContext.customBlocks[blockType][category];
1220-
let blockNames = [];
1221-
for (let i = 0; i < contextBlocks.length; i++) {
1222-
blockNames.push(contextBlocks[i].name);
1223-
}
1224-
this.addBlocksAndCategories(
1225-
blockNames,
1226-
this.mainContext.customBlocks[blockType],
1227-
categoriesInfos
1228-
);
1229-
}
1230-
}
1268+
for (let block of this.availableBlocks) {
1269+
if (BlockType.Function !== block.type) {
1270+
continue;
12311271
}
1232-
}
1233-
if (this.includeBlocks.generatedBlocks && 'singleBlocks' in this.includeBlocks.generatedBlocks) {
1234-
for (let blockType in this.includeBlocks.generatedBlocks.singleBlocks) {
1235-
this.addBlocksAndCategories(
1236-
this.includeBlocks.generatedBlocks.singleBlocks[blockType],
1237-
this.mainContext.customBlocks[blockType],
1238-
categoriesInfos
1239-
);
1272+
1273+
let colours = this.getDefaultColours();
1274+
const blockInfo = this.getBlockFromCustomBlocks(block.generatorName, block.category, block.name);
1275+
1276+
if (!(block.category in categoriesInfos)) {
1277+
categoriesInfos[block.category] = {
1278+
blocksXml: [],
1279+
colour: colours.blocks[block.name]
1280+
};
1281+
}
1282+
let blockXml = blockInfo.blocklyXml;
1283+
if (categoriesInfos[block.category].blocksXml.indexOf(blockXml) == -1) {
1284+
categoriesInfos[block.category].blocksXml.push(blockXml);
1285+
}
1286+
this.addBlocksAllowed([block.name]);
1287+
1288+
// by the way, just change the defaul colours of the blockly blocks:
1289+
if (!this.scratchMode) {
1290+
let defCat = ["logic", "loops", "math", "texts", "lists", "colour"];
1291+
for (let iCat in defCat) {
1292+
window.Blockly.Blocks[defCat[iCat]].HUE = colours.categories[defCat[iCat]];
1293+
}
12401294
}
12411295
}
1242-
for (let blockType in this.includeBlocks.generatedBlocks) {
1243-
if (blockType == 'wholeCategories' || blockType == 'singleBlocks') continue;
1244-
this.addBlocksAndCategories(
1245-
this.includeBlocks.generatedBlocks[blockType],
1246-
this.mainContext.customBlocks[blockType],
1247-
categoriesInfos
1248-
);
1249-
}
1296+
1297+
// if (this.includeBlocks.generatedBlocks && 'singleBlocks' in this.includeBlocks.generatedBlocks) {
1298+
// for (let blockType in this.includeBlocks.generatedBlocks.singleBlocks) {
1299+
// this.addBlocksAndCategories(
1300+
// this.includeBlocks.generatedBlocks.singleBlocks[blockType],
1301+
// this.mainContext.customBlocks[blockType],
1302+
// categoriesInfos
1303+
// );
1304+
// }
1305+
// }
1306+
// for (let blockType in this.includeBlocks.generatedBlocks) {
1307+
// if (blockType == 'wholeCategories' || blockType == 'singleBlocks') continue;
1308+
// this.addBlocksAndCategories(
1309+
// this.includeBlocks.generatedBlocks[blockType],
1310+
// this.mainContext.customBlocks[blockType],
1311+
// categoriesInfos
1312+
// );
1313+
// }
12501314

12511315
for (let genName in this.simpleGenerators) {
12521316
for (let iGen = 0; iGen < this.simpleGenerators[genName].length; iGen++) {
@@ -1667,7 +1731,3 @@ class BlocklyInterface {
16671731
}
16681732
}
16691733
}
1670-
1671-
export function getBlocklyHelper(maxBlocks, subTask) {
1672-
return new BlocklyInterface(maxBlocks, subTask);
1673-
}

frontend/stepper/js/index.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {LayoutType} from '../../task/layout/layout_types';
1717
import {Document, BlockDocument} from '../../buffers/buffer_types';
1818
import produce from 'immer';
1919
import {isServerTask} from '../../task/task_types';
20-
import {getBlocklyHelper} from './blockly_interface';
20+
import {BlocklyHelper} from './blockly_helper';
2121

2222
let originalFireNow;
2323
let originalSetBackgroundPathVertical_;
@@ -74,7 +74,7 @@ export function* loadBlocklyHelperSaga(context: QuickAlgoLibrary) {
7474
};
7575
}
7676

77-
context.blocklyHelper = createBlocklyHelper(context, isServerTask(state.task.currentTask));
77+
context.blocklyHelper = createBlocklyHelper(context, state, isServerTask(state.task.currentTask));
7878
context.onChange = () => {};
7979

8080
// There is a setTimeout delay in Blockly lib between blockly program loading and Blockly firing events.
@@ -98,8 +98,8 @@ export function* loadBlocklyHelperSaga(context: QuickAlgoLibrary) {
9898
yield* put(taskIncreaseContextId());
9999
}
100100

101-
export function createBlocklyHelper(context: QuickAlgoLibrary, serverTask = false) {
102-
const blocklyHelper = getBlocklyHelper(context.infos.maxInstructions, context);
101+
export function createBlocklyHelper(context: QuickAlgoLibrary, state: AppStore, serverTask = false) {
102+
const blocklyHelper = new BlocklyHelper(context.infos.maxInstructions, context);
103103
log.getLogger('blockly_runner').debug('[blockly.editor] load blockly helper', context, blocklyHelper);
104104

105105
if (context.infos.multithread) {
@@ -112,14 +112,18 @@ export function createBlocklyHelper(context: QuickAlgoLibrary, serverTask = fals
112112
}
113113

114114
// Remove printer blocks if it's a server task because in this case we don't want to display them in the blocks
115+
// TODO: integrate this into getContextBlocksDataSelector?
115116
const blocklyIncludeBlocks = produce(context.infos.includeBlocks, (includeBlocks) => {
116117
if (serverTask && includeBlocks.generatedBlocks && 'printer' in includeBlocks.generatedBlocks) {
117118
delete includeBlocks.generatedBlocks['printer'];
118119
}
119120
});
120121

122+
const availableBlocks = getContextBlocksDataSelector({state, context});
123+
121124
log.getLogger('blockly_runner').debug('[blockly.editor] load context into blockly editor');
122125
blocklyHelper.loadContext(context);
126+
blocklyHelper.setAvailableBlocks(availableBlocks);
123127
blocklyHelper.setIncludeBlocks(blocklyIncludeBlocks);
124128

125129
return blocklyHelper;
@@ -315,7 +319,7 @@ export function blocklyCount(blocks: any[], context: QuickAlgoLibrary): number {
315319
const getBlocksFromXml = function (state: AppStore, context: QuickAlgoLibrary, xmlText: string) {
316320
const xml = window.Blockly.Xml.textToDom(xmlText);
317321

318-
const blocklyHelper = createBlocklyHelper(context, isServerTask(state.task.currentTask));
322+
const blocklyHelper = createBlocklyHelper(context, state, isServerTask(state.task.currentTask));
319323
const language = state.options.language.split('-')[0];
320324
blocklyHelper.load(language, false, 1, {});
321325

frontend/stepper/python/python_runner.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,9 @@ mod.${className} = Sk.misceval.buildClass(mod, newClass${className}, "${classNam
250250
const classParts: {[className: string]: {[methodName: string]: string}} = {};
251251
for (let block of blocks.filter(block => block.type === BlockType.ClassFunction)) {
252252
const {generatorName, name, params, type, methodName, className, classInstance} = block;
253-
if (!block.placeholderClassInstance) {
254-
classInstancesToAdd[classInstance] = className;
255-
}
253+
// if (!block.placeholderClassInstance) {
254+
// classInstancesToAdd[classInstance] = className;
255+
// }
256256
if (!(className in classParts)) {
257257
classParts[className] = {};
258258
}

frontend/task/blocks/block_types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@ export interface Block {
2121
paramsCount?: number[],
2222
params?: string[], // for function
2323
showInBlocks?: boolean,
24-
returnType?: string|boolean,
24+
codeGenerators?: {[platformName: string]: Function},
25+
yieldsValue?: string,
2526
methodName?: string,
2627
className?: string,
2728
classInstance?: string,
2829
placeholderClassInstance?: boolean, // if this is a placeholder class instance that we generate for the sole purpose of creating blocks for this class
30+
blocklyJson?: object,
31+
blocklyXml?: string,
32+
blocklyInit?: Function,
2933
}

0 commit comments

Comments
 (0)