rk86 2.0.30 → 2.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/rk86.js +127 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rk86",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.31",
|
|
4
4
|
"description": "Эмулятор Радио-86РК (Intel 8080) для терминала",
|
|
5
5
|
"bin": {
|
|
6
6
|
"rk86": "rk86.js"
|
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
|
-
"url": "https://github.com/begoon/rk86-js
|
|
21
|
+
"url": "https://github.com/begoon/rk86-js"
|
|
22
22
|
}
|
|
23
23
|
}
|
package/rk86.js
CHANGED
|
@@ -165,6 +165,7 @@ function preprocess(source) {
|
|
|
165
165
|
const out = [];
|
|
166
166
|
const stack = [];
|
|
167
167
|
let counter = 0;
|
|
168
|
+
let procCounter = 0;
|
|
168
169
|
let proc = null;
|
|
169
170
|
for (let i = 0;i < lines.length; i++) {
|
|
170
171
|
const line = lines[i];
|
|
@@ -223,7 +224,14 @@ function preprocess(source) {
|
|
|
223
224
|
regs.push(up);
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
|
-
|
|
227
|
+
const id = procCounter++;
|
|
228
|
+
proc = {
|
|
229
|
+
regs,
|
|
230
|
+
line: orig,
|
|
231
|
+
source: line,
|
|
232
|
+
exitLabel: `__proc_${id}_exit`,
|
|
233
|
+
returnUsed: false
|
|
234
|
+
};
|
|
227
235
|
out.push({ text: `${name}:`, orig });
|
|
228
236
|
for (const r of regs) {
|
|
229
237
|
out.push({ text: ` PUSH ${r}`, orig });
|
|
@@ -237,6 +245,9 @@ function preprocess(source) {
|
|
|
237
245
|
if (!proc) {
|
|
238
246
|
throw new AsmError(".endp without .proc", orig, line, firstNonSpaceCol(line));
|
|
239
247
|
}
|
|
248
|
+
if (proc.returnUsed) {
|
|
249
|
+
out.push({ text: `${proc.exitLabel}:`, orig });
|
|
250
|
+
}
|
|
240
251
|
out.push(...popsAndRet(proc.regs, orig));
|
|
241
252
|
proc = null;
|
|
242
253
|
continue;
|
|
@@ -245,7 +256,12 @@ function preprocess(source) {
|
|
|
245
256
|
if (!proc) {
|
|
246
257
|
throw new AsmError(".return outside .proc", orig, line, firstNonSpaceCol(line));
|
|
247
258
|
}
|
|
248
|
-
|
|
259
|
+
if (proc.regs.length === 0) {
|
|
260
|
+
out.push({ text: ` RET`, orig });
|
|
261
|
+
} else {
|
|
262
|
+
proc.returnUsed = true;
|
|
263
|
+
out.push({ text: ` JMP ${proc.exitLabel}`, orig });
|
|
264
|
+
}
|
|
249
265
|
continue;
|
|
250
266
|
}
|
|
251
267
|
out.push({ text: line, orig });
|
|
@@ -742,6 +758,7 @@ function countDb(operands) {
|
|
|
742
758
|
function asm(source) {
|
|
743
759
|
const pp = preprocess(source);
|
|
744
760
|
const symbols = new Map;
|
|
761
|
+
const pending = [];
|
|
745
762
|
let pc = 0;
|
|
746
763
|
let lastLabel = "";
|
|
747
764
|
let ended = false;
|
|
@@ -760,7 +777,7 @@ function asm(source) {
|
|
|
760
777
|
lastLabel = parts.label;
|
|
761
778
|
}
|
|
762
779
|
if (parts.isEqu) {
|
|
763
|
-
symbols
|
|
780
|
+
tryDefineEqu(symbols, pending, labelName, parts.operands[0], pc, lastLabel, orig, line);
|
|
764
781
|
continue;
|
|
765
782
|
}
|
|
766
783
|
symbols.set(labelName.toUpperCase(), pc);
|
|
@@ -800,6 +817,7 @@ function asm(source) {
|
|
|
800
817
|
throw new AsmError(e.message, orig, line, firstNonSpaceCol(line));
|
|
801
818
|
}
|
|
802
819
|
}
|
|
820
|
+
resolvePendingEqus(symbols, pending);
|
|
803
821
|
const sections = [];
|
|
804
822
|
let current = null;
|
|
805
823
|
const sectionNames = new Set;
|
|
@@ -861,6 +879,50 @@ function asm(source) {
|
|
|
861
879
|
}
|
|
862
880
|
return sections;
|
|
863
881
|
}
|
|
882
|
+
function isUnknownSymbolErr(e) {
|
|
883
|
+
return e instanceof Error && /^unknown symbol:/.test(e.message);
|
|
884
|
+
}
|
|
885
|
+
function tryDefineEqu(symbols, pending, name, expr, pc, lastLabel, orig, line) {
|
|
886
|
+
try {
|
|
887
|
+
symbols.set(name.toUpperCase(), evalExpr(expr, symbols, pc, lastLabel));
|
|
888
|
+
} catch (e) {
|
|
889
|
+
if (isUnknownSymbolErr(e)) {
|
|
890
|
+
pending.push({ name, expr, pc, lastLabel, orig, line });
|
|
891
|
+
} else {
|
|
892
|
+
throw e;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
function resolvePendingEqus(symbols, pending) {
|
|
897
|
+
while (pending.length > 0) {
|
|
898
|
+
let progress = false;
|
|
899
|
+
const next = [];
|
|
900
|
+
for (const p of pending) {
|
|
901
|
+
try {
|
|
902
|
+
symbols.set(p.name.toUpperCase(), evalExpr(p.expr, symbols, p.pc, p.lastLabel));
|
|
903
|
+
progress = true;
|
|
904
|
+
} catch (e) {
|
|
905
|
+
if (isUnknownSymbolErr(e)) {
|
|
906
|
+
next.push(p);
|
|
907
|
+
} else {
|
|
908
|
+
throw new AsmError(e.message, p.orig, p.line, firstNonSpaceCol(p.line));
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (!progress) {
|
|
913
|
+
const p = next[0];
|
|
914
|
+
try {
|
|
915
|
+
evalExpr(p.expr, symbols, p.pc, p.lastLabel);
|
|
916
|
+
} catch (e) {
|
|
917
|
+
throw new AsmError(e.message, p.orig, p.line, firstNonSpaceCol(p.line));
|
|
918
|
+
}
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
pending.length = 0;
|
|
922
|
+
pending.push(...next);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
var DATA_DIRECTIVES = new Set(["DB", "DW", "DS"]);
|
|
864
926
|
if (false) {}
|
|
865
927
|
|
|
866
928
|
// src/lib/terminal/rk86_terminal.ts
|
|
@@ -871,7 +933,7 @@ import { basename } from "path";
|
|
|
871
933
|
// packages/rk86/package.json
|
|
872
934
|
var package_default = {
|
|
873
935
|
name: "rk86",
|
|
874
|
-
version: "2.0.
|
|
936
|
+
version: "2.0.30",
|
|
875
937
|
description: "\u042D\u043C\u0443\u043B\u044F\u0442\u043E\u0440 \u0420\u0430\u0434\u0438\u043E-86\u0420\u041A (Intel 8080) \u0434\u043B\u044F \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0430",
|
|
876
938
|
bin: {
|
|
877
939
|
rk86: "rk86.js"
|
|
@@ -889,7 +951,7 @@ var package_default = {
|
|
|
889
951
|
license: "MIT",
|
|
890
952
|
repository: {
|
|
891
953
|
type: "git",
|
|
892
|
-
url: "https://github.com/begoon/rk86-js
|
|
954
|
+
url: "https://github.com/begoon/rk86-js"
|
|
893
955
|
}
|
|
894
956
|
};
|
|
895
957
|
|
|
@@ -2261,6 +2323,8 @@ class Memory {
|
|
|
2261
2323
|
return;
|
|
2262
2324
|
}
|
|
2263
2325
|
if (vg75_reg === 49152 && this.vg75_c001_00_cmd === 3) {
|
|
2326
|
+
this.machine.screen.set_char_height((byte & 15) + 1);
|
|
2327
|
+
this.machine.screen.underline_scanline = byte >> 4 & 15;
|
|
2264
2328
|
this.vg75_c001_00_cmd += 1;
|
|
2265
2329
|
return;
|
|
2266
2330
|
}
|
|
@@ -2354,6 +2418,9 @@ class Memory {
|
|
|
2354
2418
|
// src/lib/core/rk86_runner.ts
|
|
2355
2419
|
class Runner {
|
|
2356
2420
|
paused = false;
|
|
2421
|
+
turbo = false;
|
|
2422
|
+
hardware_id_enabled = false;
|
|
2423
|
+
stc_streak = 0;
|
|
2357
2424
|
tracer = null;
|
|
2358
2425
|
last_instructions = [];
|
|
2359
2426
|
previous_batch_time = 0;
|
|
@@ -2375,7 +2442,7 @@ class Runner {
|
|
|
2375
2442
|
this.machine.cpu.jump(63488);
|
|
2376
2443
|
}
|
|
2377
2444
|
interrupt(iff) {
|
|
2378
|
-
if (!this.sound)
|
|
2445
|
+
if (!this.sound || this.turbo)
|
|
2379
2446
|
return;
|
|
2380
2447
|
if (this.last_iff == iff)
|
|
2381
2448
|
return;
|
|
@@ -2400,8 +2467,11 @@ class Runner {
|
|
|
2400
2467
|
}
|
|
2401
2468
|
}
|
|
2402
2469
|
execute(options = {}) {
|
|
2403
|
-
const { terminate_address, on_terminate, exit_on_halt, on_halt, on_batch_complete
|
|
2470
|
+
const { terminate_address, on_terminate, exit_on_halt, on_halt, on_batch_complete } = options;
|
|
2471
|
+
if (options.turbo !== undefined)
|
|
2472
|
+
this.turbo = options.turbo;
|
|
2404
2473
|
clearTimeout(this.execute_timer);
|
|
2474
|
+
const turbo = this.turbo;
|
|
2405
2475
|
const bursts = turbo ? 100 : 1;
|
|
2406
2476
|
for (let burst = 0;burst < bursts; burst++) {
|
|
2407
2477
|
if (this.paused)
|
|
@@ -2418,10 +2488,21 @@ class Runner {
|
|
|
2418
2488
|
if (this.last_instructions.length > 5) {
|
|
2419
2489
|
this.last_instructions.shift();
|
|
2420
2490
|
}
|
|
2491
|
+
const opcode_pc = this.machine.cpu.pc;
|
|
2421
2492
|
this.machine.memory.invalidate_access_variables();
|
|
2422
2493
|
const instruction_ticks = this.machine.cpu.instruction();
|
|
2423
2494
|
batch_ticks += instruction_ticks;
|
|
2424
2495
|
this.total_ticks += instruction_ticks;
|
|
2496
|
+
if (this.hardware_id_enabled) {
|
|
2497
|
+
if (this.machine.memory.read_raw(opcode_pc) === 55) {
|
|
2498
|
+
if (++this.stc_streak >= 4) {
|
|
2499
|
+
this.stc_streak = 0;
|
|
2500
|
+
this.fire_hardware_id();
|
|
2501
|
+
}
|
|
2502
|
+
} else {
|
|
2503
|
+
this.stc_streak = 0;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2425
2506
|
if (this.tracer) {
|
|
2426
2507
|
this.tracer("after");
|
|
2427
2508
|
if (this.paused)
|
|
@@ -2464,6 +2545,13 @@ class Runner {
|
|
|
2464
2545
|
this.machine.cpu.jump(63488);
|
|
2465
2546
|
this.machine.keyboard.reset();
|
|
2466
2547
|
}
|
|
2548
|
+
fire_hardware_id() {
|
|
2549
|
+
const colorIdx = COLOR_MODES.indexOf(this.machine.screen.color_mode);
|
|
2550
|
+
this.machine.cpu.set_a(1);
|
|
2551
|
+
this.machine.cpu.set_b(colorIdx < 0 ? 0 : colorIdx);
|
|
2552
|
+
this.machine.cpu.set_c(this.turbo ? 1 : 0);
|
|
2553
|
+
this.machine.cpu.cf = 0;
|
|
2554
|
+
}
|
|
2467
2555
|
}
|
|
2468
2556
|
|
|
2469
2557
|
// src/lib/core/rk86_screen.ts
|
|
@@ -2484,6 +2572,8 @@ class Screen {
|
|
|
2484
2572
|
video_memory_base = 0;
|
|
2485
2573
|
video_memory_size = 0;
|
|
2486
2574
|
transparent_attr = false;
|
|
2575
|
+
char_height = 10;
|
|
2576
|
+
underline_scanline = 7;
|
|
2487
2577
|
color_mode = DEFAULT_COLOR_MODE;
|
|
2488
2578
|
ready = false;
|
|
2489
2579
|
renderer;
|
|
@@ -2544,6 +2634,11 @@ class Screen {
|
|
|
2544
2634
|
}
|
|
2545
2635
|
last_flip_ticks = 0;
|
|
2546
2636
|
tick_cursor(total_ticks, ticks_per_flip) {
|
|
2637
|
+
if (this.machine.runner.turbo) {
|
|
2638
|
+
this.cursor_state = true;
|
|
2639
|
+
this.last_flip_ticks = total_ticks;
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2547
2642
|
while (total_ticks - this.last_flip_ticks >= ticks_per_flip) {
|
|
2548
2643
|
this.cursor_state = !this.cursor_state;
|
|
2549
2644
|
this.last_flip_ticks += ticks_per_flip;
|
|
@@ -2560,7 +2655,7 @@ class Screen {
|
|
|
2560
2655
|
this.width = width;
|
|
2561
2656
|
this.height = height;
|
|
2562
2657
|
this.video_memory_size = width * height;
|
|
2563
|
-
this.machine.ui.update_screen_geometry(this.width, this.height);
|
|
2658
|
+
this.machine.ui.update_screen_geometry(this.width, this.height, this.char_height);
|
|
2564
2659
|
if (this.last_width === this.width && this.last_height === this.height)
|
|
2565
2660
|
return;
|
|
2566
2661
|
this.machine.log(`\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u0440\u0430\u0437\u043C\u0435\u0440 \u044D\u043A\u0440\u0430\u043D\u0430: ${width} x ${height}`);
|
|
@@ -2569,6 +2664,12 @@ class Screen {
|
|
|
2569
2664
|
if (this.last_video_memory_base !== -1)
|
|
2570
2665
|
this.ready = true;
|
|
2571
2666
|
}
|
|
2667
|
+
set_char_height(char_height) {
|
|
2668
|
+
if (this.char_height === char_height)
|
|
2669
|
+
return;
|
|
2670
|
+
this.char_height = char_height;
|
|
2671
|
+
this.machine.ui.update_screen_geometry(this.width, this.height, this.char_height);
|
|
2672
|
+
}
|
|
2572
2673
|
last_video_memory_base = -1;
|
|
2573
2674
|
set_video_memory(base) {
|
|
2574
2675
|
this.video_memory_base = base;
|
|
@@ -2992,21 +3093,34 @@ class TerminalRenderer {
|
|
|
2992
3093
|
addr += w;
|
|
2993
3094
|
}
|
|
2994
3095
|
let prevAnsi = -1;
|
|
3096
|
+
let prevReverse = false;
|
|
3097
|
+
let prevUnderline = false;
|
|
2995
3098
|
for (let x = 0;x < w; x++) {
|
|
2996
3099
|
const cell = cells[x];
|
|
2997
3100
|
const ch = cell.blink && blinkOff ? 0 : cell.ch;
|
|
2998
3101
|
const glyph = rk86char(ch);
|
|
2999
|
-
let
|
|
3102
|
+
let colorAttrs = cell.attrs;
|
|
3000
3103
|
if (offset && x + 1 < w && cells[x + 1].isFA) {
|
|
3001
|
-
|
|
3104
|
+
colorAttrs = cells[x + 1].attrs;
|
|
3002
3105
|
}
|
|
3003
|
-
const ansi = rgbToAnsiBaseFg(attrToRgb(mode,
|
|
3106
|
+
const ansi = rgbToAnsiBaseFg(attrToRgb(mode, colorAttrs));
|
|
3107
|
+
const reverse = !cell.isFA && (cell.attrs & 16) !== 0;
|
|
3108
|
+
const underline = !cell.isFA && (cell.attrs & 32) !== 0;
|
|
3004
3109
|
if (ansi !== prevAnsi) {
|
|
3005
3110
|
line += `\x1B[${ansi}m`;
|
|
3006
3111
|
prevAnsi = ansi;
|
|
3007
3112
|
}
|
|
3113
|
+
if (reverse !== prevReverse) {
|
|
3114
|
+
line += reverse ? `\x1B[7m` : `\x1B[27m`;
|
|
3115
|
+
prevReverse = reverse;
|
|
3116
|
+
}
|
|
3117
|
+
if (underline !== prevUnderline) {
|
|
3118
|
+
line += underline ? `\x1B[4m` : `\x1B[24m`;
|
|
3119
|
+
prevUnderline = underline;
|
|
3120
|
+
}
|
|
3008
3121
|
if (x === screen.cursor_x && y === screen.cursor_y) {
|
|
3009
3122
|
line += `\x1B[4m${glyph}\x1B[24m`;
|
|
3123
|
+
prevUnderline = false;
|
|
3010
3124
|
} else {
|
|
3011
3125
|
line += glyph;
|
|
3012
3126
|
}
|
|
@@ -3299,6 +3413,8 @@ async function main() {
|
|
|
3299
3413
|
const machine = machineBuilder;
|
|
3300
3414
|
machine.ui = new TerminalUI;
|
|
3301
3415
|
machine.memory = new Memory(machine);
|
|
3416
|
+
io.input = (port) => machine.memory.read(port | port << 8);
|
|
3417
|
+
io.output = (port, w8) => machine.memory.write(port | port << 8, w8);
|
|
3302
3418
|
machine.cpu = new I8080(machine);
|
|
3303
3419
|
machine.screen = new Screen(machine);
|
|
3304
3420
|
machine.screen.color_mode = colorMode;
|