Skip to content

Commit a89e826

Browse files
authored
Merge pull request #35 from robalb/develop
Develop
2 parents 5fff9ce + 0d1a8d9 commit a89e826

File tree

9 files changed

+229
-35
lines changed

9 files changed

+229
-35
lines changed

webapp/biome.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,10 @@
55
"clientKind": "git",
66
"useIgnoreFile": false
77
},
8-
"files": {
9-
"ignoreUnknown": false,
10-
"ignore": []
11-
},
128
"formatter": {
139
"enabled": true,
1410
"indentStyle": "tab"
1511
},
16-
"organizeImports": {
17-
"enabled": true
18-
},
1912
"linter": {
2013
"enabled": true,
2114
"rules": {

webapp/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"preview": "vite preview"
1010
},
1111
"engines": {
12-
"npm": "^10.2.4",
13-
"node": "^20.11.1"
12+
"npm": "^9.2.0",
13+
"node": "^18.19.1"
1414
},
1515
"devDependencies": {
1616
"@melt-ui/svelte": "^0.84.0",

webapp/src/assets/blinkenlib.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ var readyPromise = new Promise((resolve, reject) => {
2828
readyPromiseResolve = resolve;
2929
readyPromiseReject = reject;
3030
});
31-
["_memory","___indirect_function_table","_blinkenlib_run_fast","_blinkenlib_run","_blinkenlib_starti","_blinkenlib_start","_blinkenlib_stepi","_blinkenlib_continue","_blinkenlib_preempt_resume","_blinkenlib_get_clstruct","_blinkenlib_get_argc_string","_blinkenlib_get_argv_string","_blinkenlib_get_progname_string","_blinkenlib_spy_address","_main","onRuntimeInitialized"].forEach((prop) => {
31+
["_memory","___indirect_function_table","_blinkenlib_run_fast","_blinkenlib_run","_blinkenlib_starti","_blinkenlib_start","_blinkenlib_stepi","_blinkenlib_continue","_blinkenlib_preempt_resume","_blinkenlib_faketty_resume","_blinkenlib_get_clstruct","_blinkenlib_get_argc_string","_blinkenlib_get_argv_string","_blinkenlib_get_progname_string","_blinkenlib_spy_address","_main","onRuntimeInitialized"].forEach((prop) => {
3232
if (!Object.getOwnPropertyDescriptor(readyPromise, prop)) {
3333
Object.defineProperty(readyPromise, prop, {
3434
get: () => abort('You are getting ' + prop + ' on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js'),
@@ -5454,6 +5454,7 @@ var _blinkenlib_start = Module['_blinkenlib_start'] = createExportWrapper('blink
54545454
var _blinkenlib_stepi = Module['_blinkenlib_stepi'] = createExportWrapper('blinkenlib_stepi', 0);
54555455
var _blinkenlib_continue = Module['_blinkenlib_continue'] = createExportWrapper('blinkenlib_continue', 0);
54565456
var _blinkenlib_preempt_resume = Module['_blinkenlib_preempt_resume'] = createExportWrapper('blinkenlib_preempt_resume', 0);
5457+
var _blinkenlib_faketty_resume = Module['_blinkenlib_faketty_resume'] = createExportWrapper('blinkenlib_faketty_resume', 0);
54575458
var _blinkenlib_get_clstruct = Module['_blinkenlib_get_clstruct'] = createExportWrapper('blinkenlib_get_clstruct', 0);
54585459
var _blinkenlib_get_argc_string = Module['_blinkenlib_get_argc_string'] = createExportWrapper('blinkenlib_get_argc_string', 0);
54595460
var _blinkenlib_get_argv_string = Module['_blinkenlib_get_argv_string'] = createExportWrapper('blinkenlib_get_argv_string', 0);

webapp/src/assets/blinkenlib.wasm

470 Bytes
Binary file not shown.

webapp/src/components/Controls.svelte

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ function handle_back() {
135135
{#if mobile && !showEditor && $state == blink.states.PROGRAM_STOPPED}
136136
<p class="exitcodeinfo">{blink.stopReason.details}</p>
137137
{/if}
138+
{#if $state == blink.states.PROGRAM_READLINE_PAUSE}
139+
<p class="lineinputinfo">Program is paused, waiting for user input</p>
140+
{/if}
138141

139142

140143
<style>
@@ -250,4 +253,12 @@ function handle_back() {
250253
padding-left: 1rem;
251254
}
252255
256+
.lineinputinfo{
257+
color: var(--theme-exitcodeinfo-fg);
258+
background-color: #33485c;
259+
border: 1px solid var(--color-blue);
260+
margin: 0;
261+
padding-left: 1rem;
262+
}
263+
253264
</style>

webapp/src/components/Terminal.svelte

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script>
22
import { blinkStore, term_buffer, state } from "../core/store";
3+
import TerminalInput from "./TerminalInput.svelte"
34
45
let blink = blinkStore.getInstance();
56
let termref;
@@ -11,9 +12,11 @@ function scroll() {
1112
});
1213
}
1314
}
15+
1416
// Scroll the terminal wen the program state
1517
// or the terminal buffer change
1618
$: ($term_buffer || $state) && scroll();
19+
1720
</script>
1821

1922
<div class="term" bind:this={termref}>
@@ -22,6 +25,8 @@ $: ($term_buffer || $state) && scroll();
2225
</div>
2326
{#if $state == blink.states.PROGRAM_STOPPED}
2427
<p class="exitcodeinfo">{blink.stopReason.details}</p>
28+
{:else if $state == blink.states.PROGRAM_READLINE_PAUSE}
29+
<TerminalInput />
2530
{/if}
2631
</div>
2732

@@ -58,4 +63,32 @@ $: ($term_buffer || $state) && scroll();
5863
5964
padding-left: 1rem;
6065
}
66+
67+
.stdin{
68+
display: flex;
69+
flex-direction: column;
70+
width: 100%;
71+
font-family: var(--code-font-family);
72+
background-color: #6ab0f3;
73+
background-color: #6ab0f35c;
74+
border: 1px solid var(--color-blue);
75+
color: var(--theme-exitcodeinfo-fg);
76+
padding-left: 1rem;
77+
font-size: 16px;
78+
margin: 16px 0;
79+
}
80+
.stdin.stdin_row {
81+
display: flex;
82+
}
83+
.stdin input{
84+
font-family: var(--code-font-family);
85+
font-size: 16px;
86+
87+
margin: 2px 0 6px 0;
88+
89+
border: 1px solid white;
90+
}
91+
.stdin input:focus-visible{
92+
outline: 3px solid var(--theme-focus);
93+
}
6194
</style>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<script>
2+
import { blinkStore, term_buffer, state } from "../core/store";
3+
4+
let blink = blinkStore.getInstance();
5+
// If the program was paused because of a read syscall,
6+
// the buffer size will be stored in rdx,per kernel ABI
7+
// https://syscalls.mebeim.net/?table=x86/64/x64/latest
8+
const bufsize = Number(blink.m.readU64("rdx"));
9+
10+
let stdin_str = "";
11+
let nonascii = false
12+
13+
$: nonascii = stdin_str.length > 0 && currentBytes > stdin_str.length;
14+
//TODO: handle non-ascii char detection
15+
16+
// Use TextEncoder API to calculate bytes in real-time
17+
const encoder = new TextEncoder();
18+
$: currentBytes = encoder.encode(stdin_str).length;
19+
$: bytesLeft = bufsize - currentBytes;
20+
$: isOverLimit = currentBytes > bufsize;
21+
22+
function lineEnter(e){
23+
e.preventDefault();
24+
blink.readLineEnter(stdin_str)
25+
stdin_str = "";
26+
}
27+
28+
</script>
29+
30+
<form class="stdin" on:submit={lineEnter}>
31+
{#if nonascii}
32+
<label class="warning">You entered non-ascii characters. The terminal will transform them into multiple utf-8 encoded bytes</label>
33+
{/if}
34+
{#if bufsize==0}
35+
<label class="warning">The read size (rdx) is set to 0. The program will not actually read your input</label>
36+
{:else if isOverLimit}
37+
<label class="warning">The read size (rdx) is set to {bufsize}. The last {currentBytes - bufsize} bytes of your input will be ignored</label>
38+
{/if}
39+
<label>Enter your input ({currentBytes}/{bufsize} bytes):</label>
40+
<div class="stdin_row">
41+
<input type="text" bind:value={stdin_str}/>
42+
<button type="submit" class="button" >submit</button>
43+
</div>
44+
</form>
45+
46+
<style>
47+
.stdin{
48+
display: flex;
49+
flex-direction: column;
50+
width: 100%;
51+
font-family: var(--code-font-family);
52+
background-color: #6ab0f3;
53+
background-color: #6ab0f35c;
54+
background-color: #33486c;
55+
background-color: #3e4c64;
56+
border: 1px solid var(--color-blue);
57+
color: var(--theme-exitcodeinfo-fg);
58+
padding-left: 1rem;
59+
font-size: 16px;
60+
margin: 16px 0;
61+
}
62+
.stdin .stdin_row {
63+
display: flex;
64+
flex-direction: row;
65+
justify-content: center;
66+
margin: 2px 0 6px 0;
67+
padding-right: 1rem;
68+
}
69+
.stdin input{
70+
font-family: var(--code-font-family);
71+
font-size: 16px;
72+
width: 100%;
73+
height: 2rem;
74+
border-radius: 4px 0px 0px 4px;
75+
padding-left: 6px;
76+
border: 1px solid white;
77+
}
78+
.stdin input:focus-visible{
79+
outline: 3px solid var(--theme-focus);
80+
}
81+
82+
label {
83+
margin: 8px 0;
84+
}
85+
86+
label.warning{
87+
background-color: var(--theme-exitcodeinfo-bg);
88+
color: var(--theme-exitcodeinfo-fg);
89+
border: 1px solid var(--theme-exitcodeinfo-border);
90+
margin: 0;
91+
margin: 6px 1rem 0 0;
92+
padding-left: 1rem;
93+
padding-right: 1rem;
94+
font-size: 14px;
95+
96+
background-color: #daaa62;
97+
color: #171515;
98+
border: 1px solid rgb(0, 0, 0);
99+
100+
}
101+
</style>
102+

webapp/src/core/blink.ts

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,10 @@ const signals = {
182182
};
183183

184184
const sigtrap_codes = {
185-
BLINK_PREEMPT: 40,
186-
BLINK_STEP: 41,
185+
BLINK_SIGTRAP: 0,
186+
BLINK_PREEMPT: 40,
187+
BLINK_STEP: 41,
188+
BLINK_FAKE_TTY: 42,
187189
};
188190

189191
const signals_info = {
@@ -253,6 +255,7 @@ export class Blink {
253255
LINKING: "LINKING",
254256
PROGRAM_LOADED: "PROGRAM_LOADED",
255257
PROGRAM_RUNNING: "PROGRAM_RUNNING",
258+
PROGRAM_READLINE_PAUSE: "PROGRAM_READLINE_PAUSE",
256259
PROGRAM_STOPPED: "PROGRAM_STOPPED",
257260
} as const;
258261

@@ -279,6 +282,9 @@ export class Blink {
279282
//assembler diagnostic errors
280283
assembler_errors = [];
281284

285+
//fake TTY readline
286+
stdin_bytes = [];
287+
282288
/**
283289
* Initialize the emscripten blink module.
284290
*/
@@ -306,7 +312,22 @@ export class Blink {
306312
noInitialRun: true,
307313
preRun: (M: any) => {
308314
M.FS.init(
309-
this.#stdinHandler,
315+
() => {
316+
//stdin read
317+
318+
// TODO: use this when in pipe mode.
319+
// right now, libblink supports only
320+
// a fake TTY mode for reading input
321+
// return this.#stdinHandler()
322+
323+
// Fake tty mode: return the data that was inserted via
324+
// blink.readLineEnter(string)
325+
if(this.stdin_bytes.length){
326+
return this.stdin_bytes.pop()
327+
}else{
328+
return null //EOF
329+
}
330+
},
310331
(charcode: number) => {
311332
this.#assembler_logcollector(charcode);
312333
this.#stdoutHandler(charcode);
@@ -389,14 +410,23 @@ export class Blink {
389410
}
390411

391412
/**
392-
* This callback is called from the wasm code
393-
* when the guest process is stopped by a terminating signal
413+
* This callback is called from the wasm code when
414+
* the guest process is stopped/paused by a signal
415+
*
416+
* If this callback got called, the execution loop
417+
* emulating the cpu on the wasm side is no longer
418+
* running.
394419
*
395-
* SIGTRAP is the only signal that does not indicate
420+
* SIGTRAP is the only signal that doesnt indicate
396421
* a program stop.
422+
* SIGTRAP is not necessarily a real POSIX SIGTRAP
423+
* There is an additional code parameter passed to
424+
* the callback, which indicates the actual reason
425+
* for the program pause.
397426
*/
398427
#extern_c__signal_callback(sig: number, code: number) {
399-
if (sig !== signals.SIGTRAP) {
428+
// signals != SIGTRAP ---> program exit
429+
if (sig !== signals.SIGTRAP){
400430
const exitCode = 128 + sig;
401431
let details = `Program terminated with Exit(${exitCode}) Due to signal ${sig}`;
402432
if (Object.prototype.hasOwnProperty.call(signals_info, sig)) {
@@ -406,23 +436,34 @@ export class Blink {
406436
}
407437
this.stopReason = {
408438
loadFail: false,
409-
exitCode: exitCode,
410-
details: details,
411-
};
412-
this.#setState(this.states.PROGRAM_STOPPED);
413-
this.#signalHandler(sig, code);
414-
} else if (
415-
sig === signals.SIGTRAP &&
416-
code === sigtrap_codes.BLINK_PREEMPT
417-
) {
418-
console.log("preempt");
419-
requestAnimationFrame(() => {
420-
this.Module._blinkenlib_preempt_resume();
421-
});
422-
} else {
423-
this.#signalHandler(sig, code);
424-
}
425-
}
439+
exitCode: exitCode,
440+
details: details,
441+
};
442+
this.#setState(this.states.PROGRAM_STOPPED);
443+
this.#signalHandler(sig, code);
444+
return;
445+
}
446+
447+
// fake SIGTRAP. it actually indicates preemption.
448+
// The emulator paused to free the js event loop.
449+
if(code === sigtrap_codes.BLINK_PREEMPT){
450+
console.log("preempt");
451+
requestAnimationFrame(() => {
452+
this.Module._blinkenlib_preempt_resume();
453+
});
454+
}
455+
// fake SIGTRAP: it actually indicates a tty line read.
456+
// Emulator paused on a read syscall, waiting for
457+
// the user to enter one line on the fake js tty.
458+
else if(code === sigtrap_codes.BLINK_FAKE_TTY){
459+
this.#setState(this.states.PROGRAM_READLINE_PAUSE);
460+
this.#signalHandler(sig, code);
461+
}
462+
// an actual SIGTRAP
463+
else {
464+
this.#signalHandler(sig, code);
465+
}
466+
}
426467

427468
/**
428469
* This callback is called from the wasm code
@@ -670,6 +711,19 @@ export class Blink {
670711
}
671712
}
672713

714+
readLineEnter(line: string) {
715+
//Note: validation should be handled on the UI side,
716+
//e.g.: if user enters an emoji, a message shoul appear
717+
//explaining the implications of that - multiple bytes,
718+
//utf-8 encoding, need to manually handle that in the
719+
//assembly side, etc.
720+
//Note: the bytes are stored in reverse order.
721+
//the stdin handler will pop bytes from this array.
722+
this.stdin_bytes = Array.from(new TextEncoder().encode(line)).reverse();
723+
this.#setState(this.states.PROGRAM_RUNNING);
724+
this.Module._blinkenlib_faketty_resume();
725+
}
726+
673727
stepi() {
674728
this.Module._blinkenlib_stepi();
675729
}

0 commit comments

Comments
 (0)