rk86 2.0.15 → 2.0.16

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/rk86.js +392 -96
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rk86",
3
- "version": "2.0.15",
3
+ "version": "2.0.16",
4
4
  "description": "Эмулятор Радио-86РК (Intel 8080) для терминала",
5
5
  "bin": {
6
6
  "rk86": "rk86.js"
package/rk86.js CHANGED
@@ -1589,6 +1589,22 @@ var init_catalog_data = __esm(() => {
1589
1589
  });
1590
1590
 
1591
1591
  // node_modules/asm8080/dist/asm8.js
1592
+ class AsmError extends Error {
1593
+ line;
1594
+ column;
1595
+ source;
1596
+ constructor(message, line, source, column = 1) {
1597
+ super(message);
1598
+ this.name = "AsmError";
1599
+ this.line = line;
1600
+ this.source = source;
1601
+ this.column = column;
1602
+ }
1603
+ }
1604
+ function firstNonSpaceCol(s) {
1605
+ const m = s.match(/\S/);
1606
+ return m ? (m.index ?? 0) + 1 : 1;
1607
+ }
1592
1608
  var REG8 = {
1593
1609
  B: 0,
1594
1610
  C: 1,
@@ -1682,6 +1698,81 @@ var ADDR16 = {
1682
1698
  LHLD: 42,
1683
1699
  SHLD: 34
1684
1700
  };
1701
+ var ALL_MNEMONICS = new Set([
1702
+ ...Object.keys(IMPLIED),
1703
+ ...Object.keys(ALU_REG),
1704
+ ...Object.keys(ALU_IMM),
1705
+ ...Object.keys(ADDR16),
1706
+ "MOV",
1707
+ "MVI",
1708
+ "INR",
1709
+ "DCR",
1710
+ "LXI",
1711
+ "DAD",
1712
+ "INX",
1713
+ "DCX",
1714
+ "PUSH",
1715
+ "POP",
1716
+ "LDAX",
1717
+ "STAX",
1718
+ "IN",
1719
+ "OUT",
1720
+ "RST",
1721
+ "DB",
1722
+ "DW",
1723
+ "DS",
1724
+ "ORG",
1725
+ "SECTION",
1726
+ "END",
1727
+ "EQU"
1728
+ ]);
1729
+ var MAX_STATEMENTS_PER_LINE = 10;
1730
+ function splitStatements(line) {
1731
+ const src = stripComment(line);
1732
+ const out = [];
1733
+ let start = 0;
1734
+ let inQ = false;
1735
+ let qc = "";
1736
+ for (let i = 0;i + 2 < src.length; i++) {
1737
+ const c = src[i];
1738
+ if (inQ) {
1739
+ if (c === qc)
1740
+ inQ = false;
1741
+ continue;
1742
+ }
1743
+ if (c === '"' || c === "'") {
1744
+ inQ = true;
1745
+ qc = c;
1746
+ continue;
1747
+ }
1748
+ if (c !== " " || src[i + 1] !== "/" || src[i + 2] !== " ")
1749
+ continue;
1750
+ let j = i + 3;
1751
+ while (j < src.length && src[j] === " ")
1752
+ j++;
1753
+ let tokStart = j;
1754
+ if (src[j] === ".")
1755
+ j++;
1756
+ let tokEnd = j;
1757
+ while (tokEnd < src.length && /\w/.test(src[tokEnd]))
1758
+ tokEnd++;
1759
+ if (tokEnd === j)
1760
+ continue;
1761
+ let tok = src.slice(tokStart, tokEnd).toUpperCase();
1762
+ if (tok.startsWith("."))
1763
+ tok = tok.slice(1);
1764
+ if (!ALL_MNEMONICS.has(tok))
1765
+ continue;
1766
+ out.push(src.slice(start, i));
1767
+ start = i + 2;
1768
+ i += 2;
1769
+ }
1770
+ out.push(src.slice(start));
1771
+ if (out.length > MAX_STATEMENTS_PER_LINE) {
1772
+ throw new Error(`too many statements on one line (max ${MAX_STATEMENTS_PER_LINE})`);
1773
+ }
1774
+ return out;
1775
+ }
1685
1776
  function instrSize(m) {
1686
1777
  if (m in IMPLIED)
1687
1778
  return 1;
@@ -1749,6 +1840,13 @@ function splitOperands(s) {
1749
1840
  r.push(current.trim());
1750
1841
  return r;
1751
1842
  }
1843
+ var DIRECTIVES = new Set(["ORG", "SECTION", "END", "DB", "DW", "DS", "EQU"]);
1844
+ function stripDirectiveDot(s) {
1845
+ if (s.startsWith(".") && DIRECTIVES.has(s.slice(1).toUpperCase())) {
1846
+ return s.slice(1);
1847
+ }
1848
+ return s;
1849
+ }
1752
1850
  function parseLine(line) {
1753
1851
  let s = stripComment(line).trim();
1754
1852
  if (!s)
@@ -1766,7 +1864,7 @@ function parseLine(line) {
1766
1864
  const rest = si < 0 ? "" : s.slice(si).trim();
1767
1865
  if (!label && rest) {
1768
1866
  const parts = rest.split(/\s+/);
1769
- if (parts[0].toUpperCase() === "EQU") {
1867
+ if (stripDirectiveDot(parts[0]).toUpperCase() === "EQU") {
1770
1868
  return {
1771
1869
  label: first,
1772
1870
  mnemonic: "EQU",
@@ -1775,43 +1873,189 @@ function parseLine(line) {
1775
1873
  };
1776
1874
  }
1777
1875
  }
1778
- return { label, mnemonic: first, operands: rest ? splitOperands(rest) : [] };
1876
+ return {
1877
+ label,
1878
+ mnemonic: stripDirectiveDot(first),
1879
+ operands: rest ? splitOperands(rest) : []
1880
+ };
1779
1881
  }
1780
- function evalAtom(s, symbols) {
1781
- s = s.trim();
1782
- if (s.length === 3 && s[0] === "'" && s[2] === "'")
1783
- return s.charCodeAt(1);
1784
- if (/^[0-9][0-9A-Fa-f]*[hH]$/.test(s))
1785
- return parseInt(s.slice(0, -1), 16);
1786
- if (/^[0-9]+$/.test(s))
1787
- return parseInt(s, 10);
1788
- const k = s.toUpperCase();
1789
- if (symbols.has(k))
1790
- return symbols.get(k);
1791
- throw new Error(`unknown symbol: ${s}`);
1882
+ function tokenizeExpr(expr) {
1883
+ const tokens = [];
1884
+ let i = 0;
1885
+ while (i < expr.length) {
1886
+ let c = expr[i];
1887
+ if (/\s/.test(c)) {
1888
+ i++;
1889
+ continue;
1890
+ }
1891
+ if (c === "'" && i + 2 < expr.length && expr[i + 2] === "'") {
1892
+ tokens.push({ kind: "num", val: expr.charCodeAt(i + 1) });
1893
+ i += 3;
1894
+ continue;
1895
+ }
1896
+ if (/[0-9]/.test(c)) {
1897
+ let j = i;
1898
+ while (j < expr.length && /[0-9A-Fa-f]/.test(expr[j]))
1899
+ j++;
1900
+ if (j < expr.length && /[hH]/.test(expr[j])) {
1901
+ tokens.push({ kind: "num", val: parseInt(expr.slice(i, j), 16) });
1902
+ j++;
1903
+ } else {
1904
+ tokens.push({ kind: "num", val: parseInt(expr.slice(i, j), 10) });
1905
+ }
1906
+ i = j;
1907
+ continue;
1908
+ }
1909
+ if (/[A-Za-z_]/.test(c)) {
1910
+ let j = i;
1911
+ while (j < expr.length && /\w/.test(expr[j]))
1912
+ j++;
1913
+ tokens.push({ kind: "id", val: expr.slice(i, j) });
1914
+ i = j;
1915
+ continue;
1916
+ }
1917
+ if (c === "<" && expr[i + 1] === "<") {
1918
+ tokens.push({ kind: "op", val: "<<" });
1919
+ i += 2;
1920
+ continue;
1921
+ }
1922
+ if (c === ">" && expr[i + 1] === ">") {
1923
+ tokens.push({ kind: "op", val: ">>" });
1924
+ i += 2;
1925
+ continue;
1926
+ }
1927
+ if ("+-*/%&|^~()".includes(c)) {
1928
+ tokens.push({ kind: "op", val: c });
1929
+ i++;
1930
+ continue;
1931
+ }
1932
+ throw new Error(`unexpected character in expression: '${c}'`);
1933
+ }
1934
+ return tokens;
1792
1935
  }
1793
1936
  function evalExpr(expr, symbols) {
1794
- expr = expr.trim();
1795
- const tokens = [];
1796
- const ops = ["+"];
1797
- let current = "";
1798
- for (const c of expr) {
1799
- if ((c === "+" || c === "-") && current.trim()) {
1800
- tokens.push(current.trim());
1801
- ops.push(c);
1802
- current = "";
1803
- } else {
1804
- current += c;
1937
+ const tokens = tokenizeExpr(expr);
1938
+ let pos = 0;
1939
+ function peek() {
1940
+ return tokens[pos];
1941
+ }
1942
+ function next() {
1943
+ return tokens[pos++];
1944
+ }
1945
+ function isOp(val) {
1946
+ const t = peek();
1947
+ return t !== undefined && t.kind === "op" && t.val === val;
1948
+ }
1949
+ function atom() {
1950
+ const t = peek();
1951
+ if (!t)
1952
+ throw new Error("unexpected end of expression");
1953
+ if (t.kind === "num") {
1954
+ next();
1955
+ return t.val;
1956
+ }
1957
+ if (t.kind === "id") {
1958
+ next();
1959
+ const k = t.val.toUpperCase();
1960
+ if (k === "LOW" || k === "HIGH") {
1961
+ if (!isOp("("))
1962
+ throw new Error(`${k} requires parentheses`);
1963
+ next();
1964
+ const v = parseOr();
1965
+ if (!isOp(")"))
1966
+ throw new Error("expected ')'");
1967
+ next();
1968
+ return k === "LOW" ? v & 255 : v >> 8 & 255;
1969
+ }
1970
+ if (symbols.has(k))
1971
+ return symbols.get(k);
1972
+ throw new Error(`unknown symbol: ${t.val}`);
1973
+ }
1974
+ if (t.kind === "op" && t.val === "(") {
1975
+ next();
1976
+ const v = parseOr();
1977
+ if (!isOp(")"))
1978
+ throw new Error("expected ')'");
1979
+ next();
1980
+ return v;
1805
1981
  }
1982
+ throw new Error(`unexpected token: ${t.val}`);
1806
1983
  }
1807
- if (current.trim())
1808
- tokens.push(current.trim());
1809
- let r = 0;
1810
- for (let i = 0;i < tokens.length; i++) {
1811
- const v = evalAtom(tokens[i], symbols);
1812
- r = ops[i] === "+" ? r + v : r - v;
1984
+ function unary() {
1985
+ if (isOp("-")) {
1986
+ next();
1987
+ return -unary() & 65535;
1988
+ }
1989
+ if (isOp("+")) {
1990
+ next();
1991
+ return unary();
1992
+ }
1993
+ if (isOp("~")) {
1994
+ next();
1995
+ return ~unary() & 65535;
1996
+ }
1997
+ return atom();
1998
+ }
1999
+ function multiplicative() {
2000
+ let v = unary();
2001
+ while (isOp("*") || isOp("/") || isOp("%")) {
2002
+ const op = next().val;
2003
+ let r = unary();
2004
+ if (op === "*")
2005
+ v = v * r & 65535;
2006
+ else if (op === "/")
2007
+ v = Math.trunc(v / r) & 65535;
2008
+ else
2009
+ v = v % r & 65535;
2010
+ }
2011
+ return v;
2012
+ }
2013
+ function additive() {
2014
+ let v = multiplicative();
2015
+ while (isOp("+") || isOp("-")) {
2016
+ const op = next().val;
2017
+ let r = multiplicative();
2018
+ v = op === "+" ? v + r & 65535 : v - r & 65535;
2019
+ }
2020
+ return v;
2021
+ }
2022
+ function shift() {
2023
+ let v = additive();
2024
+ while (isOp("<<") || isOp(">>")) {
2025
+ const op = next().val;
2026
+ let r = additive();
2027
+ v = op === "<<" ? v << r & 65535 : v >>> r & 65535;
2028
+ }
2029
+ return v;
2030
+ }
2031
+ function parseAnd() {
2032
+ let v = shift();
2033
+ while (isOp("&")) {
2034
+ next();
2035
+ v = v & shift();
2036
+ }
2037
+ return v;
2038
+ }
2039
+ function parseXor() {
2040
+ let v = parseAnd();
2041
+ while (isOp("^")) {
2042
+ next();
2043
+ v = (v ^ parseAnd()) & 65535;
2044
+ }
2045
+ return v;
2046
+ }
2047
+ function parseOr() {
2048
+ let v = parseXor();
2049
+ while (isOp("|")) {
2050
+ next();
2051
+ v = (v | parseXor()) & 65535;
2052
+ }
2053
+ return v;
1813
2054
  }
1814
- return r & 65535;
2055
+ const result = parseOr();
2056
+ if (pos < tokens.length)
2057
+ throw new Error(`unexpected token: ${tokens[pos].val}`);
2058
+ return result;
1815
2059
  }
1816
2060
  function encode(m, ops, symbols) {
1817
2061
  if (m in IMPLIED)
@@ -1825,7 +2069,9 @@ function encode(m, ops, symbols) {
1825
2069
  return [ADDR16[m], v & 255, v >> 8 & 255];
1826
2070
  }
1827
2071
  if (m === "MOV")
1828
- return [64 | REG8[ops[0].toUpperCase()] << 3 | REG8[ops[1].toUpperCase()]];
2072
+ return [
2073
+ 64 | REG8[ops[0].toUpperCase()] << 3 | REG8[ops[1].toUpperCase()]
2074
+ ];
1829
2075
  if (m === "MVI") {
1830
2076
  const v = evalExpr(ops[1], symbols);
1831
2077
  return [6 | REG8[ops[0].toUpperCase()] << 3, v & 255];
@@ -1836,7 +2082,11 @@ function encode(m, ops, symbols) {
1836
2082
  return [5 | REG8[ops[0].toUpperCase()] << 3];
1837
2083
  if (m === "LXI") {
1838
2084
  const v = evalExpr(ops[1], symbols);
1839
- return [1 | REG_PAIR[ops[0].toUpperCase()] << 4, v & 255, v >> 8 & 255];
2085
+ return [
2086
+ 1 | REG_PAIR[ops[0].toUpperCase()] << 4,
2087
+ v & 255,
2088
+ v >> 8 & 255
2089
+ ];
1840
2090
  }
1841
2091
  if (m === "DAD")
1842
2092
  return [9 | REG_PAIR[ops[0].toUpperCase()] << 4];
@@ -1882,6 +2132,24 @@ function dwBytes(operands, symbols) {
1882
2132
  }
1883
2133
  return out;
1884
2134
  }
2135
+ function parseDs(operands) {
2136
+ if (operands.length !== 1)
2137
+ throw new Error("DS takes one operand: count [(fill)]");
2138
+ const m = operands[0].match(/^(.+?)\s+\((.+)\)\s*$/);
2139
+ if (m)
2140
+ return { count: m[1], fill: m[2] };
2141
+ return { count: operands[0], fill: "0" };
2142
+ }
2143
+ function dsBytes(operands, symbols) {
2144
+ const { count, fill } = parseDs(operands);
2145
+ const n = evalExpr(count, symbols);
2146
+ const f = evalExpr(fill, symbols) & 255;
2147
+ return new Array(n).fill(f);
2148
+ }
2149
+ function countDs(operands, symbols) {
2150
+ const { count } = parseDs(operands);
2151
+ return evalExpr(count, symbols);
2152
+ }
1885
2153
  function countDb(operands) {
1886
2154
  let n = 0;
1887
2155
  for (const op of operands) {
@@ -1897,75 +2165,103 @@ function asm(source) {
1897
2165
  `);
1898
2166
  const symbols = new Map;
1899
2167
  let pc = 0;
1900
- for (const line of lines) {
1901
- const parts = parseLine(line);
1902
- if (parts.label) {
1903
- if (parts.isEqu) {
1904
- symbols.set(parts.label.toUpperCase(), evalExpr(parts.operands[0], symbols));
1905
- continue;
2168
+ let ended = false;
2169
+ for (let idx = 0;idx < lines.length && !ended; idx++) {
2170
+ const line = lines[idx];
2171
+ try {
2172
+ for (const stmt of splitStatements(line)) {
2173
+ const parts = parseLine(stmt);
2174
+ if (parts.label) {
2175
+ if (parts.isEqu) {
2176
+ symbols.set(parts.label.toUpperCase(), evalExpr(parts.operands[0], symbols));
2177
+ continue;
2178
+ }
2179
+ symbols.set(parts.label.toUpperCase(), pc);
2180
+ }
2181
+ if (!parts.mnemonic)
2182
+ continue;
2183
+ const m = parts.mnemonic.toUpperCase();
2184
+ if (m === "EQU")
2185
+ continue;
2186
+ if (m === "ORG") {
2187
+ pc = evalExpr(parts.operands[0], symbols);
2188
+ continue;
2189
+ }
2190
+ if (m === "SECTION")
2191
+ continue;
2192
+ if (m === "END") {
2193
+ ended = true;
2194
+ break;
2195
+ }
2196
+ if (m === "DB") {
2197
+ pc += countDb(parts.operands);
2198
+ continue;
2199
+ }
2200
+ if (m === "DW") {
2201
+ pc += parts.operands.length * 2;
2202
+ continue;
2203
+ }
2204
+ if (m === "DS") {
2205
+ pc += countDs(parts.operands, symbols);
2206
+ continue;
2207
+ }
2208
+ pc += instrSize(m);
1906
2209
  }
1907
- symbols.set(parts.label.toUpperCase(), pc);
1908
- }
1909
- if (!parts.mnemonic)
1910
- continue;
1911
- const m = parts.mnemonic.toUpperCase();
1912
- if (m === "EQU")
1913
- continue;
1914
- if (m === "ORG") {
1915
- pc = evalExpr(parts.operands[0], symbols);
1916
- continue;
1917
- }
1918
- if (m === "SECTION")
1919
- continue;
1920
- if (m === "END")
1921
- break;
1922
- if (m === "DB") {
1923
- pc += countDb(parts.operands);
1924
- continue;
1925
- }
1926
- if (m === "DW") {
1927
- pc += parts.operands.length * 2;
1928
- continue;
2210
+ } catch (e) {
2211
+ if (e instanceof AsmError)
2212
+ throw e;
2213
+ throw new AsmError(e.message, idx + 1, line, firstNonSpaceCol(line));
1929
2214
  }
1930
- pc += instrSize(m);
1931
2215
  }
1932
2216
  const sections = [];
1933
2217
  let current = null;
1934
2218
  const sectionNames = new Set;
1935
- for (const line of lines) {
1936
- const parts = parseLine(line);
1937
- if (parts.isEqu || !parts.mnemonic)
1938
- continue;
1939
- const m = parts.mnemonic.toUpperCase();
1940
- if (m === "EQU")
1941
- continue;
1942
- if (m === "ORG") {
1943
- if (current && current.data.length) {
1944
- current.end = current.start + current.data.length - 1;
1945
- sections.push(current);
2219
+ let endedPass2 = false;
2220
+ for (let idx = 0;idx < lines.length && !endedPass2; idx++) {
2221
+ const line = lines[idx];
2222
+ try {
2223
+ for (const stmt of splitStatements(line)) {
2224
+ const parts = parseLine(stmt);
2225
+ if (parts.isEqu || !parts.mnemonic)
2226
+ continue;
2227
+ const m = parts.mnemonic.toUpperCase();
2228
+ if (m === "EQU")
2229
+ continue;
2230
+ if (m === "ORG") {
2231
+ if (current && current.data.length) {
2232
+ current.end = current.start + current.data.length - 1;
2233
+ sections.push(current);
2234
+ }
2235
+ const addr = evalExpr(parts.operands[0], symbols);
2236
+ current = { start: addr, end: addr, data: [] };
2237
+ continue;
2238
+ }
2239
+ if (m === "SECTION") {
2240
+ if (!current)
2241
+ throw new Error("SECTION before ORG");
2242
+ const name = parts.operands[0];
2243
+ if (!name)
2244
+ throw new Error("SECTION requires a name");
2245
+ if (sectionNames.has(name.toUpperCase()))
2246
+ throw new Error(`duplicate section name: ${name}`);
2247
+ sectionNames.add(name.toUpperCase());
2248
+ current.name = name;
2249
+ continue;
2250
+ }
2251
+ if (m === "END") {
2252
+ endedPass2 = true;
2253
+ break;
2254
+ }
2255
+ if (!current)
2256
+ throw new Error("code before ORG");
2257
+ const bytes = m === "DB" ? dbBytes(parts.operands, symbols) : m === "DW" ? dwBytes(parts.operands, symbols) : m === "DS" ? dsBytes(parts.operands, symbols) : encode(m, parts.operands, symbols);
2258
+ current.data.push(...bytes);
1946
2259
  }
1947
- const addr = evalExpr(parts.operands[0], symbols);
1948
- current = { start: addr, end: addr, data: [] };
1949
- continue;
1950
- }
1951
- if (m === "SECTION") {
1952
- if (!current)
1953
- throw new Error("SECTION before ORG");
1954
- const name = parts.operands[0];
1955
- if (!name)
1956
- throw new Error("SECTION requires a name");
1957
- if (sectionNames.has(name.toUpperCase()))
1958
- throw new Error(`duplicate section name: ${name}`);
1959
- sectionNames.add(name.toUpperCase());
1960
- current.name = name;
1961
- continue;
2260
+ } catch (e) {
2261
+ if (e instanceof AsmError)
2262
+ throw e;
2263
+ throw new AsmError(e.message, idx + 1, line, firstNonSpaceCol(line));
1962
2264
  }
1963
- if (m === "END")
1964
- break;
1965
- if (!current)
1966
- throw new Error("code before ORG");
1967
- const bytes = m === "DB" ? dbBytes(parts.operands, symbols) : m === "DW" ? dwBytes(parts.operands, symbols) : encode(m, parts.operands, symbols);
1968
- current.data.push(...bytes);
1969
2265
  }
1970
2266
  if (current && current.data.length) {
1971
2267
  current.end = current.start + current.data.length - 1;
@@ -1981,7 +2277,7 @@ import { readFile } from "fs/promises";
1981
2277
  // packages/rk86/package.json
1982
2278
  var package_default = {
1983
2279
  name: "rk86",
1984
- version: "2.0.14",
2280
+ version: "2.0.15",
1985
2281
  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",
1986
2282
  bin: {
1987
2283
  rk86: "rk86.js"