Merge branch 'master' into gh-pages
This commit is contained in:
commit
d6ad19e2df
36 changed files with 3355 additions and 2743 deletions
|
|
@ -12,7 +12,7 @@ indent_style = space
|
|||
indent_size = 8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{ASM,INC,MAC,NASM,LST,asm,inc,mac,nasm,lst,d80}]
|
||||
[*.{ASM,INC,LST,MAC,NASM,asm,inc,lst,mac,nasm,d80,ASM.txt,INC.txt,LST.txt,MAC.txt,NASM.txt}]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
trim_trailing_whitespace = true
|
||||
|
|
|
|||
|
|
@ -5,14 +5,15 @@ date: 2017-02-28 22:00:00
|
|||
permalink: /blog/2017/02/28/
|
||||
---
|
||||
|
||||
Since I've more or less achieved all my goals for the PCjs [PDP-11 Emulator](/devices/pdp11/machine/), I've decided
|
||||
to turn my attention to an even older historically important DEC computer: the [PDP-10](https://en.wikipedia.org/wiki/PDP-10#Cancellation_and_influence).
|
||||
Since I've more or less achieved all my goals for the PCjs [PDP-11 Emulator](/devices/pdp11/machine/),
|
||||
I've decided to turn my attention to an even older historically important DEC computer:
|
||||
the [PDP-10](https://en.wikipedia.org/wiki/PDP-10#Cancellation_and_influence).
|
||||
|
||||
Having never used a real DEC PDP-10, I've had a steep learning curve creating a [PDP-10 Emulator](/modules/pdp10/).
|
||||
So far, the only [PDP-10 Hardware Documentation](/pubs/dec/pdp10/) I've read (or rather, skimmed) is the
|
||||
[PDP-10 System Reference Manual (1968)](http://archive.pcjs.org/pubs/dec/pdp10/ka10/DEC-10-HGAA-D_PDP-10_System_Reference_Manual_May1968.pdf).
|
||||
|
||||
To make it easier to create and run some simple [PDP-10 Opcode Tests](/apps/pdp10/tests/), most of my initial development
|
||||
To make it easier to create and run some simple [PDP-10 Opcode Tests](/apps/pdp10/tests/opcodes/), most of my initial development
|
||||
efforts have centered on the built-in PDP-10 Debugger. In addition to the usual disassembler capabilities, I've also included
|
||||
an assembler, so that I can easily enter instruction sequences like this:
|
||||
|
||||
|
|
@ -34,7 +35,7 @@ and immediately see the corresponding opcodes:
|
|||
It's not a full-featured assembler; it's little more than a brute-force reverse look-up through the disassembler's
|
||||
opcode-to-mnemonic tables. But it gets the job done.
|
||||
|
||||
Next, I've written an initial set of [PDP-10 Opcode Tests](/apps/pdp10/tests/) and compared the results to those produced
|
||||
Next, I've written an initial set of [PDP-10 Opcode Tests](/apps/pdp10/tests/opcodes/) and compared the results to those produced
|
||||
by [SIMH](https://github.com/simh/simh), and so far, so good. More than anything else, that's just a testament to DEC's
|
||||
excellent documentation.
|
||||
|
||||
|
|
|
|||
184
_posts/2017-03-21-the-macro-10-assembler-50-years-later.md
Normal file
184
_posts/2017-03-21-the-macro-10-assembler-50-years-later.md
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
---
|
||||
layout: post
|
||||
title: The MACRO-10 Assembler, 50 Years Later
|
||||
date: 2017-02-28 22:00:00
|
||||
permalink: /blog/2017/03/21/
|
||||
machines:
|
||||
- id: testka10
|
||||
type: pdp10
|
||||
config: /devices/pdp10/machine/ka10/test/debugger/machine.xml
|
||||
debugger: true
|
||||
commands: a 30724 /apps/pdp10/diags/klad/dakaa/MYDAKAA.MAC
|
||||
---
|
||||
|
||||
A few weeks ago, I finished my first cut of the *core* PDP-10 instructions in PDPjs. Most of my remaining work
|
||||
falls into these categories:
|
||||
|
||||
- UUOs (Unimplemented User Operations)
|
||||
- Floating-point instructions
|
||||
- Input-Output instructions (eg, BLKI, BLKO, DATAI, DATAO)
|
||||
- Miscellaneous instructions (eg, variations of PUSH, POP, JUMP)
|
||||
|
||||
Floating-point is the biggest chunk of work, which I'm going to save for last, with the hope that most PDP-10 software
|
||||
didn't use floating-point. However, if all PDP-10 systems included floating-point hardware (which I haven't been able to
|
||||
confirm yet), I may have no choice. Floating-point emulation in JavaScript isn't hard -- PCjs already includes an
|
||||
[8087 Coprocessor Emulator](/modules/pcx86/lib/x86fpu.js) -- but getting all the details right is time-consuming.
|
||||
|
||||
The PDP-10 has a lot of instructions, and I quickly had far more instructions than I was willing or able to write tests for.
|
||||
Besides, any tests I wrote would be based on the same potentially-flawed understandings that I had gleaned from DEC's
|
||||
[PDP-10 Hardware Documentation](/pubs/dec/pdp10/). It was obvious that I needed to find a set of DEC PDP-10 diagnostics, much
|
||||
like the [DEC PDP-11 Paper Tape Diagnostics](/apps/pdp11/tapes/diags/) I had used to flush most of the early bugs out of my
|
||||
PDP-11 emulator.
|
||||
|
||||
Unfortunately, this presented something of a chicken-and-egg problem, because the PDP-10 emulator is still too immature to
|
||||
support any simulated devices, even something as simple as paper tape. However, since I did find a series of PDP-10 Model KA10
|
||||
diagnostics in [source code form](http://pdp-10.trailing-edge.com/klad_sources/), all I needed was some way to assemble those
|
||||
files into binaries that I could load directly in a PDPjs test machine.
|
||||
|
||||
One solution would have been to run a TOPS-10 simulation using the SIMH emulator, assemble the desired tests inside that virtual
|
||||
machine, extract the binaries, and then load them into a PDPjs machine. But that would have meant familiarizing myself with
|
||||
both SIMH and TOPS-10, and the process of editing, assembling, and transferring binaries to PDPjs would have been cumbersome at
|
||||
best.
|
||||
|
||||
### Introducing the MACRO-10 "Mini-Assembler"
|
||||
|
||||
I decided that the shortest turn-around from *assembly* phase to *run* phase (aka *crash-and-burn* phase) would be to
|
||||
assemble the files in PDPjs itself. So the PDPjs MACRO-10 "Mini-Assembler" was born. Any machine that includes the PDPjs
|
||||
Debugger (like the machine below) now includes MACRO-10 support as well.
|
||||
|
||||
{% include machine.html id="testka10" %}
|
||||
|
||||
The above machine uses the Debugger's assemble ('a') command to assemble DEC's "DAKAA" diagnostic
|
||||
[KA10 Basic Instruction Diagnostic](/apps/pdp10/diags/klad/dakaa/) and load the resulting code at address 30724:
|
||||
|
||||
a 30724 /apps/pdp10/diags/klad/dakaa/MYDAKAA.MAC
|
||||
|
||||
The Debugger invokes the MACRO-10 mini-assembler whenever the argument following the target address appears to be a URL.
|
||||
|
||||
As previously described in the section on [PDPjs PDP-10 Opcode Tests](/apps/pdp10/tests/opcodes/), the Debugger already
|
||||
allowed you to assemble instructions directly into memory (eg, `a 100 hrli 1,111111`). This "immediate mode" feature is provided
|
||||
exclusively by the Debugger; MACRO-10 mini-assembler features are not available in that mode.
|
||||
|
||||
However, one feature that *is* available in both the Debugger and the mini-assembler is full MACRO-10-style expression evaluation,
|
||||
since they share the same expression parser. Here are some examples, using the Debugger's 'print' command:
|
||||
|
||||
>> print 27
|
||||
23. 0o000027
|
||||
|
||||
Both the Debugger and MACRO-10 start out by assuming a default base (radix) of 8, so the value "27" is evaluated as an octal
|
||||
number, and for convenience, the "print" command displays the result in both decimal and octal. You can change the default base
|
||||
in the Debugger with the `s base` command (eg, `s base 10`), where the new base is always interpreted as a decimal number.
|
||||
This command differs from MACRO-10, which uses the `RADIX` pseudo-op to change the default base.
|
||||
|
||||
Obviously, it would be nice if both the Debugger and the MACRO-10 mini-assembler used matching commands, but the PCjs debuggers
|
||||
were written before MACRO-10 support was a consideration, so for now, that's life.
|
||||
|
||||
All of the MACRO-10-style base prefixes are supported (^D for decimal, ^B for binary, an ^O for octal):
|
||||
|
||||
>> print ^D27
|
||||
0o000000000033 27.
|
||||
|
||||
>> print ^B1001
|
||||
0o000000000011 9.
|
||||
|
||||
>> print ^O1001
|
||||
0o000000001001 513.
|
||||
|
||||
Expressions using angle brackets, another MACRO-10 convention, is also supported:
|
||||
|
||||
>> print ^D<45-22>
|
||||
0o000000000027 23.
|
||||
|
||||
Note that the base modifier (^D) applies to the entire expression. You can also add MACRO-10 suffixes to integers:
|
||||
|
||||
K: "kilo-", thousands
|
||||
M: "mega-", millions
|
||||
G: "giga-", billions
|
||||
|
||||
so:
|
||||
|
||||
>> print 5K
|
||||
0o000000005000 2560.
|
||||
|
||||
>> print ^D5K
|
||||
0o000000011610 5000.
|
||||
|
||||
Binary shifting using a B suffix is also supported:
|
||||
|
||||
>> print 1B0
|
||||
0o400000000000 -34359738368.
|
||||
|
||||
>> print 1B17
|
||||
0o000001000000 262144.
|
||||
|
||||
>> print 1B35
|
||||
0o000000000001 1.
|
||||
|
||||
>> print -1B35
|
||||
0o777777777777 -1.
|
||||
|
||||
>> print -1B53
|
||||
0o000000777777 262143.
|
||||
|
||||
>> print -1B70
|
||||
0o000000000001 1.
|
||||
|
||||
And as the MACRO-10 ASSEMBLER PROGRAMMER'S REFERENCE MANUAL (June 1972), p. 1-17, points out, all the following expressions are
|
||||
equivalent:
|
||||
|
||||
>> print 10B32
|
||||
0o000000000100 64. '@'
|
||||
|
||||
>> print ^O10B32
|
||||
0o000000000100 64. '@'
|
||||
|
||||
>> print 10B<42-10>
|
||||
0o000000000100 64. '@'
|
||||
|
||||
>> print 10B<^D<42-10>>
|
||||
0o000000000100 64. '@'
|
||||
|
||||
>> print 10B<^D42-^D10>
|
||||
0o000000000100 64. '@'
|
||||
|
||||
"Left arrow" shifting is also supported, although the character that the original MACRO-10 manuals referred to as a "left arrow"
|
||||
is actually an underscore; later manuals refer to this as "underscore shifting":
|
||||
|
||||
>> print 1_^D18
|
||||
0o000001000000 262144.
|
||||
|
||||
>> print -1_^D-18
|
||||
0o000000777777 262143.
|
||||
|
||||
Character-based constants are also supported, in the both 7-bit ASCII the 6-bit SIXBIT formats. Use double-quotes
|
||||
for 7-bit right-justified character constants and single-quotes for 6-bit right-justified character constants:
|
||||
|
||||
>> print +'SIXBIT'
|
||||
0o635170425164 -13255955852.
|
||||
|
||||
>> print +"HELLO"
|
||||
0o221054623117 19473311311.
|
||||
|
||||
NOTE: When using the PCjs debugger, the constant must be preceded by an arithmetic operator, otherwise it interprets the
|
||||
quoted argument as a string. This is an idiosyncrasy of the "print" command, not the expression parser.
|
||||
|
||||
All MACRO-10 logical and arithmetic operators are supported, including:
|
||||
|
||||
- Unary arithmetic operators (+ and -)
|
||||
- Unary complement operator (^-, aliased to ~)
|
||||
- Unary base overrides (^D, ^O, and ^B)
|
||||
- Binary shifting (B) and underscore shifting (_) operators
|
||||
- Logical binary operations (! for OR, ^! for XOR, and & for AND)
|
||||
- Multiplication and division (* and /)
|
||||
- Addition and subtraction (+ and -)
|
||||
- Angle brackets for expression grouping (< and >)
|
||||
|
||||
Much more work is needed to make the MACRO-10 "Mini-Assembler" a general-purpose assembler, but it's already proved itself
|
||||
useful in assembling the following tests:
|
||||
|
||||
- [KA10 Basic Instruction Diagnostic #1](/apps/pdp10/diags/klad/dakaa/)
|
||||
- [KA10 Basic Instruction Diagnostic #4](/apps/pdp10/diags/klad/dakad/)
|
||||
- [Assorted MACRO-10 Mini-Assembler Tests](/apps/pdp10/tests/macro10/)
|
||||
|
||||
*[@jeffpar](http://twitter.com/jeffpar)*
|
||||
*Mar 21, 2017*
|
||||
|
|
@ -9,5 +9,6 @@ PDP-10 Software
|
|||
|
||||
PCjs provides the following PDP-10 software:
|
||||
|
||||
- [PDP-10 Diagnostics](diags/)
|
||||
- [PDP-10 Opcode Tests](tests/)
|
||||
- [DEC PDP-10 Diagnostics](diags/)
|
||||
- [PDPjs PDP-10 Opcode Tests](tests/opcodes/)
|
||||
- [PDPjs MACRO-10 Mini-Assembler Tests](tests/macro10/)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
layout: page
|
||||
title: PDP-10 Diagnostics
|
||||
title: DEC PDP-10 Diagnostics
|
||||
permalink: /apps/pdp10/diags/
|
||||
---
|
||||
|
||||
PDP-10 Diagnostics
|
||||
------------------
|
||||
DEC PDP-10 Diagnostics
|
||||
----------------------
|
||||
|
||||
PCjs has archived the following PDP-10 diagnostics:
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ permalink: /apps/pdp10/diags/klad/
|
|||
PDP-10 KLAD Diagnostics
|
||||
-----------------------
|
||||
|
||||
PCjs has archived selected files from [PDP-10 KLAD Diagnostics Sources](http://pdp-10.trailing-edge.com/klad_sources/index.html),
|
||||
including:
|
||||
PCjs has archived selected files from
|
||||
[PDP-10 KLAD Diagnostics Sources](http://pdp-10.trailing-edge.com/klad_sources/index.html), including:
|
||||
|
||||
- [KA10 Basic Instruction Diagnostic #1 (MAINDEC-10-DAKAA-B-D)](dakaa/)
|
||||
- [KA10 Basic Instruction Diagnostic #4 (MAINDEC-10-DAKAD-B-D)](dakad/)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ machines:
|
|||
type: pdp10
|
||||
config: /devices/pdp10/machine/ka10/test/debugger/machine.xml
|
||||
debugger: true
|
||||
commands: a 30724 /apps/pdp10/diags/klad/dakaa/TEST.MAC
|
||||
commands: a 30724 /apps/pdp10/diags/klad/dakaa/MYDAKAA.MAC
|
||||
---
|
||||
|
||||
PDP-10 KA10 Basic Instruction Diagnostic #1
|
||||
|
|
@ -28,13 +28,13 @@ Resources for this test include:
|
|||
{% include machine.html id="testka10" %}
|
||||
|
||||
The Debugger's assemble ("a") command can be used to test the new built-in
|
||||
[MACRO-10 mini-assembler](/modules/pdp10/lib/macro10.js), which supports a subset
|
||||
[MACRO-10 Mini-Assembler](/modules/pdp10/lib/macro10.js), which supports a subset
|
||||
of the [MACRO-10](http://archive.pcjs.org/pubs/dec/pdp10/tops10/02_1973AsmRef_macro.pdf) assembly language.
|
||||
This command:
|
||||
|
||||
a 30724 /apps/pdp10/diags/klad/dakaa/TEST.MAC
|
||||
|
||||
will automatically read the [TEST.MAC](TEST.MAC.txt) source file (a slightly modified copy of [DAKAA.MAC](DAKAA.MAC.txt)),
|
||||
will automatically read the [MYDAKAA.MAC](MYDAKAA.MAC.txt) source file (a slightly modified copy of [DAKAA.MAC](DAKAA.MAC.txt)),
|
||||
assemble it, and then load the binary output at the specified address.
|
||||
|
||||
---
|
||||
|
|
@ -243,7 +243,6 @@ SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY DEC.
|
|||
DAKAA.HST
|
||||
---------
|
||||
|
||||
```
|
||||
THIS IS A HISTORY OF THE DEVELOPMENT OF MAINDEC-10-DAKAA
|
||||
|
||||
************************************************************************
|
||||
|
|
@ -271,7 +270,6 @@ ORIGINAL AUTHOR: RICHARD MALISKA
|
|||
ORIGINAL RELEASE: 16-MAR-72
|
||||
|
||||
************************************************************************
|
||||
```
|
||||
|
||||
DAKAA.MAC
|
||||
---------
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ machines:
|
|||
type: pdp10
|
||||
config: /devices/pdp10/machine/ka10/test/debugger/machine.xml
|
||||
debugger: true
|
||||
commands: a 30724 /apps/pdp10/diags/klad/dakad/TEST.MAC
|
||||
commands: a 30724 /apps/pdp10/diags/klad/dakad/MYDAKAD.MAC
|
||||
---
|
||||
|
||||
PDP-10 KA10 Basic Instruction Diagnostic #4
|
||||
|
|
@ -28,13 +28,13 @@ Resources for this test include:
|
|||
{% include machine.html id="testka10" %}
|
||||
|
||||
The Debugger's assemble ("a") command can be used to test the new built-in
|
||||
[MACRO-10 mini-assembler](/modules/pdp10/lib/macro10.js), which supports a subset
|
||||
[MACRO-10 Mini-Assembler](/modules/pdp10/lib/macro10.js), which supports a subset
|
||||
of the [MACRO-10](http://archive.pcjs.org/pubs/dec/pdp10/tops10/02_1973AsmRef_macro.pdf) assembly language.
|
||||
This command:
|
||||
|
||||
a 30724 /apps/pdp10/diags/klad/dakad/TEST.MAC
|
||||
|
||||
will automatically read the [TEST.MAC](TEST.MAC.txt) source file (a slightly modified copy of [DAKAD.MAC](DAKAD.MAC.txt)),
|
||||
will automatically read the [MYDAKAD.MAC](MYDAKAD.MAC.txt) source file (a slightly modified copy of [DAKAD.MAC](DAKAD.MAC.txt)),
|
||||
assemble it, and then load the binary output at the specified address. Use the command `db 30724` to dump the first few words
|
||||
of binary data:
|
||||
|
||||
|
|
@ -252,7 +252,6 @@ SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY DEC.
|
|||
DAKAD.HST
|
||||
---------
|
||||
|
||||
```
|
||||
THIS IS A HISTORY OF THE DEVELOPMENT OF MAINDEC-10-DAKAD
|
||||
|
||||
************************************************************************
|
||||
|
|
@ -280,7 +279,6 @@ ORIGINAL AUTHOR: RICHARD MALISKA
|
|||
ORIGINAL RELEASE: 16-MAR-72
|
||||
|
||||
************************************************************************
|
||||
```
|
||||
|
||||
DAKAD.MAC
|
||||
---------
|
||||
|
|
|
|||
|
|
@ -1,123 +1,13 @@
|
|||
---
|
||||
layout: page
|
||||
title: DEC PDP-10 Opcode Tests
|
||||
title: PDPjs PDP-10 Tests
|
||||
permalink: /apps/pdp10/tests/
|
||||
machines:
|
||||
- id: testka10
|
||||
type: pdp10
|
||||
debugger: true
|
||||
config: /devices/pdp10/machine/ka10/test/debugger/machine.xml
|
||||
---
|
||||
|
||||
DEC PDP-10 Opcode Tests
|
||||
-----------------------
|
||||
PDPjs PDP-10 Tests
|
||||
------------------
|
||||
|
||||
The PDP-10 machine below loads the following test:
|
||||
PDPjs tests to exercise PDP-10 and MACRO-10 features include:
|
||||
|
||||
- [PDP-10 Half-Word Opcode Test](optest-v01.json)
|
||||
|
||||
Details on how to run and modify the test, and perform similar tests in [SIMH](https://github.com/simh/simh),
|
||||
are provided below.
|
||||
|
||||
{% include machine.html id="testka10" %}
|
||||
|
||||
The [PDP-10 Half-Word Opcode Test](optest-v01.json) file specifies that the following words:
|
||||
|
||||
0o505040111111, // HRLI 1,111111
|
||||
0o541040444444, // HRRI 1,444444
|
||||
0o544100000001, // HLR 2,1
|
||||
0o504100000001, // HRL 2,1
|
||||
...
|
||||
|
||||
should be loaded at address 0o100, and that the program counter (PC register) should be set to 0o100. The embedded
|
||||
comments display the instructions that correspond to each word.
|
||||
|
||||
If you want to alter or add new instructions, the PDP-10 Debugger now includes a rudimentary assembler. Using the `a` command,
|
||||
along with an optional start address, you can enter "assembler mode". For example, here's how the first four instructions from
|
||||
the [PDP-10 Half-Word Opcode Test](optest-v01.json) could be entered by hand:
|
||||
|
||||
>> a 100
|
||||
begin assemble at 000100
|
||||
>> hrli 1,111111
|
||||
000100: 505040 111111 HRLI 1,111111
|
||||
>> hrri 1,444444
|
||||
000101: 541040 444444 HRRI 1,444444
|
||||
>> hlr 2,1
|
||||
000102: 544100 000001 HLR 2,1
|
||||
>> hrl 2,1
|
||||
000103: 504100 000001 HRL 2,1
|
||||
ended assemble at 000104
|
||||
|
||||
Entering a blank line exits "assembler mode". To disassemble the four instructions, use `u 100 l4`:
|
||||
|
||||
>> u 100 l4
|
||||
000100: 505040 111111 HRLI 1,111111
|
||||
000101: 541040 444444 HRRI 1,444444
|
||||
000102: 544100 000001 HLR 2,1
|
||||
000103: 504100 000001 HRL 2,1
|
||||
|
||||
You can also copy-and-paste a series of instructions into the PDPjs Debugger, as long as each command ends
|
||||
with a semicolon:
|
||||
|
||||
a 100;
|
||||
hrli 1,111111;
|
||||
hrri 1,444444;
|
||||
hlr 2,1;
|
||||
hrl 2,1;
|
||||
end
|
||||
|
||||
Individual instructions can also be assembled *without* entering "assembler mode", as long as you precede
|
||||
each instruction with the `a` command and a specific address:
|
||||
|
||||
a 100 hrli 1,111111;
|
||||
a 101 hrri 1,444444;
|
||||
a 102 hlr 2,1;
|
||||
a 103 hrl 2,1
|
||||
|
||||
Individual instructions can be executed with the `t` command and memory can be dumped with the `d` command.
|
||||
|
||||
After executing the first four instructions and using the `d 0 l4` command to dump first four words of memory
|
||||
(also known as AC registers 0-3), you should see the following output:
|
||||
|
||||
>> r
|
||||
000100: 505040 111111 HRLI 1,111111
|
||||
>> t
|
||||
000101: 541040 444444 HRRI 1,444444
|
||||
>> t
|
||||
000102: 544100 000001 HLR 2,1
|
||||
>> t
|
||||
000103: 504100 000001 HRL 2,1
|
||||
>> t
|
||||
000104: 000000 000000 UUO 0,0
|
||||
>> d 0 l4
|
||||
000000: 000000 000000 111111 444444 444444 111111 000000 000000
|
||||
|
||||
To assemble and test the same instructions in [SIMH](https://github.com/simh/simh), using the `pdp10` binary:
|
||||
|
||||
sim> dep 100 505040111111
|
||||
sim> dep 101 541040444444
|
||||
sim> dep 102 544100000001
|
||||
sim> dep 103 504100000001
|
||||
sim> ex -m 100-103
|
||||
100: HRLI 1,111111
|
||||
101: HRRI 1,444444
|
||||
102: HLR 2,1
|
||||
103: HRL 2,1
|
||||
sim> dep pc 100
|
||||
sim> step
|
||||
|
||||
Step expired, PC: 000101 (HRRI 1,444444)
|
||||
sim> step
|
||||
|
||||
Step expired, PC: 000102 (HLR 2,1)
|
||||
sim> step
|
||||
|
||||
Step expired, PC: 000103 (HRL 2,1)
|
||||
sim> step
|
||||
|
||||
Step expired, PC: 000104 (000000000000)
|
||||
sim> ex 0-3
|
||||
0: 000000000000
|
||||
1: 111111444444
|
||||
2: 444444111111
|
||||
3: 000000000000
|
||||
- [PDPjs PDP-10 Opcode Tests](opcodes/)
|
||||
- [PDPjs MACRO-10 Mini-Assembler Tests](macro10/)
|
||||
|
|
|
|||
47
apps/pdp10/tests/macro10/README.md
Normal file
47
apps/pdp10/tests/macro10/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
layout: page
|
||||
title: PDPjs MACRO-10 Mini-Assembler Tests
|
||||
permalink: /apps/pdp10/tests/macro10/
|
||||
machines:
|
||||
- id: testka10
|
||||
type: pdp10
|
||||
config: /devices/pdp10/machine/ka10/test/debugger/machine.xml
|
||||
debugger: true
|
||||
commands: a 100 /apps/pdp10/tests/macro10/TEXT.MAC
|
||||
---
|
||||
|
||||
PDPjs MACRO-10 Mini-Assembler Tests
|
||||
-----------------------------------
|
||||
|
||||
The PDP-10 machine below automatically loads and assembles [TEXT.MAC](TEXT.MAC.txt), one of the
|
||||
first tests of the built-in PDPjs MACRO-10 Mini-Assembler.
|
||||
|
||||
[TEXT.MAC](TEXT.MAC.txt) is a copy of the file listed on p. 7-9 of the
|
||||
[MACRO-10 Assembler Programmer's Reference Manual (June 1972)](/pubs/dec/pdp10/). It's a simple test
|
||||
of nested macro definitions, recursion, expressions using shift operators and character constants,
|
||||
and pseudo-ops **IRPC**, **IFE**, **IFN**, and **EXP**. It generates 4 words of data.
|
||||
|
||||
{% include machine.html id="testka10" %}
|
||||
|
||||
After the machine executes the following command:
|
||||
|
||||
a 100 /apps/pdp10/tests/macro10/TEXT.MAC
|
||||
|
||||
the following results should appear:
|
||||
|
||||
starting PCjs MACRO-10 Mini-Assembler...
|
||||
loading TEXT.MAC
|
||||
4 words loaded at 000100-000103
|
||||
00=000000000000 01=000000000000 02=000000000000 03=000000000000
|
||||
04=000000000000 05=000000000000 06=000000000000 07=000000000000
|
||||
10=000000000000 11=000000000000 12=000000000000 13=000000000000
|
||||
14=000000000000 15=000000000000 16=000000000000 17=000000000000
|
||||
PC=000100 RA=00000000 EA=000000 C0=0 C1=0 OV=0 ND=0 PD=0
|
||||
000100: 014101 102103 UUO 2,102103(1)
|
||||
|
||||
and if you dump the first 4 words with the command `db 100 l4`:
|
||||
|
||||
000100: 014101 102103 003 004 011 004 041 ....!
|
||||
000101: 104105 106107 021 004 051 014 043 ..).#
|
||||
000102: 110111 112113 022 004 111 024 045 ..I.%
|
||||
000103: 114000 000000 023 000 000 000 000 .....
|
||||
53
apps/pdp10/tests/macro10/TEXT.MAC.txt
Normal file
53
apps/pdp10/tests/macro10/TEXT.MAC.txt
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
; From the MACR0-10 ASSEMBLER PROGRAMMER'S REFERENCE MANUAL (June 1972):
|
||||
;
|
||||
; Example 4 (Figure 7-4) shows nested macros which use IRPC. The desired operation
|
||||
; is to take an ASCII text string and store the characters four per word, left-justified,
|
||||
; with the character count stored in the first nine bits of the first word.
|
||||
;
|
||||
; The TEXT macro counts the string characters and invokes the CODE macro to store the
|
||||
; characters four per word. The CODE macro invokes a SHIFT macro which left-justifies the
|
||||
; last word if it is not already left-justified. The first part of the example shows the
|
||||
; normal listing, then SALL is set to show what code the macros are generating.
|
||||
;
|
||||
; The original listing encoded two of the lines as follows:
|
||||
;
|
||||
; IFN ZZ,<DEFINE SHIFT ;;REMAINDER LEFT
|
||||
; IFE ZZ&777B8,> ;;IF NOT LEFT JUSTIFIED
|
||||
;
|
||||
; The April 1978 MACRO-10 manual says that a macro body "must be enclosed in angle brackets",
|
||||
; so the DEFINE looks wrong, and the IFE contains only a closing angle bracket, which also seems
|
||||
; wrong. Perhaps what's shown above is a side-effect of some MACRO-10 listing generation bug
|
||||
; and not an accurate reflection of the original code. In any event, after making two small
|
||||
; modifications below, macro10.js is able to assemble the code fine.
|
||||
;
|
||||
; This example also begs the question: did MACRO-10 optimize tail recursion? Because without
|
||||
; it, if the SHIFT macro required many iterations (for example, if "ZZ==ZZ_9" was replaced with
|
||||
; "ZZ==ZZ+9"), wouldn't MACRO-10 blow its stack? I've noted this in macro10.js as well.
|
||||
;
|
||||
TITLE STORE TEXT CHARACTER BY CHARACTER
|
||||
SUBTTL %1 5-APR-72
|
||||
|
||||
DEFINE TEXT (C)<
|
||||
N==0 ;;INITIAL CONDITION
|
||||
IRPC C,<N==N+1> ;;COUNT CHARACTERS
|
||||
CODE (N,C) ;;CALL MACRO TO STORE TEXT
|
||||
> ;END OF TEXT MACRO
|
||||
|
||||
DEFINE CODE (N,C)<
|
||||
ZZ==N ;;INITIAL CONDITION
|
||||
IRPC C,<
|
||||
IFN ZZ&777B8,<
|
||||
EXP ZZ ;;WORD FULL
|
||||
ZZ==0> ;;START AGAIN WITH 0
|
||||
ZZ==ZZ_9+"C"> ;;END OF IRPC
|
||||
IFN ZZ,<DEFINE SHIFT< ;;REMAINDER LEFT
|
||||
IFE ZZ&777B8,< ;;IF NOT LEFT JUSTIFIED
|
||||
ZZ==ZZ_9 ;;MOVE LEFT 9 BITS
|
||||
SHIFT> ;;RECURSE
|
||||
> ;;END OF DEFINE
|
||||
SHIFT ;;START MOVING LEFT
|
||||
EXP ZZ> ;;END OF IFN ZZ
|
||||
> ;END OF CODE MACRO
|
||||
|
||||
TEXT (ABCDEFGHIJKL)
|
||||
END
|
||||
123
apps/pdp10/tests/opcodes/README.md
Normal file
123
apps/pdp10/tests/opcodes/README.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
---
|
||||
layout: page
|
||||
title: PDPjs PDP-10 Opcode Tests
|
||||
permalink: /apps/pdp10/tests/opcodes/
|
||||
machines:
|
||||
- id: testka10
|
||||
type: pdp10
|
||||
config: /devices/pdp10/machine/ka10/test/debugger/machine.xml
|
||||
debugger: true
|
||||
---
|
||||
|
||||
PDPjs PDP-10 Opcode Tests
|
||||
-------------------------
|
||||
|
||||
The PDP-10 machine below loads the following PDPjs opcode test:
|
||||
|
||||
- [PDP-10 Half-Word Opcode Test](OPTEST01.json)
|
||||
|
||||
Details on how to run and modify the test, and perform similar tests in [SIMH](https://github.com/simh/simh),
|
||||
are provided below.
|
||||
|
||||
{% include machine.html id="testka10" %}
|
||||
|
||||
The [PDP-10 Half-Word Opcode Test](OPTEST01.json) file specifies that the following words:
|
||||
|
||||
0o505040111111, // HRLI 1,111111
|
||||
0o541040444444, // HRRI 1,444444
|
||||
0o544100000001, // HLR 2,1
|
||||
0o504100000001, // HRL 2,1
|
||||
...
|
||||
|
||||
should be loaded at address 0o100, and that the program counter (PC register) should be set to 0o100. The embedded
|
||||
comments display the instructions that correspond to each word.
|
||||
|
||||
If you want to alter or add new instructions, the PDP-10 Debugger now includes a rudimentary assembler. Using the `a` command,
|
||||
along with an optional start address, you can enter "assembler mode". For example, here's how the first four instructions from
|
||||
the [PDP-10 Half-Word Opcode Test](OPTEST01.json) could be entered by hand:
|
||||
|
||||
>> a 100
|
||||
begin assemble at 000100
|
||||
>> hrli 1,111111
|
||||
000100: 505040 111111 HRLI 1,111111
|
||||
>> hrri 1,444444
|
||||
000101: 541040 444444 HRRI 1,444444
|
||||
>> hlr 2,1
|
||||
000102: 544100 000001 HLR 2,1
|
||||
>> hrl 2,1
|
||||
000103: 504100 000001 HRL 2,1
|
||||
ended assemble at 000104
|
||||
|
||||
Entering a blank line exits "assembler mode". To disassemble the four instructions, use `u 100 l4`:
|
||||
|
||||
>> u 100 l4
|
||||
000100: 505040 111111 HRLI 1,111111
|
||||
000101: 541040 444444 HRRI 1,444444
|
||||
000102: 544100 000001 HLR 2,1
|
||||
000103: 504100 000001 HRL 2,1
|
||||
|
||||
You can also copy-and-paste a series of instructions into the PDPjs Debugger, as long as each command ends
|
||||
with a semicolon:
|
||||
|
||||
a 100;
|
||||
hrli 1,111111;
|
||||
hrri 1,444444;
|
||||
hlr 2,1;
|
||||
hrl 2,1;
|
||||
end
|
||||
|
||||
Individual instructions can also be assembled *without* entering "assembler mode", as long as you precede
|
||||
each instruction with the `a` command and a specific address:
|
||||
|
||||
a 100 hrli 1,111111;
|
||||
a 101 hrri 1,444444;
|
||||
a 102 hlr 2,1;
|
||||
a 103 hrl 2,1
|
||||
|
||||
Individual instructions can be executed with the `t` command and memory can be dumped with the `d` command.
|
||||
|
||||
After executing the first four instructions and using the `d 0 l4` command to dump first four words of memory
|
||||
(also known as AC registers 0-3), you should see the following output:
|
||||
|
||||
>> r
|
||||
000100: 505040 111111 HRLI 1,111111
|
||||
>> t
|
||||
000101: 541040 444444 HRRI 1,444444
|
||||
>> t
|
||||
000102: 544100 000001 HLR 2,1
|
||||
>> t
|
||||
000103: 504100 000001 HRL 2,1
|
||||
>> t
|
||||
000104: 000000 000000 UUO 0,0
|
||||
>> d 0 l4
|
||||
000000: 000000 000000 111111 444444 444444 111111 000000 000000
|
||||
|
||||
To assemble and test the same instructions in [SIMH](https://github.com/simh/simh), using the `pdp10` binary:
|
||||
|
||||
sim> dep 100 505040111111
|
||||
sim> dep 101 541040444444
|
||||
sim> dep 102 544100000001
|
||||
sim> dep 103 504100000001
|
||||
sim> ex -m 100-103
|
||||
100: HRLI 1,111111
|
||||
101: HRRI 1,444444
|
||||
102: HLR 2,1
|
||||
103: HRL 2,1
|
||||
sim> dep pc 100
|
||||
sim> step
|
||||
|
||||
Step expired, PC: 000101 (HRRI 1,444444)
|
||||
sim> step
|
||||
|
||||
Step expired, PC: 000102 (HLR 2,1)
|
||||
sim> step
|
||||
|
||||
Step expired, PC: 000103 (HRL 2,1)
|
||||
sim> step
|
||||
|
||||
Step expired, PC: 000104 (000000000000)
|
||||
sim> ex 0-3
|
||||
0: 000000000000
|
||||
1: 111111444444
|
||||
2: 444444111111
|
||||
3: 000000000000
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
layout: page
|
||||
title: DEC Paper Tape Diagnostics
|
||||
title: DEC PDP-11 Paper Tape Diagnostics
|
||||
permalink: /apps/pdp11/tapes/diags/
|
||||
---
|
||||
|
||||
Paper Tape Diagnostics
|
||||
----------------------
|
||||
PDP-11 Paper Tape Diagnostics
|
||||
-----------------------------
|
||||
|
||||
Thanks to the [iamvirtual.ca](http://iamvirtual.ca/collection/systems/mediadoc/mediadoc.html#papertape)
|
||||
website, we have been able to archive the PDP-11 diagnostics shown below. The tapes have been added to the
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<name pos="center">PDP-10 (Model KA10) with Debugger</name>
|
||||
<computer id="computer" busWidth="18"/>
|
||||
<cpu id="cpu" model="1001" cycles="1000000"/>
|
||||
<ram id="ram" addr="0x0000" size="0x4000" file="/apps/pdp10/tests/optest-v01.json"/>
|
||||
<ram id="ram" addr="0x0000" size="0x4000" file="/apps/pdp10/tests/opcodes/OPTEST01.json"/>
|
||||
<device id="default" type="default"/>
|
||||
<serial id="serial" adapter="0" binding="print" upperCase="true"/>
|
||||
<panel ref="/devices/pdp10/panel/ka10/debugger/front.xml"/>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<name pos="center">PDP-10 Test Machine</name>
|
||||
<computer id="computer" busWidth="18"/>
|
||||
<cpu id="cpu" model="1001" cycles="1000000"/>
|
||||
<ram id="ram" addr="0x0000" size="0x4000" file="/apps/pdp10/tests/optest-v01.json"/>
|
||||
<ram id="ram" addr="0x0000" size="0x4000" file="/apps/pdp10/tests/opcodes/OPTEST01.json"/>
|
||||
<device id="default" type="default"/>
|
||||
<serial id="serial" adapter="0" binding="print" upperCase="true"/>
|
||||
<panel ref="/devices/pdp10/panel/test/terminal.xml"/>
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -617,7 +617,7 @@ class DebuggerPDP10 extends Debugger {
|
|||
*/
|
||||
toStrOffset(off)
|
||||
{
|
||||
return this.toStrBase(off);
|
||||
return this.toStrBase(off, 18);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -645,7 +645,7 @@ class DebuggerPDP10 extends Debugger {
|
|||
* ADDR_LIMIT is not derived from WORD_LIMIT; we're just taking advantage of the fact
|
||||
* that ADDR_LIMIT happens to be exactly half of WORD_LIMIT, and they are both powers of two.
|
||||
*/
|
||||
return Str.toOct(w / PDP10.ADDR_LIMIT, 6) + ' ' + Str.toOct(w % PDP10.ADDR_LIMIT, 6);
|
||||
return this.toStrBase(w / PDP10.ADDR_LIMIT, 18) + ' ' + this.toStrBase(w % PDP10.ADDR_LIMIT, 18);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1050,7 +1050,7 @@ class DebuggerPDP10 extends Debugger {
|
|||
if (this.sInitCommands) {
|
||||
var sCmds = this.sInitCommands;
|
||||
this.sInitCommands = null;
|
||||
this.doCommands(sCmds);
|
||||
this.doCommands(sCmds, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2867,7 +2867,7 @@ class DebuggerPDP10 extends Debugger {
|
|||
while (n-- > 0 && nWords-- > 0) {
|
||||
var w = this.getWord(dbgAddr, 1);
|
||||
if (fJSON) {
|
||||
if (sData) sData += ",";
|
||||
if (sData) sData += ',';
|
||||
sData += w;
|
||||
} else {
|
||||
sData += this.toStrWord(w);
|
||||
|
|
@ -2880,7 +2880,7 @@ class DebuggerPDP10 extends Debugger {
|
|||
var shift = 36 - nBits;
|
||||
for (var i = 0; size == 1 && shift >= 0; i++) {
|
||||
var c = ((w / Math.pow(2, shift)) % Math.pow(2, nBits));
|
||||
sData += this.toStrBase(c, nBits);
|
||||
sData += this.toStrBase(c, nBits) + ' ';
|
||||
c += (nBits == 6? 0x20 : 0);
|
||||
sChars += (c < 0x20? '.' : String.fromCharCode(c));
|
||||
shift -= nBits;
|
||||
|
|
@ -3179,7 +3179,7 @@ class DebuggerPDP10 extends Debugger {
|
|||
case "base":
|
||||
if (asArgs[2]) {
|
||||
var nBase = +asArgs[2];
|
||||
if (nBase == 8 || nBase == 10 || nBase == 16) {
|
||||
if (nBase == 2 || nBase == 8 || nBase == 10 || nBase == 16) {
|
||||
this.nBase = nBase;
|
||||
} else {
|
||||
this.println("invalid base: " + nBase);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview My homage to MACRO-10: a work-alike assembler for the PDP-10
|
||||
* @fileoverview A MACRO-10 "work-alike" Mini-Assembler for the PDP-10
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
@ -39,11 +39,14 @@ if (NODE) {
|
|||
/**
|
||||
* Elements of tblMacros.
|
||||
*
|
||||
* NOTE: The nLine property is not required; it's added for improved error reporting.
|
||||
*
|
||||
* @typedef {{
|
||||
* name:(string),
|
||||
* nOperand:(number),
|
||||
* aParms:(Array.<string>),
|
||||
* aDefaults:(Array.<string>),
|
||||
* aValues:(Array.<string>),
|
||||
* sText:(string),
|
||||
* nLine:(number)
|
||||
* }}
|
||||
|
|
@ -53,21 +56,27 @@ var Mac;
|
|||
/**
|
||||
* Elements of tblSymbols.
|
||||
*
|
||||
* NOTE: The nLine property is not required; it's added for improved error reporting.
|
||||
*
|
||||
* @typedef {{
|
||||
* name:(string),
|
||||
* value:(number),
|
||||
* nLine:(number),
|
||||
* fLabel:(boolean),
|
||||
* fGlobal:(boolean),
|
||||
* fPrivate:(boolean)
|
||||
* nType:(number),
|
||||
* nLine:(number)
|
||||
* }}
|
||||
*/
|
||||
var Sym;
|
||||
|
||||
/**
|
||||
*
|
||||
* Elements of aFixups.
|
||||
*
|
||||
* NOTE: The nLine property is not required; it's added for improved error reporting.
|
||||
*
|
||||
* @typedef {{
|
||||
* nLocation:(number),
|
||||
* sValue:(string)
|
||||
* sValue:(string),
|
||||
* nLine:(number)
|
||||
* }}
|
||||
*/
|
||||
var Fixup;
|
||||
|
|
@ -79,9 +88,10 @@ var Fixup;
|
|||
* @property {string} sOptions
|
||||
* @property {DebuggerPDP10} dbg
|
||||
* @property {function(...)} done
|
||||
* @property {string} sText
|
||||
* @property {number} iURL
|
||||
* @property {Array.<string>} asURLs
|
||||
* @property {Array.<string>} asLines
|
||||
* @property {number|null|undefined} nAddrStart
|
||||
*/
|
||||
class Macro10 {
|
||||
/**
|
||||
|
|
@ -120,9 +130,11 @@ class Macro10 {
|
|||
this.done = done;
|
||||
|
||||
/*
|
||||
* Set up shortcuts to frequently used helper services that the caller must provide.
|
||||
* The next set of properties may be updated by the assembly process and queried by the caller.
|
||||
*/
|
||||
this.println = dbg.println;
|
||||
this.nAddrStart = null;
|
||||
|
||||
this.println("starting PCjs MACRO-10 Mini-Assembler...");
|
||||
|
||||
/*
|
||||
* Initialize all the tables that MACRO-10 uses.
|
||||
|
|
@ -141,9 +153,9 @@ class Macro10 {
|
|||
* the LITERAL blocks in tblMacros and process the associated statement(s) at that time.
|
||||
*
|
||||
* Macros have the name that was assigned to them, REPEAT and conditional blocks have generated names
|
||||
* that match the pseudo-op (eg, "_REPEAT", "_IFE"), and LITERAL blocks have generated location-based
|
||||
* names. All generated names use a leading underscore ('_') so that they don't conflict with
|
||||
* normal MACRO-10 labels.
|
||||
* that match the pseudo-op (eg, "?REPEAT", "?IFE"), and LITERAL blocks have generated location-based
|
||||
* names. All generated names use a leading question mark ('?') so that they don't conflict with
|
||||
* normal MACRO-10 symbols.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -180,9 +192,12 @@ class Macro10 {
|
|||
this.nLocation = this.nAddr; // advanced by the various genXXX() functions
|
||||
|
||||
this.sOperator = null; // the active operator, if any
|
||||
this.nMacroDef = 0; // the active MACRO definition state
|
||||
this.sMacroDef = null; // the active MACRO definition name
|
||||
this.nMacroDef = 0; // the active macro definition state
|
||||
this.sMacroDef = null; // the active macro definition name
|
||||
this.chMacroOpen = this.chMacroClose = '';
|
||||
this.reLine = /\s*([A-Z$%.?][0-9A-Z$%.]*:|)\s*([A-Z$%.][0-9A-Z$%.]*|)\s*([^;]+|)(;?[\s\S]*)/i;
|
||||
|
||||
this.macroCall = null; // the active macro being called, if any
|
||||
|
||||
/**
|
||||
* If an ASCII/ASCIZ/SIXBIT pseudo-op is active, chASCII is set to the separator
|
||||
|
|
@ -197,9 +212,14 @@ class Macro10 {
|
|||
*/
|
||||
this.sASCII = "";
|
||||
|
||||
this.sText = "";
|
||||
/**
|
||||
* @type {Array.<string>}
|
||||
*/
|
||||
this.asLines = [];
|
||||
|
||||
this.iURL = 0;
|
||||
this.asURLs = sURL.split(';');
|
||||
|
||||
this.loadNextResource();
|
||||
}
|
||||
|
||||
|
|
@ -211,13 +231,15 @@ class Macro10 {
|
|||
loadNextResource()
|
||||
{
|
||||
if (this.iURL == this.asURLs.length) {
|
||||
this.done(this.parseFile());
|
||||
this.done(this.parseResources());
|
||||
return;
|
||||
}
|
||||
|
||||
var macro10 = this;
|
||||
var sURL = this.asURLs[this.iURL++];
|
||||
|
||||
this.println("loading " + Str.getBaseName(sURL));
|
||||
|
||||
/*
|
||||
* We know that local resources ending with ".MAC" are actually stored with a ".txt" extension.
|
||||
*/
|
||||
|
|
@ -243,8 +265,9 @@ class Macro10 {
|
|||
match = sText.match(/&[a-z]+;/i);
|
||||
if (match) macro10.warning("unrecognized HTML entity: " + match[0]);
|
||||
}
|
||||
if (macro10.sText) macro10.sText += '\n';
|
||||
macro10.sText += sText;
|
||||
|
||||
macro10.asLines = macro10.asLines.concat(sText.split(/(\r?\n)/));
|
||||
|
||||
setTimeout(function() {
|
||||
macro10.loadNextResource();
|
||||
}, 0);
|
||||
|
|
@ -265,14 +288,14 @@ class Macro10 {
|
|||
}
|
||||
|
||||
/**
|
||||
* parseFile()
|
||||
* parseResources()
|
||||
*
|
||||
* Begin the assembly process.
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @return {number}
|
||||
*/
|
||||
parseFile()
|
||||
parseResources()
|
||||
{
|
||||
var macro10 = this;
|
||||
|
||||
|
|
@ -280,17 +303,27 @@ class Macro10 {
|
|||
* If the "preprocess" option is set, then just return the plain text we retrieved.
|
||||
*/
|
||||
if (this.sOptions.indexOf('p') >= 0) {
|
||||
this.println(this.sText);
|
||||
this.println(this.asLines.join());
|
||||
return 0;
|
||||
}
|
||||
|
||||
var a = this.dbg.resetVariables();
|
||||
try {
|
||||
var i;
|
||||
var asLines = this.sText.split(/\r?\n/);
|
||||
for (i = 0; i < asLines.length; i++) {
|
||||
for (i = 0; i < this.asLines.length; i += 2) {
|
||||
this.nLine++;
|
||||
if (!this.parseLine(asLines[i] + '\r\n')) break;
|
||||
/*
|
||||
* Since, at this early stage, I'm not sure whether all the resources I'm interested in
|
||||
* assembling have had their original CR/LF line endings preserved (eg, some files may have
|
||||
* been converted to LF-only line endings), I'm going to skip over whatever line endings
|
||||
* are in the array and insert my own uniform CR/LF sequences.
|
||||
*/
|
||||
if (!this.parseLine(this.asLines[i] + '\r\n')) break;
|
||||
/*
|
||||
* When an END statement is encountered, nAddrStart will change from null to either undefined
|
||||
* or a starting address.
|
||||
*/
|
||||
if (this.nAddrStart !== null) break;
|
||||
}
|
||||
|
||||
if (this.nMacroDef) {
|
||||
|
|
@ -330,17 +363,21 @@ class Macro10 {
|
|||
}
|
||||
|
||||
/**
|
||||
* parseLine(sLine)
|
||||
* parseLine(sLine, aParms, aValues, aDefaults)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sLine (line contents)
|
||||
* @param {Array.<string>} [aParms]
|
||||
* @param {Array.<string>} [aValues]
|
||||
* @param {Array.<string>} [aDefaults]
|
||||
* @return {boolean}
|
||||
*/
|
||||
parseLine(sLine)
|
||||
parseLine(sLine, aParms, aValues, aDefaults)
|
||||
{
|
||||
var i;
|
||||
if (this.nMacroDef) {
|
||||
if (this.nMacroDef == 1) {
|
||||
var i = sLine.indexOf(this.chMacroOpen);
|
||||
i = sLine.indexOf(this.chMacroOpen);
|
||||
if (i >= 0) {
|
||||
this.nMacroDef++;
|
||||
sLine = sLine.substr(i+1);
|
||||
|
|
@ -358,41 +395,75 @@ class Macro10 {
|
|||
sLine = this.addASCII(sLine);
|
||||
}
|
||||
|
||||
var reLine = /\s*([A-Z$%._][0-9A-Z$%.]*[:=]|)\s*([A-Z$%.][0-9A-Z$%.]*|)\s*([^;]+|)(;?[\s\S]*)/i;
|
||||
var match = sLine.match(reLine);
|
||||
if (!match || match[4] && match[4].slice(0, 1) != ';') {
|
||||
var fParse = true;
|
||||
var sLabel, sOperator = "", sOperands, sRemainder;
|
||||
|
||||
while (fParse) {
|
||||
var matchLine = sLine.match(this.reLine);
|
||||
if (!matchLine || matchLine[4] && matchLine[4].slice(0, 1) != ';') {
|
||||
this.error("failed to parse line: " + sLine);
|
||||
return false;
|
||||
}
|
||||
fParse = false;
|
||||
sOperator = matchLine[2].toUpperCase();
|
||||
if (sOperator == Macro10.PSEUDO_OP.IRP || sOperator == Macro10.PSEUDO_OP.IRPC) {
|
||||
aParms = null;
|
||||
}
|
||||
if (aParms) {
|
||||
for (var iParm = 0; iParm < aParms.length; iParm++) {
|
||||
var sParm = aParms[iParm];
|
||||
var sReplace = aValues[iParm] || aDefaults[iParm] || "";
|
||||
var iSearch = 0;
|
||||
var iLimit = sLine.length - matchLine[4].length; // set the limit at the start of the comment, if any
|
||||
while (iSearch < iLimit) {
|
||||
var iMatch = sLine.indexOf(sParm, iSearch);
|
||||
if (iMatch < 0) break;
|
||||
iSearch = iMatch + 1;
|
||||
var iMatchEnd = iMatch + sParm.length;
|
||||
var chPre = '', chPost = '';
|
||||
if ((!iMatch || !this.isSymbolChar(chPre = sLine[iMatch - 1])) && (iMatchEnd >= sLine.length || !this.isSymbolChar(chPost = sLine[iMatchEnd]))) {
|
||||
/*
|
||||
* If the "concatenation character" (') appears before (or after) the symbol being replaced, remove it.
|
||||
*/
|
||||
if (chPre == "'") iMatch--;
|
||||
if (chPost == "'") iMatchEnd++;
|
||||
sLine = sLine.substr(0, iMatch) + sReplace + sLine.substr(iMatchEnd);
|
||||
iSearch = iMatch + sReplace.length;
|
||||
fParse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
aParms = null;
|
||||
}
|
||||
}
|
||||
|
||||
var sLabel = match[1];
|
||||
var sOperator = match[2].toUpperCase();
|
||||
var sOperands = match[3].trim();
|
||||
var sComment = match[4];
|
||||
var sRemainder = match[3] + match[4];
|
||||
sLabel = matchLine[1];
|
||||
sOperands = matchLine[3].trim();
|
||||
sRemainder = matchLine[3] + matchLine[4];
|
||||
|
||||
if (sLabel) {
|
||||
var chSep = sLabel.slice(-1);
|
||||
sLabel = sLabel.slice(0, -1);
|
||||
if (chSep == ':') {
|
||||
this.addLabel(sLabel);
|
||||
} else {
|
||||
sOperands = sOperator + sOperands;
|
||||
sOperator = chSep;
|
||||
this.addSymbol(sLabel, this.nLocation, Macro10.SYMTYPE.LABEL);
|
||||
}
|
||||
|
||||
var matchOp;
|
||||
if (sOperator && (matchOp = sOperands.match(/^([=:]+)(.*)/))) {
|
||||
var nType = 0;
|
||||
sLabel = sOperator;
|
||||
sOperator = matchOp[1];
|
||||
sOperands = matchOp[2];
|
||||
if (sOperator == '==') {
|
||||
nType |= Macro10.SYMTYPE.PRIVATE;
|
||||
}
|
||||
else if (sOperator == '=:') {
|
||||
nType |= Macro10.SYMTYPE.INTERNAL;
|
||||
}
|
||||
this.addSymbol(sLabel, sOperands, nType);
|
||||
sOperator = sOperands = "";
|
||||
}
|
||||
|
||||
if (!sOperator && !sOperands) return true;
|
||||
|
||||
/*
|
||||
* My initial read of the MACRO-10 specification suggested that lines may begin with EITHER
|
||||
* "symbol:" (for a label) or "symbol=" (for an assignment), but apparently they can have BOTH.
|
||||
*/
|
||||
if (sOperands[0] == '=') {
|
||||
sLabel = sOperator;
|
||||
sOperands = sOperands.substr(1);
|
||||
sOperator = '=';
|
||||
}
|
||||
this.sOperator = sOperator;
|
||||
|
||||
/*
|
||||
|
|
@ -416,28 +487,36 @@ class Macro10 {
|
|||
if (!this.parseMacro(sOperator, sOperands)) {
|
||||
|
||||
switch (sOperator) {
|
||||
case "=":
|
||||
this.addAssign(sLabel, sOperands);
|
||||
break;
|
||||
|
||||
case Macro10.PSEUDO_OP.ASCII:
|
||||
case Macro10.PSEUDO_OP.ASCIZ:
|
||||
case Macro10.PSEUDO_OP.SIXBIT:
|
||||
this.addASCII(sRemainder);
|
||||
break;
|
||||
|
||||
case Macro10.PSEUDO_OP.END:
|
||||
this.addEND(sOperands);
|
||||
break;
|
||||
|
||||
case Macro10.PSEUDO_OP.EXP:
|
||||
this.addEXP(sOperands);
|
||||
break;
|
||||
|
||||
case Macro10.PSEUDO_OP.XWD:
|
||||
this.addXWD(sOperands);
|
||||
break;
|
||||
|
||||
case Macro10.PSEUDO_OP.DEFINE:
|
||||
case Macro10.PSEUDO_OP.IFE:
|
||||
case Macro10.PSEUDO_OP.IFN:
|
||||
case Macro10.PSEUDO_OP.IRP:
|
||||
case Macro10.PSEUDO_OP.IRPC:
|
||||
case Macro10.PSEUDO_OP.REPEAT:
|
||||
this.addMacro(sOperator, sOperands);
|
||||
this.addMacro(sOperator, sRemainder);
|
||||
break;
|
||||
|
||||
case Macro10.PSEUDO_OP.PAGE: // TODO
|
||||
case Macro10.PSEUDO_OP.SUBTTL: // TODO
|
||||
case Macro10.PSEUDO_OP.TITLE: // TODO
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -458,24 +537,27 @@ class Macro10 {
|
|||
*/
|
||||
parseMacro(name, sOperands)
|
||||
{
|
||||
var i;
|
||||
var macro = this.tblMacros[name];
|
||||
if (!macro) return false;
|
||||
|
||||
if (sOperands != null) {
|
||||
var macroPrev = this.macroCall;
|
||||
this.macroCall = macro;
|
||||
macro.aValues = this.getValues(sOperands, true);
|
||||
this.parseText(macro.sText, macro.aParms, macro.aValues, macro.aDefaults);
|
||||
/*
|
||||
* Process this macro call; start by extracting macro argument values from sOperands.
|
||||
* WARNING: Our simplistic approach to macro expansion and processing means that recursive macros
|
||||
* (such as the SHIFT macro contained in /apps/pdp10/tests/MACTEST1.MAC) could blow the stack. Nothing
|
||||
* bad should happen (other than a JavaScript stack limit exception aborting the assembly), but it begs
|
||||
* the question: did MACRO-10 perform any tail recursion optimizations or other tricks to prevent macros
|
||||
* from running amok?
|
||||
*/
|
||||
var sDelim = ',';
|
||||
if (sOperands[0] == '(') {
|
||||
sDelim += ')';
|
||||
sOperands = sOperands.substr(1);
|
||||
}
|
||||
var aValues = this.getValues(sOperands, sDelim);
|
||||
this.parseText(macro.sText, macro.aParms, aValues);
|
||||
this.macroCall = macroPrev;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name[0] != '_') return false;
|
||||
if (name[0] != '?') return false;
|
||||
|
||||
switch(name.substr(1)) {
|
||||
case Macro10.PSEUDO_OP.IFE:
|
||||
|
|
@ -484,6 +566,19 @@ class Macro10 {
|
|||
}
|
||||
break;
|
||||
|
||||
case Macro10.PSEUDO_OP.IFN:
|
||||
if (macro.nOperand) {
|
||||
this.parseText(macro.sText);
|
||||
}
|
||||
break;
|
||||
|
||||
case Macro10.PSEUDO_OP.IRP:
|
||||
case Macro10.PSEUDO_OP.IRPC:
|
||||
for (i = 0; i < macro.aValues.length; i++) {
|
||||
this.parseText(macro.sText, macro.aParms, [macro.aValues[i]], []);
|
||||
}
|
||||
break;
|
||||
|
||||
case Macro10.PSEUDO_OP.REPEAT:
|
||||
while (macro.nOperand-- > 0) {
|
||||
this.parseText(macro.sText);
|
||||
|
|
@ -509,36 +604,20 @@ class Macro10 {
|
|||
}
|
||||
|
||||
/**
|
||||
* parseText(sText, aParms, aValues)
|
||||
* parseText(sText, aParms, aValues, aDefaults)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sText
|
||||
* @param {Array.<string>} [aParms]
|
||||
* @param {Array.<string>} [aValues]
|
||||
* @param {Array.<string>} [aDefaults]
|
||||
*/
|
||||
parseText(sText, aParms, aValues)
|
||||
parseText(sText, aParms, aValues, aDefaults)
|
||||
{
|
||||
var asLines = sText.split(/\r?\n/);
|
||||
for (var iLine = 0; iLine < asLines.length; iLine++) {
|
||||
var sLine = asLines[iLine] + '\r\n';
|
||||
if (aParms) {
|
||||
for (var iParm = 0; iParm < aParms.length; iParm++) {
|
||||
var sParm = aParms[iParm];
|
||||
var sReplace = aValues[iParm] || "";
|
||||
var iSearch = 0;
|
||||
while (true) {
|
||||
var iMatch = sLine.indexOf(sParm, iSearch);
|
||||
if (iMatch < 0) break;
|
||||
iSearch = iMatch + 1;
|
||||
var iMatchEnd = iMatch + sParm.length;
|
||||
if ((!iMatch || !this.isSymbolChar(sLine[iMatch - 1])) && (iMatchEnd >= sLine.length || !this.isSymbolChar(sLine[iMatchEnd]))) {
|
||||
sLine = sLine.substr(0, iMatch) + sReplace + sLine.substr(iMatchEnd);
|
||||
iSearch = iMatch + sReplace.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.parseLine(sLine)) break;
|
||||
if (!this.parseLine(sLine, aParms, aValues, aDefaults)) break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -575,11 +654,18 @@ class Macro10 {
|
|||
getExpression(sOperands, sDelim = ",")
|
||||
{
|
||||
var i = 0;
|
||||
var fQuotes = false;
|
||||
var sOperand = null;
|
||||
var cNesting = 0;
|
||||
while (i < sOperands.length) {
|
||||
var ch = sOperands[i];
|
||||
var ch = sOperands[i++];
|
||||
if (ch == '"') {
|
||||
fQuotes = !fQuotes;
|
||||
continue;
|
||||
}
|
||||
if (fQuotes) continue;
|
||||
if (sDelim.indexOf(ch) >= 0) {
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
if (ch == '<') {
|
||||
|
|
@ -590,7 +676,6 @@ class Macro10 {
|
|||
break;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (!cNesting) {
|
||||
sOperand = sOperands.substr(0, i);
|
||||
|
|
@ -641,9 +726,9 @@ class Macro10 {
|
|||
this.error("error parsing expression: " + sOperand);
|
||||
}
|
||||
} else {
|
||||
var wLeft = match[1]? this.parseExpression(match[1]) : 0;
|
||||
var wLeft = match[1]? this.parseExpression(match[1], fPass1, nLocation) : 0;
|
||||
if (wLeft !== undefined) {
|
||||
var wRight = match[2]? this.parseExpression(match[2]) : 0;
|
||||
var wRight = match[2]? this.parseExpression(match[2], fPass1, nLocation) : 0;
|
||||
if (wRight !== undefined) {
|
||||
/*
|
||||
* NOTE: These must be combined as UNSIGNED values, so that's what we tell truncate() to produce.
|
||||
|
|
@ -686,6 +771,30 @@ class Macro10 {
|
|||
return sLiteral;
|
||||
}
|
||||
|
||||
/**
|
||||
* getDefaults(aParms)
|
||||
*
|
||||
* Check the given array of macro parameters for default values, remove them, and return them in a parallel array.
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {Array.<string>} aParms
|
||||
* @return {Array.<string>}
|
||||
*/
|
||||
getDefaults(aParms)
|
||||
{
|
||||
var aDefaults = [];
|
||||
for (var i = 0; i < aParms.length; i++) {
|
||||
var j = aParms[i].indexOf('<');
|
||||
if (j >= 0) {
|
||||
var k = aParms[i].lastIndexOf('>');
|
||||
if (k < 0) k = aParms[i].length;
|
||||
aDefaults[i] = aParms[i].substr(j, k - j);
|
||||
aParms[i] = aParms[i].substr(0, j);
|
||||
}
|
||||
}
|
||||
return aDefaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* getReserved(sOperands)
|
||||
*
|
||||
|
|
@ -701,15 +810,18 @@ class Macro10 {
|
|||
if (match = sOperands.match(/([A-Z$%.][0-9A-Z$%.]*)#/i)) {
|
||||
sReserved = match[0];
|
||||
var sLabel = match[1];
|
||||
var name = '_' + sLabel;
|
||||
var name = '?' + sLabel;
|
||||
if (this.tblMacros[name] !== undefined) {
|
||||
this.error("reserved symbol redefined: " + sReserved);
|
||||
}
|
||||
var aParms, aDefaults, aValues;
|
||||
aParms = aDefaults = aValues = [];
|
||||
this.tblMacros[name] = {
|
||||
name: name,
|
||||
nOperand: Macro10.MACRO_OP.RESERVED,
|
||||
aParms: [],
|
||||
aDefaults: [],
|
||||
aParms,
|
||||
aDefaults,
|
||||
aValues,
|
||||
sText: sLabel + ": 0",
|
||||
nLine: this.nLine
|
||||
};
|
||||
|
|
@ -718,20 +830,29 @@ class Macro10 {
|
|||
return sReserved;
|
||||
}
|
||||
|
||||
getSymbol(sOperands)
|
||||
{
|
||||
var match;
|
||||
if (match = sOperands.match(/([A-Z$%.][0-9A-Z$%.]*)/i)) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getValues(sOperands, sDelim)
|
||||
* getValues(sOperands, fParens)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sOperands
|
||||
* @param {string} [sDelim]
|
||||
* @param {boolean} [fParens] (true to strip any parens from around the entire operands)
|
||||
* @return {Array.<string>}
|
||||
*/
|
||||
getValues(sOperands, sDelim)
|
||||
getValues(sOperands, fParens)
|
||||
{
|
||||
var aValues = [];
|
||||
if (fParens) sOperands = sOperands.replace(/^\(?(.*?)\)?$/, "$1");
|
||||
while (sOperands) {
|
||||
sOperands = sOperands.trim();
|
||||
var sOperand = this.getExpression(sOperands, sDelim);
|
||||
var sOperand = this.getExpression(sOperands);
|
||||
if (!sOperand) break;
|
||||
aValues.push(sOperand);
|
||||
sOperands = sOperands.substr(sOperand.length + 1);
|
||||
|
|
@ -773,34 +894,6 @@ class Macro10 {
|
|||
return sRemain;
|
||||
}
|
||||
|
||||
/**
|
||||
* addAssign(sName, sOperand)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sName
|
||||
* @param {string} sOperand
|
||||
*/
|
||||
addAssign(sName, sOperand)
|
||||
{
|
||||
var value = this.parseExpression(sOperand);
|
||||
if (value === undefined) {
|
||||
this.error("parseExpression(" + sOperand + ")");
|
||||
return;
|
||||
}
|
||||
this.addSymbol(sName, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* addLabel(sLabel)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sLabel
|
||||
*/
|
||||
addLabel(sLabel)
|
||||
{
|
||||
this.addSymbol(sLabel, this.nLocation, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* addMacro(sOperator, sOperands)
|
||||
*
|
||||
|
|
@ -808,7 +901,7 @@ class Macro10 {
|
|||
* REPEAT block instead.
|
||||
*
|
||||
* REPEAT blocks piggy-back on this code because they're essentially anonymous immediately-invoked macros;
|
||||
* we use an illegal MACRO-10 symbol ('_REPEAT') to name the anonymous macro while it's being defined, and the
|
||||
* we use an illegal MACRO-10 symbol ('?REPEAT') to name the anonymous macro while it's being defined, and the
|
||||
* macro's nOperand field will contain the repeat count.
|
||||
*
|
||||
* The piggy-backing continues with other pseudo-ops like IFE, which again contain an anonymous block of text
|
||||
|
|
@ -823,34 +916,29 @@ class Macro10 {
|
|||
*/
|
||||
addMacro(sOperator, sOperands)
|
||||
{
|
||||
var match, name, nOperand, aParms, aDefaults, iMatch;
|
||||
var i, match, name, nOperand, aParms, aDefaults, aValues, iMatch;
|
||||
|
||||
this.chMacroOpen = '<';
|
||||
this.chMacroClose = '>';
|
||||
aParms = aDefaults = [];
|
||||
aParms = aDefaults = aValues = [];
|
||||
|
||||
if (sOperator == Macro10.PSEUDO_OP.DEFINE) {
|
||||
/*
|
||||
* This is a DEFINE (macro) block.
|
||||
*/
|
||||
match = sOperands.match(/([A-Z$%.][0-9A-Z$%.]*)\s*(\([^)]*\)|)\s*(<|)(.*)/i);
|
||||
match = sOperands.match(/([A-Z$%.][0-9A-Z$%.]*)\s*(\([^)]*\)|)\s*(<|)([\s\S]*)/i);
|
||||
if (!match) {
|
||||
this.error("unrecognized " + sOperator + " definition: " + sOperands);
|
||||
return "";
|
||||
this.error("unrecognized " + sOperator + ": " + sOperands);
|
||||
return sOperands;
|
||||
}
|
||||
name = match[1];
|
||||
/*
|
||||
* TODO: This may not actually be an error, but I'd like to take a look if/when it happens.
|
||||
* If this macro has defined parameters, parse them (and any defaults) now.
|
||||
*/
|
||||
if (this.tblMacros[name] !== undefined) {
|
||||
this.error("macro redefined: " + name);
|
||||
if (match[2]) {
|
||||
aParms = this.getValues(match[2], true);
|
||||
aDefaults = this.getDefaults(aParms);
|
||||
}
|
||||
/*
|
||||
* TODO: Tighten up this parsing at some point. All this is doing is extracting entire symbols
|
||||
* from within the parentheses, if any; it's NOT ensuring that those symbols are comma-separated
|
||||
* with no other intervening characters.
|
||||
*/
|
||||
aParms = match[2].match(/[A-Z$%.][0-9A-Z$%.]*/g);
|
||||
nOperand = Macro10.MACRO_OP.DEFINE;
|
||||
iMatch = 3;
|
||||
}
|
||||
|
|
@ -860,7 +948,7 @@ class Macro10 {
|
|||
*/
|
||||
this.chMacroOpen = '[';
|
||||
this.chMacroClose = ']';
|
||||
name = '_' + Str.toDec(this.nLocation, 5);
|
||||
name = '?' + Str.toDec(this.nLocation, 5);
|
||||
if (this.tblMacros[name] !== undefined) {
|
||||
this.error("literal symbol redefined: " + name);
|
||||
}
|
||||
|
|
@ -869,6 +957,38 @@ class Macro10 {
|
|||
this.aLiterals.push(name);
|
||||
iMatch = 0;
|
||||
}
|
||||
else if (sOperator == Macro10.PSEUDO_OP.IRP || sOperator == Macro10.PSEUDO_OP.IRPC) {
|
||||
/*
|
||||
* IRP (and IRPC) blocks are very similar to DEFINE blocks, but they define exactly ONE macro parameter
|
||||
* with NO parentheses (whereas regular macros always define their parameters, if any, WITH parentheses),
|
||||
* and then the IRP (or IRPC) block is immediately invoked with the corresponding value from the
|
||||
* enclosing macro.
|
||||
*/
|
||||
if (!this.macroCall) {
|
||||
this.error(sOperator + " outside of macro");
|
||||
}
|
||||
match = sOperands.match(/([A-Z$%.][0-9A-Z$%.]*)\s*,\s*(<|)([\s\S]*)/i);
|
||||
if (!match) {
|
||||
this.error("unrecognized " + sOperator + ": " + sOperands);
|
||||
return sOperands;
|
||||
}
|
||||
for (i = 0; i < this.macroCall.aParms.length; i++) {
|
||||
if (match[1] == this.macroCall.aParms[i]) break;
|
||||
}
|
||||
if (i == this.macroCall.aParms.length) {
|
||||
this.error("invalid " + sOperator + " parameter: " + match[1]);
|
||||
return sOperands;
|
||||
}
|
||||
name = '?' + sOperator;
|
||||
aParms = [match[1]];
|
||||
if (sOperator == Macro10.PSEUDO_OP.IRPC) {
|
||||
aValues = this.macroCall.aValues[i].split("");
|
||||
} else {
|
||||
aValues = this.getValues(this.macroCall.aValues[i]);
|
||||
}
|
||||
nOperand = aValues.length;
|
||||
iMatch = 2;
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* This must be a REPEAT or CONDITIONAL block.
|
||||
|
|
@ -876,12 +996,12 @@ class Macro10 {
|
|||
var sOperand = this.getExpression(sOperands);
|
||||
if (!sOperand) {
|
||||
this.error("missing " + sOperator + " expression: " + sOperands);
|
||||
return "";
|
||||
return sOperands;
|
||||
}
|
||||
sOperands = sOperands.substr(sOperand.length + 1);
|
||||
sOperand = sOperand.trim();
|
||||
match = sOperands.match(/\s*(<|)(.*)/i);
|
||||
name = '_' + sOperator;
|
||||
match = sOperands.match(/\s*(<|)([\s\S]*)/i);
|
||||
name = '?' + sOperator;
|
||||
/*
|
||||
* The expression is either a repeat count or a condition. Either way, we must be able to
|
||||
* resolve it now, so we don't set fPass1 (but that doesn't mean it's the second pass, either).
|
||||
|
|
@ -897,24 +1017,14 @@ class Macro10 {
|
|||
*/
|
||||
name = name.toUpperCase();
|
||||
this.nMacroDef = 1;
|
||||
|
||||
var sText = "";
|
||||
if (match[iMatch]) { // if there IS also an opening bracket...
|
||||
this.nMacroDef = 2; // then the macro definition has begun
|
||||
sText = match[iMatch + 1];
|
||||
if (sText.slice(-1) == this.chMacroClose) { // and if there is ALSO a closing bracket...
|
||||
this.nMacroDef = 0; // the macro definition has ended as well
|
||||
sText = sText.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
this.tblMacros[name] = {name, nOperand, aParms, aDefaults, sText, nLine: this.nLine};
|
||||
|
||||
if (!this.nMacroDef) {
|
||||
this.parseMacro(name);
|
||||
} else {
|
||||
this.sMacroDef = name;
|
||||
this.tblMacros[name] = {name, nOperand, aParms, aDefaults, aValues, sText: "", nLine: this.nLine};
|
||||
|
||||
if (match[iMatch]) { // if there is an opening bracket
|
||||
this.nMacroDef = 2; // then the macro definition has started
|
||||
this.appendMacro(match[iMatch + 1]);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
|
@ -951,23 +1061,74 @@ class Macro10 {
|
|||
}
|
||||
|
||||
/**
|
||||
* addSymbol(name, value, fLabel, fGlobal, fPrivate)
|
||||
* addEND()
|
||||
*
|
||||
* Processes the END pseudo-op.
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sOperands
|
||||
*/
|
||||
addEND(sOperands)
|
||||
{
|
||||
if (!sOperands) {
|
||||
this.nAddrStart = this.nAddr;
|
||||
} else {
|
||||
this.nAddrStart = this.parseExpression(sOperands, true);
|
||||
if (this.nAddrStart === undefined) {
|
||||
this.error("unrecognized expression: " + sOperands);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* addEXP()
|
||||
*
|
||||
* Processes the EXP pseudo-op.
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sOperands
|
||||
*/
|
||||
addEXP(sOperands)
|
||||
{
|
||||
var w = this.parseExpression(sOperands, true);
|
||||
if (w !== undefined) {
|
||||
this.genWord(w, this.dbg.sUndefined);
|
||||
} else {
|
||||
this.error("unrecognized expression: " + sOperands);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* addSymbol(name, value, nType)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} name
|
||||
* @param {number} value
|
||||
* @param {boolean} [fLabel] (default is false, meaning the symbol is an assignment)
|
||||
* @param {boolean} [fGlobal] (default is false, meaning the symbol is local to the current file)
|
||||
* @param {boolean} [fPrivate] (default is false, meaning the symbol has visibility to the caller)
|
||||
* @param {number|string} value
|
||||
* @param {number} [nType]
|
||||
*/
|
||||
addSymbol(name, value, fLabel = false, fGlobal = false, fPrivate = false)
|
||||
addSymbol(name, value, nType = 0)
|
||||
{
|
||||
name = name.toUpperCase().substr(0, 6);
|
||||
if (fLabel && this.tblSymbols[name] !== undefined) {
|
||||
if ((nType & Macro10.SYMTYPE.LABEL) && this.tblSymbols[name] !== undefined) {
|
||||
this.error("label " + name + " redefined");
|
||||
return;
|
||||
}
|
||||
this.tblSymbols[name] = {name, value, nLine: this.nLine, fLabel, fGlobal, fPrivate};
|
||||
if (typeof value == 'string') {
|
||||
var v = this.parseExpression(value);
|
||||
if (v === undefined) {
|
||||
this.error("parseExpression(" + value + ")");
|
||||
return;
|
||||
}
|
||||
value = v;
|
||||
}
|
||||
var sym = this.tblSymbols[name];
|
||||
if (sym) {
|
||||
sym.value = value;
|
||||
sym.nType = nType;
|
||||
sym.nLine = this.nLine;
|
||||
} else {
|
||||
this.tblSymbols[name] = {name, value, nType, nLine: this.nLine};
|
||||
}
|
||||
this.dbg.setVariable(name, value);
|
||||
}
|
||||
|
||||
|
|
@ -981,7 +1142,7 @@ class Macro10 {
|
|||
addWord(sOperator, sOperands)
|
||||
{
|
||||
var w;
|
||||
var sExp = sOperator + sOperands;
|
||||
var sExp = sOperator + ' ' + sOperands;
|
||||
if (sExp.indexOf(",,") >= 0 || !sOperator && sExp.indexOf('@') < 0 && sExp.indexOf('(') < 0) {
|
||||
w = this.parseExpression(sExp, true);
|
||||
} else {
|
||||
|
|
@ -1004,15 +1165,17 @@ class Macro10 {
|
|||
/**
|
||||
* addXWD()
|
||||
*
|
||||
* The XWD pseudo-op appears to be equivalent to two values separated by two commas, which our fixup code
|
||||
* must also support, so we simply treat the XWD operands as a fixup expression.
|
||||
* Processes the XWD pseudo-op.
|
||||
*
|
||||
* Since the XWD pseudo-op appears to be equivalent to two values separated by two commas, which addEXP() must also
|
||||
* support, we can piggy-back on addExp().
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sOperands
|
||||
*/
|
||||
addXWD(sOperands)
|
||||
{
|
||||
this.genWord(0, sOperands.replace(",", ",,"));
|
||||
this.addEXP(sOperands.replace(",", ",,"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1070,7 +1233,7 @@ class Macro10 {
|
|||
genWord(value, sFixup)
|
||||
{
|
||||
this.aWords[this.nLocation] = this.truncate(value);
|
||||
if (sFixup != null) this.aFixups.push({nLocation: this.nLocation, sValue: sFixup});
|
||||
if (sFixup != null) this.aFixups.push({nLocation: this.nLocation, sValue: sFixup, nLine: this.nLine});
|
||||
this.nLocation++;
|
||||
}
|
||||
|
||||
|
|
@ -1090,18 +1253,45 @@ class Macro10 {
|
|||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
/**
|
||||
* println(s)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} s
|
||||
*/
|
||||
println(s)
|
||||
{
|
||||
if (!this.dbg) {
|
||||
console.log(s);
|
||||
} else {
|
||||
this.dbg.println(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Macro10.SYMTYPE = {
|
||||
LABEL: 0x01,
|
||||
PRIVATE: 0x02,
|
||||
INTERNAL: 0x04
|
||||
};
|
||||
|
||||
Macro10.PSEUDO_OP = {
|
||||
ASCII: "ASCII",
|
||||
ASCIZ: "ASCIZ",
|
||||
DEFINE: "DEFINE",
|
||||
END: "END",
|
||||
EXP: "EXP",
|
||||
IFE: "IFE",
|
||||
IFN: "IFN",
|
||||
IRP: "IRP",
|
||||
IRPC: "IRPC",
|
||||
LITERAL: "LITERAL",
|
||||
PAGE: "PAGE",
|
||||
REPEAT: "REPEAT",
|
||||
SIXBIT: "SIXBIT",
|
||||
SUBTTL: "SUBTTL",
|
||||
TITLE: "TITLE",
|
||||
XWD: "XWD"
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -459,16 +459,13 @@ class Debugger extends Component
|
|||
if (nBits <= 32) {
|
||||
vNew = v | 0;
|
||||
} else {
|
||||
/*
|
||||
* For negative values, we require them to fit within nBits - 1, reserving the left-most bit
|
||||
* for the sign bit, but for positive values, we can't really be sure if the caller is treating
|
||||
* the left-most bit as a sign bit or not, so the upper range is based on nBits.
|
||||
*/
|
||||
limit = Math.pow(2, nBits - 1);
|
||||
if (v < -limit) {
|
||||
vNew = v % limit;
|
||||
} else if (v >= limit * 2) {
|
||||
vNew = v % (limit * 2);
|
||||
if (v < -limit * 2) vNew = v % (limit * 2);
|
||||
vNew += (limit * 2);
|
||||
} else if (vNew >= limit) {
|
||||
if (v >= limit * 2) vNew = v % (limit * 2);
|
||||
vNew -= (limit * 2); // the sign bit was set in this overly large value, so make it negative
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -566,7 +563,7 @@ class Debugger extends Component
|
|||
case '|':
|
||||
valNew = this.evalIOR(val1, val2);
|
||||
break;
|
||||
case '^^': // since MACRO-10 uses '^' for base overrides, you must now use '^^' for bitwise exclusive-or (XOR)
|
||||
case '^!': // since MACRO-10 uses '^' for base overrides, '^!' is used for bitwise exclusive-or (XOR)
|
||||
valNew = this.evalXOR(val1, val2);
|
||||
break;
|
||||
case '&&':
|
||||
|
|
@ -575,16 +572,21 @@ class Debugger extends Component
|
|||
case '||':
|
||||
valNew = (val1 || val2? 1 : 0);
|
||||
break;
|
||||
case '><':
|
||||
case '_':
|
||||
case '^_':
|
||||
valNew = val1;
|
||||
val2 = 35 - (val2 & 0xff);
|
||||
/*
|
||||
* While we always try to avoid assuming any particular number of bits of precision, the 'B' shift
|
||||
* operator (which we've converted to '^_') is unique to the MACRO-10 environment, which imposes the
|
||||
* following restrictions on the shift count.
|
||||
*/
|
||||
if (chOp == '^_') val2 = 35 - (val2 & 0xff);
|
||||
if (val2) {
|
||||
/*
|
||||
* Since binary shifting is a logical operation, and since shifting by division only works properly
|
||||
* with positive numbers, we must convert a negative value to a positive value, by computing the two's
|
||||
* complement.
|
||||
* Since binary shifting is a logical (not arithmetic) operation, and since shifting by division only
|
||||
* works properly with positive numbers, we call truncate() to produce an unsigned value.
|
||||
*/
|
||||
if (valNew < 0) valNew += Math.pow(2, 36);
|
||||
valNew = this.truncate(valNew, 0, true);
|
||||
if (val2 > 0) {
|
||||
valNew *= Math.pow(2, val2);
|
||||
} else {
|
||||
|
|
@ -610,9 +612,9 @@ class Debugger extends Component
|
|||
*
|
||||
* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
||||
* - - - - - - - - - - -- -- -- -- --
|
||||
* 2 * _ { 3 + _ { 4 / 2 } _ } _
|
||||
* 2 * { 3 + { 4 / 2 } }
|
||||
*
|
||||
* This function takes care of recursively processing sub-expressions, by processing subsets of the array,
|
||||
* This function takes care of recursively processing grouped expressions, by processing subsets of the array,
|
||||
* as well as handling certain base overrides (eg, temporarily switching to base-10 for binary shift suffixes).
|
||||
*
|
||||
* @param {Array.<string>} asValues
|
||||
|
|
@ -627,7 +629,7 @@ class Debugger extends Component
|
|||
var value;
|
||||
var sValue, sOp;
|
||||
var fError = false;
|
||||
var fNegate = false;
|
||||
var nUnary = 0;
|
||||
var aVals = [], aOps = [];
|
||||
|
||||
var nBasePrev = this.nBase;
|
||||
|
|
@ -638,7 +640,9 @@ class Debugger extends Component
|
|||
sValue = asValues[iValue++].trim();
|
||||
sOp = (iValue < iLimit? asValues[iValue++] : "");
|
||||
|
||||
if (!sValue) {
|
||||
if (sValue) {
|
||||
v = this.parseValue(sValue, null, fQuiet, nUnary);
|
||||
} else {
|
||||
if (sOp == '{') {
|
||||
var cOpen = 1;
|
||||
var iStart = iValue;
|
||||
|
|
@ -656,16 +660,34 @@ class Debugger extends Component
|
|||
sOp = (iValue < iLimit? asValues[iValue++] : "");
|
||||
}
|
||||
else {
|
||||
if (sOp != '-') {
|
||||
fError = true;
|
||||
break;
|
||||
if (sOp == '^B') {
|
||||
this.nBase = 2;
|
||||
continue;
|
||||
}
|
||||
fNegate = true;
|
||||
if (sOp == '^O') {
|
||||
this.nBase = 8;
|
||||
continue;
|
||||
}
|
||||
if (sOp == '^D') {
|
||||
this.nBase = 10;
|
||||
continue;
|
||||
}
|
||||
if (!(nUnary & (0xC0000000|0))) {
|
||||
if (sOp == '+') {
|
||||
continue;
|
||||
}
|
||||
if (sOp == '-') {
|
||||
nUnary = (nUnary << 2) | 1;
|
||||
continue;
|
||||
}
|
||||
if (sOp == '~' || sOp == '^-') {
|
||||
nUnary = (nUnary << 2) | 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
v = this.parseValue(sValue, null, fQuiet, fNegate);
|
||||
fError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (v === undefined) {
|
||||
|
|
@ -693,11 +715,11 @@ class Debugger extends Component
|
|||
* The MACRO-10 binary shifting operator assumes a base-10 shift count, regardless of the current
|
||||
* base, so we must override the current base to ensure the count is parsed correctly.
|
||||
*/
|
||||
this.nBase = (sOp == '><')? 10 : nBase;
|
||||
fNegate = false;
|
||||
this.nBase = (sOp == '^_')? 10 : nBase;
|
||||
nUnary = 0;
|
||||
}
|
||||
|
||||
if (!this.evalOps(aVals, aOps) || aVals.length != 1) {
|
||||
if (fError || !this.evalOps(aVals, aOps) || aVals.length != 1) {
|
||||
fError = true;
|
||||
}
|
||||
|
||||
|
|
@ -711,6 +733,50 @@ class Debugger extends Component
|
|||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* parseASCII(sExp, chDelim, nBits, cchMax, fQuiet)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string} sExp
|
||||
* @param {string} chDelim
|
||||
* @param {number} nBits
|
||||
* @param {number} cchMax
|
||||
* @param {boolean} [fQuiet] (true for quiet parsing)
|
||||
* @return {string}
|
||||
*/
|
||||
parseASCII(sExp, chDelim, nBits, cchMax, fQuiet)
|
||||
{
|
||||
var i;
|
||||
while ((i = sExp.indexOf(chDelim)) >= 0) {
|
||||
var v = 0;
|
||||
var j = i + 1;
|
||||
var cch = cchMax;
|
||||
while (j < sExp.length) {
|
||||
var ch = sExp[j++];
|
||||
if (ch == chDelim) {
|
||||
cch = -1;
|
||||
break;
|
||||
}
|
||||
if (!cch) break;
|
||||
cch--;
|
||||
var c = ch.charCodeAt(0);
|
||||
if (nBits == 7) {
|
||||
c &= 0x7F;
|
||||
} else {
|
||||
c = (c - 0x20) & 0x3F;
|
||||
}
|
||||
v = this.truncate(v * Math.pow(2, nBits) + c, nBits * cchMax, true);
|
||||
}
|
||||
if (cch >= 0) {
|
||||
if (fQuiet === false) this.println("parse error (" + sExp + ")");
|
||||
break;
|
||||
} else {
|
||||
sExp = sExp.substr(0, i) + this.toStrBase(v, -1) + sExp.substr(j);
|
||||
}
|
||||
}
|
||||
return sExp;
|
||||
}
|
||||
|
||||
/**
|
||||
* parseExpression(sExp, fQuiet)
|
||||
*
|
||||
|
|
@ -729,7 +795,7 @@ class Debugger extends Component
|
|||
*
|
||||
* We pop 1 "binop" from aOps and 2 values from aVals whenever a "binop" of lower priority than its
|
||||
* predecessor is encountered, evaluate, and push the result back onto aVals. Only selected unary
|
||||
* operators are supported (eg, minus), and ternary operators like '?:' are not supported at all.
|
||||
* operators are supported (eg, minus and "not"); ternary operators like '?:' are not supported at all.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string|undefined} sExp
|
||||
|
|
@ -738,26 +804,34 @@ class Debugger extends Component
|
|||
*/
|
||||
parseExpression(sExp, fQuiet)
|
||||
{
|
||||
var value;
|
||||
|
||||
var value = undefined;
|
||||
this.sUndefined = null;
|
||||
|
||||
if (sExp) {
|
||||
|
||||
/*
|
||||
* The default grouping characters for sub-expressions are braces; they can be changed by altering
|
||||
* The default delimiting characters for grouped expressions are braces; they can be changed by altering
|
||||
* achGroup, but when that happens, instead of changing our regular expressions and operator tables,
|
||||
* we simply replace all achGroup characters with braces in the given expression.
|
||||
*
|
||||
* Why not just always use parentheses for sub-expressions? Because some debuggers use parseReference()
|
||||
* to perform parenthetical value replacements in message strings, and they don't want parentheses taking
|
||||
* on a different meaning. And for some machines, like the PDP-10, the convention is to use parentheses
|
||||
* for indexed addressing and angle brackets for sub-expressions.
|
||||
* Why not use parentheses for grouped expressions? Because some debuggers use parseReference() to perform
|
||||
* parenthetical value replacements in message strings, and they don't want parentheses taking on a different
|
||||
* meaning. And for some machines, like the PDP-10, the convention is to use parentheses for other things,
|
||||
* like indexed addressing, and to use angle brackets for grouped expressions.
|
||||
*/
|
||||
if (this.achGroup[0] != '{') {
|
||||
sExp = sExp.split(this.achGroup[0]).join('{').split(this.achGroup[1]).join('}');
|
||||
}
|
||||
|
||||
/*
|
||||
* Quoted ASCII characters can have a numeric value, too, which must be converted now, to avoid any
|
||||
* conflicts with the operators below.
|
||||
*/
|
||||
sExp = this.parseASCII(sExp, '"', 7, 5, fQuiet); // MACRO-10 packs up to 5 7-bit ASCII codes into a value
|
||||
if (!sExp) return value;
|
||||
sExp = this.parseASCII(sExp, "'", 6, 6, fQuiet); // MACRO-10 packs up to 6 6-bit ASCII (SIXBIT) codes into a value
|
||||
if (!sExp) return value;
|
||||
|
||||
/*
|
||||
* All browsers (including, I believe, IE9 and up) support the following idiosyncrasy of a RegExp split():
|
||||
* when the RegExp uses a capturing pattern, the resulting array will include entries for all the pattern
|
||||
|
|
@ -768,13 +842,14 @@ class Debugger extends Component
|
|||
* what IS important is listing operators than contain shorter operators first. For example, bitwise
|
||||
* shift operators must be listed BEFORE the logical less-than or greater-than operators.
|
||||
*
|
||||
* Also, to better accommodate MACRO-10 syntax, I've replaced the single '^' for XOR with '^^', since
|
||||
* MACRO-10 uses prefixes like "^D", "^O" and "^B" with numeric constants to indicate a base override, and
|
||||
* I've added '!' as an alias for '|' to perform bitwise inclusive-or.
|
||||
* Also, to better accommodate MACRO-10 syntax, I've replaced the single '^' for XOR with '^!' (since
|
||||
* MACRO-10 uses prefixes like "^D", "^O" and "^B" with numeric constants to indicate a base override).
|
||||
* Similarly, I've added '!' as an alias for '|' (bitwise inclusive-or), '^-' as an alias for '~' (unary
|
||||
* complement "not" operator), and '_' as a shift operator.
|
||||
*
|
||||
* The MACRO-10 binary shifting suffix ('B') is a bit more problematic, since a capital B can also appear
|
||||
* inside symbols. So I pre-scan for that operator and replace non-symbolic occurrences with an internal
|
||||
* operator ('><').
|
||||
* inside symbols. So I pre-scan for that suffix and replace all non-symbolic occurrences with an internal
|
||||
* shift operator ('^_').
|
||||
*
|
||||
* Note that Str.parseInt(), which parseValue() relies on, supports both the MACRO-10 base prefix overrides
|
||||
* and the binary shifting suffix. But since the B suffix can also be a bracketed expression, we have to
|
||||
|
|
@ -786,8 +861,8 @@ class Debugger extends Component
|
|||
*
|
||||
* WARNING: Whenever you make changes to this RegExp, make sure you update aBinOpPrecedence as needed, too.
|
||||
*/
|
||||
var regExp = /(\{|}|\|\||&&|\||\^\^|><|&|!=|!|==|>=|>>>|>>|>|<=|<<|<|-|\+|%|\/|\*)/;
|
||||
sExp = sExp.replace(/(^|[^A-Z0-9$%.])([0-9]+)B/, "$1$2><");
|
||||
var regExp = /(\{|}|\|\||&&|\||\^!|\^B|\^O|\^D|\^-|~|\^_|_|&|!=|!|==|>=|>>>|>>|>|<=|<<|<|-|\+|%|\/|\*)/;
|
||||
sExp = sExp.replace(/(^|[^A-Z0-9$%.])([0-9]+)B/, "$1$2^_");
|
||||
var asValues = sExp.split(regExp);
|
||||
value = this.parseArray(asValues, 0, asValues.length, this.nBase, fQuiet);
|
||||
if (value !== undefined && fQuiet === false) {
|
||||
|
|
@ -874,16 +949,26 @@ class Debugger extends Component
|
|||
}
|
||||
|
||||
/**
|
||||
* parseValue(sValue, sName, fQuiet, fNegate)
|
||||
* parseValue(sValue, sName, fQuiet, nUnary)
|
||||
*
|
||||
* nUnary is actually a small "stack" of unary operations encoded in successive pairs of bits.
|
||||
* As parseExpression() encounters each unary operator, nUnary is shifted left 2 bits, and the
|
||||
* new unary operator is encoded in bits 0 and 1 (0b00 is none, 0b01 is negate, 0b10 is complement,
|
||||
* and 0b11 is reserved). Here, we process the bits in reverse order (hence the stack-like nature),
|
||||
* ensuring that we process the unary operators associated with this value right-to-left.
|
||||
*
|
||||
* Since bitwise operators see only 32 bits, more than 16 unary operators cannot be supported
|
||||
* using this method. We'll let parseExpression() worry about that; if it ever happens in practice,
|
||||
* then we'll have to switch to a more "expensive" approach (eg, an actual array of unary operators).
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string|undefined} sValue
|
||||
* @param {string|null} [sName] is the name of the value, if any
|
||||
* @param {boolean} [fQuiet]
|
||||
* @param {boolean} [fNegate]
|
||||
* @param {number} [nUnary] (0 for none, 1 for negate, 2 for complement)
|
||||
* @return {number|undefined} numeric value, or undefined if sValue is either undefined or invalid
|
||||
*/
|
||||
parseValue(sValue, sName, fQuiet, fNegate)
|
||||
parseValue(sValue, sName, fQuiet, nUnary = 0)
|
||||
{
|
||||
var value;
|
||||
if (sValue != null) {
|
||||
|
|
@ -893,12 +978,23 @@ class Debugger extends Component
|
|||
} else {
|
||||
value = this.getVariable(sValue);
|
||||
if (value == null) {
|
||||
value = Str.parseInt(fNegate? ('-' + sValue) : sValue, this.nBase);
|
||||
fNegate = false;
|
||||
/*
|
||||
* A feature of MACRO-10 is that any single-digit number is automatically interpreted as base-10.
|
||||
*/
|
||||
value = Str.parseInt(sValue, sValue.length > 1? this.nBase : 10);
|
||||
}
|
||||
}
|
||||
if (value != null) {
|
||||
if (fNegate) value = -value;
|
||||
while (nUnary) {
|
||||
if (nUnary & 1) {
|
||||
value = -this.truncate(value);
|
||||
}
|
||||
else if (nUnary & 2) {
|
||||
value = this.evalXOR(value, -1); // this is easier than adding an evalNOT()...
|
||||
}
|
||||
nUnary >>>= 2;
|
||||
}
|
||||
value = this.truncate(value);
|
||||
} else {
|
||||
if (!fQuiet) {
|
||||
this.println("invalid " + (sName? sName : "value") + ": " + sValue);
|
||||
|
|
@ -926,7 +1022,11 @@ class Debugger extends Component
|
|||
var fDefined = false;
|
||||
if (value !== undefined) {
|
||||
fDefined = true;
|
||||
sValue = Str.toHex(value, 0, true) + " " + value + ". " + Str.toOct(value, 0, true) + " " + Str.toBinBytes(value, 4, true);
|
||||
if (this.nBase == 8) {
|
||||
sValue = this.toStrBase(value, this.nBits, 8, 1) + " " + value + '.';
|
||||
} else {
|
||||
sValue = this.toStrBase(value, this.nBits, 16, 1) + " " + this.toStrBase(value, this.nBits, 8, 1) + " " + this.toStrBase(value, this.nBits, 2, this.nBits <= 32? 8 : 6) + " " + value + '.';
|
||||
}
|
||||
if (value >= 0x20 && value < 0x7F) {
|
||||
sValue += " '" + String.fromCharCode(value) + "'";
|
||||
}
|
||||
|
|
@ -1016,21 +1116,26 @@ class Debugger extends Component
|
|||
}
|
||||
|
||||
/**
|
||||
* toStrBase(n, nBits)
|
||||
* toStrBase(n, nBits, nBase, nGrouping)
|
||||
*
|
||||
* Use this instead of Str's toOct()/toDec()/toHex() to convert numbers to the Debugger's default base.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {number|null|undefined} n
|
||||
* @param {number} [nBits] (-1 to strip leading zeros, 0 to allow a variable number of digits)
|
||||
* @param {number} [nBase]
|
||||
* @param {number} [nGrouping] (if nBase is 2, this is a grouping; otherwise, it's a prefix condition)
|
||||
* @return {string}
|
||||
*/
|
||||
toStrBase(n, nBits = 0)
|
||||
toStrBase(n, nBits = 0, nBase = 0, nGrouping = 0)
|
||||
{
|
||||
var s;
|
||||
switch(this.nBase) {
|
||||
switch(nBase || this.nBase) {
|
||||
case 2:
|
||||
s = Str.toBin(n, nBits > 0? nBits : 0, nGrouping);
|
||||
break;
|
||||
case 8:
|
||||
s = Str.toOct(n, nBits > 0? ((nBits + 2)/3)|0 : 0);
|
||||
s = Str.toOct(n, nBits > 0? ((nBits + 2)/3)|0 : 0, !!nGrouping);
|
||||
break;
|
||||
case 10:
|
||||
/*
|
||||
|
|
@ -1040,7 +1145,7 @@ class Debugger extends Component
|
|||
break;
|
||||
case 16:
|
||||
default:
|
||||
s = Str.toHex(n, nBits > 0? ((nBits + 3) >> 2) : 0);
|
||||
s = Str.toHex(n, nBits > 0? ((nBits + 3) >> 2) : 0, !!nGrouping);
|
||||
break;
|
||||
}
|
||||
return (nBits < 0? Str.stripLeadingZeros(s) : s);
|
||||
|
|
@ -1049,12 +1154,17 @@ class Debugger extends Component
|
|||
|
||||
if (DEBUGGER) {
|
||||
|
||||
/*
|
||||
* Missing from this table are the (limited) set of unary operators we support (negate and complement),
|
||||
* since this is only a BINARY operator precedence, not a general-purpose precedence table. Assume that
|
||||
* all unary operators take precedence over all binary operators.
|
||||
*/
|
||||
Debugger.aBinOpPrecedence = {
|
||||
'||': 0, // logical OR
|
||||
'&&': 1, // logical AND
|
||||
'!': 2, // bitwise OR
|
||||
'|': 2, // bitwise OR
|
||||
'^^': 3, // bitwise XOR
|
||||
'^!': 3, // bitwise XOR (added by MACRO-10 sometime between the 1972 and 1978 versions)
|
||||
'&': 4, // bitwise AND
|
||||
'!=': 5, // inequality
|
||||
'==': 5, // equality
|
||||
|
|
@ -1070,9 +1180,10 @@ if (DEBUGGER) {
|
|||
'%': 9, // remainder
|
||||
'/': 9, // division
|
||||
'*': 9, // multiplication
|
||||
'><': 10, // internal binary shift operator (MACRO-10-style; converted from a 'B' suffix)
|
||||
'{': 11, // open sub-expression (achGroup[0] default)
|
||||
'}': 11 // close sub-expression (achGroup[1] default)
|
||||
'_': 10, // MACRO-10 shift operator
|
||||
'^_': 10, // MACRO-10 internal shift operator (converted from 'B' suffix form that MACRO-10 uses)
|
||||
'{': 11, // open grouped expression (achGroup[0] default)
|
||||
'}': 11 // close grouped expression (achGroup[1] default)
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -68,11 +68,8 @@ class Str {
|
|||
* More recently, we've added support for "^D", "^O", and "^B" prefixes to accommodate the base overrides
|
||||
* that the PDP-10's MACRO-10 assembly language supports (decimal, octal, and binary, respectively).
|
||||
* If this support turns out to adversely affect other debuggers, then it will have to be "conditionalized".
|
||||
*
|
||||
* To summarize our non-standard alternatives: a 'y' suffix indicates binary, a '#' prefix indicates
|
||||
* octal, a '$' prefix indicates hex, and a "0b" prefix indicates binary IF at least one comma is present.
|
||||
* Commas are useful for grouping binary digits, but if you don't want to use them, then you must use a
|
||||
* 'y' suffix for binary numbers.
|
||||
* Similarly, we've added support for "K", "M", and "G" MACRO-10-style suffixes that add 3, 6, or 9 zeros
|
||||
* to the value to be parsed, respectively.
|
||||
*
|
||||
* @param {string} s is the string representation of some number
|
||||
* @param {number} [base] is the radix to use (default is 10); can be overridden by prefixes/suffixes
|
||||
|
|
@ -84,71 +81,66 @@ class Str {
|
|||
|
||||
if (s) {
|
||||
if (!base) base = 10;
|
||||
var chPrefix = s.charAt(0);
|
||||
|
||||
var ch, chPrefix, chSuffix;
|
||||
var fCommas = (s.indexOf(',') > 0);
|
||||
if (fCommas) s = s.replace(/,/g, '');
|
||||
|
||||
ch = chPrefix = s.charAt(0);
|
||||
if (chPrefix == '#') {
|
||||
base = 8;
|
||||
chPrefix = null;
|
||||
chPrefix = '';
|
||||
}
|
||||
else if (chPrefix == '$') {
|
||||
base = 16;
|
||||
chPrefix = null;
|
||||
chPrefix = '';
|
||||
}
|
||||
if (chPrefix == null) {
|
||||
if (ch != chPrefix) {
|
||||
s = s.substr(1);
|
||||
}
|
||||
else {
|
||||
if (chPrefix == '0') {
|
||||
chPrefix = s.charAt(1);
|
||||
if (chPrefix == 'b' && fCommas) {
|
||||
ch = chPrefix = s.substr(0, 2);
|
||||
if (chPrefix == '0b' && fCommas || chPrefix == '^B') {
|
||||
base = 2;
|
||||
chPrefix = null;
|
||||
chPrefix = '';
|
||||
}
|
||||
if (chPrefix == 'o') {
|
||||
else if (chPrefix == '0o' || chPrefix == '^O') {
|
||||
base = 8;
|
||||
chPrefix = null;
|
||||
chPrefix = '';
|
||||
}
|
||||
else if (chPrefix == 'x') {
|
||||
base = 16;
|
||||
chPrefix = null;
|
||||
}
|
||||
}
|
||||
else if (chPrefix == '^') {
|
||||
chPrefix = s.charAt(1);
|
||||
if (chPrefix == 'D') {
|
||||
else if (chPrefix == '^D') {
|
||||
base = 10;
|
||||
chPrefix = null;
|
||||
chPrefix = '';
|
||||
}
|
||||
else if (chPrefix == 'O') {
|
||||
base = 8;
|
||||
chPrefix = null;
|
||||
else if (chPrefix == '0x') {
|
||||
base = 16;
|
||||
chPrefix = '';
|
||||
}
|
||||
else if (chPrefix == 'B') {
|
||||
if (ch != chPrefix) s = s.substr(2);
|
||||
}
|
||||
ch = chSuffix = s.slice(-1);
|
||||
if (chSuffix == 'Y' || chSuffix == 'y') {
|
||||
base = 2;
|
||||
chPrefix = null;
|
||||
}
|
||||
}
|
||||
if (chPrefix == null) {
|
||||
s = s.substr(2);
|
||||
}
|
||||
else {
|
||||
var chSuffix = s.charAt(s.length - 1).toLowerCase();
|
||||
if (chSuffix == 'y') {
|
||||
base = 2;
|
||||
chSuffix = null;
|
||||
chSuffix = '';
|
||||
}
|
||||
else if (chSuffix == '.') {
|
||||
base = 10;
|
||||
chSuffix = null;
|
||||
chSuffix = '';
|
||||
}
|
||||
else if (chSuffix == 'h') {
|
||||
else if (chSuffix == 'H' || chSuffix == 'h') {
|
||||
base = 16;
|
||||
chSuffix = null;
|
||||
chSuffix = '';
|
||||
}
|
||||
if (chSuffix == null) s = s.substr(0, s.length - 1);
|
||||
else if (chSuffix == 'K') {
|
||||
chSuffix = '000';
|
||||
}
|
||||
else if (chSuffix == 'M') {
|
||||
chSuffix = '000000';
|
||||
}
|
||||
else if (chSuffix == 'G') {
|
||||
chSuffix = '000000000';
|
||||
}
|
||||
if (ch != chSuffix) s = s.slice(0, -1) + chSuffix;
|
||||
/*
|
||||
* This adds support for the MACRO-10 binary shifting (Bn) suffix, which must be stripped from the
|
||||
* number before parsing, and then applied to the value after parsing. If n is omitted, 35 is assumed,
|
||||
|
|
@ -187,7 +179,7 @@ class Str {
|
|||
}
|
||||
|
||||
/**
|
||||
* toBase(n, radix, cch, sPrefix)
|
||||
* toBase(n, radix, cch, sPrefix, nGrouping)
|
||||
*
|
||||
* Displays the given number as an unsigned integer using the specified radix and number of digits.
|
||||
*
|
||||
|
|
@ -195,9 +187,10 @@ class Str {
|
|||
* @param {number} radix (ie, the base)
|
||||
* @param {number} cch (the desired number of digits)
|
||||
* @param {string} [sPrefix] (default is none)
|
||||
* @param {number} [nGrouping]
|
||||
* @return {string}
|
||||
*/
|
||||
static toBase(n, radix, cch, sPrefix = "")
|
||||
static toBase(n, radix, cch, sPrefix = "", nGrouping = 0)
|
||||
{
|
||||
/*
|
||||
* An initial "falsey" check for null takes care of both null and undefined;
|
||||
|
|
@ -229,57 +222,49 @@ class Str {
|
|||
cch = Math.ceil(Math.log(n) / Math.log(radix));
|
||||
}
|
||||
}
|
||||
if (n == null) {
|
||||
while (cch-- > 0) s = '?' + s;
|
||||
} else {
|
||||
var g = nGrouping || -1;
|
||||
while (cch-- > 0) {
|
||||
if (!g) {
|
||||
s = ',' + s;
|
||||
g = nGrouping;
|
||||
}
|
||||
if (n == null) {
|
||||
s = '?' + s;
|
||||
} else {
|
||||
var d = n % radix;
|
||||
d += (d >= 0 && d <= 9? 0x30 : 0x41 - 10);
|
||||
s = String.fromCharCode(d) + s;
|
||||
n = Math.trunc(n / radix);
|
||||
}
|
||||
g--;
|
||||
}
|
||||
return sPrefix + s;
|
||||
}
|
||||
|
||||
/**
|
||||
* toBin(n, cch, grouping)
|
||||
* toBin(n, cch, nGrouping)
|
||||
*
|
||||
* Converts an integer to binary, with the specified number of digits (up to the default of 32).
|
||||
* Converts an integer to binary, with the specified number of digits (up to a maximum of 36).
|
||||
*
|
||||
* @param {number|null|undefined} n (interpreted as a 32-bit value)
|
||||
* @param {number} [cch] is the desired number of binary digits (32 is both the default and the maximum)
|
||||
* @param {number} [grouping]
|
||||
* @param {number|null|undefined} n (supports integers up to 36 bits now)
|
||||
* @param {number} [cch] is the desired number of binary digits (0 or undefined for default of either 8, 18, or 36)
|
||||
* @param {number} [nGrouping]
|
||||
* @return {string} the binary representation of n
|
||||
*/
|
||||
static toBin(n, cch, grouping)
|
||||
static toBin(n, cch, nGrouping)
|
||||
{
|
||||
var s = "";
|
||||
if (!cch) {
|
||||
cch = 32;
|
||||
// cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN2) || 1;
|
||||
var v = Math.abs(n);
|
||||
if (v <= 0b11111111) {
|
||||
cch = 8;
|
||||
} else if (v <= 0b111111111111111111) {
|
||||
cch = 18;
|
||||
} else {
|
||||
if (cch > 32) cch = 32;
|
||||
cch = 36;
|
||||
}
|
||||
/*
|
||||
* An initial "falsey" check for null takes care of both null and undefined;
|
||||
* we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough.
|
||||
*
|
||||
* Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,
|
||||
* since JavaScript coerces such operands to zero, but I think there's "value" in seeing those
|
||||
* values displayed differently.
|
||||
*/
|
||||
var fInvalid = (n == null || isNaN(n));
|
||||
var group = (grouping = grouping || cch);
|
||||
while (cch-- > 0) {
|
||||
if (!group) {
|
||||
s = "," + s;
|
||||
group = grouping;
|
||||
}
|
||||
s = (fInvalid? '?' : ((n & 0x1)? '1' : '0')) + s;
|
||||
n >>= 1;
|
||||
group--;
|
||||
}
|
||||
return s;
|
||||
} else if (cch > 36) cch = 36;
|
||||
return Str.toBase(n, 2, cch, "", nGrouping);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -314,17 +299,23 @@ class Str {
|
|||
* an exception, whereas this function will return '?' characters.
|
||||
*
|
||||
* @param {number|null|undefined} n (supports integers up to 36 bits now)
|
||||
* @param {number} [cch] is the desired number of octal digits (0 or undefined for default of either 6 or 11)
|
||||
* @param {number} [cch] is the desired number of octal digits (0 or undefined for default of either 6, 8, or 12)
|
||||
* @param {boolean} [fPrefix]
|
||||
* @return {string} the octal representation of n
|
||||
*/
|
||||
static toOct(n, cch, fPrefix)
|
||||
{
|
||||
if (cch) {
|
||||
if (cch > 12) cch = 12;
|
||||
if (!cch) {
|
||||
// cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(8)) || 1;
|
||||
var v = Math.abs(n);
|
||||
if (v <= 0o777777) {
|
||||
cch = 6;
|
||||
} else if (v <= 0o77777777) {
|
||||
cch = 8;
|
||||
} else {
|
||||
cch = (n & ~0xffffff)? 12 : ((n & ~0xffff)? 8 : 6);
|
||||
cch = 12;
|
||||
}
|
||||
} else if (cch > 12) cch = 12;
|
||||
return Str.toBase(n, 8, cch, fPrefix? "0o" : "");
|
||||
}
|
||||
|
||||
|
|
@ -338,16 +329,20 @@ class Str {
|
|||
* an exception, whereas this function will return '?' characters.
|
||||
*
|
||||
* @param {number|null|undefined} n (supports integers up to 36 bits now)
|
||||
* @param {number} [cch] is the desired number of decimal digits (0 or undefined for default of either 5 or 10)
|
||||
* @param {number} [cch] is the desired number of decimal digits (0 or undefined for default of either 5 or 11)
|
||||
* @return {string} the decimal representation of n
|
||||
*/
|
||||
static toDec(n, cch)
|
||||
{
|
||||
if (cch) {
|
||||
if (cch > 11) cch = 11;
|
||||
if (!cch) {
|
||||
// cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN10) || 1;
|
||||
var v = Math.abs(n);
|
||||
if (v <= 99999) {
|
||||
cch = 5;
|
||||
} else {
|
||||
cch = (n & ~0xffff)? 10 : 5;
|
||||
cch = 11;
|
||||
}
|
||||
} else if (cch > 11) cch = 11;
|
||||
return Str.toBase(n, 10, cch);
|
||||
}
|
||||
|
||||
|
|
@ -369,17 +364,23 @@ class Str {
|
|||
* s = s.substr(0, cch).toUpperCase();
|
||||
*
|
||||
* @param {number|null|undefined} n (supports integers up to 36 bits now)
|
||||
* @param {number} [cch] is the desired number of hex digits (0 or undefined for default of either 4 or 8)
|
||||
* @param {number} [cch] is the desired number of hex digits (0 or undefined for default of either 4, 8, or 9)
|
||||
* @param {boolean} [fPrefix]
|
||||
* @return {string} the hex representation of n
|
||||
*/
|
||||
static toHex(n, cch, fPrefix)
|
||||
{
|
||||
if (cch) {
|
||||
if (cch > 9) cch = 9;
|
||||
if (!cch) {
|
||||
// cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(16)) || 1;
|
||||
var v = Math.abs(n);
|
||||
if (v <= 0xffff) {
|
||||
cch = 4;
|
||||
} else if (v <= 0xffffffff) {
|
||||
cch = 8;
|
||||
} else {
|
||||
cch = (n & ~0xffff)? 8 : 4;
|
||||
cch = 9;
|
||||
}
|
||||
} else if (cch > 9) cch = 9;
|
||||
return Str.toBase(n, 16, cch, fPrefix? "0x" : "");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,4 +19,5 @@ PDP-10 Hardware Publications
|
|||
PDP-10 Software Publications
|
||||
----------------------------
|
||||
|
||||
- [MACRO-10 Assembler Programmer's Reference Manual (April 1972)](http://archive.pcjs.org/pubs/dec/pdp10/tops10/02_1973AsmRef_macro.pdf) [[Original PDF](http://bitsavers.informatik.uni-stuttgart.de/pdf/dec/pdp10/TOPS10/1973_Assembly_Language_Handbook/02_1973AsmRef_macro.pdf)]
|
||||
- [MACRO-10 Assembler Programmer's Reference Manual (June 1972)](http://archive.pcjs.org/pubs/dec/pdp10/tops10/Macro_Assembler_Reference_Manual-Jun72.pdf) [[Original PDF](http://bitsavers.informatik.uni-stuttgart.de/pdf/dec/pdp10/TOPS10/1973_Assembly_Language_Handbook/02_1973AsmRef_macro.pdf)]
|
||||
- [MACRO-10 Assembler Programmer's Reference Manual (April 1978)](http://archive.pcjs.org/pubs/dec/pdp10/tops10/Macro_Assembler_Reference_Manual-Apr78.pdf) [[Original PDF](http://www.livingcomputers.org/Discover/Online-Systems/User-Documentation/Tops-10-v7-04/4_Macro_Assembler_Reference_Manual.aspx)]
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue