diff --git a/package-lock.json b/package-lock.json index 6cefc7c..512f494 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "chai": "^4.3.6", "ieee754": "^1.2.1", "mqtt": "^4.3.7", + "source-map": "^0.7.4", "ts-retry-promise": "^0.7.0" }, "devDependencies": { @@ -4794,6 +4795,14 @@ "node": ">= 10" } }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/split2": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", @@ -9076,6 +9085,11 @@ "socks": "^2.6.1" } }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" + }, "split2": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", diff --git a/package.json b/package.json index 38d26d8..39ba660 100644 --- a/package.json +++ b/package.json @@ -287,6 +287,7 @@ "chai": "^4.3.6", "ieee754": "^1.2.1", "mqtt": "^4.3.7", + "source-map": "^0.7.4", "ts-retry-promise": "^0.7.0" } } diff --git a/src/CompilerBridges/AssemblyScriptCompilerBridge.ts b/src/CompilerBridges/AssemblyScriptCompilerBridge.ts index 9bd4548..54dca78 100644 --- a/src/CompilerBridges/AssemblyScriptCompilerBridge.ts +++ b/src/CompilerBridges/AssemblyScriptCompilerBridge.ts @@ -1,6 +1,7 @@ import {CompileBridge, CompileResult} from './CompileBridge'; import {exec, ExecException} from 'child_process'; import {SourceMap} from '../State/SourceMap'; +import {MappingItem, SourceMapConsumer} from 'source-map'; import * as fs from 'fs'; import {readFileSync} from 'fs'; import {WASMCompilerBridge} from './WASMCompilerBridge'; @@ -10,32 +11,20 @@ import * as path from 'path'; export class AssemblyScriptCompilerBridge implements CompileBridge { sourceFilePath: String; - private wat: WASMCompilerBridge; private readonly tmpdir: string; + private readonly wabt: string; + private readonly workingDir?: string; - constructor(sourceFilePath: String, tmpdir: string, wabt: string) { + constructor(sourceFilePath: String, tmpdir: string, wabt: string, workingDir?: string) { this.sourceFilePath = sourceFilePath; this.tmpdir = tmpdir; - this.wat = new WASMCompilerBridge(`${this.tmpdir}/upload.wast`, tmpdir, wabt); + this.wabt = wabt; + this.workingDir = workingDir; } async compile(): Promise { - return this.wasm().then((result) => { - return this.lineInformation(result.sourceMap).then((lines) => { - const wasm: Buffer = readFileSync(`${this.tmpdir}/upload.wasm`); - result.sourceMap.lineInfoPairs = lines; - return Promise.resolve({sourceMap: result.sourceMap, wasm: wasm}); - }); - }); - } - - async clean(path2makefile: string): Promise { - return; - } - - private async wasm() { return new Promise(async (resolve, reject) => { - const command = `cd ${path.dirname(this.sourceFilePath.toString())} ; ` + await this.getCompilationCommand(); + const command = await this.getCompilationCommand(); let out: String = ''; let err: String = ''; @@ -53,13 +42,21 @@ export class AssemblyScriptCompilerBridge implements CompileBridge { } resolve(); }); - }).then(() => { - return this.wat.compile(); + }).then(async () => { + const w: Buffer = readFileSync(`${this.tmpdir}/upload.wasm`); + return Promise.resolve({ + sourceMap: await new AsScriptMapper(this.sourceFilePath.toString(), this.tmpdir, this.wabt).mapping(), + wasm: w + }); }).catch((error) => { return Promise.reject(error); }); } + async clean(path2makefile: string): Promise { + return; + } + // private executeCompileCommand(command: string): Promise { // const compiler = this; // return new Promise(async function (resolve, reject) { @@ -115,7 +112,7 @@ export class AssemblyScriptCompilerBridge implements CompileBridge { } }); - return new Promise((resolve, reject) => { + return new Promise((resolve) => { reader.on('close', () => { resolve(mapping); }); @@ -124,36 +121,84 @@ export class AssemblyScriptCompilerBridge implements CompileBridge { private getCompilationCommand(): Promise { // builds asc command based on the version of asc - return new Promise((resolve, reject) => { + return new Promise(async (resolve) => { + let version: Version = await this.retrieveVersion(); + resolve(`${this.workingDir ? `cd ${this.workingDir}; ` : ''}npx asc ${this.sourceFilePath} --exportTable --disable bulk-memory --sourceMap --debug ` + + `${(version.major > 0 || +version.minor >= 20) ? '--outFile' : '--binaryFile'} ${this.tmpdir}/upload.wasm`); + }); + } + + private retrieveVersion(): Promise { + return new Promise((resolve, reject) => { let out: String = ''; let err: String = ''; + function handle(error: ExecException | null, stdout: String, stderr: any) { out = stdout; err = error?.message ?? ''; } - let compilerVersion = exec('asc --version', handle); + const command: string = `${this.workingDir ? `cd ${this.workingDir}; ` : ''}npx asc --version`; + let compilerVersion = exec(command, handle); compilerVersion.on('close', (code) => { if (code !== 0) { - reject(`Compilation to wasm failed: exit code ${code}. Message: ${err}`); + reject(`asc --version failed: ${err}`); } - // build command - const versionRegex: RegExp = /^Version (?[0-9]+)\.(?[0-9]+)\.(?[0-9]+)/; - const matched = out.match(versionRegex); - if (!matched || !!!matched.groups?.major || !!!matched.groups?.minor || !!!matched.groups?.patch) { - reject(`Compilation to wasm failed: asc--version did not print expected output format 'Version x.x.x'.Got instead ${out} `); - } - let command = `asc ${this.sourceFilePath} --exportTable --disable bulk-memory --sourceMap -O3s --debug `; - if (+matched!.groups!.major > 0 || +matched!.groups!.minor >= 20) { - command += '--outFile '; - } - else { - command += '--binaryFile '; + const matched = out.match(/^Version (?[0-9]+)\.(?[0-9]+)\.(?[0-9]+)/); + if (matched && matched.groups?.major && matched.groups?.minor && matched.groups?.patch) { + resolve({major: +matched.groups.major, minor: +matched.groups.minor, patch: +matched.groups.patch}); + } else { + reject(`asc --version did not print expected output format 'Version x.x.x'. Got ${out} instead.`); } - command += `${this.tmpdir}/upload.wasm --textFile ${this.tmpdir}/upload.wast`; // use .wast to get inline sourcemapping - resolve(command); }); }); } +} + +interface Version { + major: number; + minor: number; + patch: number; +} + +export abstract class SourceMapper { + abstract mapping(): Promise; +} + +export class AsScriptMapper implements SourceMapper { + private readonly sourceFile: string; + private readonly tmpdir: string; + private readonly wabt: string; + + constructor(sourceFile: string, tmpdir: string, wabt: string) { + this.sourceFile = sourceFile; + this.tmpdir = tmpdir; + this.wabt = wabt; + } + + public mapping(): Promise { + const input = fs.readFileSync(`${this.tmpdir}/upload.wasm.map`); + + return new Promise((resolve) => { + new SourceMapConsumer(input.toString()).then((consumer: SourceMapConsumer) => { + const lineInfoPairs: LineInfoPairs[] = []; + consumer.eachMapping(function (item: MappingItem) { + if (!item.source.includes('~lib')) { + lineInfoPairs.push({ + lineInfo: { + line: item.originalLine, + column: item.originalColumn, + message: '' + }, + lineAddress: item.generatedColumn.toString(16) + }); + } + }); + resolve(lineInfoPairs); + }); + }).then((lines: LineInfoPairs[]) => { + return new WASMCompilerBridge(this.sourceFile, this.tmpdir, this.wabt).sourceDump(lines); + }); + } } \ No newline at end of file diff --git a/src/CompilerBridges/CompileBridge.ts b/src/CompilerBridges/CompileBridge.ts index 4c1e5c6..a4372f9 100644 --- a/src/CompilerBridges/CompileBridge.ts +++ b/src/CompilerBridges/CompileBridge.ts @@ -3,7 +3,8 @@ import {SourceMap} from '../State/SourceMap'; export interface CompileResult{ sourceMap: SourceMap; wasm: Buffer; -}; +} + export interface CompileBridge { compile(): Promise; clean(path2makefile: string): Promise; diff --git a/src/CompilerBridges/CompileBridgeFactory.ts b/src/CompilerBridges/CompileBridgeFactory.ts index 89303b8..855326c 100644 --- a/src/CompilerBridges/CompileBridgeFactory.ts +++ b/src/CompilerBridges/CompileBridgeFactory.ts @@ -2,6 +2,7 @@ import {getFileExtension} from '../Parsers/ParseUtils'; import {CompileBridge} from './CompileBridge'; import {WASMCompilerBridge} from './WASMCompilerBridge'; import {AssemblyScriptCompilerBridge} from './AssemblyScriptCompilerBridge'; +import * as vscode from 'vscode'; export class CompileBridgeFactory { static makeCompileBridge(file: string, tmpdir: string, wabt: string): CompileBridge { @@ -10,7 +11,7 @@ export class CompileBridgeFactory { case 'wast' : return new WASMCompilerBridge(file, tmpdir, wabt); case 'ts' : - return new AssemblyScriptCompilerBridge(file, tmpdir, wabt); + return new AssemblyScriptCompilerBridge(file, tmpdir, wabt, vscode.workspace.workspaceFolders?.[0].uri.path.toString()); } throw new Error('Unsupported file type'); } diff --git a/src/CompilerBridges/WASMCompilerBridge.ts b/src/CompilerBridges/WASMCompilerBridge.ts index c64c724..22b63ac 100644 --- a/src/CompilerBridges/WASMCompilerBridge.ts +++ b/src/CompilerBridges/WASMCompilerBridge.ts @@ -1,15 +1,14 @@ -import { exec, ExecException } from 'child_process'; +import {exec, ExecException} from 'child_process'; import * as parseUtils from '../Parsers/ParseUtils'; -import { CompileTimeError } from './CompileTimeError'; -import { LineInfo } from '../State/LineInfo'; -import { LineInfoPairs } from '../State/LineInfoPairs'; -import { CompileBridge } from './CompileBridge'; -import { SourceMap } from '../State/SourceMap'; -import { FunctionInfo } from '../State/FunctionInfo'; -import { VariableInfo } from '../State/VariableInfo'; -import { TypeInfo } from '../State/TypeInfo'; -import { readFileSync } from 'fs'; -import assert = require('assert'); +import {CompileTimeError} from './CompileTimeError'; +import {LineInfo} from '../State/LineInfo'; +import {LineInfoPairs} from '../State/LineInfoPairs'; +import {CompileBridge} from './CompileBridge'; +import {EmptySourceMap, SourceMap} from '../State/SourceMap'; +import {FunctionInfo} from '../State/FunctionInfo'; +import {VariableInfo} from '../State/VariableInfo'; +import {TypeInfo} from '../State/TypeInfo'; +import {readFileSync} from 'fs'; function checkCompileTimeError(errorMessage: string) { let regexpr = /:(?(\d+)):(?(\d+)): error: (?(.*))/; @@ -48,7 +47,7 @@ function createLineInfoPairs(lines: string[]): LineInfoPairs[] { // TODO update let lastLineInfo = undefined; for (let i = 0; i < lines.length; i++) { const line = lines[i]; - const newLine = line.match(/@/); + const newLine = line.match(/@ {/); if (newLine) { lastLineInfo = extractLineInfo(line); continue; @@ -60,9 +59,8 @@ function createLineInfoPairs(lines: string[]): LineInfoPairs[] { // TODO update column: lastLineInfo!.column, message: lastLineInfo!.message, }; - result.push({ lineInfo: li, lineAddress: addr }); - } - catch (e) { + result.push({lineInfo: li, lineAddress: addr}); + } catch (e) { } } @@ -86,11 +84,11 @@ export class WASMCompilerBridge implements CompileBridge { } async compile() { - let sourceMap: SourceMap = await this.compileAndDump(this.compileToWasmCommand(), this.getNameDumpCommand()); + let sourceMap: SourceMap = await this.compileAndDump(this.compileToWasmCommand()); await this.compileHeader(); const path2Wasm = `${this.tmpdir}/upload.wasm`; const w: Buffer = readFileSync(path2Wasm); - return { sourceMap: sourceMap, wasm: w }; + return {sourceMap: sourceMap, wasm: w}; } async compileHeader() { @@ -99,8 +97,8 @@ export class WASMCompilerBridge implements CompileBridge { } async clean(path2makefile: string): Promise { - return new Promise((res, rej)=>{ - const clean = exec('make clean', { cwd: path2makefile }, (err, stdout, stderr) => { + return new Promise((res, rej) => { + const clean = exec('make clean', {cwd: path2makefile}, (err, stdout, stderr) => { if (err) { rej(err); } @@ -140,7 +138,7 @@ export class WASMCompilerBridge implements CompileBridge { } } - private compileAndDump(compileCommand: string, objDumpCommand: string): Promise { + private compileAndDump(compileCommand: string): Promise { return new Promise((resolve, reject) => { let lineInfoPairs: LineInfoPairs[]; let that = this; @@ -160,37 +158,37 @@ export class WASMCompilerBridge implements CompileBridge { } }); - }).then((result) => { - return new Promise((resolve, reject) => { - let typeInfos: Map; - let functionInfos: FunctionInfo[]; - let globalInfos: VariableInfo[]; - let importInfos: FunctionInfo[]; - let sourceMap: SourceMap; - let that = this; - - function handleObjDumpStreams(error: ExecException | null, stdout: String, stderr: any) { - that.handleStdError(stderr, reject); - that.handleError(error, reject); - typeInfos = parseUtils.getTypeInfos(stdout); - functionInfos = parseUtils.getFunctionInfos(stdout); - globalInfos = parseUtils.getGlobalInfos(stdout); - importInfos = parseUtils.getImportInfos(stdout); - } + }).then((result: LineInfoPairs[]) => this.sourceDump(result)); + } + + public sourceDump(lines: LineInfoPairs[]): Promise { + return new Promise((resolve, reject) => { + let typeInfos: Map; + let functionInfos: FunctionInfo[]; + let globalInfos: VariableInfo[]; + let importInfos: FunctionInfo[]; + let sourceMap: SourceMap = EmptySourceMap(); + let that = this; - let objDump = exec(objDumpCommand, handleObjDumpStreams); - - if (result) { - sourceMap = { lineInfoPairs: result, functionInfos: [], globalInfos: [], importInfos: [], typeInfos: new Map() }; - objDump.on('close', (code) => { - if (functionInfos && globalInfos) { - sourceMap.functionInfos = functionInfos; - sourceMap.globalInfos = globalInfos; - sourceMap.importInfos = importInfos; - sourceMap.typeInfos = typeInfos; - resolve(sourceMap); - } - }); + function handleObjDumpStreams(error: ExecException | null, stdout: String, stderr: any) { + that.handleStdError(stderr, reject); + that.handleError(error, reject); + typeInfos = parseUtils.getTypeInfos(stdout); + functionInfos = parseUtils.getFunctionInfos(stdout); + globalInfos = parseUtils.getGlobalInfos(stdout); + importInfos = parseUtils.getImportInfos(stdout); + } + + let objDump = exec(this.getNameDumpCommand(), handleObjDumpStreams); + + sourceMap.lineInfoPairs = lines; + objDump.on('close', (code) => { + if (functionInfos && globalInfos) { + sourceMap.functionInfos = functionInfos; + sourceMap.globalInfos = globalInfos; + sourceMap.importInfos = importInfos; + sourceMap.typeInfos = typeInfos; + resolve(sourceMap); } }); });