Skip to content

Commit 2b39f69

Browse files
committed
Merge branch 'dev/riknoll/multiple-block-workspaces' of https://github.com/microsoft/pxt into srietkerk/hack/new-global-var
2 parents 59e8f24 + d9bac1c commit 2b39f69

File tree

18 files changed

+347
-166
lines changed

18 files changed

+347
-166
lines changed

localtypings/pxteditor.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,7 @@ declare namespace pxt.editor {
928928
switchTypeScript(): void;
929929
openTypeScriptAsync(): Promise<void>;
930930
openPythonAsync(): Promise<void>;
931-
saveBlocksToTypeScriptAsync(): Promise<string>;
931+
saveBlocksToTypeScriptAsync(): Promise<pxt.Map<string>>;
932932

933933
saveFileAsync(): Promise<void>;
934934
saveCurrentSourceAsync(): Promise<void>;

pxtblocks/blocksProgram.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,13 @@ export interface BlocksProgram {
5757
getSymbolsForFile(fileName: string): BlocksSymbol[];
5858
renameVariable(symbol: BlocksVariableSymbol, newName: string): void;
5959
deleteSymbol(symbol: BlocksSymbol): void;
60-
getAllWorkspaces(): Blockly.Workspace[];
60+
getAllWorkspaces(): FileWorkspace[];
6161
getEnumInfo(enumName: string): string[];
6262
getKindInfo(kindName: string): string[];
6363
getVariableQualifiedName(varName: string, workspace: Blockly.Workspace): string;
6464
refreshSymbols?(): void;
6565
getVariableSymbol(varId: string): BlocksVariableSymbol;
66+
getFunctionSymbol(functionName: string): BlocksFunctionSymbol;
6667
}
6768

6869

@@ -104,8 +105,8 @@ export class SingleWorkspaceBlocksProgram implements BlocksProgram {
104105
}
105106
}
106107

107-
getAllWorkspaces(): Blockly.Workspace[] {
108-
return [this.workspace];
108+
getAllWorkspaces(): FileWorkspace[] {
109+
return [{ fileName: "main.blocks", workspace: this.workspace }];
109110
}
110111

111112
getEnumInfo(enumName: string): string[] {
@@ -136,6 +137,11 @@ export class SingleWorkspaceBlocksProgram implements BlocksProgram {
136137
}
137138
return null;
138139
}
140+
141+
getFunctionSymbol(functionName: string): BlocksFunctionSymbol {
142+
const allFunctions = getFunctionSymbols({ fileName: "main.blocks", workspace: this.workspace });
143+
return allFunctions.find(f => f.name === functionName);
144+
}
139145
}
140146

141147

@@ -145,7 +151,7 @@ export class MultiWorkspaceBlocksProgram implements BlocksProgram {
145151

146152
public currentlyLoadedFile: string;
147153

148-
constructor(protected mainPackage: BlocksProgramHost, public workspaceSvg: Blockly.WorkspaceSvg) {
154+
constructor(protected mainPackage: BlocksProgramHost, public mainWorkspace: Blockly.Workspace) {
149155
}
150156

151157
listFiles(): string[] {
@@ -200,8 +206,8 @@ export class MultiWorkspaceBlocksProgram implements BlocksProgram {
200206
this.refreshSymbols();
201207
}
202208

203-
getAllWorkspaces(): Blockly.Workspace[] {
204-
return Array.from(this.workspaces.values()).map(w => w.workspace);
209+
getAllWorkspaces(): FileWorkspace[] {
210+
return Array.from(this.workspaces.values());
205211
}
206212

207213
getEnumInfo(enumName: string): string[] {
@@ -261,23 +267,23 @@ export class MultiWorkspaceBlocksProgram implements BlocksProgram {
261267

262268
if (this.workspaces.has(file)) {
263269
const ws = this.workspaces.get(file);
264-
if (!(ws.workspace instanceof Blockly.WorkspaceSvg)) {
270+
if (!(ws.workspace === this.mainWorkspace)) {
265271
ws.workspace.dispose();
266272
}
267273
this.workspaces.delete(file);
268274
}
269275

270-
clearWithoutEvents(this.workspaceSvg);
276+
clearWithoutEvents(this.mainWorkspace);
271277

272278
const fileContents = this.mainPackage.getFile(file);
273279
const xml = Blockly.utils.xml.textToDom(fileContents);
274280
xml.querySelectorAll("block[deletable], shadow[deletable]").forEach(b => { b.removeAttribute("deletable") });
275-
domToWorkspaceNoEvents(xml, this.workspaceSvg);
281+
domToWorkspaceNoEvents(xml, this.mainWorkspace);
276282

277283
this.currentlyLoadedFile = file;
278284
this.workspaces.set(file, {
279285
fileName: file,
280-
workspace: this.workspaceSvg
286+
workspace: this.mainWorkspace
281287
});
282288

283289
this.refreshSymbols();
@@ -291,7 +297,7 @@ export class MultiWorkspaceBlocksProgram implements BlocksProgram {
291297
this.loadOrGetWorkspace(this.currentlyLoadedFile);
292298
}
293299

294-
clearWithoutEvents(this.workspaceSvg);
300+
clearWithoutEvents(this.mainWorkspace);
295301
this.currentlyLoadedFile = null;
296302
}
297303

@@ -332,6 +338,17 @@ export class MultiWorkspaceBlocksProgram implements BlocksProgram {
332338
return null;
333339
}
334340

341+
getFunctionSymbol(functionName: string): BlocksFunctionSymbol {
342+
for (const symbols of this.symbolsCache.values()) {
343+
for (const symbol of symbols) {
344+
if (symbol.type === "function" && symbol.name === functionName) {
345+
return symbol;
346+
}
347+
}
348+
}
349+
return null;
350+
}
351+
335352
protected defineSymbolInWorkspace(symbol: BlocksSymbol, workspace: Blockly.Workspace) {
336353
const map = workspace.getVariableMap();
337354

@@ -463,7 +480,7 @@ function getSymbolFromFunctionDefinitionBlock(block: FunctionDefinitionBlock, fi
463480
functionSymbol.arguments.push({
464481
name: child.getAttribute("name"),
465482
type: child.getAttribute("type"),
466-
id: child.getAttribute("argid")
483+
id: child.getAttribute("id")
467484
});
468485
}
469486
}

pxtblocks/builtins/functions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { domToWorkspaceNoEvents } from "../importer";
1111
import { shouldDuplicateOnDrag } from "../plugins/duplicateOnDrag";
1212
import { PathObject } from "../plugins/renderer/pathObject";
1313
import { FieldImageNoText } from "../fields/field_imagenotext";
14+
import { getGlobalProgram } from "../external";
1415

1516
export function initFunctions() {
1617
const msg = Blockly.Msg;
@@ -519,8 +520,7 @@ function flyoutCategory(workspace: Blockly.WorkspaceSvg, useXml: boolean): Eleme
519520
}
520521
}
521522

522-
let tuple = Blockly.Procedures.allProcedures(workspace);
523-
populateProcedures(tuple[0], 'procedures_callnoreturn');
524-
523+
const tuple = Blockly.Procedures.allProcedures(workspace);
524+
populateProcedures(tuple[0], "procedures_callnoreturn");
525525
return xmlList;
526526
}

pxtblocks/compiler/compiler.ts

Lines changed: 86 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
import * as Blockly from "blockly";
5-
import { BlockCompilationResult, BlockCompileOptions, BlockDeclarationType, BlockDiagnostic, Environment, GrayBlockStatement, StdFunc, VarInfo, mkEnv } from "./environment";
5+
import { BlockCompilationResult, BlockCompileOptions, BlockDeclarationType, BlockDiagnostic, Environment, GrayBlockStatement, PxtBlock, StdFunc, VarInfo, mkEnv } from "./environment";
66
import { IfBlock, attachPlaceholderIf, defaultValueForType, find, getConcreteType, getEscapedCBParameters, infer, isBooleanType, isFunctionRecursive, isStringType, lookup, returnType } from "./typeChecker";
77
import { append, countOptionals, escapeVarName, forEachChildExpression, getInputTargetBlock, getLoopVariableField, isFunctionDefinition, isMutatingBlock, visibleParams } from "./util";
88
import { isArrayType } from "../toolbox";
@@ -34,23 +34,28 @@ export const PXT_WARNING_ID = "WARNING_MESSAGE"
3434
export function compileBlockAsync(b: Blockly.Block, blockInfo: pxtc.BlocksInfo): Promise<BlockCompilationResult> {
3535
const w = b.workspace;
3636
const e = mkEnv(new SingleWorkspaceBlocksProgram(w), blockInfo);
37-
infer(w && w.getAllBlocks(false), e);
37+
const allBlocks = w?.getAllBlocks(false);
38+
if (allBlocks) {
39+
for (const block of allBlocks) (block as PxtBlock).PXT_FILE = "main.blocks";
40+
}
41+
42+
infer(allBlocks, e);
3843
const compiled = compileStatementBlock(e, b)
3944
e.placeholders = {};
40-
return tdASTtoTS(e, compiled);
45+
return tdASTtoTS(e, {"main.blocks": compiled});
4146
}
4247

4348
export function compileAsync(b: Blockly.Workspace, blockInfo: pxtc.BlocksInfo, opts: BlockCompileOptions = {}): Promise<BlockCompilationResult> {
4449
const e = mkEnv(new SingleWorkspaceBlocksProgram(b), blockInfo, opts);
4550
const [nodes, diags] = compileWorkspace(e, b, blockInfo);
46-
const result = tdASTtoTS(e, nodes, diags);
51+
const result = tdASTtoTS(e, {"main.blocks": nodes}, diags);
4752
return result;
4853
}
4954

5055
export function compileProgramAsync(program: BlocksProgram, blockInfo: pxtc.BlocksInfo, opts: BlockCompileOptions = {}): Promise<BlockCompilationResult> {
5156
const e = mkEnv(program, blockInfo, opts);
52-
const [nodes, diags] = compileBlocksProgram(e, program, blockInfo);
53-
return tdASTtoTS(e, nodes, diags);
57+
const [files, diags] = compileBlocksProgram(e, program, blockInfo);
58+
return tdASTtoTS(e, files, diags);
5459
}
5560

5661
function eventWeight(b: Blockly.Block, e: Environment) {
@@ -66,19 +71,31 @@ function eventWeight(b: Blockly.Block, e: Environment) {
6671
return -hash;
6772
}
6873

69-
function compileBlocksProgram(e: Environment, program: BlocksProgram, blockInfo: pxtc.BlocksInfo): [pxt.blocks.JsNode[], BlockDiagnostic[]] {
74+
function compileBlocksProgram(e: Environment, program: BlocksProgram, blockInfo: pxtc.BlocksInfo): [pxt.Map<pxt.blocks.JsNode[]>, BlockDiagnostic[]] {
7075
try {
7176
// all compiled top level blocks are events
7277
program.refreshSymbols();
7378

7479
let allBlocks: Blockly.Block[] = [];
7580
let topblocks: Blockly.Block[] = [];
76-
let topComments: Blockly.comments.WorkspaceComment[] = [];
81+
82+
const blocksByFile: pxt.Map<Blockly.Block[]> = {};
83+
const stmtsByFile: pxt.Map<pxt.blocks.JsNode[]> = {};
7784

7885
for (const workspace of program.getAllWorkspaces()) {
79-
allBlocks.push(...workspace.getAllBlocks(false));
80-
topblocks.push(...workspace.getTopBlocks(true));
81-
topComments.push(...workspace.getTopComments(true));
86+
const ws = workspace.workspace;
87+
const fileTopBlocks = ws.getTopBlocks(true);
88+
const fileAllBlocks = ws.getAllBlocks(false);
89+
90+
for (const block of fileAllBlocks) {
91+
(block as PxtBlock).PXT_FILE = workspace.fileName;
92+
}
93+
94+
allBlocks.push(...fileAllBlocks);
95+
topblocks.push(...fileTopBlocks);
96+
97+
blocksByFile[workspace.fileName] = fileTopBlocks;
98+
stmtsByFile[workspace.fileName] = [];
8299
}
83100

84101
if (pxt.react.getTilemapProject) {
@@ -94,32 +111,44 @@ function compileBlocksProgram(e: Environment, program: BlocksProgram, blockInfo:
94111
// drop disabled blocks
95112
allBlocks = allBlocks.filter(b => b.isEnabled());
96113
topblocks = topblocks.filter(b => b.isEnabled());
97-
trackAllVariables(topblocks, e);
114+
trackAllVariables(blocksByFile, e);
98115
infer(allBlocks, e);
99116

100-
const stmtsMain: pxt.blocks.JsNode[] = [];
101117

102118
// compile workspace comments, add them to the top
103-
const commentMap = groupWorkspaceComments(topblocks as Blockly.BlockSvg[],
104-
topComments as Blockly.comments.RenderedWorkspaceComment[]);
119+
let metaMap: CommentMap = {
120+
idToComments: {},
121+
orphans: []
122+
}
105123

106-
commentMap.orphans.forEach(comment => append(stmtsMain, compileWorkspaceComment(comment).children));
124+
for (const workspace of program.getAllWorkspaces()) {
125+
const topBlocks = blocksByFile[workspace.fileName];
126+
const comments = workspace.workspace.getTopComments(true);
107127

108-
topblocks.forEach(b => {
109-
if (commentMap.idToComments[b.id]) {
110-
commentMap.idToComments[b.id].forEach(comment => {
111-
append(stmtsMain, compileWorkspaceComment(comment).children);
128+
const commentMap = groupWorkspaceComments(topBlocks as Blockly.BlockSvg[], comments as Blockly.comments.RenderedWorkspaceComment[]);
129+
commentMap.orphans.forEach(comment => append(stmtsByFile[workspace.fileName], compileWorkspaceComment(comment).children));
130+
131+
for (const id of Object.keys(commentMap.idToComments)) {
132+
metaMap.idToComments[id] = commentMap.idToComments[id];
133+
}
134+
}
135+
136+
for (const b of topblocks) {
137+
const stmts = stmtsByFile[(b as PxtBlock).PXT_FILE];
138+
if (metaMap.idToComments[b.id]) {
139+
metaMap.idToComments[b.id].forEach(comment => {
140+
append(stmts, compileWorkspaceComment(comment).children);
112141
});
113142
}
114143
if (b.type == ts.pxtc.ON_START_TYPE)
115-
append(stmtsMain, compileStatementBlock(e, b));
144+
append(stmts, compileStatementBlock(e, b));
116145
else {
117146
const compiled = pxt.blocks.mkBlock(compileStatementBlock(e, b));
118147
if (compiled.type == pxt.blocks.NT.Block)
119-
append(stmtsMain, compiled.children);
120-
else stmtsMain.push(compiled)
148+
append(stmts, compiled.children);
149+
else stmts.push(compiled)
121150
}
122-
});
151+
}
123152

124153
const stmtsEnums: pxt.blocks.JsNode[] = [];
125154
e.enums.forEach(info => {
@@ -196,11 +225,14 @@ function compileBlocksProgram(e: Environment, program: BlocksProgram, blockInfo:
196225

197226
e.diagnostics.push({
198227
blockId: v.firstReference && v.firstReference.id,
199-
message: lf("Variable '{0}' is never assigned", v.name)
228+
message: lf("Variable '{0}' is never assigned", v.name),
229+
fileName: v.fileName
200230
});
201231
});
202232

203-
return [stmtsEnums.concat(leftoverVars.concat(stmtsMain)), e.diagnostics];
233+
stmtsByFile["main.blocks"] = stmtsEnums.concat(leftoverVars.concat(stmtsByFile["main.blocks"]));
234+
235+
return [stmtsByFile, e.diagnostics];
204236

205237
} catch (err) {
206238
let be: Blockly.Block = (err as any).block;
@@ -223,6 +255,10 @@ function compileWorkspace(e: Environment, w: Blockly.Workspace, blockInfo: pxtc.
223255
// all compiled top level blocks are events
224256
let allBlocks = w.getAllBlocks(false);
225257

258+
for (const block of allBlocks) {
259+
(block as PxtBlock).PXT_FILE = "main.blocks";
260+
}
261+
226262
if (pxt.react.getTilemapProject) {
227263
pxt.react.getTilemapProject().removeInactiveBlockAssets(allBlocks.map(b => b.id));
228264
}
@@ -238,7 +274,7 @@ function compileWorkspace(e: Environment, w: Blockly.Workspace, blockInfo: pxtc.
238274
// drop disabled blocks
239275
allBlocks = allBlocks.filter(b => b.isEnabled());
240276
topblocks = topblocks.filter(b => b.isEnabled());
241-
trackAllVariables(topblocks, e);
277+
trackAllVariables({"main.blocks": topblocks}, e);
242278
infer(allBlocks, e);
243279

244280
const stmtsMain: pxt.blocks.JsNode[] = [];
@@ -340,7 +376,8 @@ function compileWorkspace(e: Environment, w: Blockly.Workspace, blockInfo: pxtc.
340376

341377
e.diagnostics.push({
342378
blockId: v.firstReference && v.firstReference.id,
343-
message: lf("Variable '{0}' is never assigned", v.name)
379+
message: lf("Variable '{0}' is never assigned", v.name),
380+
fileName: v.fileName
344381
});
345382
});
346383

@@ -972,15 +1009,26 @@ function compileImage(e: Environment, b: Blockly.Block, frames: number, columns:
9721009
return pxt.blocks.H.namespaceCall(n, f, [lit].concat(args), false);
9731010
}
9741011

975-
function tdASTtoTS(env: Environment, app: pxt.blocks.JsNode[], diags?: BlockDiagnostic[]): Promise<BlockCompilationResult> {
976-
let res = pxt.blocks.flattenNode(app)
1012+
function tdASTtoTS(env: Environment, app: pxt.Map<pxt.blocks.JsNode[]>, diags?: BlockDiagnostic[]): Promise<BlockCompilationResult> {
1013+
const outfiles: pxt.Map<string> = {};
9771014

978-
// Note: the result of format is not used!
1015+
let concatenated: string = "";
1016+
const sourceMap: pxt.blocks.BlockSourceInterval[] = [];
9791017

980-
return workerOpAsync("format", { format: { input: res.output, pos: 1 } }).then(() => {
1018+
const getOutfile = (file: string) => file.replace(/\.blocks$/, ".ts");
1019+
1020+
for (const file of Object.keys(app)) {
1021+
const res = pxt.blocks.flattenNode(app[file]);
1022+
outfiles[getOutfile(file)] = res.output;
1023+
sourceMap.push(...res.sourceMap);
1024+
concatenated += res.output + "\n";
1025+
}
1026+
1027+
// Note: the result of format is not used!
1028+
return workerOpAsync("format", { format: { input: concatenated, pos: 1 } }).then(() => {
9811029
return {
982-
source: res.output,
983-
sourceMap: res.sourceMap,
1030+
outfiles,
1031+
sourceMap,
9841032
stats: env.stats,
9851033
diagnostics: diags || []
9861034
};
@@ -1329,13 +1377,15 @@ function compileReturnStatement(e: Environment, b: Blockly.Block, comments: stri
13291377
if (!parentFunction) {
13301378
e.diagnostics.push({
13311379
blockId: b.id,
1332-
message: lf("Return statements can only be used within function bodies.")
1380+
message: lf("Return statements can only be used within function bodies."),
1381+
fileName: (b as PxtBlock).PXT_FILE
13331382
});
13341383
}
13351384
else if (hasReturn && parentFunction.type !== FUNCTION_DEFINITION_BLOCK_TYPE) {
13361385
e.diagnostics.push({
13371386
blockId: b.id,
1338-
message: lf("Return statements can only return values inside function definitions.")
1387+
message: lf("Return statements can only return values inside function definitions."),
1388+
fileName: (b as PxtBlock).PXT_FILE
13391389
});
13401390
}
13411391

0 commit comments

Comments
 (0)