From cfa37d833d588b44b6f3cd64020b1922ab43cd3e Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 31 Dec 2021 11:04:02 -0800 Subject: [PATCH] support writeMemory requests --- package.json | 15 ++++++------- src/activateMockDebug.ts | 39 +++++++++++++--------------------- src/mockDebug.ts | 15 ++++++++++--- src/mockRuntime.ts | 46 +++++++++++++++++++++++++++------------- tsconfig.json | 5 +++-- yarn.lock | 2 +- 6 files changed, 69 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 354965d..6fbe44f 100644 --- a/package.json +++ b/package.json @@ -31,30 +31,27 @@ "compile": "tsc -p ./", "lint": "eslint src --ext ts", "typecheck": "tsc -p tsconfig.json --noEmit", - "esbuild-base": "esbuild ./src/extension.ts --bundle --tsconfig=./tsconfig.json --external:vscode --format=cjs --platform=node --outfile=dist/extension.js", "watch": "npm run -S esbuild-base -- --sourcemap --sources-content=false --watch", - "esbuild-web": "esbuild ./src/web-extension.ts --bundle --tsconfig=./tsconfig.json --external:vscode --format=cjs --platform=browser --outfile=dist/web-extension.js", "watch-web": "npm run -S esbuild-web -- --sourcemap --sources-content=false --watch", - "build": "npm run -S esbuild-base -- --sourcemap --sources-content=false && npm run -S esbuild-web -- --sourcemap --sources-content=false", - "package": "vsce package", "publish": "vsce publish", "publish-pre-release": "vsce publish --pre-release", "vscode:prepublish": "rimraf dist && npm run -S esbuild-base -- --minify && npm run -S esbuild-web -- --minify" }, "devDependencies": { - "@types/vscode": "^1.61.0", "@types/glob": "^7.2.0", "@types/mocha": "^9.0.0", "@types/node": "^14.14.37", + "@types/vscode": "^1.61.0", "@typescript-eslint/eslint-plugin": "^5.2.0", "@typescript-eslint/parser": "^5.2.0", "await-notify": "1.0.1", - "eslint": "^8.1.0", + "base64-js": "^1.5.1", "esbuild": "^0.13.12", + "eslint": "^8.1.0", "events": "^3.3.0", "glob": "^7.2.0", "mocha": "^9.1.3", @@ -137,7 +134,9 @@ "debuggers": [ { "type": "mock", - "languages": ["markdown"], + "languages": [ + "markdown" + ], "label": "Mock Debug", "program": "./out/debugAdapter.js", "runtime": "node", @@ -207,4 +206,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/activateMockDebug.ts b/src/activateMockDebug.ts index da65649..e3bc85e 100644 --- a/src/activateMockDebug.ts +++ b/src/activateMockDebug.ts @@ -183,38 +183,29 @@ class MockConfigurationProvider implements vscode.DebugConfigurationProvider { } export const workspaceFileAccessor: FileAccessor = { - async readFile(path: string): Promise { + async readFile(path: string): Promise { let uri: vscode.Uri; try { - uri = vscode.Uri.file(path); + uri = pathToUri(path); } catch (e) { - try { - uri = vscode.Uri.parse(path); - } catch (e) { - return `cannot read '${path}'`; - } + return new TextEncoder().encode(`cannot read '${path}'`); } - const bytes = await vscode.workspace.fs.readFile(uri); - var result: string[] = []; - for (let i = 0; i < bytes.length;) { - const c = bytes[i++]; - var cp: number; - if (c <= 0x7F) { - cp = c; - } else if (c <= 0xDF) { - cp = ((c & 0x1F) << 6) | (bytes[i++] & 0x3F); - } else if (c <= 0xEF) { - cp = ((c & 0x0F) << 12) | ((bytes[i++] & 0x3F) << 6) | (bytes[i++] & 0x3F); - } else { - cp = ((c & 0x07) << 18) | ((bytes[i++] & 0x3F) << 12) | ((bytes[i++] & 0x3F) << 6) | (bytes[i++] & 0x3F); - } - result.push(String.fromCodePoint(cp)); - } - return result.join(''); + return await vscode.workspace.fs.readFile(uri); + }, + async writeFile(path: string, contents: Uint8Array) { + await vscode.workspace.fs.writeFile(pathToUri(path), contents); } }; +function pathToUri(path: string) { + try { + return vscode.Uri.file(path); + } catch (e) { + return vscode.Uri.parse(path); + } +} + class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory { createDebugAdapterDescriptor(_session: vscode.DebugSession): ProviderResult { diff --git a/src/mockDebug.ts b/src/mockDebug.ts index b1edd9d..e7cbcae 100644 --- a/src/mockDebug.ts +++ b/src/mockDebug.ts @@ -21,6 +21,7 @@ import { DebugProtocol } from 'vscode-debugprotocol'; import { basename } from 'path-browserify'; import { MockRuntime, IRuntimeBreakpoint, FileAccessor, IRuntimeVariable, timeout, IRuntimeVariableType } from './mockRuntime'; import { Subject } from 'await-notify'; +import * as base64 from 'base64-js'; /** * This interface describes the mock-debug specific launch attributes @@ -198,8 +199,9 @@ export class MockDebugSession extends LoggingDebugSession { response.body.supportsSteppingGranularity = true; response.body.supportsInstructionBreakpoints = true; - // make VS Code able to read variable memory + // make VS Code able to read and write variable memory response.body.supportsReadMemoryRequest = true; + response.body.supportsWriteMemoryRequest = true; this.sendResponse(response); @@ -384,14 +386,21 @@ export class MockDebugSession extends LoggingDebugSession { this.sendResponse(response); } + protected async writeMemoryRequest(response: DebugProtocol.WriteMemoryResponse, { data, memoryReference, offset = 0 }: DebugProtocol.WriteMemoryArguments) { + const [start] = JSON.parse(memoryReference); + const decoded = base64.toByteArray(data); + this._runtime.writeData(start + offset, decoded); + this.sendResponse(response); + } + protected async readMemoryRequest(response: DebugProtocol.ReadMemoryResponse, { offset = 0, count, memoryReference }: DebugProtocol.ReadMemoryArguments) { const [start, end] = JSON.parse(memoryReference); const realCount = Math.max(0, Math.min(count, end - (start + offset))); - const data = realCount > 0 ? this._runtime.memory.slice(offset + start, offset + start + realCount) : Buffer.alloc(0); + const encoded = realCount > 0 ? this._runtime.memory.slice(offset + start, offset + start + realCount) : new Uint8Array(); response.body = { address: (start + offset).toString(), - data: data.toString('base64'), + data: base64.fromByteArray(encoded), unreadableBytes: count - realCount }; this.sendResponse(response); diff --git a/src/mockRuntime.ts b/src/mockRuntime.ts index 7f54a54..85c7bfe 100644 --- a/src/mockRuntime.ts +++ b/src/mockRuntime.ts @@ -5,7 +5,8 @@ import { EventEmitter } from 'events'; export interface FileAccessor { - readFile(path: string): Promise; + readFile(path: string): Promise; + writeFile(path: string, contents: Uint8Array): Promise; } export interface IRuntimeBreakpoint { @@ -87,7 +88,7 @@ export class MockRuntime extends EventEmitter { private instructions: Word[] = []; private starts: number[] = []; private ends: number[] = []; - private sourceTextAsMemory = Buffer.alloc(0); + private sourceTextAsMemory = new Uint8Array(); public get memory() { return this.sourceTextAsMemory; @@ -447,21 +448,27 @@ export class MockRuntime extends EventEmitter { private async loadSource(file: string): Promise { if (this._sourceFile !== file) { this._sourceFile = file; - const contents = await this.fileAccessor.readFile(file); - this.sourceTextAsMemory = Buffer.from(contents); - this.sourceLines = contents.split(/\r?\n/); + this.initializeContents(await this.fileAccessor.readFile(file)); + } + } - this.instructions = []; + private initializeContents(memory: Uint8Array) { + this.sourceTextAsMemory = memory; + this.sourceLines = new TextDecoder().decode(memory).split(/\r?\n/); - for (let l = 0; l < this.sourceLines.length; l++) { - this.starts.push(this.instructions.length); - const words = this.getWords(l, this.sourceLines[l]); - for (let word of words) { - this.instructions.push(word); - } - this.ends.push(this.instructions.length); + this.instructions = []; + + this.starts = []; + this.instructions = []; + this.ends = []; + + for (let l = 0; l < this.sourceLines.length; l++) { + this.starts.push(this.instructions.length); + const words = this.getWords(l, this.sourceLines[l]); + for (let word of words) { + this.instructions.push(word); } - + this.ends.push(this.instructions.length); } } @@ -506,6 +513,15 @@ export class MockRuntime extends EventEmitter { return false; } + /** + * Updates the source file contents and re-parses variables/instructions. + */ + public async writeData(offset: number, data: Uint8Array) { + this.sourceTextAsMemory.set(data, offset); + await this.fileAccessor.writeFile(this._sourceFile, this.sourceTextAsMemory); + this.initializeContents(this.sourceTextAsMemory); + } + /** * "execute a line" of the readme markdown. * Returns true if execution sent out a stopped event and needs to stop. @@ -652,7 +668,7 @@ export class MockRuntime extends EventEmitter { private lineByteOffset(lineNumber: number) { let offset = 0; for (; lineNumber > 0; lineNumber--) { - offset = this.sourceTextAsMemory.indexOf('\n', offset) + 1; + offset = this.sourceTextAsMemory.indexOf(10 /* \n */, offset) + 1; } return offset; diff --git a/tsconfig.json b/tsconfig.json index 86b8334..9e202d6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,11 @@ { "compilerOptions": { "module": "commonjs", - "target": "es6", + "target": "ES2020", "outDir": "out", "lib": [ - "es6" + "WebWorker", + "ES2020" ], "sourceMap": true, "rootDir": "src", diff --git a/yarn.lock b/yarn.lock index 4fe6b9f..e177e97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -293,7 +293,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base64-js@^1.3.1: +base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==