Skip to content

Commit

Permalink
Link scopes during transpile, fixes namespace transpiling (#1271)
Browse files Browse the repository at this point in the history
* Link scope before serializing each file

* Link scope during `prepareFile` events too.
Add scope to events

* Perf boost for getting sorted scope keys

* Fastest so far

* Simple scope linking because complex didn't help at all

* Remove unnecessary test
  • Loading branch information
TwitchBronBron authored Aug 3, 2024
1 parent 1b2ddf4 commit 825b2ce
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 40 deletions.
30 changes: 30 additions & 0 deletions src/Program.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { StringType, TypedFunctionType, DynamicType, FloatType, IntegerType, Int
import { AssociativeArrayType } from './types/AssociativeArrayType';
import { ComponentType } from './types/ComponentType';
import * as path from 'path';
import undent from 'undent';

const sinon = createSandbox();

Expand Down Expand Up @@ -2700,6 +2701,35 @@ describe('Program', () => {
fsExtra.pathExistsSync(`${stagingDir}/source/bslib.brs`)
).to.be.true;
});

it('transpiles namespaces properly', async () => {
program.options.autoImportComponentScript = true;
program.setFile('manifest', '');
program.setFile('components/MainScene.xml', trim`
<component name="MainScene" extends="Scene">
</component>
`);
program.setFile('source/main.bs', `
namespace alpha
sub test()
end sub
end namespace
sub init()
alpha.test()
end sub
`);
await program.build();
expect(
fsExtra.readFileSync(`${stagingDir}/source/main.brs`).toString()
).to.eql(undent`
sub alpha_test()
end sub
sub init()
alpha_test()
end sub
`);
});
});

describe('global symbol table', () => {
Expand Down
95 changes: 55 additions & 40 deletions src/Program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { CodeAction, Position, Range, SignatureInformation, Location, Docum
import type { BsConfig, FinalizedBsConfig } from './BsConfig';
import { Scope } from './Scope';
import { DiagnosticMessages } from './DiagnosticMessages';
import type { FileObj, SemanticToken, FileLink, ProvideHoverEvent, ProvideCompletionsEvent, Hover, ProvideDefinitionEvent, ProvideReferencesEvent, ProvideDocumentSymbolsEvent, ProvideWorkspaceSymbolsEvent, BeforeFileAddEvent, BeforeFileRemoveEvent, PrepareFileEvent, PrepareProgramEvent, ProvideFileEvent, SerializedFile, TranspileObj } from './interfaces';
import type { FileObj, SemanticToken, FileLink, ProvideHoverEvent, ProvideCompletionsEvent, Hover, ProvideDefinitionEvent, ProvideReferencesEvent, ProvideDocumentSymbolsEvent, ProvideWorkspaceSymbolsEvent, BeforeFileAddEvent, BeforeFileRemoveEvent, PrepareFileEvent, PrepareProgramEvent, ProvideFileEvent, SerializedFile, TranspileObj, SerializeFileEvent } from './interfaces';
import { standardizePath as s, util } from './util';
import { XmlScope } from './XmlScope';
import { DependencyGraph } from './DependencyGraph';
Expand Down Expand Up @@ -285,6 +285,14 @@ export class Program {

protected addScope(scope: Scope) {
this.scopes[scope.name] = scope;
delete this.sortedScopeNames;
}

protected removeScope(scope: Scope) {
if (this.scopes[scope.name]) {
delete this.scopes[scope.name];
delete this.sortedScopeNames;
}
}

/**
Expand Down Expand Up @@ -773,7 +781,7 @@ export class Program {
scope.dispose();
//notify dependencies of this scope that it has been removed
this.dependencyGraph.remove(scope.dependencyGraphKey!);
delete this.scopes[file.destPath];
this.removeScope(this.scopes[file.destPath]);
this.plugins.emit('afterScopeDispose', scopeDisposeEvent);
}
//remove the file from the program
Expand Down Expand Up @@ -1047,28 +1055,33 @@ export class Program {
}
}

private sortedScopeNames: string[] = undefined;

/**
* Gets a sorted list of all scopeNames, always beginning with "global", "source", then any others in alphabetical order
*/
private getSortedScopeNames() {
return Object.keys(this.scopes).sort((a, b) => {
if (a === 'global') {
return -1;
} else if (b === 'global') {
return 1;
}
if (a === 'source') {
return -1;
} else if (b === 'source') {
return 1;
}
if (a < b) {
return -1;
} else if (b < a) {
return 1;
}
return 0;
});
if (!this.sortedScopeNames) {
this.sortedScopeNames = Object.keys(this.scopes).sort((a, b) => {
if (a === 'global') {
return -1;
} else if (b === 'global') {
return 1;
}
if (a === 'source') {
return -1;
} else if (b === 'source') {
return 1;
}
if (a < b) {
return -1;
} else if (b < a) {
return 1;
}
return 0;
});
}
return this.sortedScopeNames;
}

/**
Expand Down Expand Up @@ -1457,21 +1470,22 @@ export class Program {
* @param files the list of files that should be prepared
*/
private async prepare(files: BscFile[]) {
const programEvent = {
const programEvent: PrepareProgramEvent = {
program: this,
editor: this.editor,
files: files
} as PrepareProgramEvent;
};

//assign an editor to every file
for (const file of files) {
for (const file of programEvent.files) {
//if the file doesn't have an editor yet, assign one now
if (!file.editor) {
file.editor = new Editor();
}
}

files.sort((a, b) => {
//sort the entries to make transpiling more deterministic
programEvent.files.sort((a, b) => {
if (a.pkgPath < b.pkgPath) {
return -1;
} else if (a.pkgPath > b.pkgPath) {
Expand All @@ -1489,6 +1503,10 @@ export class Program {
const entries: TranspileObj[] = [];

for (const file of files) {
const scope = this.getFirstScopeForFile(file);
//link the symbol table for all the files in this scope
scope?.linkSymbolTable();

//if the file doesn't have an editor yet, assign one now
if (!file.editor) {
file.editor = new Editor();
Expand All @@ -1497,6 +1515,7 @@ export class Program {
program: this,
file: file,
editor: file.editor,
scope: scope,
outputPath: this.getOutputPath(file, stagingDir)
} as PrepareFileEvent & { outputPath: string };

Expand All @@ -1506,6 +1525,9 @@ export class Program {

//TODO remove this in v1
entries.push(event);

//unlink the symbolTable so the next loop iteration can link theirs
scope?.unlinkSymbolTable();
}

await this.plugins.emitAsync('afterPrepareProgram', programEvent);
Expand All @@ -1529,34 +1551,27 @@ export class Program {
files: files,
result: allFiles
});
await this.plugins.emitAsync('onSerializeProgram', {
program: this,
files: files,
result: allFiles
});

//sort the entries to make transpiling more deterministic
files = serializeProgramEvent.files.sort((a, b) => {
return a.srcPath < b.srcPath ? -1 : 1;
});
await this.plugins.emitAsync('onSerializeProgram', serializeProgramEvent);

// serialize each file
for (const file of files) {
const event = {
const scope = this.getFirstScopeForFile(file);
//link the symbol table for all the files in this scope
scope?.linkSymbolTable();
const event: SerializeFileEvent = {
program: this,
file: file,
scope: scope,
result: allFiles
};
await this.plugins.emitAsync('beforeSerializeFile', event);
await this.plugins.emitAsync('serializeFile', event);
await this.plugins.emitAsync('afterSerializeFile', event);
//unlink the symbolTable so the next loop iteration can link theirs
scope?.unlinkSymbolTable();
}

this.plugins.emit('afterSerializeProgram', {
program: this,
files: files,
result: allFiles
});
this.plugins.emit('afterSerializeProgram', serializeProgramEvent);

return allFiles;
}
Expand Down
13 changes: 13 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,7 @@ export type BeforePrepareProgramEvent = PrepareProgramEvent;
export interface PrepareProgramEvent {
program: Program;
editor: Editor;
files: BscFile[];
}
export type AfterPrepareProgramEvent = PrepareProgramEvent;

Expand All @@ -797,6 +798,12 @@ export interface PrepareFileEvent<TFile extends BscFile = BscFile> {
program: Program;
file: TFile;
editor: Editor;
/**
* The scope that was linked for this event. A file may be included in multiple scopes, but we choose the most relevant scope.
* Plugins may unlink this scope and link another one, but must then reassign this property to that new scope so that other
* plugins can reference it.
*/
scope: Scope;
}
export type OnPrepareFileEvent<TFile extends BscFile = BscFile> = PrepareFileEvent<TFile>;
export type AfterPrepareFileEvent<TFile extends BscFile = BscFile> = PrepareFileEvent<TFile>;
Expand Down Expand Up @@ -837,6 +844,12 @@ export type BeforeSerializeFileEvent<TFile extends BscFile = BscFile> = Serializ
export interface SerializeFileEvent<TFile extends BscFile = BscFile> {
program: Program;
file: TFile;
/**
* The scope that was linked for this event. A file may be included in multiple scopes, but we choose the most relevant scope.
* Plugins may unlink this scope and link another one, but must then reassign this property to that new scope so that other
* plugins can reference it.
*/
scope: Scope;
/**
* The list of all files created across all the `SerializeFile` events.
* The key is the pkgPath of the file, and the
Expand Down

0 comments on commit 825b2ce

Please sign in to comment.