Skip to content

Commit 3c81696

Browse files
committed
add input queueing
1 parent 6bf3f66 commit 3c81696

File tree

5 files changed

+178
-27
lines changed

5 files changed

+178
-27
lines changed

package.json

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Mipsy Editor Features",
44
"description": "",
55
"publisher": "xavc",
6-
"version": "0.3.1",
6+
"version": "0.4.0",
77
"repository": {
88
"url": "https://github.com/XavierCooney/mipsy-editor-features"
99
},
@@ -102,6 +102,18 @@
102102
"title": "Memory",
103103
"command": "mips.debug.viewMemory",
104104
"enablement": "inDebugMode && debugType == 'mipsy-1'"
105+
},
106+
{
107+
"category": "MIPS",
108+
"title": "Send selection to MIPS input",
109+
"command": "mipsy.debug.sendSelectionToInput",
110+
"enablement": "inDebugMode && debugType == 'mipsy-1' && editorHasSelection"
111+
},
112+
{
113+
"category": "MIPS",
114+
"title": "Send file to MIPS input",
115+
"command": "mipsy.debug.sendFileToInput",
116+
"enablement": "inDebugMode && debugType == 'mipsy-1'"
105117
}
106118
],
107119
"menus": {
@@ -122,6 +134,18 @@
122134
"command": "mips.debug.viewMemory",
123135
"when": "inDebugMode && debugType == 'mipsy-1'"
124136
}
137+
],
138+
"editor/context": [
139+
{
140+
"command": "mipsy.debug.sendSelectionToInput",
141+
"when": "inDebugMode && debugType == 'mipsy-1'"
142+
}
143+
],
144+
"editor/title/context": [
145+
{
146+
"command": "mipsy.debug.sendFileToInput",
147+
"when": "inDebugMode && debugType == 'mipsy-1'"
148+
}
125149
]
126150
},
127151
"views": {

src/extension.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as vscode from 'vscode';
22

33
import { setupDecompilationButton } from './decompileView';
44
import { setupIOView } from './ioViewProvider';
5-
import { setupDebugButton } from './launchDebug';
5+
import { setupDebugButton, setupSendInputButton } from './launchDebug';
66
import { deactivateClient, startLSP } from './lspClient';
77
import { setupMemoryButton } from './memoryViewer';
88

@@ -15,15 +15,7 @@ export function activate(context: vscode.ExtensionContext) {
1515
setupDebugButton(context);
1616
setupMemoryButton(context);
1717
setupIOView(context);
18-
19-
// context.subscriptions.push(vscode.debug.registerDebugAdapterTrackerFactory('*', {
20-
// createDebugAdapterTracker(session: vscode.DebugSession) {
21-
// return {
22-
// onWillReceiveMessage: m => console.log(`> ${JSON.stringify(m, undefined, 2)}`),
23-
// onDidSendMessage: m => console.log(`< ${JSON.stringify(m, undefined, 2)}`)
24-
// };
25-
// }
26-
// }));
18+
setupSendInputButton(context);
2719
}
2820

2921
export function deactivate() {

src/launchDebug.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,21 @@ export function setupDebugButton(context: vscode.ExtensionContext) {
7171
vscode.window.showErrorMessage(`Debug session must be reloaded before changes take effect!`);
7272
}));
7373
}
74+
75+
export function setupSendInputButton(context: vscode.ExtensionContext) {
76+
context.subscriptions.push(vscode.commands.registerTextEditorCommand('mipsy.debug.sendSelectionToInput', (editor, edit) => {
77+
let text = editor.document.getText(editor.selection);
78+
vscode.debug.activeDebugSession?.customRequest('queueInput', {
79+
contents: text
80+
});
81+
}));
82+
83+
context.subscriptions.push(vscode.commands.registerCommand('mipsy.debug.sendFileToInput', (uri: vscode.Uri) => {
84+
vscode.workspace.openTextDocument(uri).then(document => {
85+
let text = document.getText();
86+
vscode.debug.activeDebugSession?.customRequest('queueInput', {
87+
contents: text
88+
});
89+
});
90+
}));
91+
}

src/mipsDebugAdapter.ts

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import {
2-
LoggingDebugSession, OutputEvent, LoadedSourceEvent,
2+
DebugSession, OutputEvent, LoadedSourceEvent,
33
InitializedEvent, StoppedEvent, TerminatedEvent, InvalidatedEvent, ContinuedEvent
44
} from '@vscode/debugadapter';
55
import {
66
DebugProtocol
77
} from '@vscode/debugprotocol';
88
import { make_new_runtime, DebugRuntime } from '../mipsy_vscode/pkg/mipsy_vscode';
9+
import { ScanBuffer } from './scanBuffer';
910
const { promises: fs } = require("fs");
1011

1112
// const rand = Math.floor(Math.random() * 9000) + 1000;
@@ -21,7 +22,7 @@ class MipsRuntime {
2122
public runningReverse: boolean;
2223
private isAtExit: boolean = false;
2324

24-
constructor(readonly source: string, readonly filename: string, readonly path: string, readonly session: MipsSession) {
25+
constructor(readonly source: string, readonly filename: string, readonly path: string, readonly session: MipsSession, private readonly scanBuffer: ScanBuffer) {
2526
this.runtime = make_new_runtime(
2627
source, filename
2728
);
@@ -62,6 +63,17 @@ class MipsRuntime {
6263
this.autoRunning = true;
6364
}
6465

66+
sendToIOView(str: string) {
67+
this.session.sendEvent({
68+
event: 'mipsyOutput',
69+
body: {
70+
charCodes: [...str].map(c => c.charCodeAt(0))
71+
},
72+
seq: 0,
73+
type: 'event'
74+
});
75+
}
76+
6577
step(): boolean {
6678
if (this.isAtExit) {
6779
this.session.sendStdoutLine('exiting...');
@@ -101,14 +113,7 @@ class MipsRuntime {
101113
this.session.sendStdoutLine(`syscall ${printType}: ${sanitisedContents}`);
102114
}
103115

104-
this.session.sendEvent({
105-
event: 'mipsyOutput',
106-
body: {
107-
charCodes: [...printContents].map(c => c.charCodeAt(0))
108-
},
109-
seq: 0,
110-
type: 'event'
111-
});
116+
this.sendToIOView(printContents);
112117

113118
return true;
114119
} else if (syscallGuard === 'exit') {
@@ -119,6 +124,39 @@ class MipsRuntime {
119124
this.runtime.acknowledge_breakpoint();
120125
return !this.autoRunning; // stop the autorun, but don't stop single stepping
121126
} else if (syscallGuard.startsWith('read_')) {
127+
if (!this.scanBuffer.isExhausted) {
128+
let result;
129+
let todoSyscall = false;
130+
131+
if (syscallGuard === 'read_int') {
132+
result = this.scanBuffer.readInt();
133+
} else if (syscallGuard === 'read_character') {
134+
result = this.scanBuffer.readChar();
135+
} else {
136+
todoSyscall = true;
137+
this.session.sendStderrLine(
138+
`Queued input with a ${syscallGuard} syscall not currently supported`
139+
);
140+
}
141+
142+
if (result === undefined) {
143+
if (this.scanBuffer.isExhausted) {
144+
this.session.sendStderrLine('All queued input now exhausted');
145+
} else if (!todoSyscall) {
146+
this.session.sendStderrLine(
147+
`Invalid format encountered in queued input during ${syscallGuard} syscall: ${this.scanBuffer.initialContents()}. Queued input removed.`
148+
);
149+
}
150+
this.scanBuffer.clear();
151+
} else {
152+
this.session.sendStdoutLine(`[from queued input] ${this.provideInput(result.toString())}`);
153+
154+
if (!this.inputNeeded) {
155+
return true;
156+
}
157+
}
158+
}
159+
122160
if (this.inputNeeded) {
123161
// we've already told the user to enter input, maybe say something different?
124162
this.session.sendStderrLine(
@@ -246,6 +284,8 @@ class MipsRuntime {
246284
this.session.sendEvent(new StoppedEvent('step', THREAD_ID));
247285
}
248286

287+
this.sendToIOView(input.trimEnd() + '\n');
288+
249289
return `syscall ${sycallType}: ${input}`;
250290
} else if (result) {
251291
return result;
@@ -262,15 +302,20 @@ function numTo32BitHex(value: number) {
262302
}
263303

264304
// TODO: this is structured incredibly badly
265-
class MipsSession extends LoggingDebugSession {
305+
class MipsSession extends DebugSession {
266306
private sourceFilePath: string = '';
267307
private source: string = '';
268308
private sourceName: string = '<source code>';
269309
private initialBreakpoints: number[] = [];
270310
private isVSCode: boolean = false;
311+
private scanBuffer: ScanBuffer = new ScanBuffer();
271312

272313
private runtime: MipsRuntime | undefined;
273314

315+
constructor() {
316+
super();
317+
}
318+
274319
protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
275320
response.body = response.body || {};
276321

@@ -356,21 +401,18 @@ class MipsSession extends LoggingDebugSession {
356401
this.sendEvent(new TerminatedEvent());
357402
}
358403

359-
// this.sendDebugLine(`wow ${this.source}`);
360404
const pathParts = fsPath.split(/[\/\\]/);
361405
this.sourceName = pathParts[pathParts.length - 1];
362406

363407
try {
364-
this.runtime = new MipsRuntime(this.source, this.sourceName, this.sourceFilePath, this);
408+
this.runtime = new MipsRuntime(this.source, this.sourceName, this.sourceFilePath, this, this.scanBuffer);
365409
} catch (e) {
366410
this.sendError('Error:\n' + e);
367411
this.sendEvent(new TerminatedEvent());
368412
}
369413

370414
this.runtime?.setBreakpoints(this.initialBreakpoints);
371415

372-
// this.sendSource();
373-
374416
this.sendEvent(new StoppedEvent(
375417
'entry',
376418
THREAD_ID
@@ -636,6 +678,26 @@ class MipsSession extends LoggingDebugSession {
636678
return super.sendEvent(event);
637679
}
638680

681+
protected customRequest(command: string, response: DebugProtocol.Response, args: any, request?: DebugProtocol.Request | undefined): void {
682+
if (command === 'queueInput') {
683+
if (this.scanBuffer.isExhausted) {
684+
this.sendStdoutLine('[input queued]');
685+
} else {
686+
this.sendStdoutLine('[previous input queue cleared, and new input queued]');
687+
}
688+
this.scanBuffer.clear();
689+
690+
this.scanBuffer.addToQueue(args.contents);
691+
this.sendResponse(response);
692+
693+
if (this.runtime?.inputNeeded) {
694+
this.runtime.step();
695+
}
696+
697+
return;
698+
}
699+
}
700+
639701
protected dispatchRequest(request: DebugProtocol.Request) {
640702
// this.sendDebugLine(`request ${JSON.stringify(request)}`);
641703
return super.dispatchRequest(request);
@@ -647,5 +709,5 @@ class MipsSession extends LoggingDebugSession {
647709
}
648710
}
649711

650-
const session = new MipsSession("out-file.txt");
712+
const session = new MipsSession();
651713
session.start(process.stdin, process.stdout);

src/scanBuffer.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
export class ScanBuffer {
2+
private buffer: string[] = [];
3+
private index: number = 0;
4+
public isExhausted: boolean = true;
5+
6+
constructor() {
7+
8+
}
9+
10+
addToQueue(input: string) {
11+
this.buffer.push('\n');
12+
this.buffer.push(...input);
13+
this.isExhausted = false;
14+
}
15+
16+
consumeWhileMatchesRegex(regex: RegExp) {
17+
const result = [];
18+
while (this.index < this.buffer.length && regex.test(this.buffer[this.index])) {
19+
result.push(this.buffer[this.index++]);
20+
}
21+
if (this.index >= this.buffer.length && !this.isExhausted) {
22+
this.isExhausted = true;
23+
}
24+
return result.join('');
25+
}
26+
27+
skipWhitespace() {
28+
this.consumeWhileMatchesRegex(/[ \r\n\t]/);
29+
}
30+
31+
initialContents() {
32+
return JSON.stringify(this.buffer.slice(this.index, this.index + 10).join(''));
33+
}
34+
35+
readInt() {
36+
this.skipWhitespace();
37+
const contents = this.consumeWhileMatchesRegex(/[0-9-]/);
38+
let result: number | undefined = parseInt(contents);
39+
if (isNaN(result)) {
40+
result = undefined;
41+
}
42+
return result;
43+
}
44+
45+
readChar() {
46+
this.skipWhitespace(); // todo: this isn't necessarily correct
47+
return this.consumeWhileMatchesRegex(/./) || undefined;
48+
}
49+
50+
clear() {
51+
this.index = 0;
52+
this.buffer = [];
53+
this.isExhausted = true;
54+
}
55+
}

0 commit comments

Comments
 (0)