ADDED .agignore Index: .agignore ================================================================== --- /dev/null +++ .agignore @@ -0,0 +1,2 @@ +inst +test Index: .fossil-settings/crlf-glob ================================================================== --- .fossil-settings/crlf-glob +++ .fossil-settings/crlf-glob @@ -1,5 +1,12 @@ -src/scp.* -src/sim_*.[ch] -src/sim_*.in +examples/*.fc + +src/*.[ch] +src/*.in + +src/cc8/*/*.[ch] +src/cc8/include/* +src/cc8/os8/*.sb +src/cc8/os8/bldcc8.bi + src/PDP8/pdp8_*.[ch] src/PDP8/pidp8i.c.in Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -1,1 +1,2 @@ doc/simh/*.pdf +test/* ADDED .ignore Index: .ignore ================================================================== --- /dev/null +++ .ignore @@ -0,0 +1,1 @@ +.agignore Index: AUTHORS.md ================================================================== --- AUTHORS.md +++ AUTHORS.md @@ -1,8 +1,8 @@ -# Creators and Major Contributors to the PiDP-8/I Project +# Creators of and Major Contributors to the PiDP-8/I Project -* **Oscar Vermeulen **: +* **[Oscar Vermeulen](mailto:oscar.vermeulen@hotmail.com)**: - Creator of the project (both hardware and software) - Author of the initial modifications to the SIMH PDP-8 simulator necessary to make it use the PiDP-8/I front panel hardware @@ -18,58 +18,80 @@ - Host and major contributor to the PiDP-8/I support forum on Google Groups - Hardware kit assembler and distributor -* **Robert M Supnik** Primary author of the SIMH PDP-8 simulator upon - which this project is based. +* **Robert M Supnik** is the primary author of the SIMH PDP-8 + simulator upon which this project is based. -* **Mike Barnes** Ported Oscar Vermeulen's SIMH 3.9 based PiDP-8/I +* **Mike Barnes** ported Oscar Vermeulen's SIMH 3.9 based PiDP-8/I simulator to the new SIMH 4.0 code base. (September 2015.) -* **Dylan McNamee** Ported the software to Buildroot for the official - 2015.12.15 binary OS images, and helped to merge the James L-W +* **Dylan McNamee** ported the software to Buildroot for the official + 2015.12.15 binary OS images and helped to merge the James L-W "alt-serial" mode in. -* **Mark G. Thomas** Creator of the installation scripts for the - 2015.12.15 release, which were folded into the `make install` - handler within `Makefile.in`. Also wrote the version of the SysV - init script that came with that release as `rc.pidp8`, shipped here - as `pidp8i-init`. - -* **Ian Schofield ** Modified the LED lamp driving - code in the simulator to better simulate the incandescent lamps in - the original PDP-8/I hardware. (The bulk of his original code has - since been rewritten, but the core idea remains, and it is doubtful - whether the current method would exist without his instigation.) - -* **Henk Gooijen ** Pushed the PDP-8 - simulator's internal EAE step counter value down into the PiDP-8/I's - LED manipulation code, without which the step counter LEDs remain - dark even when using the EAE. - -* **Paul R. Bernard ** wrote `src/test.c` and the - core of what now appears as `doc/pidp8i-test.md`. (The program builds - and installs as `pidp8i-test`.) He also provided a one-line fix - that completes the work of Henk Gooijen's step counter patch. - -* **Rick Murphy ** optimized the `pep001.pal` - example so that it fits into a single page of PDP-8 core, and - provided several useful files in his OS/8 disk images that have - managed to land in this software distribution's OS/8 disk image. - -* **Tony Hill ** Merged all the upstream SIMH - changes produced between late September 2015 and late December 2016 - into the PiDP-8/I simulator. (Since then, this merge process is - automated as `tools/simh-update`.) - -* **Bill Cattey ** (a.k.a. poetnerd) did the - bulk of the work on automatic generation of OS/8 system pack images - from original source media, replacing the hand-assembled and -hacked - `os8.rk05` image. - -* **Jonathan Trites ** wrote the original - version of what has become `tools/mkos8s.in`, the script that - generates `bin/os8v3d-*.rk05` from source tapes. - -* **Warren Young ** Did everything listed in - `ChangeLog.md` that is not attributed to anyone else. +* **Mark G. Thomas** wrote the installation scripts for the 2015.12.15 + release, which were folded into the `make install` handler within + the current `Makefile.in`. He also wrote the version of the SysV + init script shipped here as `etc/pidp8i-init.in`. + +* **[Ian Schofield](mailto:isysxp@gmail.com)** modified the LED lamp + driving code in the simulator to better simulate the incandescent lamps + in the original PDP-8/I hardware. (The bulk of his original code + has since been rewritten, but the core idea remains, and it is + doubtful whether the current method would exist without his + instigation.) + +* **[Henk Gooijen](mailto:henk.gooijen@boschrexroth.nl)** pushed the + PDP-8 simulator's internal EAE step counter value down into the + PiDP-8/I's LED manipulation code, without which the step counter + LEDs remain dark even when using the EAE. + +* **[Paul R. Bernard](mailto:prb@downspout.ca)** wrote `src/test.c` + and the core of what now appears as `doc/pidp8i-test.md`. (The program + builds and installs as `pidp8i-test`.) He also provided a one-line + fix that completes the work of Henk Gooijen's step counter patch. + +* **[Rick Murphy](mailto:k1mu.nospam@gmail.com)** is the current + maintainer of [OS/8 Adventure][advent] which we've included in our + OS/8 disk image. He's also provided several other files which have + landed in the distribution such as the [VTEDIT][vtedit] feature. He + also optimized the `pep001.pal` example so that it fits into a + single page of PDP-8 core. + +* **[Tony Hill](mailto:hill.anthony@gmail.com)** merged all the + upstream SIMH changes produced between late September 2015 and late + December 2016 into the PiDP-8/I simulator. This is the basis for the + current automatic upstream feature merge capability, which is why + many releases since December 2016 include an update to the latest + version of upstream SIMH. His contributions are made to the project + [as `tony`][thcomm]. + +* **[Jonathan Trites](mailto:tritesnikov@gmail.com)** wrote the + initial version of the script now called `libexec/mkos8`, which + builds the OS/8 disk images from source tapes. + +* **[Bill Cattey](mailto:bill.cattey@gmail.com)** is the project lead + and primary developer of the system that builds the OS/8 RK05 disk + images from source tapes. He greatly extended the `mkos8` script, + curated the tape collection we ship as `media/.../*.tu56`, created + some of those tapes, and more. He has also contributed to other + areas of the software project. His contributions are made to the + project [as `poetnerd`][pncomm]. + +* **[Warren Young](mailto:tangentsoft@gmail.com)** Did everything + listed in [the change log][cl] that is not attributed to anyone + else. + + His contributions are made to the project [as `tangent`][wycomm], + though keep in mind that some of those are commits of external + contributions made by people who do not have commit rights on our + software repository. The changelog provides proper attribution for + these where the checkin comments do not. + +[advent]: http://www.rickmurphy.net/advent/ +[cl]: https://tangentsoft.com/pidp8i/doc/trunk/ChangeLog.md +[pncomm]: https://tangentsoft.com/pidp8i/timeline?u=poetnerd +[thcomm]: https://tangentsoft.com/pidp8i/timeline?u=tony +[vtedit]: https://tangentsoft.com/pidp8i/wiki?name=Using+VTEDIT +[wycomm]: https://tangentsoft.com/pidp8i/timeline?u=tangent Index: COPYING.md ================================================================== --- COPYING.md +++ COPYING.md @@ -3,11 +3,11 @@ The PiDP-8/I software distribution is an agglomeration of software from multiple sources. Several different licenses apply to its parts. This file guides you to those individual licenses. -## SIMH License +## SIMH License Most of the files in this software distribution are released under the terms of the SIMH license, a copy of which typically appears at the top of each file it applies to. This includes not only SIMH proper but also several files written by PiDP-8/I software project contributors who @@ -18,11 +18,11 @@ included with the distribution][sl]. [sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md -## PiDP-8/I Design Files +## PiDP-8/I Design Files The PiDP-8/I design files in [`hardware/pidp8i`][hwp] were released under the Creative Commons [Attribution-NonCommercial-ShareAlike 4.0 International][ccl] license [on the mailing list][pdp8il] by their author, Oscar Vermeulen. @@ -30,36 +30,76 @@ [ccl]: https://creativecommons.org/licenses/by-nc-sa/4.0/ [hwp]: https://tangentsoft.com/pidp8i/dir?name=hardware/pdp8i&ci=trunk [pdp8il]: https://groups.google.com/d/msg/pidp-8/bcIH9uEB_kU/zg9uho7NDAAJ -## autosetup License +## autosetup License The `configure` script and the contents of the `autosetup` directory are released under the FreeBSD license given in [`autosetup/LICENSE`][as]. [as]: https://tangentsoft.com/pidp8i/doc/trunk/autosetup/LICENSE -## palbart License +## palbart License The `palbart` program and its manual page are released under the terms -of the license given in [`palbart/LICENSE.md`][pl]. +of the license given in [`src/palbart/LICENSE.md`][pl]. + +[pl]: https://tangentsoft.com/pidp8i/doc/trunk/src/palbart/LICENSE.md + + +## d8tape License + +The `d8tape` program is distributed under the license given in +[`src/d8tape/LICENSE.md`][d8tl]. + +[d8tl]: https://tangentsoft.com/pidp8i/doc/trunk/src/d8tape/LICENSE.md + + +## CC8 Compiler License + +The license for the `src/cc8` subtree is messy as it comes to us from +multiple authors over many years. + +There are two compilers here. + +First we have the OS/8 "native" compiler in `src/cc8/os8`, which is +entirely Ian Schofield's work, released under the terms of the [GNU +General Public License version 3][gpl3]. + +Then we have the CC8 cross-compiler which is based on Ron Cain's +[Small-C][smc], originally published in [Dr. Dobbs' Journal][ddj]. +Wikipedia describes Small-C as "copyrighted but sharable," which I take +to mean that we cannot claim it as our exclusive property, but we can +modify it and distribute those modifications to others, which is what +we're doing here. + +Ian Schofield then took the Small-C source base and added a SABR +back-end, `code8.c`, which is also distributed under the [GPLv3][gpl3]. + +There is [another PDP-8 C compiler project][smsc] based on Small-C by +Vincent Slyngstad, which uses an entirely different approach for code +generation. Ian Schofield took some of the library routines from this +implementation. -[pl]: https://tangentsoft.com/pidp8i/doc/trunk/palbart/LICENSE.md +[ddj]: https://en.wikipedia.org/wiki/Dr._Dobb%27s_Journal +[gpl3]: https://tangentsoft.com/pidp8i/doc/trunk/src/cc8/LICENSE.txt +[smc]: https://en.wikipedia.org/wiki/Small-C +[smsc]: http://so-much-stuff.com/pdp8/C/C.php -## OS/8 License +## OS/8 License The OS/8 media images included with this software distribution are released under the Digital License Agreement presented in [`media/os8/LICENSE.md`][dla]. [dla]: https://tangentsoft.com/pidp8i/doc/trunk/media/os8/LICENSE.md -## Other DEC Software +## Other DEC Software The other files in the [`media`][md] and [`examples`][ed] directories that originate from Digital Equipment Corporation are believed to fall under the [public domain license][pdp8pd] DEC released all their PDP-8 software under after it stopped being ecomonmically viable. Documented @@ -66,15 +106,16 @@ releases for specific software (e.g. TSS/8) may be difficult to come by, however. [md]: https://tangentsoft.com/pidp8i/dir?ci=trunk&name=media [ed]: https://tangentsoft.com/pidp8i/dir?ci=trunk&name=examples +[pdp8pd]: http://mailman.trailing-edge.com/pipermail/simh/2017-January/016164.html -## ETOS License +## ETOS License ETOS was a commercial product produced outside of DEC. No public documented declaration of license is known to be available for it, but we have [a third-hand report][el] that its creators are fine with ETOS being redistributed. [el]: http://mailman.trailing-edge.com/pipermail/simh/2017-January/016169.html Index: ChangeLog.md ================================================================== --- ChangeLog.md +++ ChangeLog.md @@ -1,6 +1,370 @@ # PiDP-8/I Changes + +## Version 2017.12.eh? — The "Languages and Custom OS/8 Disk Packs" release + +* All prior versions were shipping `os8.rk05`, a "Field Service + Diagnostic" OS/8 disk pack image with uncertain provenance, + configuration, and modification history. We have replaced that with + a script run at build time that programmatically assembles a set of + clean OS/8 RK05 disk images from curated, pristine, tested sources + based on the user's chosen configuration options. + + This provides the following features and benefits as compared to the + old `os8.rk05`: + + - The PiDP-8/I software build process now builds up to three RK05 + disk images: + + - `os8v3d-bin.rk05` is a bootable OS/8 V3D disk configured + according to your chosen configuration options, which are + described below and in [`README.md`][tlrm]. It is made from + the pristine DECtape images shipped by DEC for OS/8 V3D plus + several third-party tapes curated for and built by the + project's maintainers. See [the OS/8 media `README.md` + file][os8rm] for more details. + + - `os8v3d-patched.rk05` is a copy of the `bin` disk with + [most][os8p] of the patches DEC published over the years for + OS/8 V3D applied. That set of patches was chosen and tested + for applicability to our modern PiDP-8/I world and for + mutual compatibility. + + This is the boot disk used for the IF=0 and IF=7 cases + unless you give `--disable-os8-patches` to the `configure` + script, in which case these boot options use the `bin` disk. + + - `os8v3d-src.rk05` is a non-bootable disk containing the + contents of the OS/8 V3D source code tapes plus the source + code for the extensions to OS/8 V3D. The *ten* TU56 tape + images used as input to this process are also included among + the PiDP-8/I software — see `media/os8/al-*-sa-*.tu56` — but + we find it much more convenient to explore the sources on a + single RK05 disk than to repeatedly attach and detach the + TU56 tapes. + + You can suppress building this with `--disable-os8-src`. + + Default versions of these disk images are also now published on + the project's home page for the benefit of those not running our + PiDP-8/I software. There are quite a few OS/8 RK05 disk images + floating around on the Internet these days, and many of them + have bugs and breakage in them that we've fixed. It would + completely fail to break our hearts if these images were used by + many people outside the PiDP-8/I project. + + - U/W FOCAL V4E is installed on SYS: by default. Start with our + [U/W FOCAL Manual Supplement for the PiDP-8/I][uwfs], then + follow links from there to further information. + + The primary linked source is the [U/W FOCAL Manual][uwfm] by Jim + van Zee (creator of U/W FOCAL) converted from scanned and OCR'd + PDF form to Markdown format, which Fossil renders nicely for us + on the web. + + This is a fascinating programming language, well worth studying! + + - Ian Schofield's CC8 OS/8 C compiler is installed on `SYS:` by + default, and its examples and other files are on `DSK:`. We + have also merged in his `cc8` host-side cross-compiler. See + [the CC8 `README`][cc8rm] for details. + + This is a considerably improved compiler relative to what + was distributed on the mailing list in August 2017. Ian has + been working within the PiDP-8/I project since that initial + public release, which we are now distributing publicly for + the first time. We thank him for trusting us to host and + distribute his project. + + - The MACREL v2 macro assembler and its associated FUTIL V8B tool + are installed by default. Not only is this new functionality + relative to prior releases of the PiDP-8/I software, it is a + considerable upgrade over to the original MACREL and FUTIL V7 + that are more commonly found on the net. + + - DCP disassembler is installed by default. + + - John Comeau's CHECKMO-II chess program is installed by default. + + - By default, SIMH no longer folds lowercase input and output to + uppercase. Instead, we apply patches to OS/8's command + processor and its BASIC implementation to up-case input, since + neither OS/8 nor BASIC can cope with lowercase input. + + All other programs are left to fend for themselves, which + often works out fine. U/W FOCAL, Adventure, and TECO all handle + lowercase input to some extent, for example, and all three can + emit lowercase text if given it. With the prior SIMH setting, + you could not use lowercase in these programs at all. + + This default can be overridden. See the documentation for the + new `--lowercase` configuration option in `README.md`. + + - The `INIT.TX` message displayed by default on OS/8 boot is now + more informative than the old `FIELD SERVICE PDP-8 DIAGNOSTIC + SYSTEM` message. It also now uses lowercase unless you built + the simulator to force uppercase with `--lowercase=upper`. + + Those that do not want any boot message can disable it at + configuration time with the `--disable-os8-init` flag. + + The message can be modified by editing `media/os8/init.tx.in` + and saying `make`, which will rebuild the OS/8 media. + + - All of the above features can be disabled if not wanted, as can + several features present on the old `os8.rk05` disk: Adventure, + FORTRAN IV, FORTRAN II, Kermit-12, and the BASIC game and demo + programs. + + You can disable each feature above with a `--disable-os8-*` + option to the `configure` script, or you can disable all of them + collectively with the `--os8-minimal` option, which gets you a + nearly bare-bones OS/8 installation with lots of spare disk + space with which you can do what *you* want. + + - Replaced the mismatched FORTRAN compiler and runtime with + matched versions from the distribution DECtapes, and ensured that + Adventure runs under this version of the FORTRAN Run Time System + (FRTS). At various points in the past, either FORTRAN or + Adventure has been broken. + + - Repaired several broken BASIC programs on `RKB0:` by going back + to primary sources. Either the `os8.rk05` disk image was corrupted + at some point or it is an image of a real RK05 disk pack that + was corrupted, causing several of these BASIC programs to not + run properly. + + - The `*.MU` and music player files are left off of `RKB0:` by + default, since they apparently do not cause sufficient RFI on + the PiDP-8/I hardware to be picked up by normal AM radios. This + saves space for things that *do* work. + + - No longer installing the `VTEDIT` macros for TECO by default. + Although some may enjoy this alternative way of running TECO, it + was decided that we should offer stock TECO by default for its + historical value. If you want VTEDIT back, it can be re-enabled + with a `configure` script option. + + - In the old `os8.rk05` disk image, both `SYS:` and `DSK:` were + set to `RKA0:`, which meant that to address anything on `RKB0:`, you + had to specify the device path explicitly or manually change the + default in OS/8 with an `ASSIGN RKB0 DSK` command or similar. + + In the new disk pack, programs run with the OS/8 `R` command are + installed on `RKA0:` which is the `SYS:` disk, and user data + files, FOCAL and BASIC programs, etc. are on `RKB0:` which is + assigned as `DSK:`. This means OS/8 and its programs are now far + more likely to find files without an explicit device name, + because files are installed where OS/8 looks for them by + default. Example: + + .R FRTS ⇠ loads FRTS from SYS: (RKA0:) + *ADVENT ⇠ loads ADVENT.LD from DSK: (RKB0:) + *[Esc] + Welcome to Adventure!! + + Notice that no device names had to be specified. OS/8 did the + right thing by default here, even though the files involved are + on two separate OS/8 devices. + + To a very rough approximation, `SYS:` on these new RK05 disk + packs acts like the Unix `PATH` and `DSK:` acts like your user's + home directory. + + The idea for this came from Ian Schofield's `cc8.rk05` disk + image, which we are also shipping in this release. + + - OS/8 has a limit on the number of devices it can support, and we + made different choices than the creator of `os8.rk05`. + + Briefly, we replaced the second floppy (`RXA1:`) with a third + RK05 disk, that being deemed a more useful configuration for + this hard disk based OS/8 configuration. A dual-floppy + configuration implies that you are booting from floppy and need + the second one for user files and such. In our RK05 based + configuration, users should need floppy disk support rarely, and + then primarily to get data on and off of the attached hard + disk(s). + + We chose to stick with the dual TU56 tape drive setup of the + prior version as we found the ability to mount two tapes very + helpful, particularly during the `mkos8` build process. + + The difference in the `RESORC` output between the versions is: + + Old: RKA0,RKB0,RKA1,RKB1, RXA0,RXA1,DTA0,DTA1,TTY,LPT,PTP,PTR + New: RKA0,RKB0,RKA1,RKB1,RKA2,RKB2,RXA0, DTA0,DTA1,TTY,LPT,PTP,PTR + + This automatic OS/8 media build feature was suggested by Jonathan + Trites who wrote the initial version of the script that is now + called `libexec/mkos8`. That script was then extended and factored + into its current form by Bill Cattey and Warren Young. + + Warren thinks Bill did most of the hard work in the items above. + + The source media used by the `mkos8` script comes from many sources + and was curated for the PiDP-8/I project by Bill Cattey. See the + [OS/8 media README][os8rm] for more details. + + See the [the top-level `README`][tlrm] for information on modifying + the default OS/8 configuration. Pretty much everything above can be + disabled if it's enabled by default, and vice versa. + +* Added several new wiki articles covering the above: + + * More Project Euler Problem #1 solutions in: + + * [C][pe1c] + * [FORTRAN IV][pe1f4] + * [FORTRAN II][pe1f2] + * [U/W FOCAL][pe1u] + + * [Demos in BASIC][dibas], deescribing `DSK:*.BA` + + * [OS/8 Console TTY Setup][os8ct], describing how we have + modified the stock behavior of OS/8 to behave appropriately + with a glass terminal or SSH on its console, as opposed to + its default behavior, which assumes a teletype. + + * [OS/8 LCSYS.BI Disassembled][os8lc], a symbolic + disassembly of the `LCSYS.BI` patch we distribute with the + system, which is widely available online elsewhere. That script + is a raw binary patch, which makes its operation a mystery + unless you happen to be able to read PDP-8 machine code. + +* Added Bill Cattey's `txt2ptp` program which converts plain ASCII + text files files to the paper tape format used by SIMH, which eases + transfer of text data into the simulator. That program is also + linked to `ptp2txt`, which makes it perform the inverse function: + given a SIMH paper tape image file, produce an ASCII text file on + the host machine with its contents. + + This program was written to ease the movement of FOCAL program text + between SIMH and its host OS, but they should prove useful for other + similar tasks. + +* Integrated Robert Krten's `d8tape` PDP-8 host-side disassembler. + This is distinct from the OS/8 DCP disassembler, which runs inside + the simulator. It is intended as a companion to `palbart`, which we + integrated last year. + +* Added a new "blinkenlights" demo program called `bin/teco-pi-demo` + which drives SIMH from the outside, running a TECO macro under OS/8 + to calculate *pi* to 248 digits at a very slow rate, producing a + display that manages to land somewhere between the random default + display of [Deeper Thought][dt2vk] and the clear, boring patterns of + our preexisting IF=5 demo script. + + Why 248 digits? Because at 249, TECO8 runs out of memory, aborting + the demo early. At the default execution rate, it would take over + 17 years to complete this demo, making it a good choice to run on + PiDP-8/I units primarily being used as *objets d'art*. The demo has + a finite run time, but your Raspberry Pi is likely to die before it + completes. `;)` + + This script is also included as a demonstration of how the end user + can reuse the technology that we developed to automatically build + the custom OS/8 disk images described above to achieve different + ends. Perhaps you have some other program you'd like to run within + SIMH in an automated fashion? This shows one way how, and + demonstrates a pre-built and tested set of tools for achieving it. + + We have also written [a tutorial][csd] explaining how + `bin/teco-pi-demo` works and how to reuse the components it is built + atop for your own ends. + + This demo also has a benchmark mode (command line option `-b`) which + has two purposes: + + 1. It lets you see how much faster your host system runs PDP-8 code + than a Raspberry Pi Model B+ running the PiDP-8/I simulator. + + 2. Given that information, the benchmark overrides a hardcoded + timing value in the source code as distributed which prevents + programs like `teco-pi-demo` from spamming the OS/8 terminal + input handler. The default is for the slowest host we support + this software on, that same Model B+ referred to above, but if + we know you're running on a faster host, we can shorten this + delay and remain reliable. + + If you run the demo in benchmark mode twice, you'll notice that the + TECO script is input nearly instantaneously the second time, whereas + you can see the demo "type" the script in very quickly the first + time. (Remove `lib/pidp8i/ips.py`, say `make reconfig` and run the + demo again to see the difference.) + +* The `DF` + `SING_STEP` feature for automatically attaching binary + media images to the simulator from files on USB sticks now looks + at all directories under `/media`, not just `usb0` through `usb7` + so that it works with several other common Linux USB automounting + schemes, such as the one Raspbian Desktop uses. + +* Fixed the order of initialization in the GPIO setup code for the + James L-W serial mod case. Fix by Dylan McNamee. + +* The helper program that selects which boot script to run when the + PiDP-8/I boots based on the IF switch settings broke at some point + in the past, apparently because it was using its own idiosyncratic + GPIO handling code, and thus did not track our evolving GPIO + handling methods. Now it shares the same code used by `pidp8i-sim` + and `pidp8i-test`, so it works properly again. + +* The SysV init script that starts `pidp8i-sim` under GNU Screen on + the PiDP-8/I now sets the working directory to `$prefix/share/media` + on start, so relative paths given to SIMH commands (e.g. `ATTACH`) + are more likely to do what you want. In prior releases, you + generally had to give absolute paths to attach media and such + because CWD would be set somewhere unhelpful. + +* The Fetch LED is no longer lit when in STOP or single-step mode. In + real hardware, it can be either on or off in this mode, depending + on various conditions, but it is most often off, so while it is not + perfectly correct now, it is less wrong. Most of the investigation + into this issue is by Bill Cattey, with the current partial fix by + me. A more precise fix may come later. (See ticket [347ae45403] if + you care to know the details.) + +* The Pause LED state was over-counted in the LED sub-sampling scheme + so that it would tend to be brighter than it should have been. + Problem noticed by Ian Schofield. + +* The MB row's state was not showing the right thing. The problem was + noticed in comparison to real PDP-8/I hardware by Vincent Slyngstad + and verified by William Cattey. Ian Schofield suggested the current + fix. + +* Updated SIMH to upstream checkin ID 27f9fc3c3, December 11, 2017. + There have been no substantial changes to the PDP-8 simulator since + the last update, 8 months ago, but there have been a lot of bug + fixes to the SCP core program. + +* Updated for Raspbian Stretch, released in September 2017. It should + still run on Raspbian Jessie, however. + +* Assorted portability, build system, and documentation improvements. + +* **TODO** + + - Create udev script replacement for usbmount so that `DF` + + `SING_STEP` works on Stretch. + +[apt]: https://linux.die.net/man/8/apt +[cc8rm]: https://tangentsoft.com/pidp8i/doc/trunk/src/cc8/README.md +[csd]: https://tangentsoft.com/pidp8i/doc/trunk/doc/class-simh.md +[dibas]: https://tangentsoft.com/pidp8i/wiki?name=Demos+in+BASIC +[dt2vk]: https://github.com/VentureKing/Deeper-Thought-2 +[os8ct]: https://tangentsoft.com/pidp8i/wiki?name=OS/8+Console+TTY+Setup +[os8lc]: https://tangentsoft.com/pidp8i/wiki?name=OS/8+LCSYS.BI+Disassembled +[os8p]: https://tangentsoft.com/pidp8i/doc/trunk/doc/os8-patching.md +[os8rm]: https://tangentsoft.com/pidp8i/doc/trunk/media/os8/README.md +[pe1c]: https://tangentsoft.com/pidp8i/wiki?name=PEP001.C +[pe1f2]: https://tangentsoft.com/pidp8i/wiki?name=PEP001.FT#fortran-ii +[pe1f4]: https://tangentsoft.com/pidp8i/wiki?name=PEP001.FT#fortran-iv +[pe1u]: https://tangentsoft.com/pidp8i/wiki?name=PEP001.FC +[uwfm]: https://tangentsoft.com/pidp8i/doc/trunk/doc/uwfocal-manual.md +[uwfs]: https://tangentsoft.com/pidp8i/doc/trunk/doc/uwfocal-manual-supp.md + ## Version 2017.04.04 * Removed the PDP-8 CPU idle detection feature. Oscar Vermeulen reports that it also interfered with NLS LED driving mode in his @@ -21,11 +385,11 @@ than in earlier releases, it's reliable in the face of varying background CPU usage. See the single-core section of `README-throttle.md` for details. -## Version 2017.04.01 The "I May Be a Fool, but I am *Your* Fool" Release +## Version 2017.04.01 — The "I May Be a Fool, but I am *Your* Fool" release * Added the `configure --alt-serial-mod` option to change the GPIO code to work with [James L-W's alternative serial mod][sm2]. * Increased the stock CPU throttle from 0.67 MIPS to 0.85 MIPS on most @@ -531,11 +895,11 @@ SIMH project, you can give them a version number that will be meaningful to them. (They don't care about our vYYYYMMDD release numbers or our Fossil checkin IDs.) -## Version 2016.12.26 (The Boxing Day release) +## Version 2016.12.26 — The Boxing Day release * Tony Hill updated SIMH to the latest upstream version. This change represents about 15 months worth of work in the [upstream project][simh] — plus a fair bit of work by Tony to merge @@ -737,11 +1101,11 @@ ## Version 2016.11.28 * Added an intelligent, powerful build system, replacing the bare-bones `Makefile` based build system in the upstream version. - See [`README.md`][readme] for more info on this. + See [`README.md`][tlrm] for more info on this. * The installation is now completely relocatable via `./configure --prefix=/some/other/place`. The upstream version would not work if installed somewhere other than `/opt/pidp8` due to many hard-coded absolute paths. (This is enabled by the new build system, but @@ -808,11 +1172,11 @@ currently misbehaves if the PiDP-8/I panel is not present. Fixing this is on the radar.) * Fixed a bunch of bugs! -[readme]: https://tangentsoft.com/pidp8i/doc/trunk/README.md +[tlrm]: https://tangentsoft.com/pidp8i/doc/trunk/README.md [dupatch]: https://groups.google.com/forum/#!topic/pidp-8/fmjt7AD1gIA [dudis]: https://tangentsoft.com/pidp8i/tktview?name=e06f8ae936 [wiki]: https://tangentsoft.com/pidp8i/wcontent [ex]: https://tangentsoft.com/pidp8i/doc/trunk/examples/README.md [art]: https://tangentsoft.com/pidp8i/dir?c=trunk&name=labels Index: HACKERS.md ================================================================== --- HACKERS.md +++ HACKERS.md @@ -25,57 +25,51 @@ If you started with one of our PiDP-8/I binary OS images made in or after April 2017, Fossil 2.x is already installed. If you're starting from some other OS, you either won't have Fossil -installed at all, or you'll most likley be using an older version, since -the Debian project is still shipping version 1.37 and likely will -continue to do so until 2020 or so. You'll have to build Fossil from -source: - - $ sudo apt install libssl-dev - $ wget -O fossil-release.tar.gz \ - https://fossil-scm.org/index.html/tarball/fossil-release?uuid=release - $ tar xvf fossil-release.tar.gz - $ cd fossil-release - $ ./configure - $ make - $ sudo make install +installed at all, or you'll most likley be using an older version, +since the Debian project is still shipping version 1.37 and likely +will continue to do so until 2020 or so. You could build Fossil from +source, or you could just go grab a prebuilt binary we keep on the +project site: + + $ wget https://tangentsoft.com/pidp8i/uv/fossil-raspbian-9.1-stretch + $ sudo install -m 755 fossil-* /usr/local/bin/fossil Fossil is also available for all common desktop platforms. One of [the official binaries][fbin] may work on your system. If you're getting binaries from a third party, be sure it is Fossil 2.1 or higher. -[fbin]: http://fossil-scm.org/index.html/uv/download.html -[dvcs]: http://en.wikipedia.org/wiki/Distributed_revision_control -[fbook]: http://www.fossil-scm.org/schimpf-book/home -[fml]: http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/fossil-users -[fossil]: http://fossil-scm.org/ -[fqsg]: http://fossil-scm.org/index.html/doc/trunk/www/quickstart.wiki +[fbin]: https://fossil-scm.org/index.html/uv/download.html +[dvcs]: https://en.wikipedia.org/wiki/Distributed_revision_control +[fbook]: https://www.fossil-scm.org/schimpf-book/home +[fml]: https://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/fossil-users +[fossil]: https://fossil-scm.org/ +[fqsg]: https://fossil-scm.org/index.html/doc/trunk/www/quickstart.wiki [ggml]: https://groups.google.com/forum/#!forum/pidp-8 Fossil Anonymous Access ---- To clone the code repository anonymously, say: $ mkdir -p ~/museum ~/src/pidp8i/trunk - $ sudo apt install fossil $ fossil clone https://tangentsoft.com/pidp8i ~/museum/pidp8i.fossil $ cd ~/src/pidp8i/trunk $ fossil open ~/museum/pidp8i.fossil The `clone` command gets you a file called `pidp8i.fossil` containing -the full history of PiDP-8/I from the upstream 2015.12.15 release -onward. You can call that clone file anything you like and put it in -any directory you like. Even the `.fossil` extension is largely a -convention, not a requirement. +the full history of the PiDP-8/I software project from the upstream +2015.12.15 release onward. You can call that clone file anything you +like and put it in any directory you like. Even the `.fossil` extension +is largely a convention, not a requirement. -Working With Existing Tags and Branches +Working with Existing Tags and Branches ---- The directory structure shown in the commands above is more complicated than strictly necessary, but it has a number of nice properties. @@ -111,14 +105,14 @@ This directory scheme shows an important difference between Fossil and Git: with Git, the checkout and the clone are intermingled in the same directory tree, but in Fossil, they are strictly separate. Git can emulate Fossil's normal working style through its [worktree][gitwt] -feature, but it's a kind of lash-up using symlinks and such, whereas -with Fossil, there is no confusion: the repository clone is a single -SQLite database file — here, `pidp8i.fossil` — and the checkouts are -made from the contents of that database. +feature, but it employs some trickery that causes some unwanted side +effects that don't affect Fossil by design: the repository clone is a +single SQLite database file — here, `pidp8i.fossil` — and the checkouts +are made from the contents of that database. Another important difference relative to Git is that with Fossil, local checkins attempt to automatically sync checked-in changes back to the repository you cloned from. (This only works if you have a login on the remote repository, the subject of the next section.) This solves a @@ -149,21 +143,28 @@ ---- If you have a developer account on tangentsoft.com's Fossil instance, just add your username to the URL like so: - $ fossil clone http://username@tangentsoft.com/pidp8i pidp8i.fossil - -Fossil will ask you for the password for `username` on the remote Fossil -instance, and it will offer to remember it for you. If you let it -remember the password, operation from then on is scarcely different from -working with an anonymous clone, except that on checkin, your changes -will be sync'd back to the repository on tangentsoft.com if you're -online at the time. - -If you're working offline, Fossil will still do the checkin, but you'll -be able to sync with the central repoisitory once you get back online. + $ fossil clone https://username@tangentsoft.com/pidp8i pidp8i.fossil + +If you've already cloned anonymously, you don't have to clone again to +inform Fossil about your developer account. Just do a manual sync, +changing the URL to include the user name: + + $ fossil sync https://username@tangentsoft.com/pidp8i + +Either way, Fossil will ask you for the password for `username` on the +remote Fossil instance, and it will offer to remember it for you. If +you let it remember the password, operation from then on is scarcely +different from working with an anonymous clone, except that on checkin, +your changes will be sync'd back to the repository on tangentsoft.com if +you're online at the time, and you'll get credit under your developer +account name for the checkin. + +If you're working offline, Fossil will still do the checkin locally, and +it will sync up with the central repoisitory after you get back online. It is best to work on a branch when unable to use Fossil's autosync feature, as you are less likely to have a sync conflict when attempting to send a new branch to the central server than in attempting to merge your changes to the tip of trunk into the current upstream trunk, which may well have changed since you went offline. @@ -174,11 +175,11 @@ Until you re-enable it (`autosync 1`) Fossil will stop trying to sync your local changes back to the central repo. In this mode, Fossil works more like Git's default mode, buying you many of the same problems that go along with that working style. I recommend disabling autosync mode -only when you are truly going to be offline, and don't want Fossil +only when you are truly going to be offline and don't want Fossil attempting to sync when you know it will fail. Getting Developer Access ---- @@ -260,20 +261,22 @@ The [autosetup build system][asbs] is composed of these files and directories: auto.def - autosetup/ + autosetup/* configure Makefile.in Unlike with GNU Autoconf, which you may be familiar with, the `configure` script is not output from some other tool. It is just a -driver for the Tcl and C code under the `autosetup` directory. If you -have to modify any of these files to get some needed effect, you should -try to get that change into the upstream project, then merge that change -down into the local copy when it lands upstream. +driver for the Tcl and C code under the `autosetup` directory. + +If you have to modify any of the files in `autosetup/` to get some +needed effect, you should try to get that change into the upstream +[Autosetup][asbs] project, then merge that change down into the local +copy when it lands upstream. The bulk of the customization to the build system is in `auto.def`, which is a Tcl script run by `autosetup` via the `configure` script. Some knowledge of [Tcl syntax][tcldoc] will therefore be helpful in modifying it. @@ -286,18 +289,17 @@ work with the `jimsh0` interpreter should also work with "real" Tcl, but not vice versa. If you have Tcl installed and don't really need it, consider uninstalling it to force `autosetup` to build and use `jimsh0`. The `Makefile.in` file is largely a standard [GNU `make`][gmake] file -excepting only that it has variables substituted into it by -[`autosetup`][asbs] using its `@VARIABLE@` syntax. At this time, we do -not attempt to achieve compatibility with other `make` programs, though -in the future we may need it to work with [BSD `make`][bmake] as well, -so if you are adding features, you might want to stick to the common -subset of features implemented by both the GNU and BSD flavors of -`make`. We do not anticpate any need to support any other `make` -flavors. +excepting only that it has variables substituted into it by `autosetup` +using its `@VARIABLE@` syntax. At this time, we do not attempt to +achieve compatibility with other `make` programs, though in the future +we may need it to work with [BSD `make`][bmake] as well, so if you are +adding features, you might want to stick to the common subset of +features implemented by both the GNU and BSD flavors of `make`. We do +not anticpate any need to support any other `make` flavors. (This, by the way, is why we're not using some heavy-weight build system such as the GNU Autotools, CMake, etc. The primary advantage of GNU Autotools is that you can generate source packages that will configure and build on weird and ancient flavors of Unix; we don't need that. @@ -314,10 +316,156 @@ [gmake]: https://www.gnu.org/software/make/ [jim]: http://jim.tcl.tk/ [tcldoc]: http://wiki.tcl.tk/11485 + +Directory Structure +---- + +The directory structure of the PiDP-8/I project is as follows: + +* `.` - Top level, occupied only by the few files the end user + of the source code needs immediately at hand on first unpacking the + project: the top level build system files, the top-level + `README*.md` files, and licensing information. If a given file *can* + be buried deeper, it *should* be buried to reduce clutter at this + most precious level of the hierarchy. + +* `.fossil-settings` - Versioned settings for the Fossil build + system. Say `fossil help set` at the command line for more on this. + Such settings are intended to be correct for all users of the + system; rather than expressing defaults, they express *policy*. + + Any setting whose value may vary between users of the Fossil + repository should be done locally with a `fossil set` command. + +* `autosetup` - The bulk of the [Autosetup build system][asbs]. + These are generic files, not modified by the project itself. We + occasionally run `tools/autosetup-update` to merge in upstream + changes. + +* `bin` - Programs run both in development and after + installation. Some files here are created directly by the project's + developers, while others are outputs of the build system. The + content of this directory is copied to `$prefix/bin` at installation + time, which is added to the user's `PATH` by the installer. + +* `boot` - SIMH initialization scripts. The `*.script.in` files + are written by the project developers but have build-time values + substituted in by the `configure` script to produce a `*.script` + version. Some of the remaining `*.script` files are hand-written and + as such are checked into Fossil directly. The remainder are outputs + of `tools/mkbootscript`, which produces them from `palbart` assembly + listings. + + All of these `*.script` files are installed to `$prefix/share/boot` + regardless of their origin. + +* `doc` - Documentation files not immediately important enough + to a new user of the software that they do not have to be at the top + level of the project tree. + + Fossil allows us to treat the contents of `doc` much like the wiki, + so how do we decide whether to put a given document into `doc` or + the wiki? The rule is simple: is the document's history tied to the + history of the PiDP-8/I project itself? If so, it goes in `doc`, + else it goes in the wiki. When checking out older versions of the + PiDP-8/I software, you expect to roll back to contemporaneous + versions of the project documentation; such files go into `doc`. + Documents which are independent of the PiDP-8/I project history go + into the wiki. + + (The wiki does also have history, but rolling back to a prior + version of the PiDP-8/I repository and then saying `fossil ui` will + show you the current version of the wiki documents, not the versions + as they existed at the time of the historical checkin you rolled + back to.) + + The `doc/graphics` subdirectory holds JPEGs and SVGs displayed + inline within wiki articles. + +* `etc` - Files which get copied to `/etc` or one of its + subdirectories at installation time. + +* `examples` - Example programs for the end user's edification. + Many of these are referenced by documentation files. + +* `hardware` - Schematics and such for the PiDP-8/I board or + associated hardware. + +* `labels` - Graphics intended to be printed out and used as + labels for removable media. + +* `lib` - Library routines used by other programs. + +* `libexec` - A logical extension of `lib`, these are + standalone programs that nevertheless are intended to be run + primarily by other programs. Whereas a file in `lib` might have its + interface described by a programmer's reference manual, the + interface of a program in `libexec` is described by its usage + message. Examples: + + * `mkos8` - Run by the build system. + +

It is sometimes run by hand in development, but primarily + only to further its development. Once it runs correctly after + adding some feature, we let make run it for us.

+ + * `scanswitch` - Run by `etc/pidp8i`. + +

As with mkos8, it is generally run by hand only + by developers modifying its behavior.

+ + Programs in `libexec` are installed to `$prefix/libexec`, which is + *not* put into the user's `PATH`, on purpose. If a program should + end up in the user's `PATH`, it belongs in `bin`. Alternately, a + wrapper may be put in `bin` which calls a `libexec` program as a + helper. + +* `media` - Binary media images used either by SIMH directly or + by tools like `mkos8` to produce media used by SIMH. + + The contents of this tree are installed to `$prefix/share/media`. + +* `obj` - Intermediate output directory used by the build + system. It is safe to remove this directory at any time, as its + contents may be recreated by `make`. No file checked into Fossil + should be placed here. + + (Contrast `bin` which does have some files checked into Fossil; all + of the *other* files that end up in `bin` can be recreated by + `make`, but not these few hand-written programs.) + +* `src` - Source code for the project's programs, especially + those that cannot be used until they are built. The build system's + output directories are `bin`, `boot`, `libexec`, and `obj`. + + Programs that can be used without being "built", example programs, + and single-file scripts are placed elsewhere: `bin`, `examples`, + `libexec`, `tools`, etc. Basically, we place such files where the + build system *would* place them if they were built from something + under `src`. + + The top level of `src` is for the SIMH core, with the PDP-8 + simulator specific bits in the `PDP8` subdirectory. + + The subdirectories of `src` are for other programs' source code. + +* `test` - Output directory used by `tools/test-*`. + +* `tools` - Programs run only during development and not + installed. + + If a program is initially created here but we later decide that it + should be installed for use by end users of the PiDP-8/I system, we + move it to either `bin` or `libexec`, depending on whether it is run + directly at the command line or run from some other program that is + also installed, respectively. + + + Submitting Patches ---- If you do not have a developer login on the PiDP-8/I software repository, you can still send changes to the project. @@ -364,11 +512,11 @@ of the current trunk. PiDP-8/I often drifts enough during development that a patch against a stable release may not apply to the trunk cleanly otherwise. [osil]: https://opensource.org/licenses -[repo]: http://tangentsoft.com/pidp8i/ +[repo]: https://tangentsoft.com/pidp8i/ [simhl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md [viral]: https://en.wikipedia.org/wiki/Viral_license The PiDP-8/I Software Project Code Style Rules Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -34,26 +34,75 @@ # authorization from those authors. ######################################################################## # Git commit ID of the latest version of the SIMH 4 project on GitHub # that has been merged into this source base. -SGCID=17903827bdb294f7e60d4c7f172bd6a1a71dfbd5 +SGCID=27f9fc3c3e7ff105c6102dfffc6da7fb838d1e8f -CFLAGS = @CFLAGS@ -Wno-unused-result -Wno-parentheses @BUILDMODE@ \ - -DUSE_READER_THREAD -DHAVE_DLOPEN=so -DPIDP8I -DSIM_ASYNCH_IO \ - -DHAVE_REGEX_H -DHAVE_GLOB -DSIM_GIT_COMMIT_ID=$(SGCID) \ - -D_GNU_SOURCE -U__STRICT_ANSI__ \ +# C build flags for src/*.c and PDP8/*.c, containing the SIMH core +# modules and the SIMH PDP-8 simulator, respectively. +TOP_CFLAGS := @CFLAGS@ @BUILDMODE@ \ + -Wno-unused-result -Wno-parentheses \ + -DUSE_READER_THREAD -DHAVE_DLOPEN=$(subst .,,@SH_SOEXT@) -DPIDP8I \ + -DSIM_ASYNCH_IO -DHAVE_REGEX_H -DHAVE_GLOB \ + -DSIM_GIT_COMMIT_ID=$(SGCID) -D_GNU_SOURCE \ + -U__STRICT_ANSI__ \ -I @srcdir@/src -I @srcdir@/src/PDP8 -I src +PDP8_CFLAGS = $(TOP_CFLAGS) + +# Greatly stripped-down build options for the cc8 cross-compiler +# primarily because it's K&R C. Building under TOP_CFLAGS spews +# pages of warnings. The only thing we share is whether to build +# a debug or optimized version, from BUILDMODE, set by auto.def. +CC8_CROSS_CFLAGS = -w @BUILDMODE@ + +# Krten's d8tape doesn't need much in the way of options. It builds +# without warnings under the supported version of Raspbian. On some +# other OS types, it complains about one of the printf() format +# specifiers, which we cannot fix portably, so we suppress that one. +D8TAPE_CFLAGS = -Wno-format @BUILDMODE@ + +# Ditto palbart. +PALBART_CFLAGS = @BUILDMODE@ SIM = bin/pidp8i-sim -BINS = bin/palbart $(SIM) bin/pidp8i-test libexec/scanswitch +BINS = $(SIM) @CC8_CROSS@ \ + bin/d8tape \ + bin/palbart \ + bin/pidp8i-test \ + bin/ptp2txt \ + libexec/scanswitch +BIN_SCRIPTS = \ + bin/pidp8i \ + @srcdir@/bin/teco-pi-demo + +# Some *.py files are generated by Autosetup, so they need different +# handling than the other libraries for the out-of-tree build case. +PIDP8I_DIRS := lib/pidp8i/dirs.py +PIDP8I_DIN := @srcdir@/$(PIDP8I_DIRS).in +GENNED_PY := \ + lib/pidp8i/__init__.py \ + lib/pidp8i/ips.py \ + $(PIDP8I_DIRS) + +SIMH_PY := lib/simh.py +SIMH_PY_SRC := @srcdir@/$(SIMH_PY) + +MKOS8 := @srcdir@/libexec/mkos8 +MKOS8_LIB := @srcdir@/lib/mkos8 +MKOS8_PY := \ + $(MKOS8_LIB)/__init__.py \ + $(MKOS8_LIB)/argparser.py \ + lib/mkos8/opts.py +MKOS8_PY_ALL := $(GENNED_PY) $(MKOS8_PY) $(SIMH_PY_SRC) +MKOS8_SRCS := $(MKOS8) $(MKOS8_PY_ALL) $(PIDP8I_DIN) + +BUILDDIRS := bin libexec obj/cc8/cross obj/d8tape obj/palbart obj/PDP8 -BUILDDIRS = bin libexec obj/PDP8 +INSTDIRS := bin etc lib/mkos8 lib/pidp8i libexec share/boot share/media share/include share/man/man1 -INSTDIRS = bin etc libexec share/boot share/media share/man/man1 - -OBJS = \ +SIM_OBJS := \ obj/gpio-common.o \ obj/PDP8/pdp8_df.o \ obj/PDP8/pdp8_cpu.o \ obj/PDP8/pdp8_clk.o \ obj/PDP8/pdp8_ct.o \ @@ -82,103 +131,170 @@ obj/sim_tape.o \ obj/sim_timer.o \ obj/sim_tmxr.o \ obj/sim_video.o +CC8_OBJS := \ + obj/cc8/cross/code8.o \ + obj/cc8/cross/data.o \ + obj/cc8/cross/error.o \ + obj/cc8/cross/expr.o \ + obj/cc8/cross/function.o \ + obj/cc8/cross/gen.o \ + obj/cc8/cross/io.o \ + obj/cc8/cross/lex.o \ + obj/cc8/cross/main.o \ + obj/cc8/cross/preproc.o \ + obj/cc8/cross/primary.o \ + obj/cc8/cross/stmt.o \ + obj/cc8/cross/sym.o \ + obj/cc8/cross/while.o + +D8TAPE_OBJS := \ + obj/d8tape/dasm.o \ + obj/d8tape/flow.o \ + obj/d8tape/main.o \ + obj/d8tape/version.o + +MISC_OBJS := \ + obj/ptp2txt.o \ + obj/scanswitch.o \ + obj/test.o + +PALBART_OBJS := obj/palbart/palbart.o + ifeq (@BUILD_DEEPER_THOUGHT@, 1) BINS += bin/deeper endif LIBS = -lm -ldl -lpthread -ASM_PTS := $(wildcard @srcdir@/asm/*.pal) -ASM_PTS := $(subst @srcdir@/asm,bin,$(ASM_PTS)) -ASM_PTS := $(ASM_PTS:.pal=.pt) -EX_PTS := $(wildcard @srcdir@/examples/*.pal) -EX_PTS := $(subst @srcdir@/examples,bin,$(EX_PTS)) -EX_PTS := $(EX_PTS:.pal=.pt) -LISTINGS := $(ASM_PTS:.pt=.lst) $(EX_PTS:.pt=.lst) +ASM_PTS := $(wildcard @srcdir@/src/asm/*.pal) +ASM_PTS := $(subst @srcdir@/src/asm,bin,$(ASM_PTS)) +ASM_PTS := $(ASM_PTS:.pal=-pal.pt) +FC_EX_PTS := $(wildcard @srcdir@/examples/*.fc) +FC_EX_PTS := $(subst @srcdir@/examples,bin,$(FC_EX_PTS)) +FC_EX_PTS := $(FC_EX_PTS:.fc=-focal.pt) +PAL_EX_PTS := $(wildcard @srcdir@/examples/*.pal) +PAL_EX_PTS := $(subst @srcdir@/examples,bin,$(PAL_EX_PTS)) +PAL_EX_PTS := $(PAL_EX_PTS:.pal=-pal.pt) +LISTINGS := $(ASM_PTS:-pal.pt=.lst) $(PAL_EX_PTS:-pal.pt=.lst) LISTINGS := $(subst bin/,obj/,$(LISTINGS)) BOOTSCRIPTS := $(LISTINGS:.lst=.script) BOOTSCRIPTS := $(subst obj/,boot/,$(BOOTSCRIPTS)) \ boot/1.script \ boot/5.script # List of *.in files from auto.def file, except for this present file # (Makefile.in) which is handled separately. This list should only # change when the list of "make-template" calls in auto.def changes. +# +# The MKOS8_INFILES set of files are those which, if changed, require +# rebuilding the OS/8 media. Touching the combined set merely causes +# a reconfig and build. # -# If the first file listed below changes, change the AUTOREBUILD rule -# near the end of this file to match! +# The PRECIOUS set are those whose outfiles we want make(1) to treat as +# "precious", meaning it won't delete files generated by a target if the +# rule being processed to create that target fails. +# +# The rest have no special treatment. +MKOS8_INFILES = \ + @srcdir@/lib/pidp8i/__init__.py.in \ + @srcdir@/lib/pidp8i/ips.py.in \ + @srcdir@/media/os8/init.tx.in \ + @srcdir@/src/PDP8/pidp8i.c.in \ + $(PIDP8I_DIN) +PRECIOUS_INFILES = \ + @srcdir@/Makefile.in \ + @srcdir@/examples/Makefile.in \ + @srcdir@/src/Makefile.in \ + @srcdir@/src/cc8/Makefile.in \ + @srcdir@/src/PDP8/Makefile.in INFILES = \ @srcdir@/bin/pidp8i.in \ @srcdir@/boot/0.script.in \ @srcdir@/boot/2.script.in \ @srcdir@/boot/3.script.in \ @srcdir@/boot/4.script.in \ @srcdir@/boot/6.script.in \ @srcdir@/boot/7.script.in \ + @srcdir@/boot/run.script.in \ @srcdir@/etc/pidp8i-init.in \ @srcdir@/etc/sudoers.in \ @srcdir@/src/gpio-common.c.in \ - @srcdir@/src/PDP8/pidp8i.c.in \ - @srcdir@/tools/mkos8.in \ - @srcdir@/tools/simh-update.in -PRECIOUS_INFILES = \ - @srcdir@/Makefile.in \ - @srcdir@/examples/Makefile.in \ - @srcdir@/src/Makefile.in \ - @srcdir@/src/PDP8/Makefile.in + @srcdir@/tools/simh-update.in \ + $(MKOS8_INFILES) +MKOS8_OUTFILES := $(subst @srcdir@/,,$(MKOS8_INFILES)) +MKOS8_OUTFILES := $(subst .in,,$(MKOS8_OUTFILES)) +PRECIOUS_OUTFILES := $(subst @srcdir@/,,$(PRECIOUS_INFILES)) +PRECIOUS_OUTFILES := $(subst .in,,$(PRECIOUS_OUTFILES)) OUTFILES := $(subst @srcdir@/,,$(INFILES)) OUTFILES := $(subst .in,,$(OUTFILES)) -PRECIOUS_OUTFILES := $(subst @srcdir@/,,$(PRECIOUS_INFILES)) -PRECIOUS_OUTFILES := $(subst .in,,$(PRECIOUS_OUTFILES)) OS8_BIN_RK05 = bin/os8v3d-bin.rk05 -OS8_SRC_RK05 = bin/os8v3d-src.rk05 +OS8_SRC_RK05 = @OS8_SRC_RK05@ OS8_RK05S = $(OS8_BIN_RK05) $(OS8_SRC_RK05) CLTXT = /boot/cmdline.txt -.PHONY: tags +ADF := adrules.mk + +.PHONY: all tags .PRECIOUS: $(PRECIOUS_OUTFILES) -all: $(OUTFILES) $(PRECIOUS_OUTFILES) $(BUILDDIRS) $(BINS) $(BOOTSCRIPTS) $(LISTINGS) $(ASM_PTS) $(EX_PTS) $(OS8_RK05S) - @chmod 755 bin/pidp8i +all: $(OUTFILES) $(PRECIOUS_OUTFILES) $(BUILDDIRS) $(BINS) $(BOOTSCRIPTS) $(LISTINGS) $(ASM_PTS) $(FC_EX_PTS) $(PAL_EX_PTS) $(OS8_RK05S) clean: - @rm -f $(BINS) $(BOOTSCRIPTS) $(ASM_PTS) $(EX_PTS) $(LISTINGS) $(OBJS) $(OUTFILES) \ - tags \ - obj/*.d \ - obj/*.o \ - obj/PDP8/*.d \ + @rm -f $(BINS) $(BOOTSCRIPTS) $(ASM_PTS) $(PAL_EX_PTS) $(LISTINGS) \ + $(OUTFILES) $(ADF) \ + config.log cscope.out tags \ + bin/*.pt bin/*.rk05 bin/*.save bin/txt2ptp \ + lib/*.pyc lib/*/*.pyc lib/mkos8/opts.py \ + obj/*.log obj/*.pt obj/mkos8.opts \ + src/config.h \ @srcdir@/examples/*.err + @find obj \( -name \*.o -o -name \*.d \) -delete @-rmdir -p $(BUILDDIRS) 2> /dev/null || true distclean: clean - @rm -f $(PRECIOUS_OUTFILES) \ + @rm -f \ + $(PRECIOUS_OUTFILES) \ config.log \ autosetup/jimsh0 \ src/config.h ctags tags: - @ctags -R @srcdir@ + ctags -R @srcdir@/src @srcdir@/lib @srcdir@/libexec/mkos8 ifeq (@HAVE_PROG_CSCOPE@, 1) @cscope -bR -s@srcdir@ endif -install: all +install: all instdirs @echo Installing to @prefix@... - @# Create any missing install tree directories - for d in $(INSTDIRS) ; do @INSTALL@ -m 755 -d @prefix@/$$d ; done - @# Install files into those dirs and set their perms - for f in $(BINS) ; do @INSTALL@ -m 755 -D -s $$f @prefix@/$$f ; done - @INSTALL@ -m 755 @srcdir@/bin/pidp8i @prefix@/bin - -for f in @prefix@/bin/pidp8i-* ; do setcap 'cap_sys_nice=eip' $$f ; done || true - test -e @MEDIADIR@/os8/os8.rk05 || $(MAKE) mediainstall + @for f in $(BINS) ; do \ + dest=@prefix@/bin/$$(basename $$f) ; \ + echo "Installing binary $$dest..." ; \ + @INSTALL@ -m 755 -D -s $$f $$dest ; \ + done + @for f in $(BIN_SCRIPTS) ; do \ + dest=@prefix@/bin/$$(basename $$f) ; \ + echo "Installing script $$dest..." ; \ + @INSTALL@ -m 755 -D $$f $$dest ; \ + done + @( cd @prefix@/bin ; \ + echo "Installing txt2ptp symlink..." ; \ + ln -f ptp2txt txt2ptp ; \ + ) + @(test -x /sbin/setcap && \ + for f in @prefix@/bin/pidp8i-* ; do \ + echo "Setting real-time priority capabilities on $$(basename $$f)..." ; \ + /sbin/setcap 'cap_sys_nice=eip' $$f ; \ + done \ + ) || true + @test -e @MEDIADIR@/os8/os8.rk05 || echo "Installing media..." && $(MAKE) mediainstall @# If this is a Debian-type system, install needed helper programs @test -x /usr/bin/apt-get -a ! -h /media/usb && apt-get -y install usbmount || true @test -x /usr/bin/apt-get -a ! -x /usr/bin/screen && apt-get -y install screen || true @@ -185,11 +301,11 @@ @# Disable competing services if this is a Raspberry Pi @(test -x /bin/systemctl && /bin/systemctl disable deeper || true) @(test -x /bin/systemctl && /bin/systemctl disable pidp8 || true) @# Install the init script if this system is systemd based. - @INSTALL@ -m 755 @srcdir@/etc/pidp8i-init @prefix@/etc + @@INSTALL@ -m 755 @srcdir@/etc/pidp8i-init @prefix@/etc @( test -w /etc/init.d -a -x /bin/systemctl && \ ln -sf @ABSPREFIX@/etc/pidp8i-init /etc/init.d/pidp8i && \ /bin/systemctl enable pidp8i \ ) || true @@ -214,11 +330,12 @@ @# Ditto for MANPATH @(for p in .profile .bash_profile ; do \ test -n "$$SUDO_USER" -a -w "/home/$$SUDO_USER/$$p" && \ ! grep -qF "@ABSPREFIX@/share/man" "/home/$$SUDO_USER/$$p" && \ echo "export MANPATH=\$$MANPATH:@ABSPREFIX@/share/man" >> "/home/$$SUDO_USER/$$p" ; \ - done) || true + done \ + ) || true @# If serial mod is disabled, turn off serial console and kgdb stuff @# in case they were enabled previously, else they will fight with @# our use of GPIO. @( test -z "@PCB_SERIAL_MOD_ANY@" -a -r $(CLTXT) && ! -w $(CLTXT) && \ @@ -225,90 +342,128 @@ cp -p $(CLTXT) "$(CLTXT)"_orig && \ sed -e 's/console\=[a-zA-Z0-9]+,[0-9]+ //' \ -e 's/kgdboc\=[a-zA-Z0-9]+,[0-9]+ //' -i $(CLTXT) \ ) || true + @# Install CC8 stuff if built + @test -n "@CC8_CROSS@" && \ + echo "Installing cc8 cross-compiler..." ; \ + @INSTALL@ -m 755 bin/cc8 @prefix@/bin && \ + @INSTALL@ -m 644 @srcdir@/src/cc8/include/* @prefix@/share/include + @# Install palbart stuff - @INSTALL@ -m 755 bin/palbart @prefix@/bin - @INSTALL@ -m 644 @srcdir@/palbart/palbart.1 @prefix@/share/man/man1 + @@INSTALL@ -m 755 bin/palbart @prefix@/bin + @@INSTALL@ -m 644 @srcdir@/src/palbart/palbart.1 @prefix@/share/man/man1 + + @# Install mkos8 and its dependencies + @echo "Installing mkos8..." + @@INSTALL@ -m 775 -g @INSTGRP@ $(MKOS8) @prefix@/libexec + @( for src in $(MKOS8_PY_ALL) ; do \ + test -e $$src || src=@srcdir@/$$src ; \ + dest=@prefix@/$$(echo $$src | sed -e 's_^@srcdir@/__') ; \ + echo "Installing $$src to $$dest..." ; \ + @INSTALL@ -m 644 -g @INSTGRP@ -D $${src} $${dest} ; \ + @INSTALL@ -m 644 -g @INSTGRP@ -D $${src}c $${dest}c ; \ + done \ + ) + @sed -e 's#^build =.*#build = "@ABSPREFIX@"#' \ + -e 's#^media =.*#media = os.path.join (build, "share/media/")#' \ + -e 's#^os8mo =.*#os8mo = os8mi#' \ + < $(PIDP8I_DIRS) > @prefix@/$(PIDP8I_DIRS) + @chgrp @INSTGRP@ @prefix@/$(PIDP8I_DIRS) + +instdirs: + @echo "Creating installation directory tree..." + @for d in $(INSTDIRS) ; do @INSTALL@ -m 755 -d @prefix@/$$d ; done -mediainstall: +mediainstall: instdirs @echo "[Re]installing OS and program media..." @cd @srcdir@ ; \ find media \( \ -name \*.bin -o \ -name \*.dsk -o \ -name \*.rk05 -o \ -name \*.tu56 \ \) -exec @INSTALL@ -D -m 664 -o @INSTUSR@ -g @INSTGRP@ {} @ABSPREFIX@/share/{} \; - @INSTALL@ -m 644 -o @INSTUSR@ -g @INSTGRP@ bin/os8v3d-*.rk05 @ABSPREFIX@/share/media/os8 - @INSTALL@ -m 664 -o @INSTUSR@ -g @INSTGRP@ boot/*.script @BOOTDIR@ + @@INSTALL@ -m 644 -o @INSTUSR@ -g @INSTGRP@ bin/os8v3d-*.rk05 @ABSPREFIX@/share/media/os8 + @@INSTALL@ -m 664 -o @INSTUSR@ -g @INSTGRP@ boot/*.script @BOOTDIR@ + +# No-dependencies versions of the bin/os8v3d-*.rk05 targets used by +# tools/test-mkos8, because there's no point rebuilding the simulator +# on each iteration. +os8-bin: + $(MKOS8)@MKOS8_OPTS@ bin +os8-patched: + $(MKOS8)@MKOS8_OPTS@ bin patch reconfig: @AUTOREMAKE@ release: all @srcdir@/tools/mkrel -run: $(OS8_BIN_RK05) install - $(SIM) @srcdir@/boot/0.script +run: $(SIM) + $(SIM) boot/run.script simh-update simh-update-f: @@srcdir@/tools/simh-update $(subst simh-update,,$@) +test-mkos8: + tools/test-mkos8 + -# Build the OS/8 binary media needed by 0.script -OS8_BIN_SRCS = \ - @srcdir@/tools/mkos8.in \ +# Build the OS/8 binary media needed by 0.script. +# +# We use order-only prerequisites for the simulator here because we only +# care that it *exists*, not whether it is newer than the previously +# built RK05 media or not. +# +# Also notice that the init.tx file is not a prerequisite: we purposely +# do not rebiuld the RK05 media just because the configure script was +# re-run, which *always* regenerates the init.tx file because the +# timestamp always changes from one run to the next. (Until computers +# get fast enough to do a complete re-configure in under a second, +# anyway!) The thing is, we only want the RK05 bin media rebuilt when +# the configure --*-os8-* options change. *That* is when we care about +# the updated init.tx file, not before. We needn't even make it an +# order-only prereq because configure and the INFILES rules above ensure +# that it always exists. +OS8_BIN_SRCS := $(MKOS8_SRCS) \ @srcdir@/media/os8/al-*-ba-*.tu56 \ @srcdir@/media/os8/subsys/*.tu56 -$(OS8_BIN_RK05): $(SIM) $(OS8_BIN_SRCS) - tools/mkos8@MK_OS8_RK05_OPTS@ bin +ifneq (@MKOS8_BIN_PATCHES@,) + OS8_BIN_SRCS += @srcdir@/media/os8/patches/patch_list.txt +endif +$(OS8_BIN_RK05): $(OS8_BIN_SRCS) | $(SIM) $(MKOS8_OUTFILES) + $(MKOS8)@MKOS8_OPTS@ bin @MKOS8_BIN_PATCHES@ # Also build an OS/8 source disk, as a convenience to avoid the # need to mount up the 7 source tapes in succession. -OS8_SRC_SRCS = \ - @srcdir@/tools/mkos8.in \ +# +# Using an order-only dependency for the simulator and the bin disk: we +# only need *a* version of each, they don't have to be recent! +OS8_SRC_SRCS = $(MKOS8_SRCS) \ @srcdir@/media/os8/al-*-sa-*.tu56 -$(OS8_SRC_RK05): $(OS8_SRC_SRCS) - tools/mkos8@MK_OS8_RK05_OPTS@ src - -# Rule for compiling *.c to *.o and autogenerating dependency info. -# Explained at http://scottmcpeak.com/autodepend/autodepend.html -# -# Reflect any changes here into near-duplicate below! -obj/%.o: @srcdir@/src/%.c - $(CC) -c $(CFLAGS) @srcdir@/src/$*.c -o obj/$*.o - $(CC) -MM $(CFLAGS) @srcdir@/src/$*.c > obj/$*.d - @mv -f obj/$*.d obj/$*.d.tmp - @sed -e 's|.*:|obj/$*.o:|' < obj/$*.d.tmp > obj/$*.d - @sed -e 's/.*://' -e 's/\\$$//' < obj/$*.d.tmp | fmt -1 | \ - sed -e 's/^ *//' -e 's/$$/:/' >> obj/$*.d - @rm -f obj/$*.d.tmp - -# Near-duplicate of above rule for those *.c files generated from *.c.in -# files in the srcdir. Needed when building out-of-tree. -obj/%.o: src/%.c - $(CC) -c $(CFLAGS) src/$*.c -o obj/$*.o - $(CC) -MM $(CFLAGS) src/$*.c > obj/$*.d - @mv -f obj/$*.d obj/$*.d.tmp - @sed -e 's|.*:|obj/$*.o:|' < obj/$*.d.tmp > obj/$*.d - @sed -e 's/.*://' -e 's/\\$$//' < obj/$*.d.tmp | fmt -1 | \ - sed -e 's/^ *//' -e 's/$$/:/' >> obj/$*.d - @rm -f obj/$*.d.tmp - -# Rule for building PAL assembly language programs in asm/*.pal. -obj/%.lst bin/%.pt: @srcdir@/asm/%.pal bin/palbart +$(OS8_SRC_RK05): $(OS8_SRC_SRCS) | $(SIM) $(OS8_BIN_RK05) + $(MKOS8)@MKOS8_OPTS@ src + +# Rule for building PAL assembly language programs in src/asm/*.pal. +obj/%.lst bin/%-pal.pt: @srcdir@/src/asm/%.pal bin/palbart bin/palbart -lr $< || cat obj/$*.err - mv @srcdir@/asm/$*.lst obj - mv @srcdir@/asm/$*.rim bin/$*.pt + mv @srcdir@/src/asm/$*.lst obj + mv @srcdir@/src/asm/$*.rim bin/$*-pal.pt # Ditto for those in examples/*.pal. -obj/%.lst bin/%.pt: @srcdir@/examples/%.pal bin/palbart +obj/%.lst bin/%-pal.pt: @srcdir@/examples/%.pal bin/palbart bin/palbart -lr $< || cat obj/$*.err mv @srcdir@/examples/$*.lst obj - mv @srcdir@/examples/$*.rim bin/$*.pt + mv @srcdir@/examples/$*.rim bin/$*-pal.pt + +# Rule for converting ASCII FOCAL examples in examples/*.fc to +# bin/*-focal.pt. +bin/%-focal.pt: @srcdir@/examples/%.fc bin/txt2ptp + bin/txt2ptp < $< > bin/$*-focal.pt # Rule for translating PAL assembly language program listings to SIMH # boot scripts. boot/%.script: obj/%.lst @srcdir@/tools/mkbootscript $< @@ -318,53 +473,67 @@ boot/1.script: boot/hs-rim-loader.script ln -f $< $@ boot/5.script: boot/ac-mq-blinker.script ln -f $< $@ -bin/palbart: @srcdir@/palbart/palbart.c - $(CC) -Wall -Wno-strict-prototypes @BUILDMODE@ $< -o $@ - $(CC) -MM $< -o obj/palbart.d - $(BUILDDIRS): mkdir -p $@ -$(SIM): $(OBJS) obj/gpio-@LED_DRIVER_MODULE@ls.o +$(SIM): $(SIM_OBJS) obj/gpio-@LED_DRIVER_MODULE@ls.o + $(CC) -o $@ $^ $(LIBS) + +bin/cc8: $(CC8_OBJS) $(CC) -o $@ $^ $(LIBS) + +bin/d8tape: $(D8TAPE_OBJS) + $(CC) -o $@ $^ + +bin/palbart: $(PALBART_OBJS) + $(CC) -o $@ $^ bin/pidp8i-test: obj/test.o obj/gpio-nls.o obj/gpio-common.o $(CC) -o $@ $^ $(LIBS) -lncurses +bin/ptp2txt: obj/ptp2txt.o + $(CC) -o $@ $^ + ln -f bin/ptp2txt bin/txt2ptp + +bin/txt2ptp: bin/ptp2txt + ln -f bin/ptp2txt bin/txt2ptp + bin/deeper: obj/deeper.o obj/gpio-@LED_DRIVER_MODULE@ls.o obj/gpio-common.o $(CC) -o $@ $^ $(LIBS) libexec/scanswitch: obj/scanswitch.o obj/gpio-nls.o obj/gpio-common.o $(CC) -o $@ $^ $(LIBS) +# Reconfigure whenever one of the *.in or autosetup files changes unless +# this is "make clean". +# +# We purposely list only one of the OUTFILES on the left hand side +# because to list them all is to invite Make to run N copies of the +# configure script in parallel up to the limit of -j or the number of +# files in INFILES, whichever is lower. Order-only prerequisites can't +# help here (|) as that only affects the right hand side. ifeq ($(findstring clean,$(MAKECMDGOALS)),) -Makefile: @srcdir@/Makefile.in @srcdir@/auto.def $(INFILES) $(PRECIOUS_INFILES) @AUTODEPS@ +media/os8/init.tx: $(INFILES) $(PRECIOUS_INFILES) @AUTODEPS@ @AUTOREMAKE@ && $(MAKE) -# Rebuild simulator whenever one of its *.in files change. -# -# This is trickier than it appears. If you simply make $(OUTFILES) -# depend on $(INFILES), make(1) thinks it can build all of $(OUTFILES) -# in parallel if given -jN, which causes ./configure --args to run N -# times in parallel, which blows the build system up right good and -# proper; bits everywhere. We purposely list only the first file in -# $(INFILES) here to prevent that. The other files in $(INFILES) will -# also be built, which confuses make(1) somewhat, but it figures things -# out on the subsequent $(MAKE) call. -# -# We also list files like tools/version which, when they change, trigger -# a rebuild of $(OUTFILES) because their change may affect what gets -# generated. This happened when we moved from 8 to 10 character Fossil -# artifact IDs in the version string to match Fossil timeline's default. -bin/pidp8i: @srcdir@/bin/pidp8i.in @srcdir@/tools/version +# Also do it if the autodep tool is newer than its output, suggesting +# that if re-run, it would generate different output. +$(ADF): @srcdir@/tools/mkadrules @AUTOREMAKE@ && $(MAKE) endif # Rebuild simulator if the version string tool changes, since its output # may have changed. src/gpio-common.c: @srcdir@/tools/version --include $(OBJS:.o=.d) obj/scanswitch.d - +# Pull in *.d files generated by the autodependency mechanism. See the +# header comment of tools/mkadrules. +-include \ + $(SIM_OBJS:.o=.d) \ + $(CC8_OBJS:.o=.d) \ + $(D8TAPE_OBJS:.o=.d) \ + $(MISC_OBJS:.o=.d) \ + $(PALBART_OBJS:.o=.d) +-include $(ADF) Index: README.md ================================================================== --- README.md +++ README.md @@ -4,69 +4,228 @@ * A Raspberry Pi with the 40-pin GPIO connector. That rules out the first-generation Raspberry Pi model A and B boards which had a 26-pin GPIO connector. -* An SD card containing Raspbian or something sufficiently close. - This software is currently tested with the Jessie Lite distribution. - - Ideally, you will install a fresh OS image onto an unused SD card - rather than use this software to modify an existing OS installation, - but there is currently no known hard incompatibilty that prevents - you from integrating this software into an existing OS. +* An SD card containing [a compatible OS][os]. * This software distribution, unpacked somewhere convenient within the - Raspberry Pi filesystem. - - Unlike with the [upstream 2015.12.15 release][upst], this present - release of the software should *not* be unpacked into `/opt/pidp8`. - I recommend that you unpack it into `$HOME/src`, `/usr/local/src` or - similar, but it really doesn't matter where you put it, as long as - your user has full write access to that directory. - -* A working C compiler and other standard Linux build tools, such as - `make(1)`. On Debian type systems — including Raspbian — you can - install such tools with `sudo apt install build-essential` + filesystem on the Raspberry Pi. + + We recommend that you unpack it somewhere your user has read/write + access like `$HOME/src/pidp8i`. Since it installs as a system + service, you might prefer `/usr/local/src` or `/opt/src`, though + you'll have to adjust permissions for that. + + The [old stable 2015.12.15 release][osd] required that you unpack + the software into `/opt/pidp8`, but we now neither require nor + recommend that. + +* We require several tools and libraries that aren't always installed: + + * A working C compiler and other standard Linux build tools, + such as `make(1)`. + + * Python's `pexpect` library + + * The `usbmount` tool + + This is provides two things: + + * USB stick auto-mounting on stripped-down OSes like Raspbian + Lite so you can use the PiDP-8/I `SING_STEP` + `DF` feature + without having to manually mount the USB stick first. + + * A known directory structure that allows the PiDP-8/I + software to find the media image files on those sticks. + (`*.pt`, `*.dt`, `*.rk`, etc.) + + Full-blown GUI OSes tend to have USB auto-mounting set up + already, though they won't meet the second criteria unless they + use the same directory layout as `usbmount`: `/media/usbN`, + where `N` is a number from 0 to 7, depending on the order + you attached the USB stick. Many Linuxes use `/media/LABEL` + instead, for example, where `LABEL` is the partition's label; + the PiDP-8/I software won't find the files on those USB sticks + in that case. + + * The `ncurses` development libraries + + To install all of this on a Raspbian type OS, say: + + $ sudo apt update + $ sudo apt install build-essential + $ sudo apt install libncurses-dev python-pip usbmount + $ sudo pip install pexpect + +[os]: https://tangentsoft.com/pidp8i/wiki?name=OS+Compatibility - + +## Getting the Software onto Your Pi + +If you're reading this file within an unpacked distribution of the +PiDP-8/I software, you should skip this section, because you have +already achieved its aim. + +If you are reading this [online][this] and have not yet downloaded and +unpacked the software source code onto your Pi, this section will get +you going. + +[this]: https://tangentsoft.com/pidp8i/doc/trunk/README.md + + + +### Transferring the File to the Pi + +The first step is to get the tarball (`*.tar.gz` file) or Zip file onto +the Pi. There are many options: + +1. **Copy the file to the SD card** you're using to boot the Pi. + When inserted into a Mac or Windows PC, typically only the `/boot` + partition mounts as a drive your OS can see. (There's a much + larger partition on the SD card, but most PCs cannot see it.) + There should be enough free space left in this small partition to + copy the file over. When you boot the Pi up with that SD card, + you will find the tarball or Zip file in `/boot`. + +2. **Pull the file down to the Pi** over the web, directly to the Pi: + + $ wget -O pidp8i.tar.gz https://goo.gl/JowPoC + + That will get you a file called `pidp8i.tar.gz` in the current + working directory. + +3. **SCP the file over** to a running Pi from another machine. + If your Pi has OpenSSH installed and running, you can use + [WinSCP][wscp], [Cyberduck][cd], [FileZilla][fz] or another SCP + or SFTP-compatible file transfer program to copy the file to the + Pi over the network. + +[cd]: https://cyberduck.io/ +[fz]: https://filezilla-project.org/ +[wscp]: https://winscp.net/eng/ + +4. **Clone the Fossil repository** using the instructions in the + [`HACKERS.md` file][hack]. (Best for experts or those who wish to + become experts.) + +[hack]: https://tangentsoft.com/pidp8i/doc/trunk/HACKERS.md + +5. **Switch to the binary OS installation images** available from the + [top-level project page][cprj]. These are default installations of + Raspbian Lite with the PiDP-8/I software already downloaded, built, + and installed. These images were produced in part using option #4 + above, so you can use Fossil to update your software to the current + version at any time, as long as the Pi is connected to the Internet. + + + +### Unpacking the Software on Your Pi + +Having transferred the distribution file onto your Pi, you can unpack it +with one of the two following commands. + +If you grabbed the tarball: + + $ tar xvf /path/to/pidp8i-VERSION.tar.gz + +If you grabbed the Zip file instead: + + $ unzip /path/to/pidp8i-VERSION.zip + +The file name will vary somewhat, depending on when and how you +transferred the file. After unpacking it, you will have a new +directory beginning with `pidp8i`. `cd` into that directory, then +proceed with the [configuration](#configuring) steps below. + + + +### If You Need More Help + +If the above material is not sufficient to get you started, you might +want to look at [the documentation][rpfd] provided by the Raspberry +Pi Foundation. In particular, we recommend their [Linux][rpfl] and +[Raspbian][rpfr] guides. The book "[Make: Linux for Makers][lfm]" +by Aaron Newcomb is also supposed to be good. + + +[rpfd]: https://www.raspberrypi.org/documentation/ +[rpfl]: https://www.raspberrypi.org/documentation/linux/ +[rpfr]: https://www.raspberrypi.org/documentation/raspbian + +[lfm]: https://www.makershed.com/products/make-linux-for-makers + + + ## Configuring, Building and Installing This software distribution builds and installs in the same way as most other Linux/Unix software these days. The short-and-sweet is: $ ./configure && make && sudo make install -If you've checked out a new version of the source code and the `make` -step fails, try redoing the `configure` step. Sometimes changes made to -the source code invalidate prior `make` dependencies, which are -implicitly repaired by the `configure` script. +The `configure` step is generally needed only the first time you build +the software in a new directory. You may want to add options after it, +as described [below](#options). + +After that initial configuration, the software normally auto-reconfigures +itself on updates using the same options you gave before, but occasionally +we make some change that prevents this from happening. If you get a +build error after updating to a new version of the software, try saying: + + $ make reconfig + +...and then continuing with the `make && sudo make install` steps before +reporting a build error. + +If `make reconfig` also fails, you can try running the `configure` +script again manually. - + +### Running the Software + +For the most part, this is covered in the documentation linked from the +[Learning More](/#learning) section of the project home page. + +The only tricky bit is that if this is the first time you have +configured, built and installed the software as above on a given system, +you will have to log out and back in before commands like `pidp8i` will +be in your user's `PATH`. + + + ### Configure Script Options -You can change a few things about the way the software is built and +You can change many things about the way the software is built and installed by giving options to the `configure` script: - + #### --prefix Perhaps the most widely useful `configure` script option is `--prefix`, -which lets you override the default installation directory, -`/opt/pidp8i`. You could make it install the software under your home -directory on the Pi with this command: +which lets you override the default installation directory, `/opt/pidp8i`. +There are many good reasons to change where the software gets installed, +but the default is also a good one, so unless you know for a fact that +you want to change this default, leave it alone. + +For example, you might prefer that the installer put the built software +under your home directory. This will do that: $ ./configure --prefix=$HOME/pidp8i && sudo make install -Although this is installing to a directory your user has write access -to, you still need to install via `sudo` because the installation -process does other things that do require `root` access. +You might think that installing to a directory your user has complete +control over would remove the need for installing via `sudo`, but that +is not the case, since the installation script needs root privileges to +mark a few of the executables as having permission to run at high priority +levels, which improves the quality of the display, particularly with the +[incandescent lamp simulator][ils] feature enabled. - + #### --lowercase The American Standards Association (predecessor to ANSI) delivered the second major version of the ASCII character encoding standard the same year the first PDP-8 came out, 1965. The big new addition? Lowercase. @@ -127,11 +286,11 @@ [sa]: http://homepage.cs.uiowa.edu/~jones/pdp8/faqs/#charsets [tty]: https://tangentsoft.com/pidp8i/wiki?name=OS/8+Console+TTY+Setup - + #### --no-lamp-simulator If you build the software on a multi-core host, the PDP-8/I simulator is normally built with the [incandescent lamp simulator][ils] feature, which drives the LEDs in a way that mimics the incandescent lamps used @@ -148,11 +307,11 @@ method not only uses less CPU, which may be helpful if you're trying to run a lot of background tasks on your Pi 2 or Pi 3, it can also be helpful when the CPU is [heavily throttled][thro]. - + #### --serial-mod If you have done [Oscar's serial mod][sm1] to your PiDP-8/I PCB and the Raspberry Pi you have connected to it, add `--serial-mod` to the `configure` command above. @@ -164,11 +323,11 @@ If you give this flag and your PCBs are *not* modified, most of the hardware will work correctly, but several lights and switches will not work correctly. - + #### --alt-serial-mod This flag is for an [alternative serial mod by James L-W][sm2]. It doesn't require mods to the Pi, and the mods to the PiDP-8/I board are different from Oscar's. This flag changes the GPIO code to work with @@ -185,17 +344,28 @@ See [`README-throttle.md`][thro] for the values this option takes. If you don't give this option, the simulator runs as fast as possible, more or less. - +#### --disable-cc8-cross + +Give this option if you do not want to build Ian Schofield's `cc8` C +cross-compiler on the host. + +Note that this is different from `--disable-os8-cc8`, which disables the +*native OS/8* C compiler. They are two different C compilers: one runs +outside the SIMH PDP-8 simulator and the other runs inside the simulator +under OS/8. + + + #### --disable-os8-\* -Several default components of the OS/8 RK05 disk image used by boot -options IF=0 and IF=7 can be left out to save space and build time: +Several default components of the [OS/8 RK05 disk image](#os8di) used by +boot options IF=0 and IF=7 can be left out to save space and build time: -* **--disable-os8-advent** — Leave out the Adventure game. +* **--disable-os8-advent** — Leave out the [Adventure][advent] game. * **--disable-os8-ba** - Leave out the BASIC games and demos which come from DEC's book "101 BASIC Computer Games." These are normally installed to `RKB0:` as `*.BA`, thus the option's name. @@ -202,63 +372,265 @@ (We considered naming it `--disable-os8-basic-games-and-demos`, but that's too long, and it can't be `--disable-os8-basic` because that implies that it is the OS/8 BASIC subsystem that is being left out, which is not even currently an option.) -* **--disable-os8-chess** — Leave out John Comeau's CHECKMO-II chess - implementation. +* **--disable-os8-chess** — Leave out John Comeau's + [CHECKMO-II chess implementation][chess]. * **--disable-os8-cc8** - Leave out Ian Schofield's native OS/8 CC8 - compiler normally installed to `RKA0:` + compiler normally installed to `SYS:`. This option is implicitly + given if you give `--disable-os8-fortran-ii` because the output of + the OS/8 CC8 compiler is a SABR file, and SABR is part of the + FORTRAN II subsystem. * **--disable-os8-crt** — Suppress the [console rubout behavior][tty] enabled while building the OS/8 binary RK05 disk image. You probably only want to do this if you have attached a real teletype to your PiDP-8/I, and thus do not want video terminal style rubout processing. + +* **--disable-os8-focal** - Do not install any version of FOCAL on the + OS/8 system disk. This option sets `--disable-os8-uwfocal` and + overrides `--enable-os8-focal69`, both discussed below. + +* **--disable-os8-fortran-ii** - Leaves out the FORTRAN II compiler, + SABR, the linking loader (`LOADER`), the `LIBSET` tool, and the + `*.RL` library files. + +* **--disable-os8-fortran-iv** - Leave the FORTRAN IV compiler out. + +* **--disable-os8-init** - Generate `RKB0:INIT.TX` but do not display + it on OS/8 boot. Rather than disable the default on-boot init + message, you may want to edit `media/os8/init.tx.in` to taste + and rebuild. + + (We still build the file when you give this option in case you + later decide you want to enable the boot message, or you need to + call up configuration information stored in `INIT.TX`.) * **--disable-os8-k12** - Leave out the Kermit-12 implementation normally installed to `RKA0:` +* **--disable-os8-macrel** - Leave the MACREL v2 assembler and its + associated FUTIL V8B tool out. - +* **--disable-os8-patches** - Do not apply any of the OS/8 V3D + patches published by DEC. See the [documentation][os8p] for this + option for more information. + +* **--disable-os8-src** - Do not build the `os8v3d-src.rk05` disk + image from the OS/8 source tapes. This is not controlled by + `--os8-minimal` because that only affects `os8v3d-bin.rk05`. + +* **--disable-os8-uwfocal** - Leave out the U/W FOCAL V4E programming + environment normally installed to `RKA0:`. + + Note that the default installation only installs `UWF16K.SV`, not + the rest of the files on `media/os8/subsys/uwfocal*.tu56`. There is + much more to explore here, but we cannot include it in the default + installation set because that would overrun OS/8's limitation on the + number of files on a volume. + +[advent]: http://www.rickmurphy.net/advent +[chess]: https://chessprogramming.wikispaces.com/CHEKMO-II +[os8p]: https://tangentsoft.com/pidp8i/doc/trunk/doc/os8-patching.md + + + #### --enable-os8-\* -There are a few file sets not normally installed to the OS/8 RK05 disk -image used by boot options IF=0 and IF=7. You can install them with the -following options: - -* **--enable-os8-music** — The `*.MU` files and the player program for - it are not normally installed to the built OS/8 binary RK05 disk - image because the Raspberry Pi reportedly does not emit eufficient - RFI at AM radio frequencies when running these programs to cause - audible music on a typical AM radio, the very point of these demos. - Until a way is found around this problem — what, low RFI is a - *problem* now? — this option will default to "off". +There are a few file sets not normally installed to the [OS/8 RK05 disk +image](#os8di) used by boot options IF=0 and IF=7. You can install them +with the following options: + +* **--enable-os8-music** — The `*.MU` music scores and Rich Wilson's + associated compiler (`MUSIC.PA`) and player overlay (`PLAYOV.PA`) + are not normally installed to the built OS/8 binary RK05 disk image + because the Raspberry Pi reportedly does not emit eufficient RFI at + AM radio frequencies when running these programs to cause audible + music on a typical AM radio, the very point of these demos. Until a + way is found around this problem — what, low RFI is a *problem* now? + — this option will default to "off". * **--enable-os8-vtedit** — This option installs a default-run macro pack called VTEDIT which causes the OS/8 version of TECO to run in full-screen mode and to react to [several special keyboard - commands](/wiki?name=Using+VTEDIT) not normally recognized by TEDO. - - This feature is currently disabled because it is not yet fully - tested by the person in charge of the OS/8 disk building process. - - It may remain disabled after that because it changes the behavior of - the `TECO` command in OS/8, which violates the expectations of - people expecting a historically accurate TECO experience. On the - other hand, people don't go to a ren fair and expect to experience - the historical ubiquity of typhoid fever either, so we might change - our mind on this. + commands][uvte] not normally recognized by TECO. + + This feature is disabled by default because the VTEDIT macro pack + changes the way TECO operates, and many people want TECO to behave + like *TECO*. VTEDIT was first created during the PDP-8's commercial + lifetime, so enabling this option is not an anachronism, but TECO is + much older and had a much more wide-reaching impact in history, so + we choose to provide unvarnished TECO by default. + + That having been said, people don't go to a ren fair and expect to + experience the historical ubiquity of typhoid fever, so do not feel + guilty if you choose to try this option. + +[uvte]: https://tangentsoft.com/pidp8i/wiki?name=Using+VTEDIT + +* **--enable-os8-focal69** — Because the default installation includes + U/W FOCAL, we have chosen to leave FOCAL 69 out by default to save + space on the O/S 8 system disk. You can give this option to install + this implementation alongside U/W FOCAL, or you can couple this + option with `--disable-os8-uwfocal` to reverse our choice of which + FOCAL implementation to install by default. + + You should know that the reason we made this choice is that the + version of FOCAL 69 we are currently shipping is fairly minimal: we + believe we are shipping the original DEC version of FOCAL 69 plus a + few carefully-selected overlays. There are many more overlays and + patches available on the Internet for FOCAL 69, but we have not had + time to sort through these and make choices of which ones to ship or + how to manage which ones get installed. Thus our choice: we want to + provide the most functional version of FOCAL by default, and within + the limitations of the time we have chosen to spend on this, that is + U/W FOCAL today. + + (See our [U/W FOCAL manual supplement][suppd] for a list of + differences between these versions of FOCAL, which implicitly + explains why we chose it.) + + It is possible that we will eventually add enough patches and + overlays to FOCAL 69 that it will become more powerful than U/W + FOCAL, so we might then choose to switch the defaults, but that is + just speculation at the time of this writing. + +[suppd]: https://tangentsoft.com/pidp8i/doc/trunk/doc/uwfocal-manual-supp.md#diffs + + +#### --os8-minimal + +If you set this flag, it sets all `--enable-os8-*` flags to false and +all `--disable-os8-*` flags to true. If you explicitly give any of these +flags to the `configure` script, this flag overrides them. + +This flag only affects the optional installs made after the `BUILD` +step: it does not remove optional features of OS/8 itself, such as its +BASIC interpreter. + +Giving this option therefore gets you an empty OS/8 `DSK:` device and +nothing in `SYS:` beyond what was left after the OS/8 `BUILD` step. + +There are a few exceptions: + +1. This option does not affect the `--lowercase` option because that + affects only OS/8's command interpreter and OS/8's BASIC + implementation, so we deem it to be orthogonal to the purpose of the + `--os8-minimal` flag, which only affects the optional post-`BUILD` + features. If you want a *truly* pristime OS/8 disk, you should + therefore also give `--lowercase=none`. + +2. This option does not affect `--disable-os8-src`, because it only + suppresses optional features in the "bin" media. If you want a + minimal OS/8 bin disk and no src disk, give that option as well. #### --help Run `./configure --help` for more information on your options here. - +## OS/8 Disk Images + +For the first several years of the PiDP-8/I project, the OS/8 RK05 disk +image included with the PiDP-8/I software (called `os8.rk05`) was based +on an image of a real RK05 disk pack that someone allegedly found in a +salvaged PDP-8 system. Parts of the image were corrupt, and not all of +the pieces of software on the disk worked properly with the other parts. +It was also a reflection of the time it was created and used out in the +world, which was not always what we would wish to use today. + +In late 2017 [several of us][aut] created the `mkos8` tool, which takes +the `--*-os8-*` options documented above and generates the +`os8v3d-*.rk05` RK05 disk image files with your chosen configuration +options. + +This set of disk images entirely replaces the old `os8.rk05` disk image, +in that all features of the old disk image are still available, though +not necessarily in the default configuration. In some cases, we have +disabled some features that were included in the stock `os8.rk05` disk +image, and in other cases we have changed the behavior of features. +Mostly, though, the new disk images are simply more functional than the +old ones. + +If you wish to know the full details of how these disk images are +created, the best documentation so far is [the source code for the +`mkos8` script][mkos8] and the [documentation for `class simh`][cs]. + +The remainder of this section describes some aspects of these disk +images which are not clear from the descriptions of the `--*-os8-*` +configuration options above. + +[aut]: https://tangentsoft.com/pidp8i/doc/trunk/AUTHORS.md + + +### Baseline + +The baseline for the bootable OS/8 disk images comes from a set of +DECtapes distributed by Digital Equipment Corporation which are now +included with the PiDP-8/I software; see the [`media/os8/*.tu56` +files][os8mf]. From these files and your configuration options, the +`mkos8` script creates the baseline `os8v3d-bin.rk05` disk image. + +The default build creates a complete OS/8 system including `BUILD` +support, FORTRAN IV, MACREL v2, and more. + + +### Subtractions + +It turns out that it's pretty easy to run out of directory space on an +OS/8 RK05 disk due to a limitation in the number of files on an OS/8 +filesystem. For this reason, the archive of device drivers and TD8E +system are left off the system packs. They can be found in [OS/8 +Binary Distribution DECtape #2][bdt2]. + +If you do fancy work with `BUILD`, you may need to attach that DECtape +image and copy files in from it. + + +### Default Additions + +The OS/8 RK05 disk image build process normally installs many software +and data file sets to the disk image. See the [option descriptions +above](#disable-os8): the "disable" option set effectively lists those +packages that `mkos8` installs by default, and the following set of +["enable" options](#enable-os8) lists those left out by default. + + +### Console Enhancements + +The default build [enhances the console](/wiki?name=Console+TTY+Setup), +adding support for lower case terminals where: + +1. The SIMH PDP-8 simulator and a few select parts of OS/8 are adjusted + to cope with lowercase input to [varying degrees](#lowercase). + +2. Rubout/backspace handling is set to assume a video terminal rather + than a teletype by default. + + +### Patches + +After the baseline disk image is created, `mkos8` makes a copy of it as +`os8v3d-patched.rk05` and applies a [carefully selected set of official +DEC patches][os8p] to it unless you give the `--disable-os8-patches` +configuration option. The IF=0 and IF=7 boot options boot from the +patched disk unless you give that option. + + +[bdt2]: https://tangentsoft.com/pidp8i/file/media/os8/al-4712c-ba-os8-v3d-2.1978.tu56 +[cl]: https://tangentsoft.com/pidp8i/doc/trunk/ChangeLog.md +[cs]: https://tangentsoft.com/pidp8i/doc/trunk/doc/class-simh.md +[mkos8]: https://tangentsoft.com/pidp8i/doc/trunk/libexec/mkos8 +[os8mf]: https://tangentsoft.com/pidp8i/file/media/os8 +[tlrm]: https://tangentsoft.com/pidp8i/doc/trunk/README.md + + + ## Overwriting the Local Simulator Setup When you run `sudo make install` step on a system that already has an existing installation, it purposely does not overwrite two classes of files: @@ -268,10 +640,17 @@ image files are considered "precious" because you may have modified the OS configuration or saved personal files to the disk the OS boots from, which in turn modifies this media image file out in the host operating environment. + There is an important exception here: when upgrading from v20170404 + to v201712xx or newer, the old `os8.rk05` disk image will be left + untouched per the above, but because `os8v3d-*.rk05` does not exist + yet, those will be copied alongside `os8.rk05`. However, the next + item still holds, so that the simulator will continue to use + `os8.rk05` because it will be booted by the preexisting scripts. + 2. **The PDP-8 simulator configuration files**, installed as `$prefix/share/boot/*.script`, which may similarly have local changes, and thus be precious to you. Sometimes this "protect the precious" behavior isn't what you want. @@ -280,26 +659,28 @@ that the newer software you're installing contains changes that you want to reflect into your local configuration. You have several options here: -1. If you just want to reflect upstream PDP-8 simulator configuration +1. If you just want to reflect the prior PDP-8 simulator configuration file changes into your local versions, you can hand-edit the installed simulator configuration scripts to match the changes in the newly-generated `boot/*.script` files under the build directory. -2. If the upstream change is to the binary PDP-8 media image files and - you're unwilling to overwrite them wholesale, you'll have to mount - both versions of the media image files under the PDP-8 simulator and - copy the changes over by hand. - -3. If your previously installed binary OS media images — e.g. the OS/8 - RK05 disk image that the simulator boots from by default — are - precious but the simulator configuration scripts aren't precious, - you can just copy the generated `boot/*.script` files from the build - directory into the installation directory, `$prefix/share/boot`. - (See the `--prefix` option above for the meaning of `$prefix`.) +2. If the change is to the binary PDP-8 media image files — including + the [generated OS/8 disk images](#os8di) — and you're unwilling to + overwrite your existing ones wholesale, you'll have to mount both + versions of the media image files under the PDP-8 simulator and copy + the changes over by hand. + +3. If your previously installed binary OS media images — e.g. the + [OS/8 RK05 disk image](#os8di) that the simulator boots from by + default — are precious but the simulator configuration scripts + aren't precious, you can just copy the generated `boot/*.script` + files from the build directory into the installation directory, + `$prefix/share/boot`. (See the `--prefix` option above for the + meaning of `$prefix`.) 4. If neither your previously installed simulator configuration files nor the binary media images are precious, you can force the installation script to overwrite them both with a `sudo make mediainstall` command after `sudo make install`. @@ -307,12 +688,12 @@ Beware that this is potentially destructive! If you've made changes to your PDP-8 operating systems or have saved files to your OS system disks, this option will overwrite those changes! - -## Testing + +## Testing Your PiDP-8/I Hardware You can test your PiDP-8/I LED and switch functions with these commands: $ sudo systemctl stop pidp8i $ pidp8i-test @@ -329,15 +710,15 @@ $ sudo systemctl start pidp8i See [its documentation][test] for more details. - + ## Using the Software -For the most part, this software distribution works like the upstream -[2015.12.15 distribution][usd]. Its [documentation][prj] therefore +For the most part, this software distribution works like the [old stable +2015.12.15 distribution][osd]. Its [documentation][oprj] therefore describes this software too, for the most part. The largest user-visible difference between the two software distributions is that all of the shell commands affecting the software were renamed to include `pidp8i` in their name: @@ -366,28 +747,27 @@ 5. To shut the simulator down from the Raspbian command line: $ sudo systemctl stop pidp8i -There are [other major differences][mdif] between the upstream +There are [other major differences][mdif] between the old stable distribution and this one. See that linked wiki article for details. ## License Copyright © 2016-2017 by Warren Young. This document is licensed under the terms of [the SIMH license][sl]. -[prj]: https://tangentsoft.com/pidp8i/ -[upst]: http://obsolescence.wixsite.com/obsolescence/pidp-8 +[cprj]: https://tangentsoft.com/pidp8i/ [sm1]: http://obsolescence.wixsite.com/obsolescence/2016-pidp-8-building-instructions [sm2]: https://groups.google.com/d/msg/pidp-8/-leCRMKqI1Q/Dy5RiELIFAAJ -[usd]: http://obsolescence.wixsite.com/obsolescence/pidp-8-details +[osd]: http://obsolescence.wixsite.com/obsolescence/pidp-8-details [dt2]: https://github.com/VentureKing/Deeper-Thought-2 [sdoc]: https://tangentsoft.com/pidp8i/uv/doc/simh/main.pdf -[prj]: http://obsolescence.wixsite.com/obsolescence/pidp-8 +[oprj]: http://obsolescence.wixsite.com/obsolescence/pidp-8 [test]: https://tangentsoft.com/pidp8i/doc/trunk/doc/pidp8i-test.md [thro]: https://tangentsoft.com/pidp8i/doc/trunk/README-throttle.md [mdif]: https://tangentsoft.com/pidp8i/wiki?name=Major+Differences [sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md [ils]: https://tangentsoft.com/pidp8i/wiki?name=Incandescent+Lamp+Simulator DELETED RELEASE-PROCESS.md Index: RELEASE-PROCESS.md ================================================================== --- RELEASE-PROCESS.md +++ /dev/null @@ -1,140 +0,0 @@ -# PiDP-8/I Software Release Process - -This documents the process for producing release versions of the -software. - - -## Update ChangeLog.md - -Trawl the Fossil timeline for user-visible changes since the last -release, and write them up in user-focused form into the `ChangeLog.md` -file. If a regular user of the software cannot see a given change, it -shouldn't go in the `ChangeLog.md`; let it be documented via the -timeline only. - - -## Update the Release Branch - -Run `make release` to check the `ChangeLog.md` file changes in, tagging -that checkin with a release version tag of the form vYYYYMMDD and merge -those changes into the `release` branch. - -It runs entirely automatically unless an error occurs, in which case it -stops immediately, so check its output for errors before continuing. - - -## Update the Home Page Links - -The zip and tarball links on the front page produce files named after -the date of the release. Those dates need to be updated immediately -after tagging the release, since they point at the "release" tag applied -by the previous step, so they begin shipping the new release immediately -after tagging it. - - -## Produce the Normal Binary OS Image - -Start with the latest version of [Raspbian Lite][os] on a multi-core -Raspberry Pi. - -1. If the version of the base OS has changed since the last binary OS - image was created, download the new one. - - While the OS is downloading, zero the SD card you're going to use - for this, so the prior contents don't affect this process. - - Blast the base OS image onto the cleaned SD card. - -2. Boot it up on a multi-core Pi. - - Log in, then retreive and initialize BOSI: - - $ wget https://tangentsoft.com/bosi - $ chmod +x bosi - $ exec sudo ./bosi init - - The `exec` bit is required so that the `bosi` invocation is run as - root without any processes running as `pi` in case the `init` step - sees that user `pi` hasn't been changed to `pidp8i` here: the - `usermod` command we give to make that change will refuse to do what - we ask if there are any running processes owned by user `pi`. - - It will either reboot the system after completing its tasks - successfully or exit early, giving the reason it failed. - -3. Clone the software repo and build the softare: - - $ ./bosi build - - On reboot, say `top -H` to make sure the software is running and - that the CPU percentages are reasonable for the platform. - - You may also want to check that it is running properly with a - `pidp8i` command. Is the configuration line printed by the - simulator correct? Does OS/8 run? Are there any complaints from - SIMH, such as about insufficient CPU power? - -4. Do final inside-the-image steps: - - $ ./bosi prepare - -5. Move the SD card to a USB reader plugged into the Pi, boot the Pi - from its prior SD card, and shrink the OS image: - - $ wget https://tangentsoft.com/bosi - $ chmod +x bosi - $ ./bosi shrink - -6. Move the USB reader to the Mac,¹ then say: - - $ bosi image [nls] - - For the ILS images, you can give "ils" as a parameter or leave it - blank. - -7. The prior step rewrote the SD card with the image file it created. - Boot it up and make sure it still works. If you're happy with it, - give this command *on the desktop PC*. - - $ bosi finish [nls] - - As above, the parameter can be "ils" or left off for the ILS images. - -[os]: https://www.raspberrypi.org/downloads/raspbian/ - - -## Produce the "No Lamp Simulator" Binary OS Image - -Do the same series of steps above on a single-core Raspberry Pi, except -that you give "nls" parameters to the `image` and `finish` steps. - - -## Publicizing - -While the NLS image uploads — the ILS image was already sent in step 7 -in the first pass through the list above — compose the announcement -message, and modify the front page to point to the new images. You -might also need to update the approximate image sizes reported on that -page. Post the announcement message and new front page once that second -upload completes. - - ----------------------- - -### Footnotes - -1. The image production steps could just as well be done on a Linux box - or on a Windows box via Cygwin or WSL, but the commands in that - final stage change due to OS differences. Since this document - exists primarily for use by the one who uses it, there is little - point in having alternatives for other desktop OSes above. Should - someone else take over maintainership, they can translate the above - commands for their own desktop PC. - - -### License - -Copyright © 2016-2017 by Warren Young. This document is licensed under -the terms of [the SIMH license][sl]. - -[sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md DELETED asm/asr33-rim-loader.pal Index: asm/asr33-rim-loader.pal ================================================================== --- asm/asr33-rim-loader.pal +++ /dev/null @@ -1,49 +0,0 @@ -/ asr33-rim-loader.pal -/ -/ Load paper tapes in RIM format from a Teletype Model 33 ASR's paper -/ tape reader. By contrast with the loader in hs-rim-loader.pal, this -/ is the low-speed paper tape RIM loader. -/ -/ This is the RIM loader printed on the front panel of the PDP-8/I. The -/ RIM loader in hs-rim-loader.pal (which is translated to boot/1.script -/ at build time) is similar to this one, but it works on the DEC high- -/ speed paper tape reader peripheral, which is where the PiDP-8/I's -/ automatic paper tape mounting feature attaches any tape images it -/ finds via USB. -/ -/ Therefore, you cannot use this RIM loader if you want to use the -/ PiDP-8/I's automatic media attachment mechanism. It is included -/ here mainly for documentation at the moment, but also in case -/ someone works out a way to build the simulator so that it can -/ conditionally load tapes from an emulated ASR-33. -/ -/ Raw disassembly done from the octal values by Bernhard Baehr's PDP-8/E -/ Simulator. Comments and labels by Warren Young. Original copyright -/ by Digital Equipment Corporation: this program appeared in DEC manuals -/ and was printed on the front panel of every PDP-8/I. -/ -/ SIMH: echo Installing the RIM loader for the ASR 33 paper tape reader... -/ SIMH: set df disabled - - *7755 - HLT / nonstandard: auto-halt on SIMH startup - - / normal RIM loader entry point, 7756 -NEXT, KCC / clear PTR flag -RBYTE1, KSF / loop until PTR is ready - JMP RBYTE1 - KRB / read first byte in - CLL RTL / shift it left by 2, clearing link as well - RTL / and 2 more again - SPA / if top bit of AC is set... - JMP RBYTE1 / ...AC contains the addr's value - RTL / ...else rotate it another 2 positions -RBYTE2, KSF / wait for next character - JMP RBYTE2 - KRS / read second byte in - SNL / if link is set... - DCA I BYTE / ...it's the value's address -GOTVAL, DCA BYTE / ...else it's the value at that addr - JMP NEXT / go round again, getting next value from PTR -BYTE, 0 -$ DELETED asm/hs-rim-loader.pal Index: asm/hs-rim-loader.pal ================================================================== --- asm/hs-rim-loader.pal +++ /dev/null @@ -1,47 +0,0 @@ -/ hs-rim-loader.pal -/ -/ Load paper tapes in RIM format from the DEC high-speed PTR. -/ -/ This routine differs from that printed in DEC's manuals in that it -/ starts with a HLT instruction so that when you [re]start SIMH with -/ IF=1, running this program, the processor auto-halts, giving the user -/ the opportunity to attach a paper tape via DF or ATTACH, then start -/ the processor at 7756 as normal. -/ -/ The RIM loader code printed on the front panel of the PDP-8/I differs -/ because it is for the the paper tape reader built into a Teletype -/ Model 33 ASR. See asr33-rim-loader.pal for that other implementation, -/ including more information about it. -/ -/ Raw disassembly done from the octal values by Bernhard Baehr's PDP-8/E -/ Simulator. Comments and labels by Warren Young. Original copyright -/ by Digital Equipment Corporation: this program appeared in many DEC -/ manuals printed throughout the PDP-8 era variants of which were made -/ (and thus documented) from 1965 to 1979. -/ -/ SIMH: echo Installing the RIM loader for the DEC high-speed tape reader... -/ SIMH: set df disabled -/ SIMH: set cpu noidle - - *7755 - HLT / nonstandard: auto-halt on SIMH startup - - / normal RIM loader entry point, 7756 - RFC / clear PTR flag -RBYTE1, RSF / loop until PTR is ready - JMP RBYTE1 - RCC / read first byte in - CLL RTL / shift it left by 2, clearing link as well - RTL / and 2 more again - SPA / if top bit of AC is set... - JMP GOTVAL / ...AC contains the addr's value - RTL / ...else rotate it another 2 positions -RBYTE2, RSF / wait for next character - JMP RBYTE2 - RCC / read second byte in - SNL / if link is set... - DCA I BYTE / ...it's the value's address -GOTVAL, DCA BYTE / ...else it's the value at that addr - JMP RBYTE1 / go round again, getting next value from PTR -BYTE, 0 -$ Index: auto.def ================================================================== --- auto.def +++ auto.def @@ -31,28 +31,120 @@ define defaultprefix /opt/pidp8i use cc use cc-lib +use cc-shared + +# Canonicalize some paths which may be relative and generate others from them +set abspfx [file-normalize [get-define prefix]] +define ABSPREFIX $abspfx +define BOOTDIR "$abspfx/share/boot" +define MEDIADIR "$abspfx/share/media" +# Define command line options options { alt-serial-mod => "use GPIO drive scheme suitable for James L-W's serial mod method" + cc8-cross=1 => "do not build the cc8 cross-compiler on the host" debug-mode => "create a debug build (default is release)" lowercase: => "select how lowercase input is to be handled" no-lamp-simulator => "use simple LED driver instead of incandescent lamp simulator" - os8-advent=1 => "add the game of ADVENT to built OS/8 RK05 image" - os8-ba=1 => "leave *.BA BASIC games and demos off built OS/8 RK05 disk image" - os8-cc8=1 => "leave the native OS/8 CC8 compiler off the built OS/8 RK05 disk image" - os8-crt=1 => "set CRT-style rubout processing in built OS/8 KR05 image" - os8-chess=1 => "add the game of CHESS to built OS/8 RK05 image" - os8-k12=1 => "leave 12-bit Kermit off built OS/8 RK05 disk image" - os8-music => "add *.MU files to built OS/8 RK05 disk image" + os8-advent=1 => "leave Adventure off the built OS/8 RK05 image" + os8-ba=1 => "leave *.BA BASIC games and demos off the built OS/8 RK05 image" + os8-cc8=1 => "leave the native OS/8 CC8 compiler off the built OS/8 RK05 image" + os8-crt=1 => "suppress CRT-style rubout processing in the built OS/8 RK05 image" + os8-chess=1 => "leave the CHECKMO-II game of chess off the built OS/8 RK05 image" + os8-dcp=1 => "leave the DCP disassembler off the built OS/8 RK05 image" + os8-focal=1 => "leave FOCAL 69 and U/W FOCAL off the built OS/8 RK05 image" + os8-focal69 => "add FOCAL 69 to the built OS/8 RK05 image" + os8-fortran-ii=1 => "leave FORTRAN II off the built OS/8 RK05 image" + os8-fortran-iv=1 => "leave FORTRAN IV off the built OS/8 RK05 image" + os8-init=1 => "do not show the OS/8 INIT message in the built OS/8 RK05 image" + os8-k12=1 => "leave 12-bit Kermit off the built OS/8 RK05 image" + os8-macrel=1 => "leave MACREL assembler off the built OS/8 RK05 image" + os8-minimal => "set all --disable-os8-* options, giving minimal OS/8 bin disk" + os8-music => "add *.MU files to the built OS/8 RK05 image" + os8-patches=1 => "do not apply DEC patches to the built OS/8 RK05 bin image" + os8-src=1 => "do not build os8v3d-src.rk05 from OS/8 source tapes" + os8-uwfocal=1 => "leave U/W FOCAL (only) off the built OS/8 RK05 image" os8-vtedit => "add the TECO VTEDIT setup to built OS/8 RK05 image" serial-mod => "use GPIO drive scheme suitable for Oscar Vermeulen's serial mod method" throttle: => "override the throttle values in the boot scripts" } +# Handle meta-options first. +if {![opt-bool os8-focal]} { + if {[opt-bool os8-focal69]} { + dict set $::autosetup(optset) "os8-focal69" 0 + } + if {[opt-bool os8-uwfocal]} { + dict set $::autosetup(optset) "os8-uwfocal" 0 + } +} +set os8min [opt-bool os8-minimal] +set os8src [opt-bool os8-src] + +# Translate the --*-os8-* options to mkos8 command line option format +# and build the Python option map it uses to interpret them. This +# avoids the need to define the options by hand in both places. +set mkos8_opts "" +set os "# GENERATED BY auto.def; DO NOT MODIFY.\n\nopts = {\n" +foreach opt [lsort [dict keys $::autosetup(optdefault)]] { + # Skip meta-options handled above and non --*-os8-* options. + if {$opt in {os8-focal os8-minimal os8-src}} { continue } + if {[string first "os8-" $opt] != 0} { continue } + + # Add next line to Python output + set od [dict get $::autosetup(optdefault) $opt] + set oh [dict get $::autosetup(opthelpline) $opt] + set obn [string replace $opt 0 3] + append os "\t\"" $obn "\": \[ $od, \"" $oh "\" \],\n" + + # Fix grammar for upcoming msg-result calls + if {[string first "do not " $oh] == 0} { + set oh [string replace $oh 0 6] + } elseif {[string first "do " $oh] == 0} { + set oh [string replace $oh 0 2] + } + + # Choose option to pass to mkos8 corresponding to this configure + # script option. Also tell user what we're going to do; grammar + # is inverted because the option help text says what happens when + # you give the opposite of the option's default argument. + set ov [opt-bool $opt] + if {$ov == $od} { + # Don't append a mkos8 option; the default is correct. + msg-result "Will not $oh." + } elseif {!$os8min && $ov} { + append mkos8_opts " --enable-" $obn + } else { + append mkos8_opts " --disable-" $obn + } +} +append os "}\n" +if {![file exists "lib/mkos8"]} { file mkdir "lib/mkos8" } +write-if-changed "lib/mkos8/opts.py" $os + +# Handle the OS/8 options not passed to mkos8 generically by above. +if {$os8src} { + msg-result "Building os8v3d-src.rk05 from OS/8 source tapes." + define OS8_SRC_RK05 bin/os8v3d-src.rk05 +} else { + msg-result "Will not build os8v3d-src.rk05 from OS/8 source tapes." + define OS8_SRC_RK05 {} +} +if {$os8min || ![opt-bool os8-patches]} { + msg-result "Will not patch OS/8 on the RK05 bin disk." + define OS8_BOOT_DISK "os8v3d-bin.rk05" + define MKOS8_BIN_PATCHES {} +} else { + msg-result "Will apply OS/8 V3D patches to the RK05 bin disk." + define OS8_BOOT_DISK "os8v3d-patched.rk05" + define MKOS8_BIN_PATCHES patch +} + +# React to remaining chosen command line options if {[opt-bool alt-serial-mod]} { msg-result "GPIO drive adjusted for James L-W's serial mods to the PiDP-8/I PCB." define PCB_SERIAL_MOD_JLW define PCB_SERIAL_MOD_ANY } @@ -60,70 +152,55 @@ if {[opt-bool serial-mod]} { msg-result "GPIO drive adjusted for O. Vermeulen's serial mods to the Pi & PiDP-8/I PCBs." define PCB_SERIAL_MOD_OV define PCB_SERIAL_MOD_ANY } + +if {[opt-bool cc8-cross]} { + define CC8_CROSS bin/cc8 +} else { + define CC8_CROSS {} + msg-result "Will not build the cc8 cross-compiler." +} if {[opt-bool debug-mode]} { msg-result "Creating a debuggable build." define BUILDMODE {-O0 -g} } else { msg-result "Creating a release build." define BUILDMODE {-O2} } -set mk_os8_rk05s_options "" - set lv [opt-val lowercase] if {$lv == ""} { set lv "auto" } if {$lv == "auto"} { define SIMH_PASS_LOWERCASE } elseif {$lv == "pass"} { define SIMH_PASS_LOWERCASE - append mk_os8_rk05s_options " --without-lcmod" + append mkos8_opts " --disable-lcmod" } elseif {$lv == "upper"} { - append mk_os8_rk05s_options " --without-lcmod" + append mkos8_opts " --disable-lcmod" } else { user-error "Legal values for --lowercase are {auto,pass,upper}." } msg-result "Lowercase handling set to '$lv' mode." -if {![opt-bool os8-advent]} { - msg-result "Will not add game of ADVENT to the OS/8 RK05 disk image." - append mk_os8_rk05s_options " --without-advent" -} -if {![opt-bool os8-ba]} { - msg-result "Will not add BASIC games and demos to the OS/8 RK05 disk image." - append mk_os8_rk05s_options " --without-ba" -} -if {![opt-bool os8-cc8]} { - msg-result "Will not add CC8 to the OS/8 RK05 disk image." - append mk_os8_rk05s_options " --without-cc8" -} -if {![opt-bool os8-chess]} { - msg-result "Will not add game of CHESS to the OS/8 RK05 disk image." - append mk_os8_rk05s_options " --without-chess" -} -if {![opt-bool os8-crt]} { - msg-result "Will not add BASIC games and demos to the OS/8 RK05 disk image." - append mk_os8_rk05s_options " --without-crt" -} -if {![opt-bool os8-k12]} { - msg-result "Will not add Kermit-12 to the OS/8 RK05 disk image." - append mk_os8_rk05s_options " --without-k12" -} -if {[opt-bool os8-music]} { - msg-result "Will add music files to the OS/8 RK05 disk image." - append mk_os8_rk05s_options " --with-music" -} else { - msg-result "Will not add music files to the OS/8 RK05 disk image." -} -if {[opt-bool os8-vtedit]} { - msg-result "Will add VTEDIT setup to the OS/8 RK05 disk image." - append mk_os8_rk05s_options " --with-vtedit" -} -define MK_OS8_RK05_OPTS $mk_os8_rk05s_options +if {[opt-bool os8-cc8] && ![opt-bool os8-fortran-ii]} { + msg-result "Must clear --disable-os8-fortran-ii when CC8 is enabled." + regsub -- {--disable-os8-fortran-ii } $mkos8_opts {} +} + +# We've purposely held off exporting the mkos8 option set until now +# because some of the configuration options can affect the option set. +define MKOS8_OPTS $mkos8_opts + +# Force a rebuild of the OS/8 media if the option set changed. +if {![file exists "obj"]} { file mkdir "obj" } +write-if-changed "obj/mkos8.opts" $mkos8_opts { + file delete -force bin/os8v3d-bin.rk05 + msg-result "mkos8 options changed; will rebuild OS/8 disk images." +} # High-level definitions set builddir [get-define builddir] set srcdir [get-define srcdir] set cores [exec $srcdir/tools/corecount] @@ -224,15 +301,10 @@ msg-result "Found GNU install(1) program as [get-define INSTALL]." # If we have cscope here, we'll use it in the "tags" target define HAVE_PROG_CSCOPE [cc-check-progs cscope] -# Canonicalize some paths which may be relative and generate others from them -define ABSPREFIX [file normalize [get-define prefix]] -define BOOTDIR "[get-define ABSPREFIX]/share/boot" -define MEDIADIR "[get-define ABSPREFIX]/share/media" - # Remember the name and primary group of the user who installed this, since # we want to give that group write privileges to some files when they're # installed, and we want them to own the screen(1) session. set instgrp [exec id -grn] set instusr [exec id -urn] @@ -272,11 +344,11 @@ set cmd "$srcdir/$tool" set status [catch {exec $cmd} version] if {$status != 0} { # The most likely cause for tools/version to fail is that the repo # file has become disassociated from the local checkout directory. - set sql "select value from vvar where name=\"repository\"" + set sql ".timeout 5000 ; select value from vvar where name=\"repository\"" set cmd "fossil sql --no-repository $srcdir/.fslckout '$sql'" set status [catch {exec /bin/sh -c $cmd} path] if {$status != 0} { user-error "Fossil doesn't seem to work here. Is it installed?\nCMD: $cmd" } elseif {[file exists $path]} { @@ -285,11 +357,17 @@ user-error "$tool failed: $path does not exist." } } define VERSION $version -# We need Python and pexpect installed here for mkos8 to run. +# Get host, user, and date info for use by media/os8/init.tx. +catch {exec hostname} host +set user $::env(USER) +define BUILDUSER "$user@$host" +define BUILDTS [clock format [clock seconds] -format "%Y.%m.%d at %T %Z"] + +# The mkos8 script requires Python and some non-core modules. set status [catch {exec python -c exit} result] if {$status != 0} { user-error "Python does not appear to be installed here. It is required." } msg-result "Python is installed here." @@ -297,20 +375,47 @@ if {$status != 0} { set msg "The Python pexpect module is not installed here. Fix with\n" append msg "\n sudo pip install pexpect\n" append msg "\nOR:\n" append msg "\n sudo easy_install pexpect\n" + append msg "\nOR:\n" + append msg "\n sudo apt install python-pexpect\n" user-error $msg } msg-result "Python module pexpect is installed here." +set status [catch {exec python -c "import pkg_resources" 2> /dev/null} result] +if {$status != 0} { + set msg "The Python pkg_resources module is not installed here. Fix with\n" + append msg "\n sudo pip install pkg_resources\n" + append msg "\nOR:\n" + append msg "\n sudo easy_install pkg_resources\n" + append msg "\nOR:\n" + append msg "\n sudo apt install python-pkg-resources\n" + user-error $msg +} +msg-result "Python module pkg_resources is installed here." # Build Deeper Thought if we find it here if {[file exists "[get-define srcdir]/src/deeper.c"]} { set ls [string toupper "[get-define LED_DRIVER_MODULE]ls"] msg-result "Found Deeper Thought; building it against $ls GPIO module" define BUILD_DEEPER_THOUGHT 1 } + +# Generate autodependency Makefile rule sets. +# +# It is important to list "src" last, because GNU make takes the first +# one that matches, and the wildcards in the generated rules for "src" +# match all "src/*" subdirs. +set status [catch { + exec $srcdir/tools/mkadrules $srcdir src/cc8/cross src/d8tape src/palbart src/PDP8 src +} result] +if {$status == 0} { + msg-result $result +} else { + user-error "Failed to generate autodependency rules: $result!" +} # Write outputs. # # NOTE: If you change the list of files here, change INFILES in # Makefile.in, too. @@ -323,16 +428,21 @@ make-template boot/2.script.in make-template boot/3.script.in make-template boot/4.script.in make-template boot/6.script.in make-template boot/7.script.in +make-template boot/run.script.in make-template etc/pidp8i-init.in make-template etc/sudoers.in make-template examples/Makefile.in +make-template lib/pidp8i/__init__.py.in +make-template lib/pidp8i/dirs.py.in +make-template lib/pidp8i/ips.py.in +make-template media/os8/init.tx.in make-template src/Makefile.in +make-template src/cc8/Makefile.in +make-template src/cc8/os8/Makefile.in make-template src/gpio-common.c.in make-template src/PDP8/Makefile.in make-template src/PDP8/pidp8i.c.in -make-template tools/mkos8.in make-template tools/simh-update.in -exec chmod +x "$builddir/tools/mkos8" exec chmod +x "$builddir/tools/simh-update" Index: autosetup/autosetup ================================================================== --- autosetup/autosetup +++ autosetup/autosetup @@ -72,13 +72,16 @@ set autosetup(cmdline) {} # options is a list of known options set autosetup(options) {} # optset is a dictionary of option values set by the user based on getopt set autosetup(optset) {} - # optdefault is a dictionary of default values for options + # optdefault is a dictionary of default values set autosetup(optdefault) {} + # options-defaults is a dictionary of overrides for default values for options + set autosetup(options-defaults) {} set autosetup(optionhelp) {} + set autosetup(opthelpline) {} set autosetup(showhelp) 0 # Parse options use getopt @@ -92,12 +95,23 @@ licence license => "display the autosetup license" version => "display the version of autosetup" ref:=text manual:=text reference:=text => "display the autosetup command reference. 'text', 'wiki', 'asciidoc' or 'markdown'" debug => "display debugging output as autosetup runs" - sysinstall:path => "install standalone autosetup to the given directory (e.g.: /usr/local)" install:=. => "install autosetup to the current or given directory" + } + if {$autosetup(installed)} { + # hidden options so we can produce a nice error + options-add { + sysinstall:path + } + } else { + options-add { + sysinstall:path => "install standalone autosetup to the given directory (e.g.: /usr/local)" + } + } + options-add { force init:=help => "create initial auto.def, etc. Use --init=help for known types" # Undocumented options option-checking=1 nopager quiet @@ -109,12 +123,12 @@ puts $autosetup(version) exit 0 } # autosetup --conf=alternate-auto.def - if {[opt-val conf] ne ""} { - set autosetup(autodef) [lindex [opt-val conf] end] + if {[opt-str conf o]} { + set autosetup(autodef) $o } # Debugging output (set this early) incr autosetup(debug) [opt-bool debug] incr autosetup(force) [opt-bool force] @@ -128,47 +142,47 @@ } # Now any auto-load modules autosetup_load_auto_modules - if {[opt-val help] ne ""} { + if {[opt-str help o]} { incr autosetup(showhelp) use help - autosetup_help [lindex [opt-val help] end] + autosetup_help $o } if {[opt-bool licence license]} { use help autosetup_show_license exit 0 } - if {[opt-val {manual ref reference}] ne ""} { + if {[opt-str {manual ref reference} o]} { use help - autosetup_reference [lindex [opt-val {manual ref reference}] end] + autosetup_reference $o } # Allow combining --install and --init set earlyexit 0 - if {[opt-val install] ne ""} { + if {[opt-str install o]} { use install - autosetup_install [lindex [opt-val install] end] + autosetup_install $o incr earlyexit } - if {[opt-val init] ne ""} { + if {[opt-str init o]} { use init - autosetup_init [lindex [opt-val init] end] + autosetup_init $o incr earlyexit } if {$earlyexit} { exit 0 } - if {[opt-val sysinstall] ne ""} { + if {[opt-str sysinstall o]} { use install - autosetup_install [lindex [opt-val sysinstall] end] 1 + autosetup_install $o 1 exit 0 } if {![file exists $autosetup(autodef)]} { # Check for invalid option first @@ -253,15 +267,14 @@ # @opt-val optionlist ?default=""? # # Returns a list containing all the values given for the non-boolean options in '$optionlist'. # There will be one entry in the list for each option given by the user, including if the # same option was used multiple times. -# If only a single value is required, use something like: -# -## lindex [opt-val $names] end # # If no options were set, '$default' is returned (exactly, not as a list). +# +# Note: For most use cases, 'opt-str' should be preferred. # proc opt-val {names {default ""}} { option-check-names {*}$names foreach opt $names { @@ -272,10 +285,83 @@ if {[info exists result]} { return $result } return $default } + +# @opt-str optionlist varname ?default? +# +# Sets '$varname' in the callers scope to the value for one of the given options. +# +# For the list of options given in '$optionlist', if any value is set for any option, +# the option value is taken to be the *last* value of the last option (in the order given). +# +# If no option was given, and a default was specified with 'options-defaults', +# that value is used. +# +# If no 'options-defaults' value was given and '$default' was given, it is used. +# +# If none of the above provided a value, no value is set. +# +# The return value depends on whether '$default' was specified. +# If it was, the option value is returned. +# If it was not, 1 is returns if a value was set, or 0 if not. +# +# Typical usage is as follows: +# +## if {[opt-str {myopt altname} o]} { +## do something with $o +## } +# +# Or: +## define myname [opt-str {myopt altname} o "/usr/local"] +# +proc opt-str {names varname args} { + global autosetup + + option-check-names {*}$names + upvar $varname value + + if {[llength $args]} { + # A default was given, so always return the string value of the option + set default [lindex $args 0] + set retopt 1 + } else { + # No default, so return 0 or 1 to indicate if a value was found + set retopt 0 + } + + foreach opt $names { + if {[dict exists $::autosetup(optset) $opt]} { + set result [lindex [dict get $::autosetup(optset) $opt] end] + } + } + + if {![info exists result]} { + # No user-specified value. Has options-defaults been set? + foreach opt $names { + if {[dict exists $::autosetup(options-defaults) $opt]} { + set result [dict get $autosetup(options-defaults) $opt] + } + } + } + + if {[info exists result]} { + set value $result + if {$retopt} { + return $value + } + return 1 + } + + if {$retopt} { + set value $default + return $value + } + + return 0 +} proc option-check-names {args} { foreach o $args { if {$o ni $::autosetup(options)} { autosetup-error "Request for undeclared option --$o" @@ -304,10 +390,11 @@ # This is a special heading lappend autosetup(optionhelp) $opt "" set header {} continue } + unset -nocomplain defaultvalue equal value #puts "i=$i, opt=$opt" regexp {^([^:=]*)(:)?(=)?(.*)$} $opt -> name colon equal value if {$name in $autosetup(options)} { autosetup-error "Option $name already specified" @@ -321,10 +408,16 @@ # This is a documentation-only option, like "-C " set opthelp $opt } elseif {$colon eq ""} { # Boolean option lappend autosetup(options) $name + + # Check for override + if {[dict exists $autosetup(options-defaults) $name]} { + # A default was specified with options-defaults, so use it + set value [dict get $autosetup(options-defaults) $name] + } if {$value eq "1"} { set opthelp "--disable-$name" } else { set opthelp "--$name" @@ -332,11 +425,12 @@ # Set the default if {$value eq ""} { set value 0 } - dict set autosetup(optdefault) $name $value + set defaultvalue $value + dict set autosetup(optdefault) $name $defaultvalue if {[dict exists $autosetup(getopt) $name]} { # The option was specified by the user. Look at the last value. lassign [lindex [dict get $autosetup(getopt) $name] end] type setvalue if {$type eq "str"} { @@ -353,19 +447,39 @@ #puts "Found boolean option --$name=$setvalue" } } else { # String option. lappend autosetup(options) $name + + if {$colon eq ":"} { + # Was ":name=default" given? + # If so, set $value to the display name and $defaultvalue to the default + # (This is the preferred way to set a default value for a string option) + if {[regexp {^([^=]+)=(.*)$} $value -> value defaultvalue]} { + dict set autosetup(optdefault) $name $defaultvalue + } + } + + # Maybe override the default value + if {[dict exists $autosetup(options-defaults) $name]} { + # A default was specified with options-defaults, so use it + set defaultvalue [dict get $autosetup(options-defaults) $name] + dict set autosetup(optdefault) $name $defaultvalue + } elseif {![info exists defaultvalue]} { + # For backward compatiblity, if ":name" was given, use name as both + # the display text and the default value, but only if the user + # specified the option without the value + set defaultvalue $value + } if {$equal eq "="} { # String option with optional value set opthelp "--$name?=$value?" } else { # String option with required value set opthelp "--$name=$value" } - dict set autosetup(optdefault) $name $value # Get the values specified by the user if {[dict exists $autosetup(getopt) $name]} { set listvalue {} @@ -374,11 +488,11 @@ if {$type eq "bool" && $setvalue} { if {$equal ne "="} { user-error "Option --$name requires a value" } # If given as a boolean, use the default value - set setvalue $value + set setvalue $defaultvalue } lappend listvalue $setvalue } #puts "Found string option --$name=$listvalue" @@ -387,17 +501,21 @@ } # Now create the help for this option if appropriate if {[lindex $opts $i+1] eq "=>"} { set desc [lindex $opts $i+2] + if {[info exists defaultvalue]} { + set desc [string map [list @default@ $defaultvalue] $desc] + } #string match \n* $desc if {$header ne ""} { lappend autosetup(optionhelp) $header "" set header "" } # A multi-line description lappend autosetup(optionhelp) $opthelp $desc + dict set autosetup(opthelpline) $name $desc incr i 2 } } } @@ -477,11 +595,11 @@ } # @options optionspec # # Specifies configuration-time options which may be selected by the user -# and checked with 'opt-val' and 'opt-bool'. '$optionspec' contains a series +# and checked with 'opt-str' and 'opt-bool'. '$optionspec' contains a series # of options specifications separated by newlines, as follows: # # A boolean option is of the form: # ## name[=0|1] => "Description of this boolean option" @@ -495,10 +613,13 @@ ## name:[=]value => "Description of this option" # # If the 'name:value' form is used, the value must be provided with the option (as '--name=myvalue'). # If the 'name:=value' form is used, the value is optional and the given value is used as the default # if it is not provided. +# +# The description may contain '@default@', in which case it will be replaced with the default +# value for the option (taking into account defaults specified with 'options-defaults'. # # Undocumented options are also supported by omitting the '=> description'. # These options are not displayed with '--help' and can be useful for internal options or as aliases. # # For example, '--disable-lfs' is an alias for '--disable=largefile': @@ -521,10 +642,21 @@ user-error "Unknown option --$o" } } } } + +# @options-defaults dictionary +# +# Specifies a dictionary of options and a new default value for each of those options. +# Use before any 'use' statements in 'auto.def' to change the defaults for +# subsequently included modules. +proc options-defaults {dict} { + foreach {n v} $dict { + dict set ::autosetup(options-defaults) $n $v + } +} proc config_guess {} { if {[file-isexec $::autosetup(dir)/autosetup-config.guess]} { if {[catch {exec-with-stderr sh $::autosetup(dir)/autosetup-config.guess} alias]} { user-error $alias @@ -573,13 +705,19 @@ # Any extra values are similarly appended. # If any value is already contained in the variable (as a substring) it is omitted. # proc define-append {name args} { if {[get-define $name ""] ne ""} { - # Make a token attempt to avoid duplicates + # Avoid duplicates foreach arg $args { - if {[string first $arg $::define($name)] == -1} { + set found 0 + foreach str [split $::define($name) " "] { + if {$str eq $arg} { + incr found + } + } + if {!$found} { append ::define($name) " " $arg } } } else { set ::define($name) [join $args] @@ -1552,15 +1690,20 @@ # autosetup(installed)=1 means that autosetup is not running from source # autosetup(sysinstall)=1 means that autosetup is running from a sysinstall verion # shared=1 means that we are trying to do a sysinstall. This is only possible from the development source. proc autosetup_install {dir {shared 0}} { - if {$shared && $::autosetup(installed)} { - user-error "Can only --sysinstall from development sources" + global autosetup + if {$shared} { + if {$autosetup(installed) || $autosetup(sysinstall)} { + user-error "Can only --sysinstall from development sources" + } + } elseif {$autosetup(installed) && !$autosetup(sysinstall)} { + user-error "Can't --install from project install" } - if {$::autosetup(sysinstall)} { + if {$autosetup(sysinstall)} { # This is the sysinstall version, so install just uses references cd $dir puts "[autosetup_version] creating configure to use system-installed autosetup" autosetup_create_configure 1 @@ -1590,11 +1733,11 @@ set f [open $target w] set publicmodules {} # First the main script, but only up until "CUT HERE" - set in [open $::autosetup(dir)/autosetup] + set in [open $autosetup(dir)/autosetup] while {[gets $in buf] >= 0} { if {$buf ne "##-- CUT HERE --##"} { puts $f $buf continue } @@ -1602,11 +1745,11 @@ # Insert the static modules here # i.e. those which don't contain @synopsis: # All modules are inserted if $shared is set puts $f "set autosetup(installed) 1" puts $f "set autosetup(sysinstall) $shared" - foreach file [lsort [glob $::autosetup(libdir)/*.{tcl,auto}]] { + foreach file [lsort [glob $autosetup(libdir)/*.{tcl,auto}]] { set modname [file tail $file] set ext [file ext $modname] set buf [readfile $file] if {!$shared} { if {$ext eq ".auto" || [string match "*\n# @synopsis:*" $buf]} { @@ -1619,20 +1762,20 @@ puts $f "\nset modsource($modname) \{" puts $f $buf puts $f "\}\n" } if {$shared} { - foreach {srcname destname} [list $::autosetup(libdir)/README.autosetup-lib README.autosetup \ - $::autosetup(srcdir)/LICENSE LICENSE] { + foreach {srcname destname} [list $autosetup(libdir)/README.autosetup-lib README.autosetup \ + $autosetup(srcdir)/LICENSE LICENSE] { dputs "install: importing $srcname as $destname" puts $f "\nset modsource($destname) \\\n[list [readfile $srcname]\n]\n" } } } close $in close $f - exec chmod 755 $target + catch {exec chmod 755 $target} set installfiles {autosetup-config.guess autosetup-config.sub autosetup-test-tclsh} set removefiles {} if {!$shared} { @@ -1655,12 +1798,11 @@ lassign $fileinfo source dest } else { lassign $fileinfo source set dest $source } - autosetup_install_file $::autosetup(dir)/$source $targetdir/$dest - exec chmod 755 $targetdir/$dest + autosetup_install_file $autosetup(dir)/$source $targetdir/$dest } # Remove obsolete files foreach file $removefiles { if {[file exists $targetdir/$file]} { @@ -1726,10 +1868,14 @@ dputs "install: $source => $target" if {![file exists $source]} { error "Missing installation file '$source'" } writefile $target [readfile $source]\n + # If possible, copy the file mode + file stat $source stat + set mode [format %o [expr {$stat(mode) & 0x1ff}]] + catch {exec chmod $mode $target} } proc autosetup_install_readme {target sysinstall} { set readme "README.autosetup created by [autosetup_version]\n\n" if {$sysinstall} { Index: autosetup/cc.tcl ================================================================== --- autosetup/cc.tcl +++ autosetup/cc.tcl @@ -28,15 +28,10 @@ use system module-options {} -# Note that the return code is not meaningful -proc cc-check-something {name code} { - uplevel 1 $code -} - # Checks for the existence of the given function by linking # proc cctest_function {function} { cctest -link 1 -declare "extern void $function\(void);" -code "$function\();" } @@ -153,11 +148,11 @@ } } # @cc-check-defines define ... # -# Checks that the given preprocessor symbol is defined. +# Checks that the given preprocessor symbols are defined. proc cc-check-defines {args} { cc-check-some-feature $args { cctest_define $each } } @@ -240,13 +235,12 @@ } } } } } - if {$found} { - define [feature-define-name $function] - } else { + define-feature $function $found + if {!$found} { msg-result "no" } return $found } Index: autosetup/pkg-config.tcl ================================================================== --- autosetup/pkg-config.tcl +++ autosetup/pkg-config.tcl @@ -47,12 +47,12 @@ msg-result $version define PKG_CONFIG_VERSION $version set found 1 - if {[opt-val sysroot] ne ""} { - define SYSROOT [file-normalize [lindex [opt-val sysroot] end]] + if {[opt-str sysroot o]} { + define SYSROOT [file-normalize $o] msg-result "Using specified sysroot [get-define SYSROOT]" } elseif {[get-define build] ne [get-define host]} { if {[catch {exec-with-stderr [get-define CC] -print-sysroot} result errinfo] == 0} { # Use the compiler sysroot, if there is one define SYSROOT $result Index: autosetup/system.tcl ================================================================== --- autosetup/system.tcl +++ autosetup/system.tcl @@ -20,19 +20,22 @@ ## includedir # # If '--prefix' is not supplied, it defaults to '/usr/local' unless 'defaultprefix' is defined *before* # including the 'system' module. -set defaultprefix [get-define defaultprefix /usr/local] +if {[is-defined defaultprefix]} { + user-notice "Note: defaultprefix is deprecated. Use options-defaults to set default options" + options-defaults [list prefix [get-define defaultprefix]] +} module-options [subst -noc -nob { host:host-alias => {a complete or partial cpu-vendor-opsys for the system where the application will run (defaults to the same value as --build)} build:build-alias => {a complete or partial cpu-vendor-opsys for the system where the application will be built (defaults to the result of running config.guess)} - prefix:dir => {the target directory for the build (defaults to '$defaultprefix')} + prefix:dir=/usr/local => {the target directory for the build (default: '@default@')} # These (hidden) options are supported for autoconf/automake compatibility exec-prefix: bindir: sbindir: @@ -45,10 +48,11 @@ sysconfdir: sharedstatedir: localstatedir: maintainer-mode=0 dependency-tracking=0 + silent-rules=0 }] # @check-feature name { script } # # defines feature '$name' to the return value of '$script', @@ -128,11 +132,12 @@ } } # @make-template template ?outfile? # -# Reads the input file '/$template' and writes the output file '$outfile'. +# Reads the input file '/$template' and writes the output file '$outfile' +# (unless unchanged). # If '$outfile' is blank/omitted, '$template' should end with '.in' which # is removed to create the output file name. # # Each pattern of the form '@define@' is replaced with the corresponding # "define", if it exists, or left unchanged if not. @@ -220,25 +225,25 @@ } continue } lappend result $line } - writefile $out [string map $mapping [join $result \n]]\n - - msg-result "Created [relative-path $out] from [relative-path $template]" + write-if-changed $out [string map $mapping [join $result \n]] { + msg-result "Created [relative-path $out] from [relative-path $template]" + } } # build/host tuples and cross-compilation prefix -set build [lindex [opt-val build] end] +opt-str build build "" define build_alias $build if {$build eq ""} { define build [config_guess] } else { define build [config_sub $build] } -set host [lindex [opt-val host] end] +opt-str host host "" define host_alias $host if {$host eq ""} { define host [get-define build] set cross "" } else { @@ -256,11 +261,11 @@ define ${type}_cpu $cpu define ${type}_vendor $vendor define ${type}_os $os } -set prefix [lindex [opt-val prefix $defaultprefix] end] +opt-str prefix prefix /usr/local # These are for compatibility with autoconf define target [get-define host] define prefix $prefix define builddir $autosetup(builddir) @@ -268,33 +273,43 @@ define top_srcdir $autosetup(srcdir) define abs_top_srcdir [file-normalize $autosetup(srcdir)] define abs_top_builddir [file-normalize $autosetup(builddir)] # autoconf supports all of these -set exec_prefix [lindex [opt-val exec-prefix $prefix] end] -define exec_prefix $exec_prefix +define exec_prefix [opt-str exec-prefix exec_prefix $prefix] foreach {name defpath} { bindir /bin sbindir /sbin libexecdir /libexec libdir /lib } { - define $name [lindex [opt-val $name $exec_prefix$defpath] end] + define $name [opt-str $name o $exec_prefix$defpath] } foreach {name defpath} { datadir /share - sysconfdir /etc sharedstatedir /com - localstatedir /var infodir /share/info mandir /share/man includedir /include } { - define $name [lindex [opt-val $name $prefix$defpath] end] + define $name [opt-str $name o $prefix$defpath] +} +if {$prefix ne {/usr}} { + opt-str sysconfdir sysconfdir $prefix/etc +} else { + opt-str sysconfdir sysconfdir /etc } +define sysconfdir $sysconfdir + +define localstatedir [opt-str localstatedir o /var] define SHELL [get-env SHELL [find-an-executable sh bash ksh]] + +# These could be used to generate Makefiles following some automake conventions +define AM_SILENT_RULES [opt-bool silent-rules] +define AM_MAINTAINER_MODE [opt-bool maintainer-mode] +define AM_DEPENDENCY_TRACKING [opt-bool dependency-tracking] # Windows vs. non-Windows switch -glob -- [get-define host] { *-*-ming* - *-*-cygwin - *-*-msys { define-feature windows Index: autosetup/tmake.auto ================================================================== --- autosetup/tmake.auto +++ autosetup/tmake.auto @@ -21,45 +21,34 @@ cc-check-tools ar ranlib set objdir [get-env BUILDDIR objdir] make-config-header $objdir/include/autoconf.h -make-tmake-settings $objdir/settings.conf {[A-Z]*} +make-tmake-settings $objdir/settings.conf {[A-Z]*} *dir lib_* } autosetup_check_create project.spec \ {# Initial project.spec created by 'autosetup --init=tmake' + +tmake-require-version 0.7.3 # vim:set syntax=tcl: define? DESTDIR _install # XXX If configure creates additional/different files than include/autoconf.h # that should be reflected here - -# We use [set AUTOREMAKE] here to avoid rebuilding settings.conf -# if the AUTOREMAKE command changes -Depends {settings.conf include/autoconf.h} auto.def -msg {note Configuring...} -do { - run [set AUTOREMAKE] >$build/config.out -} -onerror {puts [readfile $build/config.out]} -fatal -Clean config.out -DistClean --source config.log -DistClean settings.conf include/autoconf.h - -# If not configured, configure with default options -# Note that it is expected that configure will normally be run -# separately. This is just a convenience for a host build -define? AUTOREMAKE configure TOPBUILDDIR=$TOPBUILDDIR --conf=auto.def - -Load settings.conf - -# e.g. for up autoconf.h +Autosetup include/autoconf.h + +# e.g. for autoconf.h IncludePaths include -ifconfig CONFIGURED - -# Hmmm, but should we turn off AutoSubDirs? -#AutoSubDirs off +ifconfig !CONFIGURED { + # Not configured, so don't process subdirs + AutoSubDirs off + # And don't process this file any further + ifconfig false +} } if {![file exists build.spec]} { puts "Note: I don't see build.spec. Try running: tmake --genie" } ADDED bin/.agignore Index: bin/.agignore ================================================================== --- /dev/null +++ bin/.agignore @@ -0,0 +1,2 @@ +*.pt +*.rk05 ADDED bin/.ignore Index: bin/.ignore ================================================================== --- /dev/null +++ bin/.ignore @@ -0,0 +1,1 @@ +.agignore ADDED bin/cc8-to-os8 Index: bin/cc8-to-os8 ================================================================== --- /dev/null +++ bin/cc8-to-os8 @@ -0,0 +1,68 @@ +#!/usr/bin/env perl +######################################################################## +# cc8-to-os8 - Filter C code meant for the CC8 cross-compiler so that it +# will work with the OS/8 version of CC8. +# +# This is not meant to fix up 100% of the differences in the C +# compilers, just the ones which prevent src/cc8/examples/*.c +# from building under OS/8 CC8 as-is. +# +# You can run it like a filter: +# +# $ cc8-to-os8 < foo.c | txt2ptp > foo.pt +# +# Or you can just give it one or more file name(s), with output going +# to stdout, which gives this way to hack around the lack of #include: +# +# $ cc8-to-os8 myheader.h foo.c > OS8FOO.C +# +# Copyright © 2017 Warren Young +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +use strict; +use warnings; + +my $nblines = 0; # number of non-blank lines written out + +while (<>) { + if (m{^[a-zA-Z_][a-zA-Z0-9_]*\s*\(}) { + # Looks like a function definition without a type; assume int. + print "int $_"; + ++$nblines; + } + elsif (!m{^\#include } and length and $nblines) { + # Pass it through unchanged. + print; + } + else { + # Unlike the CC8 cross-compiler, the OS/8 version of CC8 doesn't + # process #includes, or in fact any C preprocessor directives, + # not even #asm. Strip just the #includes in the hope that it's + # init.h or libc.h, the contents of which are hard-coded into + # the OS/8 CC8 compiler. + } +} ADDED bin/teco-pi-demo Index: bin/teco-pi-demo ================================================================== --- /dev/null +++ bin/teco-pi-demo @@ -0,0 +1,179 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +######################################################################## +# teco-pi-demo - Starts the simulator with the OS/8, sends one of the +# famous TECO "calculate pi" program to it, and starts it running at +# a very slow rate of speed to act as a blinkenlights demo. +# +# Copyright © 2017 by Warren Young. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +# Bring in just the basics so we can bring in our local modules +import os +import sys +sys.path.insert (0, os.path.dirname (__file__) + '/../lib') +sys.path.insert (0, os.getcwd () + '/lib') + +# Other core modules we need +from datetime import datetime +import time + +# Our local modules +from pidp8i import * +from simh import * + + +#### main ############################################################## + +def main (): + # Check for command line flags + benchmark = len (sys.argv) > 1 and sys.argv[1] == '-b' + + # Create the SIMH child instance and tell it where to send log output + try: + s = simh (dirs.build) + except (RuntimeError) as e: + print "Could not start simulator: " + e.message + '!' + exit (1) + s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0)) + + # Find and boot the built OS/8 bin disk + rk = os.path.join (dirs.os8mo, 'os8v3d-bin.rk05') + if not os.path.isfile (rk): + print "Could not find " + rk + "; OS/8 media not yet built?" + exit (1) + print "Booting " + rk + "..." + s.send_cmd ("att rk0 " + rk) + s.send_cmd ("boot rk0") + + # Start TECO8 in the simulator under OS/8 + s.os8_send_cmd ('\\.', "R TECO") + + # The macro comes from http://www.iwriteiam.nl/HaPi_TECO_macro.html + # + # The 248 preceding "UN" in the first line of the macro is the number + # of digits of pi to calculate. That value was reached by experiment + # as the largest value that runs without crashing TECO with a + # + # ?MEM STORAGE CAPACITY EXCEEDED + # + # error. You can see that by increasing the value below, commenting + # out the throttle setting below, and running the demo. On a Pi 3, it + # should take a bit over an hour to complete, if it doesn't error out. + # + # With the simulator throttled, generating 248 digits takes 17 years! + # + # That is based on generating 1 digit every ~16 seconds on a Pi 3 when + # running unthrottled, roughly 8 MIPS. When throttled to 59 IPS — or + # 17ms per instruction, as below — you multiply the seconds by the + # factor 8 MIPS / 59 IPS = ~136000, giving about 2.2 million seconds + # per digit. Multiplying that by 248 gives ~17 years. + macro = [ + 'GZ0J\UNQN"E 248UN \' BUH BUV HK', + 'QN< J BUQ QN*10/3UI', + 'QI< \+2*10+(QQ*QI)UA B L K QI*2-1UJ QA/QJUQ', + 'QA-(QQ*QJ)-2\ 10@I// -1%I >', + 'QQ/10UT QH+QT+48UW QW-58"E 48UW %V \' QV"N QV^T \' QWUV QQ-(QT*10)UH >', + 'QV^T @^A/', + '/HKEX', + ] + + # First and last lines are handled specially, so slice them off. + first = macro.pop (0) + last = macro.pop () + + # Send the first line of the macro; implicitly awaits 1st TECO prompt + s.os8_send_cmd ('\*', first) + + # Blindly send core lines of the macro; TECO gives no prompts for 'em. + for line in macro: + s.os8_send_line (line) + + # Send last line of macro sans CR, followed by two Esc characters to + # start it running. + s.os8_send_str (last) # not os8_send_line! + s.os8_send_ctrl ('[') + s.os8_send_ctrl ('[') + + if benchmark: + # Run demo long enough to get a good sense of the simulator's + # execution rate while unthrottled on this host hardware. If + # you don't run it long enough, the IPS value is untrustworthy. + try: + s.spin (10) + except pexpect.TIMEOUT: + # Find out how many IPS was executing + s.os8_send_ctrl ('e') + s.send_cmd ('show clocks') + line = s.read_tail ('Execution Rate:') + curr_ips = int (line.strip().replace(',', '').split(' ')[0]) + pf = open ('lib/pidp8i/ips.py', 'a') + pf.write ('current = ' + str (curr_ips) + ' # ' + \ + str (datetime.today ()) + '\n') + pf.close () + s.send_cmd ('quit') + pdp_ratio = float (curr_ips) / ips.pdp8i + rpi_ratio = float (curr_ips) / ips.raspberry_pi_b_plus + print "\nYour system is " + format (rpi_ratio, '.1f') + \ + " times faster than a Raspberry Pi Model B+" + print "or " + format (pdp_ratio, '.1f') + \ + " times faster than a PDP-8/I.\n" + else: + # Normal mode. Pop out to SIMH and throttle it down to a rate + # suitable for a blinkenlights demo. 1/17 means SIMH runs one + # instruction then waits for 17ms, yielding ~59 IPS. + time.sleep (0.02) # FIXME: simulator chokes on 'cont' without this + s.os8_send_ctrl ('e') + s.send_cmd ('set throttle 1/17') + + # You can't hit Ctrl-E while running this script in the foreground + # since pexpect takes over stdio. Therefore, if you want to be able + # to send commands to the simulator while the demo is running, + # uncomment the line below, which will let you send commands to the + # simulator via telnet. From another terminal or SSH session: + # + # $ telnet localhost 3141 + # + # or from a remote machine: + # + # $ telnet 192.168.1.2 3141 + # + # It's disabled by default because SIMH can't be made to listen only + # on localhost, so doing this may be a security risk. SIMH disables + # obviously-unsafe commands like ! on the remote console, but it is + # possible some mischief may be possible via this path anyway. It + # could be used to exfiltrate a sensitive file via ATTACH, for one + # thing. For another, it's a potential DoS vector. + #s.send_cmd ('set remote telnet=3141') + + # Let it run. Never exits. + s.send_cmd ('cont') + s.spin () + + +if __name__ == "__main__": + main() ADDED bin/txt2os8 Index: bin/txt2os8 ================================================================== --- /dev/null +++ bin/txt2os8 @@ -0,0 +1,150 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +######################################################################## +# Create a tu56 or rk05 image and fill it with ASCII files, i.e. source code. +# +# It is intended to be be called manually when we have a POSIX +# directory full of ASCII files we want to bulk-copy into SIMH. +# +# The argument is taken both as the name of the image to create +# and the list of files to copy in. +# +# For now, it takes all input and produces all output in the +# current working directory. +# +# IMPORTANT: Currently all input files are mindlessly passed through +# txt2ptp which transforms POSIX ASCII files to OS/8 ASCII files. +# It WILL mutilate non-ASCII files. +# +# This program is based on cc8-tu56-update. +# +# Copyright © 2017 by Warren Young and Bill Cattey +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +# Bring in just the basics so we can bring in our local modules +import os +import sys +import argparse + +sys.path.insert (0, os.path.dirname (__file__) + '/../lib') +sys.path.insert (0, os.getcwd () + '/lib') + +# Our local modules +from pidp8i import * +from simh import * + +# Other global Python modules +import glob +import subprocess + + +#### GLOBALS AND CONSTANTS ############################################# + +progmsg = True + + +#### main ############################################################## + +def main (): + global progmsg + + # Set up the arg parser and use it to parse the command line. + parser = argparse.ArgumentParser() + parser.add_argument("name", + help="Create an OS/8 image from a list of ASCII files.") + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("--tu56", "-t", action="store_true") + group.add_argument("--rk05a", "-ra", action="store_true") + group.add_argument("--rk05b", "-rb", action="store_true") + + args = parser.parse_args() + + print "Filename: " + args.name + if args.tu56: + sdev = "dt0" + os8dev = "DTA0:" + imagename = args.name + ".tu56" + stat_str = "DECtape" + + if args.rk05a: + sdev = "rk1" + os8dev = "RKA1:" + imagename = args.name + ".rk05" + stat_str = "partition A of" + + if args.rk05b: + sdev = "rk1" + os8dev = "RKB1:" + imagename = args.name + ".rk05" + stat_str = "partition B of" + + listname = args.name + ".list" + + # Create the SIMH child instance and tell it where to send log output + try: + s = simh (dirs.build, True) + except (RuntimeError) as e: + print "Could not start simulator: " + e.message + '!' + exit (1) + s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0)) + + # Attach a clean version of the image to the simulator + if os.path.exists (imagename): + print "Overwriting old " + stat_str + " image " + imagename + + s.send_cmd ("att " + sdev + " " + imagename) + + # Find and boot the bootable OS/8 disk. Use the "patched" version + # because that is what "make run" uses; we use that command to + # inspect this script's work. + rk = os.path.join (dirs.os8mo, 'os8v3d-patched.rk05') + if not os.path.isfile (rk): + print "Could not find " + rk + "; OS/8 media not yet built?" + exit (1) + print "Booting " + rk + "..." + s.send_cmd ("att rk0 " + rk) + s.send_cmd ("boot rk0") + + s.os8_send_cmd ('\\.', "ZERO " + os8dev) + + manifest = open (listname, "r") + + for line in manifest: + src = line.strip() + if src == "": continue + if src[0] == '#': continue # Allow commenting out files + + dest = src.upper () + s.os8_send_file (src, os8dev + dest) + + # Exit simulator nicely so that image detaches cleanly + s.back_to_cmd ('\\.') + s.send_cmd ("det " + sdev) + s.send_cmd ('quit') + + +if __name__ == "__main__": main() Index: boot/0.script.in ================================================================== --- boot/0.script.in +++ boot/0.script.in @@ -39,7 +39,7 @@ ; wants lowercase text to be forced to uppercase. This is bidirectional, ; affecting both input to the simulated PDP-8 and output from it. set tti ksr @endif -att rk0 @MEDIADIR@/os8/os8v3d-bin.rk05 +att rk0 @MEDIADIR@/os8/@OS8_BOOT_DISK@ boot rk0 ADDED boot/ratio.script Index: boot/ratio.script ================================================================== --- /dev/null +++ boot/ratio.script @@ -0,0 +1,23 @@ +; A stripped down version of 5.script with the throttle value set to a +; ratio between two values. Used primarily to test our handling of this +; throttle type. For example, we need to disable ILS at simulator +; start/continue time when this throttle type is used because it +; throws our timing logic way off. + +set throttle 30/1 +set df disabled +set cpu noidle +echo Running reeeealy slow version of the AC/MQ blinker... +dep 00000 2020 +dep 00001 5000 +dep 00002 7200 +dep 00003 1021 +dep 00004 7421 +dep 00005 1021 +dep 00006 7040 +dep 00007 2021 +dep 00010 7000 +dep 00011 5000 +dep 00020 0000 +dep 00021 0000 +go 00000 ADDED boot/run.script.in Index: boot/run.script.in ================================================================== --- /dev/null +++ boot/run.script.in @@ -0,0 +1,19 @@ +; Same as 0.script, only with RK0 attached to the *.rk05 file in the +; build directory, not the one in the installation directory. Used +; by "make run" so you don't have to "make mediainstall" first. +; +reset +echo Loading OS/8 from the RK05 cartridge disk in the build directory... +set cpu 32k +set cpu noidle +set df disabled +@SET_THROTTLE@ + +@if SIMH_PASS_LOWERCASE +set tti 7b +@else +set tti ksr +@endif + +att rk0 bin/@OS8_BOOT_DISK@ +boot rk0 ADDED doc/RELEASE-PROCESS.md Index: doc/RELEASE-PROCESS.md ================================================================== --- /dev/null +++ doc/RELEASE-PROCESS.md @@ -0,0 +1,180 @@ +# PiDP-8/I Software Release Process + +This documents the process for producing release versions of the +software. + + +## *Can* You Release Yet? + +Before release, you must: + +* Fix all Immediate, High, and Medium priority [Bugs](/bugs) +* Implement all Immediate and High priority [Features](/features) + +Or reclassify them, of course. + +Most of the bug levels simply aid scheduling: Immediate priority bugs +should be fixed before High, etc. Low priority bugs are "someone should +fix this someday" type of problems, so they can move from release to +release. + +The Features levels may be read as: + +* **Immediate**: ASAP, or sooner. :) +* **High**: Features for this release. +* **Medium**: Features we'll look at lifting individually to High for + the next release. +* **Low**: "Wouldn't it be nice if..." + + +## Update SIMH + +If `tools/simh-update` hasn't been run recently, you might want to do +that and re-test before publishing a new version. + + +## Publish OS/8 RK05s + +Re-configure the software with default settings, remove `bin/*.rk05`, +rebuild, and run `tools/publish-os8` to send the "final" OS/8 disk +images for this version of the software up to tangentsoft.com as +unversioned assets. + +Update the date stamp in the "OS/8 RK05 Media" section of the project +home page. + + +## Update ChangeLog.md + +Trawl the Fossil timeline for user-visible changes since the last +release, and write them up in user-focused form into the `ChangeLog.md` +file. If a regular user of the software cannot see a given change, it +shouldn't go in the `ChangeLog.md`; let it be documented via the +timeline only. + + +## Update the Release Branch + +Run `make release` to check the `ChangeLog.md` file changes in, tagging +that checkin with a release version tag of the form vYYYYMMDD and merge +those changes into the `release` branch. + +It runs entirely automatically unless an error occurs, in which case it +stops immediately, so check its output for errors before continuing. + + +## Update the Home Page Links + +The zip and tarball links on the front page produce files named after +the date of the release. Those dates need to be updated immediately +after tagging the release, since they point at the "release" tag applied +by the previous step, so they begin shipping the new release immediately +after tagging it. + + +## Produce the Normal Binary OS Image + +Start with the latest version of [Raspbian Lite][os] on a multi-core +Raspberry Pi. + +1. If the version of the base OS has changed since the last binary OS + image was created, download the new one. + + While the OS is downloading, zero the SD card you're going to use + for this, so the prior contents don't affect this process. + + Blast the base OS image onto the cleaned SD card. + +2. Boot it up on a multi-core Pi. + + Log in, then retreive and initialize BOSI: + + $ wget https://tangentsoft.com/bosi + $ chmod +x bosi + $ exec sudo ./bosi init + + The `exec` bit is required so that the `bosi` invocation is run as + root without any processes running as `pi` in case the `init` step + sees that user `pi` hasn't been changed to `pidp8i` here: the + `usermod` command we give to make that change will refuse to do what + we ask if there are any running processes owned by user `pi`. + + It will either reboot the system after completing its tasks + successfully or exit early, giving the reason it failed. + +3. Clone the software repo and build the softare: + + $ ./bosi build + + On reboot, say `top -H` to make sure the software is running and + that the CPU percentages are reasonable for the platform. + + You may also want to check that it is running properly with a + `pidp8i` command. Is the configuration line printed by the + simulator correct? Does OS/8 run? Are there any complaints from + SIMH, such as about insufficient CPU power? + +4. Do final inside-the-image steps: + + $ ./bosi prepare + +5. Move the SD card to a USB reader plugged into the Pi, boot the Pi + from its prior SD card, and shrink the OS image: + + $ wget https://tangentsoft.com/bosi + $ chmod +x bosi + $ ./bosi shrink + +6. Move the USB reader to the Mac,¹ then say: + + $ bosi image [nls] + + For the ILS images, you can give "ils" as a parameter or leave it + blank. + +7. The prior step rewrote the SD card with the image file it created. + Boot it up and make sure it still works. If you're happy with it, + give this command *on the desktop PC*. + + $ bosi finish [nls] + + As above, the parameter can be "ils" or left off for the ILS images. + +[os]: https://www.raspberrypi.org/downloads/raspbian/ + + +## Produce the "No Lamp Simulator" Binary OS Image + +Do the same series of steps above on a single-core Raspberry Pi, except +that you give "nls" parameters to the `image` and `finish` steps. + + +## Publicizing + +While the NLS image uploads — the ILS image was already sent in step 7 +in the first pass through the list above — compose the announcement +message, and modify the front page to point to the new images. You +might also need to update the approximate image sizes reported on that +page. Post the announcement message and new front page once that second +upload completes. + + +---------------------- + +### Footnotes + +1. The image production steps could just as well be done on a Linux box + or on a Windows box via Cygwin or WSL, but the commands in that + final stage change due to OS differences. Since this document + exists primarily for use by the one who uses it, there is little + point in having alternatives for other desktop OSes above. Should + someone else take over maintainership, they can translate the above + commands for their own desktop PC. + + +### License + +Copyright © 2016-2017 by Warren Young. This document is licensed under +the terms of [the SIMH license][sl]. + +[sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md ADDED doc/class-simh.md Index: doc/class-simh.md ================================================================== --- /dev/null +++ doc/class-simh.md @@ -0,0 +1,253 @@ +# How to Control SIMH and OS/8 from Python + +## Introduction + +While we were building the `libexec/mkos8` tool, we built up a set of +functionality for driving SIMH and OS/8 running under SIMH from the +outside using [Python][py], a very powerful programming language well +suited to scripting tasks. It certainly beats writing PDP-8 code to +achieve the same ends! + +When someone on the mailing list asked for a way to automatically drive +a demo script he'd found online, it was natural to generalize the core +functionality of `mkos8` as a reusable Python class, then write a script +to make use of it. I called resulting demo script `teco-pi-demo` and the +reusable bits `class simh`, which is now shared with `mkos8`. + +This document describes how `teco-pi-demo` works, and through it, how +`class simh` works, with an eye toward teaching you how to reuse this +functionality for your own ends. + +[py]: https://www.python.org/ + + +## Invocation + +Because we do not install these components in the system's Python +library path, you must modify that path to allow your script to find +these components. Simply copy this invocation block into the top of your +script: + + import os + import sys + sys.path.insert (0, os.path.dirname (__file__) + '/../lib') + sys.path.insert (0, os.getcwd () + '/lib') + + from pidp8i import * + from simh import * + +That adjusts the path, then imports all of the generic functionality +from the PiDP-8/I `lib` directory into the current namespace. + +We do not pull the `mkos8` components into `teco-pi-demo` because they +are intended only to be used by `libexec/mkos8`. If you find something +in the `lib/mkos8` directory that you think is widely useful, make a +case for it on the mailing list, and we'll see about moving it to either +the `simh` or `pidp8i` namespace. + +The `sys.path.insert` business assumes that your script is installed +into the PiDP-8/I's `bin` directory alongside `teco-pi-demo`. If you've +installed it somewhere else, you'll need to adjust these paths. + + +## Starting SIMH + +The first thing we'lld do is start SIMH as a child process of our Python +script under control of an instance of `class simh`: + + s = simh (dirs.build) + +We call that instance `s` for short, because we will be calling its +methods a lot in this script. + +We pass `dirs.build` to its constructor, which tells it how to find the +`pidp8i-sim` program, which is a version of the PDP-8 simulator from the +SIMH project, configured and modified for the needs of the PiDP-8/I +project. We call this the child program, which is what `class simh` +controls from the outside. + +(Currently, we don't have a way to make it use other versions of the +SIMH `pdp8` simulator. Please send a patch if you do that.) + +The next step is to tell the `s` object where to send its logging +output: + + s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0)) + +Contrast the corresponding line in `mkos8` which chooses whether to send +logging output to the console or to a log file: + + s.set_logfile (open (dirs.log + 'mkos8-' + first_act + '.log', 'w') \ + if progmsg else os.fdopen (sys.stdout.fileno (), 'w', 0)) + + +## Finding and Booting the OS/8 Media + +If your program will use our OS/8 boot disk, you can find it +programmatically by using the `dirs.os8mo` constant, which means "OS/8 +media output directory", where "output" refers to the worldview of +`mkos8`. Contrast `dirs.os8mi`, which points to the directory holding +the input media for `mkos8`. + +This snippet shows how to use it: + + rk = os.path.join (dirs.os8mo, 'os8v3d-bin.rk05') + if not os.path.isfile (rk): + print "Could not find " + rk + "; OS/8 media not yet built?" + exit (1) + +Now we attach the RK05 disk image to the PiDP-8/I simulator found by the +`simh` object and boot from it: + + print "Booting " + rk + "..." + s.send_cmd ("att rk0 " + rk) + s.send_cmd ("boot rk0") + +This shows one of the most-used methods in `class simh`, `send_cmd`, +which sends a line of text along with a carriage return to the spawned +child program, which again is `pidp8i-sim`. + + +## Driving SIMH and OS/8 + +After the simulator starts up, we want to wait for an OS/8 `.` prompt +and then send the first OS/8 command to start our demo. We use the +`simh.os8_send_cmd` method for that: + + s.os8_send_cmd ('\.', "R TECO") + +This method differs from `send_cmd` in a couple of key ways. + +First, it waits for a configurable prompt character — sent as the first +parameter — before sending the command. This is critical when driving +OS/8 because OS/8 lacks a keyboard input buffer, so if you send text to +it too early, all or part of your input is likely to be lost, so your +command won't work. + +Second, because OS/8 can only accept so many characters of input per +second, `os8_send_cmd` inserts a small delay between each input +character to prevent character losses. + +(See the commentary for `simh._kbd_delay` if you want to know how that +delay value was calculated.) + +The bulk of `teco-pi-demo` consists of more calls to `simh.os8_send_cmd` +and `simh.send_cmd`. Read the script if you want more examples. + +Notice that we escape the OS/8 `.` command prompt in the first parameter +because `class simh` treats it as a [regular expression][re], and a `.` +in REs means "any character," which we definitely do *not* want to match +on! If we did not escape this special RE character with a backslash, +`class simh` would send the command when the first character from the +running OS/8 instance came through, likely causing some or all of the +command to be lost by the time OS/8 is ready to accept input. + +[re]: https://en.wikipedia.org/wiki/Regular_expression + + +## Escaping OS/8 to SIMH + +Sometimes you need to escape from OS/8 back to SIMH with a +Ctrl-E keystroke so that you can send more SIMH commands +after OS/8 starts up. This accomplishes that: + + s.os8_send_ctrl ('e') + +While out in the SIMH context, you *could* continue to call the +`simh.os8_*` methods, but since SIMH can accept input as fast as your +program can give it, it is best to use methods like `simh.send_cmd` +which don't insert artificial delays. For many programs, this +difference won't matter, but it results in a major speed improvement in +a program like `mkos8` which sends many SIMH and OS/8 commands +back-to-back! + + +## Getting Back to OS/8 from SIMH + +There are several ways to get back to the simulated OS/8 environment +from SIMH context, each with different tradeoffs. + + +### Rebooting + +You saw the first one above: send a `boot rk0` command to SIMH. This +restarts OS/8 entirely. This is good if you need a clean environment. +If you need to save state between one run of OS/8 and the next, save it +to the RK05 disk pack or other SIMH media, then re-load it when OS/8 +reboots. + + +### Continuing + +The way `teco-pi-demo` does it is to send a `cont` command to SIMH. + +The problem with this method is that it sometimes hangs the simulator. +The solution is to insert a small delay *before* escaping to the SIMH +context. I'm not sure why this is sometimes necessary. My best guess is +required to give OS/8 time to settle into an interruptible state before +escaping to SIMH, so that on "continue," we re-enter OS/8 in a sane +state. + +You can usually avoid the need for that delay by waiting for an OS/8 +command prompt before escaping to SIMH, since that is a reliable +indicator that OS/8 is in such an interruptible state. + +You don't see these anomalies when using OS/8 interactively because +humans aren't fast enough to type commands at OS/8 fast enough to cause +the problem. That is doubtless why there this bug still exists in OS/8 +in 2017. + + +### Re-Entering + +If your use of OS/8 is such that all required state is saved to disk +before re-entering OS/8, you can call the `simh.os8_restart` method to +avoid the need for a delay *or* a reboot. It re-calls OS/8's entry +point from SIMH context, which we've found through much testing is +entirely reliable, as compared to sending a SIMH `cont` command without +having delayed before escaping to SIMH context. + +`mkos8` uses this option extensively. + + +## Sending Escape Characters + +Several OS/8 programs expect an Escape (a.k.a. `ALTMODE`) +keystroke to do things. Examples are `TECO` and `FRTS`. There isn't a +specific method to do this because we can do that in terms of one we've +just described: + + s.os8_send_ctrl ('[') + +Yes, Escape is Ctrl-\[. Now you can be the life of +the party with that bit of trivia up your sleeve. Or maybe you go to +better parties than I do. + + +## But There's More! + +The above introduced you to most of the functionality of `class simh` +used by `teco-pi-demo`, but there's more to the class than that, +primarily because the `mkos8` script's needs are broader. Rather than +just recapitulate the class documentation here, please read through [the +class's source code][ssc], paying particular attention to the method +comments. It's a pretty simple class, making it a quick read. + +Another useful module is [`pidp8i.dirs`][dsc] which contains paths to +many directories in the PiDP-8/I system, which you can reuse to avoid +having to hard-code their locations. This not only makes your script +independent of the installation location, which is configurable at build +time via `./configure --prefix=/some/path`, but also allows it to run +correctly from the PiDP-8/I software's build directory, which has a +somewhat different directory structure from the installation tree. + +[ssc]: https://tangentsoft.com/pidp8i/file/lib/simh.py +[dsc]: https://tangentsoft.com/pidp8i/file/lib/pidp8i/dirs.py + + +## Credits and License + +Written by and copyright © 2017 by Warren Young. Licensed under the +terms of [the SIMH license][sl]. + +[sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md ADDED doc/dcp_wu.md Index: doc/dcp_wu.md ================================================================== --- /dev/null +++ doc/dcp_wu.md @@ -0,0 +1,480 @@ +# DCP Disassembler for PDP-8 + +This document is based on the file DCP.WU found with DCP binaries. + +| Author | A.E. Brouwer, Math. Center, Amsterdam | +| Date | 73-10-03 | +| Version Number | DCP AB-V21 | +| Last Update | 74-11-12 | +| Environment | OS/8 operating system | +| Memory Requirements | 16K, Optional 24K mode | + + +## DCP (Preliminary Description) + +DCP (sometimes called `deass`) is a program to deassemble (or disassemble) +a PAL program given in binary or in core image format as 1st input file. +Information about the program and meaningful tags can be given +in a second input file. A well readable listing with meaningful +tags but without comment can be obtained in a few passes +(typically four). The first time no information is supplied; while +reading the output one recognizes certain parts as messages +("NO ROOM FOR OUTPUT") or numeric tables (6030,7634,7766,7777) +or simple subroutines (TTYOUT, PUSH, PRINT). + +Putting these things in an information file and then running +dcp again gives you a much nicer output the second time. +Now you may embark on the program itself and obtain after a small +number of passes (depending on the complexity of the program and +your laziness.) A source that might have been the original one except +for its lack of comment. At this moment you could profitably use +the CTRL/E feature of MCEDIT to provide the whole source of comment. +(For example, we obtained a source of a fortran compiler in three +days after five passes.) + +Below we will describe the OS/8 version of the program + +## Assembly Instructions + +(Alas, we do not yet have source.) + + .R PAL8 + *102,DCP_SBIN,DCPZ/L$ + .SAVE SYS DCP + +## Operating Instructions + + .R DCP + *OUTPUTDSK:DCPLS.TM + If command closed with altmode then exit to OS/8 monitor + else call command decoder again. + +2. If no output given but an output file is required because + chaining to CREF.SV is requested then DSK:DCPLS.TM is used. + +3. If no input given then use output filename with extensions + .SV and .SM (if present.) + E.G. + + *DEASS< + + is equivalent to + + *DEASS3777 the command has to be closed with altmode instead of return.) + +## Translation Is Done One Field at A Time + +Therefore, binaries that load into extended memory must be +disassembled with /(F) for all memory fields used. + +This causes some flaws in the output: + + CIF 10 + JMS I (200 + +is translated as: + + CIF 10 + JMS I (START + +If LOC 200 in the current field is labeled START. +Note that assembling the produced source gives the +correct binary.) + +## Input Format + +Each input section starts with $X (where X is a letter indicating +the type of the section) and ends with $ . + +$\ indicates the end of all input (when not within a secion). +Between the sections comment not containing $ may be inserted. + +### Section Types + +| $A | Translate as 6bit ASCII (TEXT "STRING") +| $D | Dont translate +| $I | Translate as instruction (overriding other specs) +| $L | Translate as identifier rather than as instruction +| $N | Translate octal +| $S | Subroutine with args +| $T | Symbol definitions +| $Z | Special coding +| $ | End of input + +### Content of Section + +1. Sections $X where X is A,D,I,L or N. + + Contents: Lines of the form: + + MMMM-NNNN + + or + + NNNN + + Where NNNN and MMMM are octal addresses. + + e.g. the section: + + $N + 1717-1730 + 1750 + $ + + specifies that the locations 1717-1730 and 1750 are + to be translated as octal numbers. + +2. Sections $S. + + Contents: Lines of the form: + + SSSS:XXXXX + + Where SSSS is a subroutine address and XXXXX specifies + the kind of arguments the subroutine has. + + e.g. the section: + + $S + 1000:NL + $ + + indicates that each call to the subroutine at LOC 1000 has two + arguments of type octal and label respectively. + +3. Sections $T. + + Contents: Lines of the form: + + TAG=NNNN + + or + + TAG + + Meaning: If no octal value of a tag is specified then its value is + taken as one more than the value of the previous tag. + +4. Section $Z. + + This is an ad hoc construct to enable the translation of + symbol tables like those of PAL8 and CREF. + + e.g. + + $Z=52;0=240;1=301;40=260 + NNNN-MMMM:(UUUL) + $ + + indicates that the range NNNN-MMMM is a table of four-word entries + three words in a special format and one label. + + The special format is as follows: + + The value is divided by 52 giving a quotient and a remainder. + Both are converted into a character as follows: 0 gives a space, + 1-37 give letters A-_, and 40-51 give digits 0-9. + + The coding here is not foolproof yet: A strange command might + give strange output instead of an error message. + + In later versions this command will be generalized, so we don't + describe it in full here. + +## Error Messages + +These are very poor (because of lack of space): HLTNNNN, +where NNNN indicates the address of the routine in DCP that +detected the error. + +Errors are almost always violations of the input format. + +A complete list will appear in the final report. + +| Name | DCP (ERROR TABLE) +| Author | A.E. Brouwer +| Date | 75-02-13 + +As noted: The error messages of DCP look like 'HLT....' +where .... stands for the octal address of the routine +that detected the error. + +(Of course giving intelligible messages is highly desirable +but lack of space prevented this. Some future version of DCP +will chain to a file `DECPERR.SV` containing the messages.) + +Below the error numbers are given for DCP AB-V21. +[Note: These numbers may change slightly each time that +DCP is assembled anew.] + +### DCP16 Error Table + +| Number | ERROR +| ------ | -------------------------------------------------------------| +| 0000 | PREMATURE END OF .BN INPUT | +| 0230 | CLOSE ERROR | +| 0301 | LOOKUP FOR SYS:CREV.SV FAILED | +| 1414 | OUTPUT ERROR OR NO ROOM FOR OUTPUT | +| 1451 | INPUT ERROR (INFO FILE) | +| 1522 | NO CARRIAGE RETURN WHERE EXPECTED IN THE INFO FILE | +| 1755 | UPPER BOUND IN BOUND PAIR LESS THAN LOWER BOUND | +| 2031 | ASCII STRING CONTAINED A SIXBIT ZERO, BUT NOT AT THE END | +| | (I.E. A WORD 00XX). (THIS MIGHT HAVE BEEN AN @, | +| | BUT IS USUALLY AN ERROR.) | +| 2046 | ASCII STRING WITHOUT TRAILING ZERO | +| 2061 | DCP COULD NOT FIND A SUITABLE DELIMITER FOR THE ASCII STRING | +| | IN THE RANGE "" TO "? | +| 2125 | IMPOSSIBLE | +| 2214 | TEXT BUFFER OVERFLOW (TOO MANY OR TOO LONG IDENTIFIERS). | +| 2234 | NO IDENTIFIER WHERE EXPECTED (IN A $T SECTION). | +| 2666 | ZERO SUBROUTINE ADDRESS SPECIFIED IN A $S SECTION | +| 2705 | S-BUFFER OVERFLOW (TOO MANY SUBROUTINES WITH ARGS.) | +| 2761 | UNKNOWN TYPE LETTER IN SPECIFICATION OF SUBROUTINE ARGS | +| 3006 | $Z NO FOLLOWED BY = | +| 3011 | $Z= NOT FOLLOWED BY A NONZERO NUMBER | +| 3022 | NO CARRIAGE RETURN OR SEMICOLON WHERE EXPECTED IN $Z HEADER | +| 3030 | NO = WHERE EXPECTED IN $Z HEADER LINE | +| 3041 | ZERO LOWER BOUND IN BOUND PAIR IN $Z SECTION | +| 3064 | Z-BUFFER OVERFLOW | +| 3117 | PREMATURELY EXHAUSTED Z-FORMAT | +| 3135 | UNKNOWN Z-FORMAT SYMBOL | +| 3470 | T-BUFFER OVERFLOW | +| 3723 | NO VALUE ASSIGNED TO FIRST TAG IN $T SECTION | +| 4213 | NO INPUT AND NO OUTPUT AND NO DSK:DCPLS.TM TO DELETE | +| 4245 | HANDLER FETCH ERROR | +| 4341 | LOOKUP FOR INPUTFILE FAILED | +| 4442 | OUTPUT OPEN ERROR | +| 4456 | NO 16K MEMORY AVAILABLE | +| 4470 | CHECKSUM OR FORMAT ERROR IN BINARY INPUT FILE | +| 4613 | FORMAT ERROR IN CORE CONTROL BLOCK OF .SV INPUT FILE | +| 4647 | ERROR READING CORE CONTROL BLOCK OF .SV INPUT | +| 4723 | ERROR READING .SV INPUT FILE | + +## DCP24 + +DCP Version 24 is a 24K version of DCP. + +| Name | DCP-AB-WW-V24 +| Author | W.F. Wakker, Math. Center, Amsterdam +| Date | 76-03-25 + +### The Following Extensions Are Made + +- DCP24 Translates EAE instructions in both A and B mode + + (For mode switching, see below.) + + Example: + + 1200 DAD;1234 + + is translated as if the info-file contains the following info: + + $I + 1200 + $ + $L + 1201 + $ + $N + 1234+ + $ + +- In the info-file one can give : NNNN+ which has the same + effect as NNNN-MMMM where MMMM=NNNN+1. + +- Several buffers have been enlarged. + +- The output is paginated and has a heading on each page. + (The page number is in octal ....) + +- Error messages are unfortunately as poor as before (See DCP24 + error table). + +- New sections, now possible in the info-file are: + + | $B | TRANSLATE AS 8-BIT ASCII | + | $C | GIVE COMMENT | + | $E | FORCE EAE MODE A | + | $F | FORCE EAE MODE B | + | $M | TRANSLATE NEGATIVE | + +- Section $B + + $B + NNNN-MMMM + $ + + Causes the location NNNN-MMMM to be translated as + 8-bit ASCII, E.G. 0301 is translated as "A. + Values less then 241 are translated as octal numbers. + +- Sections $E and $F + + When DCP encounters EAE instructions, some slight heuristics + are done to determine the mode. The mode is initially A; SWAB, + DAD, and DST cause the mode to change to mode B etc. + When these heuristics are too poor, you can use the $E section + to force mode A and the $F section to force mode B. + +- Section $M + + This section has the same effect as section $N, only all + octals are given negative, E.G. 7770 becomes -10. + It is also possible to give $B and $M to the same LOC. + Example: 7477 is now translated as -"A. + +- Section $C + + Now you can give comment!! + + | Format | NNNN:THIS IS COMMENT + | Effect | NNNN ........ /THIS IS COMMENT + | Attention | The $C section must be the last one in the info-file: + + When $C is seen in the info-file, a setup is made to + give the comment and no more input will be read ( E.G. The program + acts like $$ on the end is seen). The comments are added to + the listing in the last pass of the program. + + __YOU MUST SORT THE ADDRESSES.__ + + 300:COMM1 + 200:COMM2 + + Has as effect that from adress 300 on, no more comment will + be given, since address 200 is not found any more. + + __DO NOT GIVE COMMENT ON ADDRESSES WHICH DO NOT BELONG + TO THE PROGRAM__ + +- Extension of $S section + + As arguments in the $S section you can give N, L, A, I, B, M, + (with the obvious meaning, see above ) and also U. + U should only be used for the addresses 200 and 7700. + It marks the entrypoint of the user service routine and gives + a nice translation of each USR call. + +- Extension of $Z section + + New possible arguments: M, B. + +### DCP-V24 (ERROR TABLE) + +| Number | ERROR +| ------ | -------------------------------------------------------------| +| 0000 | PREMATURE END OF .BN INPUT | +| 0237 | CLOSE ERROR | +| 0305 | LOOKUP FOR SYS:CREF.SV FAILED | +| 1414 | OUTPUT ERROR OR NO ROOM FOR OUTPUT | +| 1511 | NO CARRIAGE RETURN WHERE EXPECTED IN THE INFO FILE | +| 1707 | NO : AS SEPARATOR IN $C SECTION | +| 2145 | UPPER BOUND IN BOUND PAIR LESS THAN LOWER BOUND | +| 2235 | NO : AS SEPARATOR IN FIRST LINE OF $C SECTION | +| 2331 | INPUT ERROR (INFO FILE) | +| 2431 | ASCII STRING CONTAINED A SIXBIT ZERO, BUT NO AT THE END | +| | (I.E. A WORD 00XX). (THIS MIGHT HAVE BEEN AN @, | +| | BUT IS USUALLY AN ERROR.) | +| 2446 | ASCII STRING WITHOUT TRAILING ZERO | +| 2461 | DCP COULD NOT FIND A SUITABLE DELIMITER FOR THE ASCII STRING | +| | IN THE RANGE "" TO "? | +| 2525 | IMPOSSIBLE | +| 2614 | TEXT BUFFER OVERFLOW (TOO MANY OR TOO LONG IDENTIFIERS) | +| 2634 | NO IDENTIFIER WHERE EXPECTED (IN A $T SECTION) | +| 3266 | ZERO SUBROUTINE ADDRESS SPECIFIED IN A $S SECTION | +| 3305 | S-BUFFER OVERFLOW (TOO MANY SUBROUTINES WITH ARGS) | +| 3367 | UNKNOWN TYPE LETTER IN SPECIFICATION OF SUBROUTINE ARGS | +| 3406 | $Z NOT FOLLOWED BY = | +| 3411 | $Z= NOT FOLLOWED BY A NONZERO NUMBER | +| 3422 | NO CARRIAGE RETURN OR SEMICOLON WHERE EXPECTED IN $Z HEADER | +| 3430 | NO = WHERE EXPECTED IN $Z HEADER LINE | +| 3441 | ZERO LOWER BOUND IN BOUND PAIR IN $Z SECTION | +| 3463 | Z-BUFFER OVERFLOW | +| 3517 | PREMATURELY EXHAUSTED Z-FORMAT | +| 3541 | UNKNOWN Z-FORMAT SYMBOL | +| 4070 | T-BUFFER OVERFLOW | +| 4324 | NO VALUE ASSIGNED TO FIRST TAG IN $T SECTION | ADDED doc/graphics/back.jpg Index: doc/graphics/back.jpg ================================================================== --- /dev/null +++ doc/graphics/back.jpg cannot compute difference between binary files ADDED doc/graphics/front.jpg Index: doc/graphics/front.jpg ================================================================== --- /dev/null +++ doc/graphics/front.jpg cannot compute difference between binary files ADDED doc/graphics/power-switch.png Index: doc/graphics/power-switch.png ================================================================== --- /dev/null +++ doc/graphics/power-switch.png cannot compute difference between binary files ADDED doc/graphics/schofield-brtval.R Index: doc/graphics/schofield-brtval.R ================================================================== --- /dev/null +++ doc/graphics/schofield-brtval.R @@ -0,0 +1,62 @@ +# schofield-brtval.R: Generate SVG graph of the brightness curves +# produced by Ian Schofield's ILS patch. +# +# The schofield-brtval.svg file checked into Fossil is modified from the +# version output by this program: +# +# 1. The colors are modified to match the scheme on tangentsoft.com/pidp8i +# +# 2. The data line thickness was increased +# +# 3. The data lines were smoothed by Inkscape's "simplify" function +# +# Copyright © 2017 Warren Young +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above shall +# not be used in advertising or otherwise to promote the sale, use or other +# dealings in this Software without prior written authorization from those +# authors. +# + +min = 0 +max = 32 +a = min +b = max + +rising = c(min); +falling = c(max); + +for (i in 1:400) { + a = a + (max - a) * 0.01 + b = b + (min - b) * 0.01 + + rising[i] = a + falling[i] = b + + if (a > 31 || b < 1) break +} + +data = data.frame(Rising = rising, Falling = falling) +dts = ts(data) +svg("schofield-brtval.svg", width=8, height=6) +plot.ts(dts, plot.type='single', ylab='Brightness', + yaxp=c(min, max, 8)) +dev.off() ADDED doc/graphics/schofield-brtval.svg Index: doc/graphics/schofield-brtval.svg ================================================================== --- /dev/null +++ doc/graphics/schofield-brtval.svg @@ -0,0 +1,954 @@ + + + Schofield ILS Brightness Curves + + + + image/svg+xml + + Schofield ILS Brightness Curves + + 2017-02-02 + + + Warren Young + + + English + Curves showing the brightness levels of an LED driven from 0 to full brightness and from full brightness back to 0 under Ian Schofield's indandescent lamp simulator for the PiDP-8/I. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ADDED doc/graphics/serial-db9.jpg Index: doc/graphics/serial-db9.jpg ================================================================== --- /dev/null +++ doc/graphics/serial-db9.jpg cannot compute difference between binary files ADDED doc/graphics/serial-kk.jpg Index: doc/graphics/serial-kk.jpg ================================================================== --- /dev/null +++ doc/graphics/serial-kk.jpg cannot compute difference between binary files ADDED doc/graphics/system.jpg Index: doc/graphics/system.jpg ================================================================== --- /dev/null +++ doc/graphics/system.jpg cannot compute difference between binary files ADDED doc/os8-fortran-iv.md Index: doc/os8-fortran-iv.md ================================================================== --- /dev/null +++ doc/os8-fortran-iv.md @@ -0,0 +1,71 @@ +# OS/8 FORTRAN IV + +OS/8 `FORTRAN IV` is extensively documented: + +* Chapter 8 of the [1974 OS/8 Handbook][os8h1974] (large file!). +* Section 2 of the [OS/8 Language Reference Manual AA-H609A-TA][os8lang]. +* The [OS/8 Fortran IV Software Support Manual DEC-S8-LFSSA-A-D][os8f4suppt] + +This file serves more as a quick guide to what has been gathered +to this repository as far as distribution DECtapes, and patches +are concerned. + +The [1979 OS/8 Software Components Catalog][os8cat] provides an +inventory of and part numbers for the OS/8 V3D binary and source +distribution DECtapes. Images of these DECtapes are all found in +media/os8/subsys as follows: + +| DEC Part | Filename | Description | +| ----------- | ------------------------------- | ----------------- | +| AL-4549D-BA | al-4549d-ba-fr4-v3d-1.1978.tu56 | Binary DECtape #1 | +| AL-5596D-BA | al-5596d-ba-fr4-v3d-2.1978.tu56 | Binary DECtape #2 | +| AL-4545D-SA | al-4545d-sa-fr4-v3d-1.tu56 | Source DECtape #1 | +| AL-4546D-SA | al-4546d-sa-fr4-v3d-2.tu56 | Source DECtape #2 | +| AL-4547D-SA | al-4547d-sa-fr4-v3d-3.tu56 | Source DECtape #3 | + +When with-os8-fortran-iv is enabled (which is the default), the +contents of Binary DECtape #1 are installed on the OS/8 RK05 images +built by `mkos8`. + +Binary DECtape #2 contains the `.RA` sources for the components +of the FORTRAN IV library. Those files are already assembled +and archived in `FORLIB.RL` and installed from Binary DECtape #1. + +The three source DECtapes contain the buildable source code for the +rest of OS/8 FORTRAN IV system. Build instructions are found in +Appendix B of the _OS/8 Fortran IV Software Support Manual_. + +If with-os8-patches is configured, the `os8v3d-patched.rk05` image will contain the following patches: + +* `F4 21.1.2 M` Fix for the `EQUIVALENCE` statement that brings + `F4.SV` and `PASS3.SV` up to version 4B. +* `F4 51.3.1 M` Enable recognition of `"` as an incorrect character in + a subroutine call argument that brings `F4.SV` up to version 4C. +* `F4 51.3.2 M` Enable recognition of syntax errors in type + declarations. +* `FORLIB 51.10.1 M` Updates `FORLIB.RL` to contain an corrected + `DLOG` function that will correctly handle numbers smaller than + `1.1-018`. + +There is one more patch available, `FRTS 51.3.3 O`, an optional patch +that enables the FORTRAN IV runtime system to accommodate 2 page +system handlers in addition to the TD8E handler. This patch has not +yet been verified. + +FORTRAN IV on the system packs should work. + +The sources are available in the tree in the files named above. + +Enjoy! + +[os8h1974]: http://bitsavers.trailing-edge.com/pdf/dec/pdp8/os8/OS8_Handbook_Apr1974.pdf +[os8lang]: http://bitsavers.trailing-edge.com/pdf/dec/pdp8/os8/AA-H609A-TA_OS8_Language_Reference_Manual_Mar79.pdf +[os8f4suppt]: http://bitsavers.trailing-edge.com/pdf/dec/pdp8/os8/DEC-S8-LFSSA-A-D_F4swSupp.pdf +[os8cat]: https://ia601002.us.archive.org/8/items/bitsavers_decpdp8sofoftwareComponentsCatalogJul79_7798622/AV-0872E-TA_PDP-8_Software_Components_Catalog_Jul79.pdf + +### License + +Copyright © 2017 by Bill Cattey. Licensed under the terms of +[the SIMH license][sl]. + +[sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md ADDED doc/os8-macrel.md Index: doc/os8-macrel.md ================================================================== --- /dev/null +++ doc/os8-macrel.md @@ -0,0 +1,86 @@ +# OS/8 `MACREL` + +`MACREL`, the MAcro RELocating assembler, was a late development. It +was an attempt to replace `PAL8` with a Macro assembler capable of +producing relocatable modules. When `MACREL` first came on the scene, +several companies decided to port their next major upgrade to `MACREL` +from `PAL8`. `MACREL` was so buggy that everybody basically had to +revert to `PAL8` and back-port all the new code originally intended +for the new major upgrade. This situation befell ETOS Version 5. + +We have a binary distribution DECtape image of `MACREL` version 1, DEC +part number `AL-5642A-BA`. Unfortunately the version numbers of the +patches did not match what was shown in the binaries. + +With the `MACREL` V1 patches, I wanted to do more research before +recommending application of the patches. In the course of that +research, I discovered that all the archived manuals to be found online +were for `MACREL` v2. + +See: [Willem van der Mark's PDP-8 Manuals archive][vandermarkman] for: + +* [OS/8 MACREL/LINK -- Software Support -- Version 2C -- September 1980 AA-J7073-TA][maclinkss] +* [OS/8 MACREL/LINK -- User Manual -- Version 2D -- January 1979 AA-5664B-TA][maclinkuser] + +Or see the [PDP8 doc tree on ftp.dbit com][dbitdocs] for: + +* [maclkssm.doc -- OS/8 MACREL/LINK V2 Software Support Manual][dbitmacssm] +* [maclnkum.doc -- OS/8 MACREL/LINK V2 User's Manual.][dbitmacuser] +* [macrelrn.doc -- OS/8 MACREL/LINK V2 Release Notes][dbitmacrel] + +Version 2 was the clearly better baseline. I didn't hold out much +hope to find binary and source distributions of `MACREL` v2. (DEC +Part numbers `AL-5642B-BA` for the binary DECtape and the 4 source +DECtapes, `AL-5643B-SA`, `AL-5644B-SA`, `AL-H602B-SA`, and +`AL-H602B-SA`.) + +Very recently we found a complete set of `MACREL` version 2 binaries +as part of a buildable RTS-8 Archive at [ibiblio.org ... pdp-8/rts8/v3/release][rts8rel] + +We found a source distribution of `MACREL` v2 in Dave Gesswein's +[misc_floppy][dgfloppy] archive. Part one is flagged as having +errors, but another obscure site had a mis-labeled archive of this +same stuff so we may be ok. + +The `MACREL` v2 source would not build under `MACREL` v1, but now we +have `MACREL` v2 and initial tests look promising. + +Baseline `MACREL` v2 will be integrated into the system packs. +Because we didn't have `MACREL` v2, no work was done to create patch +files, or to validate them. With both source and binary for `MACREL` +v2 now in hand, this work can proceed. The plan is to fetch the +patches, validate them, and install all mandatory patches that can be +verified. + +The current integration of `MACREL` v2 includes a hand-applied patch +to `FUTIL`. We want the latest version of `FUTIL` because it contains +new code handles extended memory and certain `MACREL` data +structures. However version 8A of `FUTIL` shipped on the `MACREL` v2 +tape had a bug that causes it to *hang* when run under `BATCH`. + +Patch `35.13.1M` fixes this problem and upgrades `FUTIL` to version +8B. This patch was applied by hand tested, and grouped with what we +integrate onto the system packs when we add `MACREL`. + +To reduce uncertainty around the operation of `OVRDRV.MA`, Source +patch `41.5.1M` has been applied by hand to `OVRDRV.MA`. + +See also: [our documentaiton on the OS/8 Patching][os8patches] + +[vandermarkman]: http://vandermark.ch/pdp8/index.php?n=PDP8.Manuals +[maclinkss]: http://vandermark.ch/pdp8/uploads/PDP8/PDP8.Manuals/AA-J073A-TA.txt +[maclinkuser]: http://vandermark.ch/pdp8/uploads/PDP8/PDP8.Manuals/AA-5664B-TA.txt +[dbitdocs]: ftp://ftp.dbit.com/pub/pdp8/doc/ +[dbitmacssm]: ftp://ftp.dbit.com/pub/pdp8/doc/maclkssm.doc +[dbitmacuser]: ftp://ftp.dbit.com/pub/pdp8/doc/maclnkum.doc +[dbitmacrel]: ftp://ftp.dbit.com/pub/pdp8/doc/macrelrn.doc +[rts8rel]: http://www.ibiblio.org/pub/academic/computer-science/history/pdp-8/rts8/v3/release +[dgfloppy]: http://www.pdp8online.com/images/images/misc_floppy.shtml +[os8patches]: https://tangentsoft.com/pidp8i/doc/trunk/doc/os8-patching.md + +### License + +Copyright © 2017 by Bill Cattey. Licensed under the terms of +[the SIMH license][sl]. + +[sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md ADDED doc/os8-patching.md Index: doc/os8-patching.md ================================================================== --- /dev/null +++ doc/os8-patching.md @@ -0,0 +1,404 @@ +# OS/8 System Patches + +Between major updates to distribution media, DEC would send out +important information and patches to customers through its publication +_PDP-8 Digital Software News_ (_DSN_ for short). + +Many issues of _DSN_ can be found on bitsavers.org under +[pdf/dec/pdp8/softwarenews][dsn]. + +To help customers keep track of which patches to apply, _DSN_ added a +Cumulative Index. + +Using the _PDP-8 DIGITAL Software News Cumulative Index_ found in the +latest available issue of _DSN_, [October/November 1980][dsn8010], I +created a spreadsheet of all patches relevant to the OS/8 V3D packs +under construction. That spreadsheet enabled me to go to the +particular issues containing the patches, and keep track of what +action I took with them. + +I reviewed all the patches and came up with a list of the mandatory +patches. Using OCR'd text from each relevant _DSN_ issue, I created a +file per patch, which I then compared to the scanned PDF and corrected +the OCR errors. + +Then I enhanced our `mkos8` script to apply the patches in an +automated way. Most of the patches were for programs available +in source form, so I built the programs from source, and then bench +checked the patch against the source. In a few cases the code was too +obscure, and I marked the patch as "plausable" rather than "verified" +in my spreadsheet. + +The file [patch_list.txt][pl] lists all of the patch files in +`media/os8/patches`. Comments in that file begin with `#` and are +used to disable patches we have rejected for one reason or another. +Each rejected patch also has a comment that explains why that +particular patch was rejected from the default set. Typical reasons +are: + +* The patch requires hardware our simulator doesn't have. +* The patch conflicts with another patch we deem more important. +* The patch changes some behavior, and we prefer that the unpatched + behavior be the default. + +You may want to examine this file to see if there are any decisions you +would reverse. After modifying that file, say "`make`" to rebuild the +OS/8 binary RK05 disk image file with your choice of patches. + +You can disable all of these OS/8 patches by giving the +`--disable-os8-patches` option to the `configure` script. + +[dsn]: http://bitsavers.org/pdf/dec/pdp8/softwarenews/ +[dsn8010]: http://bitsavers.org/pdf/dec/pdp8/softwarenews/198010_PDP8swNews_AA-K629A-BA.pdf +[pl]: https://tangentsoft.com/pidp8i/doc/trunk/media/os8/patches/patch_list.txt + + +## Review of Recommendations + +`BRTS 31.11.2O` is an optional patch which disables 8th bit parity. It +is recommended because sometimes we may want to allow output that +does not force the 8th bit. + +`BRTS 31.11.3O` is an optional patch that enables 132 column +output. It is recommended because it is expected that wide column +output is desirable. + +`TECO 31.20.1O` is an optional patch that permanently forces no case +flagging. It is not recommended because we want to allow the option +of case flagging. + +`TECO 31.20.2O` is an optional patch that turns off verbose +errors. It was for slow terminals and experienced users who didn't +want to wait to see the long error messages they already knew. It is +not recommended because we expect a majority of users to be on high +speed terminals needing the verbose errors. + +`TECO 31.20.3O` turns off a warning that you are using the `YANK` +command to completely overwrite a buffer full of text. Issuing the +command a second time succeeds. It was again to avoid experienced +users. It is not recommended because we expect fewer advanced users +who would be annoyed by the protection. + +`TECO 31.20.4O` implements rubout support specifically and uniquely +for the `VT05` terminal in a way that breaks it for all other video +terminals. It is not recommended because there are VERY few `VT05` +deployments that would use it. + +It is at this point that I began to notice that in later years, patches +became less carefully produced, and more prone to errors. Some are not +correctable, even today. + +`BASIC.UF-31.5.1M` shows: + + 4044/4514 4556 + +changing location `4044` from `4514` to `4556`. Such a change would be +consistent with the stated purpose of the patch, to correct references +to page zero literals that moved with the `V3D` version of `BRTS`. +The source around location '4044' looks like this: + + 04043 4775 JMS I (BUFCDF /SET UP USER BUF + 04044 1273 TAD NSAM + 04045 7041 CIA + 04046 3276 DCA NCTR /-#OF BOARDS TO CLAR + +In my judgment the `TAD NSAM` to get the subscript into the `AC` +should be retained, and the `4556` call to `UNSFIX` to truncate the +value of the Floating Point Accumulator should NOT be inserted. I +modified the patch to leave out that change. It remains to be seen if +calls to User Functions in OS/8 `BASIC` will ever be run to test this +code. Here at least is an analysis to later explorers. + +`EDIT 21.17.4 M` is supposedly a mandatory patch. It fixes a problem +with line printer output through a specific parall interface card. +Unfortunately, the patch overwrites mandatory patch in 21.17.2 and is +NOT recommended. + +`ABSLDR 21.29.1 M` is supposedly a mandatory patch that enables +`ABSLDR` to work with `SAVE` image files. Normally `ABSLDR` only +loads `BIN` format files. The patch sequence number, `21.29` +identifies the patch as being for the OS/8 V3D version of `ABSLDR`. +But the patch changes locations that are not used by `ABSLDR.SV`. +Furthermore, the patch says it upgrades `ABSLDR` from version 6B to +version 6C. + +Version 6 of `ABSLDR` was part of the OS/8 V3D Device Extensions kit. +See [our documention on the OS/8 V3D Device Extensions][os8ext]. +Verification of this now seems within reach, with the expectation that +it is mis-labeled, and is properly applied to the version with the +Extensions kit. Until it is verified, applying this patch is *not* +recommended. + +`PAL8-21.22.4M` is broken and doubly mis-labeled. Mis-label #1: It is +an optional, not mandatory patch. Mis-label #2: It is for product +sequence `35.14`, the `V13` codeline of `PAL-8` that, like `ABSLDR +V6`, is in the Device Extensions kit. The breakage: Source listing +quits working. *Do not apply this patch!* + +Patch `FRTS-51.3.3-O.patch8` is to enable 2-page system drivers like +`RL01`. Except that the `RL01` driver is only available in the +Extensions kit. The patch overwrites existing code that makes `FRTS` +able to function with the `TD8E` 2-page system handler. I've read the +code but don't fully understand it. Perhaps it generalizes the `TD8E` +support. But if you happen to be using this setup under `TD8E` and +`FRTS` doesn't work, then back out this patch. + + +## Patch Application Order + +The `patch` routine in `mkos8` applies the patches in the order they +appear in `patch_list.txt`. That list is currently in alphabetical +order. However, there may in future emerge patches that impose an +order. + +For example, if the `ABSLDR` patch actually did work, it needs the +`FUTIL 31.21.2 M` in order to patch into the `ABSLDR` overlay bits. + +I was skeptical of `FUTIL 31.21.2M` because, when I +load `ABSLDR.SV` into core with GET, the contents of memory showed by +`ODT` are *DIFFERENT* from those shown by `FUTIL`. With deeper +understanding of the OS/8 Device Extensions kit, I see that the patch +was incorporated into the version 8 `FUTIL` source, and also that +`ODT` is expected to be updated in version 3S of the Keyboard Monitor. + +## Then There's `MACREL` + +I've gone into detail on the explorations and understandings with +regard to `MACREL` in a [sister document to this one][macreldoc]. + +Originally I reviewed the patches for `MACREL` v1, because that's all +we had. But the version numbers of the patches did not match the +version numbers of the executables. A little diversion into the guess +work surrounding patch verification: + +Version number mismatches sometimes do occur with patches. For +example, `TECO 31.20.11 M` says that it upgrades `TECO` to version +`5.06`, but got the bits wrong. Instead of changing contents from +`0771` to `0772`, it looked to change from `0772` to `0773`. `772` +octal is `506` decimal, and the `TECO` version number is represented +with a 12 bit number. It's called "5.06" but it's represented as +`0772` octal, or `506` decimal. + +With that TECO patch, I simply changed the version amendment line in +that `TECO` patch, because the rest was correct. Whoever published +the patch got the version number wrong, and nobody complained. + +With no `MACREL` v1 source verification was not really possible, so +applying those patches was postponed. But then we found both binary +and source of `MACREL` v2! + +In the interests of shipping out system packs in finite time, we will +integrate `MACREL` v2 into the system packs, and verify/apply `MACREL` +v2 patches as follow-on work. + +After further testing of 'MACREL' I have concluded that integrating +the source-level patch `41.5.1M` will reduce uncertainty. So I have +hand-integrated that patch into the `MACREL` tu56 image as well. + +[macreldoc]:https://tangentsoft.com/pidp8i/doc/trunk/doc/os8-macrel.md + +## `FUTIL`: + +I was dubious of some of the `FUTIL` patches, but with finding source +to version 8A, I gained confidence in the version 7 patches, and +understood how seriously important the first patch was to version 8. + +The `MACREL` v2 tape shipped with version 8A of `FUTIL`. That was +necessary because V2 of `MACREL` supported the latest memory expansion, +and so the OS/8 Core Control Block needed to change. + +Unfortunately, the `FUTIL.SV` distributed as version 8A had the wrong +starting address and Job Status Word settings. It *hangs* when run +under `BATCH`. Our automated pack builder and patcher `mkos8` run +`FUTIL` under `BATCH`. + +The `MACREL` v2 DECtape image we use with `mkos8` contains a +hand-applied patch `35.13.1M` that fixes this problem. + +Currently if you opt in to having `MACREL` on the system packs, you +get `FUTIL` version 8B. If not, you get `FUTIL` version 7 and `mkos8` +applies the relevant patches. If `FUTIL` version 8 is installed, the +automated patch applier recognizes the version 7 patches don't fit and +fails to install them. + +## One-off Patches + +Most of the patches are parsed and applied in an automated manner +by mkos8. However some are one-offs. + +See the `FUTIL` section above with regards to patch `35.13.1M`. + +`FORLIB 51.10.1 M` is a one line source change to `DLOG.RA`. The patch +file provides that line. It also provides instructions on how to use +`RALF` to assemble the source and on how to to use `LIBRA` to replace +the old version of `DLOG` with the new one in `FORLIB.RL`. I followed +the instructions to hand-tool a patched `FORLIB.RL` which I then put +in the `local.tu56` DECtape image along with the other local hacks. +The `patch` routine `mkos8` has in-line code to replace `FORLIB.RL` on +`SYS:` if installation of FORTRAN IV is enabled. + + +## Unfinished Business + +There remain the following patches that are still under development, +because they are not simple binary overlays on executables that could +be applied with simple scripts driving `ODT` or `FUTIL`. Instead they +are either batch scripts, or are applied to source code that is +rebuilt either with an assembler or high level language compiler. + +`LQP 21.49.1 M` patches a device driver `.BN` file, then using `BUILD` +to insert it into the system. At the present time the OS/8 V3D packs +we build do not use the `LPQ` driver. (We ran out of device ID space +and so we don't have anywhere to put an active `LPQ` driver.) + + +## The Tracking Spreadsheet + +Below is the latest snapshot of the tracking spreadsheet. + +Status column key: + +| **A** | Patch Applies Successfully | +| **V** | Patch Source Verified | +| **K** | Patch Source Probably OK. Weaker confidence than "Verified". | +| **P** | Patch Source Plausible. Weaker confidence than "OK". | +| **N** | Not recommended | +| **O** | OCR Cleaned up. No other verification or application done. | +| **D** | Does not apply. | +| **B** | Bad patch. DO NOT APPLY. | + + +### OS/8 V3D Patches + +| Component | Issue | Sequence | Mon/Yr | Notes | Status | +| ------ | ------ | ------ | ------ | ------ | ------ | +| `HANDLER` | `CTRL/Z` and `NULL` | `01 O *` | Oct-77 | Optional. Not going to apply. | | +| `CREF` | Bug with `FIXTAB` | `21.15.1M` | Apr/May-78 | `CREF-21.15.1-v2B.patch8` Corrects bad patch | AV | +| | Input and output file specifications | `21.15.2M` | Feb/Mar-80 | `CREF-21.15.2-v2C.patch8` | AK | +| `EDIT` | `EDIT` Problem with no `FORMFEED` at end of the input file | `21.17.1M` | Mar-78 | `EDIT-21.17.1M-v12B.patch8` | AV | +| | `EDIT` `Q` command after `L` command | `21.17.2M` | Jun/Jul-79 | `EDIT-21.17.2M-v12C.patch8` | AV | +| | `EDIT` `Q` command patch | `21.17.3M` | Jun/Jul-79 | `EDIT-21.17.3M-v12D.patch8` | AV | +| | `EDIT.SV` `V` option will not work with `LPT DKC8-AA` | `21.17.4M` | Feb/Mar-80 | `EDIT-21.17.4M-v12C.patch8` Overwrites patch `21.12.2M` | AVB | +| `FOTP` | Incorrect directory validation | `21.19.1M` | Jun/Jul-79 | `FOTP-21.19.1M-v9B.patch8` (Corrected from Aug/Sep 1978, Detailed in Apr/May 79) | AV | +| `MCPIP` | `DATE-78` Patch for `MCPIP` | `21.21.1M` | Mar-78 | `MCPIP-21.21.1M-v6B.patch8` | AV | +| `PAL8` | Incorrect core size routine | `21.22.1M` | Aug/Sep-78 | `PAL8-21.22.1M-v10B.patch8` | AV | +| | Erroneous `LINK` generation noted on `PAGE` directive | `21.22.2M` | Aug/Sep-78 | `PAL8-21.22.2M-v10C.patch8` | AV | +| | `EXPUNGE` patch to `PAL8` | `21.22.3M` | Feb/Mar-80 | `PAL8-21.22.3M-v10D.patch8` | AK | +| | `TAB`s are translated incorrectly | `21.22.4M` | Oct/Nov-80 | `PAL8-21.22.4M` (Supercedes June/July 1980 (which had wrong contents of memory.)) Bad! Wrong version of `PAL8`! Breaks list output. | AB | +| `PIP` | `PIP` `/Y` option does not work properly when transferring a system | `21.23-1M` | Aug/Sep-78 | `PIP-21.23.1M-V12B.patch8` | AK | +| `PIP10` | `DATE-78` Patch to `PIP 10` | `21.24.1M` | Jun/Jul-79 | `PIP10-21.24.1M-V3B.patch8` (Corrected from Dec 78/Jan 79) | AV | +| `SET` | Using `SET` with two-page system handlers | `21.26.1M` | Apr/May-78 | `SET-21.26.1M-v1C.patch8` | AV | +| | `SCOPE` `RUBOUT`s fail in `SET` | `21.26.2M` | Apr/May-78 | `SET-21.26.2M-v1D.patch8` | AV | +| | Parsing of `=` in `TTY WIDTH` option | `21.26.3M` | Aug/Sep-78 | `SET-21.26.3M-v1E.patch8` | AV | +| `LPQ` | `LDP01` Handler fails to recognize `TAB`s | `21.49.1M` | Dec/Jan-80 | `LQP-21.49.1M-vB.patch8` (supercedes Mar 1978) | O | +| `TM8E` | Write protect patch to `TM8E.PA` | `21.61.1H` | Feb/Mar-80 | New `TM8E` Source. Too hard to correct. | | + + +### OS/8 Extension Kit V3D Patches + +| Component | Issue | Sequence | Mon/Yr | Notes | Status | +| ------ | ------ | ------ | ------ | ------ | ------ | +| `SABR` | Line buffer problem in `SABR` | `21.91.1M` | Oct/Nov-79 | `SABR-21.91.1M-v18B.patch8` | AV | +| `BASIC.UF` | `BASIC.UF` Incompatible from OS/8 V3C | `31.5.1M` | Aug/Sep-78 | `BASIC.UF-31.5.1M-V5B.patch8` Source also in _DSN_. | AV | +| `BLOAD` | `BLOAD` Will not build `CCB` properly | `31.10.1M` | Feb/Mar-80 | `BLOAD-31.10.1M-v5B.patch8` | AV | +| `BRTS` | `IOTABLE` Overflow | `31.11.1M` | Mar-78 | `BRTS-31.11.1-M-v5b.patch8` | AV | +| | `BASIC` `PNT` Function | `31.11.2O` | Jun/Jul-78 | `BRTS-31.11.2-O.patch8` (superceds/corrects Mar 1978) | AV | +| | Line size on output of `BASIC` | `31.11.3O` | Jun/Jul-78 | `BRTS-31.11.3-O.patch8` | AV | +| | Change line printer width | `31.11.4F` | Oct/Nov-79 | Optional change of width to 132 columns | | +| | Patch to `BRTS` for addressing `LAB 8/E` functions | `31.11.5M` | Oct/Nov-79 | `BRTS-31.11.5-x.patch8` (`BASIC.UF` patch is a prerequisite.) | AV | +| `TECO` | Changing the default `EU` value for no `case` flagging | `31.20.1O` | Mar-78 | `TECO-31.20.01O.patch8` | AVN | +| | Changing the default `EH` value for one line error printouts | `31.20.2O` | Mar-78 | `TECO-31.20.02O.patch8` | AVN | +| | Removing `YANK` protection | `31.20.3O` | Mar-78 | `TECO-31.20.03O.patch8` | AVN | +| | `SCOPE` Support for `VT05` users | `31.20.4O` | Mar-78 | `TECO-31.20.04O.patch8` | AP N | +| | Problem with `AY` command | `31.20.5M` | Mar-78 | `TECO-31.20.05M-v5A.patch8` | AV | +| | Conditionals inside iterations | `31.20.6M` | Mar-78 | `TECO-31.20.06M-v5B.patch8` | AV | +| | Echoing of warning bells | `31.20.7M` | Mar-78 | `TECO-31.20.07M-v5B.patch8` | AV | +| | `CTRL/U` Sometimes fails after `*` | `31.20.8M` | Apr/May-78 | `TECO-31.20.08M-v5.04.patch8` | AK | +| | Multiplying by `0` in `TECO` | `31.20.10M` | Apr/May-78 | `TECO-31.20.10M-v5.05.patch8` | AV | +| | `Q` registers don't work in 8K | `31.20.11M` | Apr/May-78 | `TECO-31.20.11M-v5.06.patch8` | AV | +| | Can't skip over `W` | `31.20.12M` | Apr/May-78 | `TECO-31.20.12M-v5.07.patch8` | AV | +| | Unspecified iterations after inserts | `31.20.13M` | Oct/Nov-78 | `TECO-31.20.13M-v5.08.patch8` (Corrected from Jun/Jul 78) | AV | +| | New features in `TECO V5` | `31.20.14` N | Aug/Sep-78 | Documentation Only | | +| `FUTIL` | `FUTIL` Patch | `31.21.1M` | Apr/May-78 | `FUTIL-31.21.1M-v7B.patch8` | AV | +| | Fix `SHOW CCB` and mapping of `CD` modules | `31.21.2M` | Oct/Nov-78 | `FUTIL-31.21.2M-v7D.patch8` (Corrected from Aug/Sep 78) | AV | +| | Optional: change `XS` format from `excess-240` to `excess-237`. Useful for viewing `COS` data files. | `31.21.3O` | Aug/Sep-78 | `FUTIL-31.21.3O.patch8` | AVN | +| | `FUTIL` Patch to `MACREL`/`LINK` overlays | `31.21.4 N` | Jun/Jul-79 | Documentation Only | | +| `MSBAT` | `DIM` Statement not working in `MSBAT` | `31.22.1M` | Dec 78/Jan-79 | `MSBAT-31.22.1M-v3B.patch8` | AV | +| `BATCH` | `MANUAL INTERVENTION REQUIRED` Erroneously | `31.23.1M` | Aug/Sep-78 | `BATCH-31.23.1M-v7B.patch8` | AV | + + +### OS/8 FORTRAN IV V3D Patches + +| Component | Issue | Sequence | Mon/Yr | Notes | Status | +| ------ | ------ | ------ | ------ | ------ | ------ | +| `F4` | `EQUIVALENCE` Statement | `02M` / `21.1.2M` | Dec/Jan-80 | `F4-21.1.2M-v4B.patch8` (Revised, Oct 77: `F4` and `PASS3` not `FRTS` patched.) | AP | +| | `FORTRAN` Compiler fails to recognize `"` as an error | `51.3.1M` | Jun/Jul-78 | `F4-51.3.1M-v4C.patch8` (Corrects March 1978) | AP | +| | `FORTRAN` Compiler not recognizing syntax error | `51.3.2M` | Jun/Jul-78 | `F4-51.3.2M-v4x.patch8` | AP | +| | `FORTRAN` runtime system 2-page handler | `51.3.3O` | Oct/Nov-78 | `FRTS-51.3.3-O.patch8` Needed for RL02. (Corrected from Aug/Sep 78) | A | +| | Restriction with subscripted variables | `51.3.4R` | Aug/Sep-80 | Documentation: `FIV` `FORTRAN IV` will not allow subscripting to be used on both sides of an arithmetic expression. | | +| `FORLIB` | `FORTRAN IV` `DLOG` Patch | `51.10.1M` | Feb/Mar-80 | `FORLIB-51.10.1M.patch8` (apply to `DLOG.RA`) | AV | + + +### OS/8 MACREL/LINKER V1A Patches + +These patches are listed for completeness. The version numbers don't +match. We lack source so we cannot verify them. we've moved on to +`MACREL` v2 as canon. + +| Component | Issue | Sequence | Mon/Yr | Notes | Status | +| ------ | ------ | ------ | ------ | ------ | ------ | +| `LINK` | Patch `V1D` to `LINK` | `40.2.1M` | Apr/May-78 | `LINK-40.2.1M-v1D.patch8` | O | +| | Patch `VIE` to `LINK` | `40.2.2M` | Apr/May-78 | `LINK-40.2.2M-v1E.patch8` | O | +| | `LINK` Corrections | `40.2.3M` | Apr/May-78 | `LINK-40.2.3M-v1F.patch8` | O | +| `MACREL` | Patch `V1D` to `MACREL` | `40.5.1M` | Apr/May-78 | `MACREL-40.5.1M-v1D.patch8` | OD | +| | Patch `V1E` to `MACREL` | `40.5.2M` | Apr/May-78 | `MACREL-40.5.2M-v1E.patch8` | OD | +| `OVRDRV` | Patch `V1B` to `OVRDRV.MA` | `40.6.1M` | Apr/May-78 | `OVRDRV-40.6.1M-v1B-8srccom` | O | + + +### OS/8 V3D Device Extensions December 1978 Patches + +**WARNING**: Do not use this kit without first consulting _DSN_ Apr/May 1979. +See also: [Our OS/8 Device Extensions documentation][os8ext] + +| Component | Issue | Sequence | Mon/Yr | Notes | Status | +| ------ | ------ | ------ | ------ | ------ | ------ | +| `FRTS` | `FRTS` Patch | `35.1.3M` | Apr/May-79 | | | +| `MONITOR` | `MONITOR` `V3S` Patch | `35.2.1M` | Apr/May-79 | | | +| `FUTIL` | `FUTIL` hangs under `BATCH` | `35.13.1M` CRITICAL! | Apr/May-79 | | AV | +| `PAL8` | `EXPUNGE` Patch to `PAL8` | `35.14.1M` | Feb/Mar-80 | `PAL8-35.14.1M-v13B.patch8` | AN | +| `ABSLDR` | Loader problem with `SAVE` image files | `21.29.1M` | Oct/Nov-80 | `ABSLDR-21.29.1M-v6C.patch8` (Supercedes June/July 1980) Bad: v6B was with OS/8 Device Extensions. | OB | +| `ABSLDR` | `ABSLDR` Patch | `35.18.1M` | Apr/May-79 | | | +| `BLOAD` | `BLOAD` Will not build `CCB` properly | `35.51.1M` | Feb/Mar-80 | `BLOAD-35.51.1M-v5C.patch8` | ON | + + +### OS/8 MACREL/LINKER V2A Patches + +These patches have not been turned into files. Armed with newly +discovered sources verification is possible. Work on these will begin +soon. + +| Component | Issue | Sequence | Mon/Yr | Notes | Status | +| ------ | ------ | ------ | ------ | ------ | ------ | +| User's |`EXPUNGE` Documentation error | `41.1.1N` | Jun/Jul-79 | | | +| Guide | `MACREL` Version numbers: `MACREL` is `V2C` not `V2D`; `LINK` is `V2A` not `V2B`. | `41.1.2N` | Jun/Jul-79 | | | +| | Macro restriction in `MACREL` | `41.1.3N` | Aug/Sep-79 | | | +| | Error in `.MCALL` macro example | `41.1.4N` | Feb/Mar-80 | | | +| `KREF` | Correct printing of numeric local symbols | `41.3.1M` | Apr/May-80 | | | +| `MACREL` | `EXPUNGE` Patch to `MACREL` | `41.4.1F` | Jun/Jul-79 | | | +| | Inconsistencies in `MACREL` error reporting | `41.4.2N` | Aug/Sep-79 | | | +| | Forward reference patch to `MACREL` | `41.4.3M` | Aug/Sep-79 | | | +| | Correct macro substring problem | `41.4.4M` | Apr/May-80 | | | +| | Correct printing of numeric local symbols | `41.4.5M` | Apr/May-80 | | | +| `OVRDRV` | Correct `CDF` problem | `41.5.1M` | Dec/Jan-80 | Source change applied by hand. | AV | +| `FUTIL` | `FUTIL` hangs under `BATCH` | `35.13.1M` | Apr/May-79 | Critical to proper operation of our automated builder. Applied by hand to the `MACREL` v2 integration. | AV | + + + +### License + +Copyright © 2017 by Bill Cattey. Licensed under the terms of +[the SIMH license][sl]. + +[sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md +[os8ext]: https://tangentsoft.com/pidp8i/doc/trunk/doc/os8-v3d-device-extenaions.md + ADDED doc/os8-v3d-device-extensions.md Index: doc/os8-v3d-device-extensions.md ================================================================== --- /dev/null +++ doc/os8-v3d-device-extensions.md @@ -0,0 +1,149 @@ +# OS/8 V3D Device Extensions + +The _OS/8 V3D Device Extensions_ kit (product code `QF026`) was released +in December 1978. It was created to support the newest PDP-8 hardware: + +* The `KT8A` Memory Management option which enables addressing by a + factor of four beyond the previous maximum of 32K to a whopping + 128K of memory. The highest memory field for a PDP-8a goes up from + 7 to 37 (octal). + +* The `RL01` disk supporting 4 times the previous usual disk capacity, + now up to nearly 5 Meg. + +* The `RX02` double-density floppy disks. + +* Device drivers `VXSY` and `VXNS` enables use of `KT8A` extended + memory as a file oriented system or non-system device. + +This distribution contains software updates: + +* A version of `BUILD`, the system builder that could run under + `BATCH`. The previous version would just hang. + +* An update to the OS/8 system including `Keybord Monitor` version + 3S, and a version of `ODT` that works with memory fields greater + than 7. + +* `ABSLDR` version 6A supports loading into memory fields greater + than 7. + +* `PAL8` version 13A allows code to specify memory fields greater + than 7. + +* `CCL` version 7A updates the 'MEMORY' command to recognize up + to 128K words of memory. + +* `PIP` version 14A knows the sizes of the new devices, and has + updated how it copies in the monitor system head. + +* `RESORC` version 5A includes new devices. + +* `BOOT` version 5A boots new devices. + +* `RXCOPY` version 4B formats and copies single and double density + floppies. + +* `FUTIL` version 8A recognizes new core control block format that + can represent extended above field 7. + +The _OS/8 V3D Device Extensions User's Guide_ can be found +in [Willem van der Mark's PDP-8 doc archive][vdmdoc], under +[OS/8 - Device Extensions - User's Guide - December 1978 AA-D319A-TA.pdf][vdmextensions]. +or on the [ftp.dbit.com pdp8 doc archive][dbitdoc] at [devextug.doc -- +OS/8 Device Extensions User's Guide][dbitug] + +The release notes can be found on ftp.dbit.com at [devextrn.doc OS/8 +Device Extensions Release Notes ][dbitrn]. + +Details on how the `KT8A` Memory Extension hardware works, physically +and programatically, can also be found at Willem van der Mark's site: +[vandermark.ch ... Emulator.128KMemory/EK-KT08A-UG_jul78-ocr.pdf][kt8adoc]. + +When reference is made to `PAL8` version 13, that version originally came +from this kit. + +The distribution DECtape for this kit, part number `AL-H525A-BA` has +not yet been found. The PDP-8 Software Components Catalog July 1979 +gives no part number for a Source DECtape distribution of this kit. +There is an RK05 source distribution, part number +`AN-H529A-SA`. However, plausable source and binary have recently been +found! + +The binaries were on someone's local hard disk and not published to +the net anywhere I could find. Sadly those binaries did not include +the DECtape's system area, and so the updated version of the OS/8 +Keyboard Monitor, Command Decoder and `ODT` seemed lost until a tape +could be found. It appears that the original source of these .en files +is Johnny Billingquist's site, [ftp.update.uu.se ... dectape1][uuseext1]. + +Then, however, a self-extracting archive called, `os8v3f.exe` was +found on a mirror site of ibiblio.org, [rtk.mirrors.pdp-11.ru +... fromhichols][rtknicnols]. Mainline ibiblio.org didn't have it, perhaps it was +purged because of its `.exe` extension. The archive is also available +directly from [ftp.update.uu.se ... fromnichols][uusenichols], but at slow speed. + +When that archive was extracted, the manifest of source files +corresponds exactly to the manifest of binaries in the Extensions Kit +file archive are present. This looks quite promising for a future +project to upgrade to OS/8 V3D with the Device Extensions software, +and to create system packs useful even on PDP-8a hardware with 128K +words of memory! + +After comparing sources found for OS/78, and OS/278, as well as Willem +van der Mark's locally modified sources labeled OS/8 version 4, I have +moderate confidence that these sources will enable validation and +integration of most, if not all the OS/8 V3D Device Extensions +functionality. + +[rtknichols]: http://rtk.mirrors.pdp-11.ru/ftp.update.uu.se/pdp8/pdp-8/fromnichols/ +[uuseext1]: ftp://ftp.update.uu.se/pub/pdp8/pdp-8/os8/os8.v3d/binaries/devext/dectapes/dectape1/ +[uusenichols]: ftp://ftp.update.uu.se/pub/pdp8/pdp-8/fromnichols/ + +## FUTIL + +This validation has been done with regards to `FUTIL`. + +The `MACREL` v2 tape shipped with version 8A of `FUTIL`. That was +necessary because V2 of `MACREL` supported the latest memory +expansion, and so the OS/8 Core Control Block format needed to change. + +`FUTIL` version 8A integrated patches for `FUTIL` version 7 into the +source. Finding those patches in the version 8A source strongly +increased my confidence in those patches. + +Unfortunately the `FUTIL.SV` verson 8A executable was saved +incorrectly and then shipped. The Core Control Block setting and +starting address were mis-specified. So `FUTIL` version 8A *hangs* +when run under `BATCH`. + +The [April-May 1979][dsn1979apr] issue of _PDP-8 Digital Software +News_ contained patch `35.13.1M` which fixed this problem and upgraded +`FUTIL` to version 8B. I've confirmed both the problem and the fix. + +Currently if you opt in to having `MACREL` on the system packs, you +get `MACREL` v2 and `FUTIL` version 8B. If leave `MACREL` out, you get +`FUTIL` version 7. The automated pack builder recognizes that the +version 7 patches won't apply to version 8, and fails to apply them. +The research I did on the OS/8 Device Extensions kit and on +`MACREL` increased my confidence about the `FUTIL` version 7 patches. + +See also [our documentation on the `MACREL` integration][macreldoc] +and [our documentation on applying OS/8 patches][patchdoc]. + +### License + +Copyright © 2017 by Bill Cattey. Licensed under the terms of +[the SIMH license][sl]. + +[vdmdoc]: http://vandermark.ch/pdp8/index.php?n=PDP8.Manuals +[vdmextensions]: http://vandermark.ch/pdp8/uploads/PDP8/PDP8.Manuals/AA-D319A-TA.pdf +[dbitdoc]: ftp://ftp.dbit.com/pub/pdp8/doc/ +[dbitug]: ftp://ftp.dbit.com/pub/pdp8/doc/devextug.doc +[dbitrn]: ftp://ftp.dbit.com/pub/pdp8/doc/devextrn.doc +[kt8adoc]: http://www.vandermark.ch/pdp8/uploads/Emulator/Emulator.128KMemory/EK-KT08A-UG_jul78-ocr.pdf +[dsn1979apr]: http://bitsavers.org/pdf/dec/pdp8/softwarenews/198010_PDP8swNews_AA-K629A-BA.pdf +[macreldoc]:https://tangentsoft.com/pidp8i/doc/trunk/doc/os8-macrel.md +[macreldoc]:https://tangentsoft.com/pidp8i/doc/trunk/doc/os8-patching.md +[sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md + Index: doc/pidp8i-test.md ================================================================== --- doc/pidp8i-test.md +++ doc/pidp8i-test.md @@ -1,6 +1,6 @@ -# PCB Test Program +# PiDP-8/I PCB Test Program ## Compiling and Installing `pidp8i-test` is a simple program to test [Oscar Vermeulen's PiDP-8/I @@ -8,28 +8,40 @@ the other software with the normal `make` process. ## Running It -If you are building the software on the Pi for the first time, log out -of your user account after installing it, then back in so that the -install script's changes to your user's `PATH` take effect. - -Thereafter, simply give these commands: +If you're running one of the [binary OS images][devhome], simply give +these commands: $ sudo systemctl stop pidp8i - $ pidp8i-test + $ pidp8i-test The first command ensures that the modified PDP-8 simulator is stopped during the test, since only one program can be talking to the switch and LED array at a given time. (This also applies to other programs like [Deeper Thought 2][dt2].) +If you built the PiDP-8/I software from source atop some other Raspberry +Pi operating system installation, you will have to log out and back in +after installing the software so the installer's adjustments to your +`PATH` take effect. Then you can use the commands above. + ## Test Procedure -You can at any time hit Ctrl-C to stop the test. +The test normally proceeds automatically forward, but you can take +control of the test sequence with these keys: + +| Key | Effect +| --------------------------------- | ------ +| or | Skip to next test +| or | Go back to previous test +| R | Resume auto-advance behavior +| X or Ctrl-C | Exit program + +Any of the arrow keypresses stop the auto-advancing behavior. The test proceeds as follows: * All On test: @@ -92,8 +104,9 @@ This document is licensed under the same terms as the associated [`src/test.c` program][program]. +[devhome]: https://tangentsoft.com/pidp8i/ [project]: http://obsolescence.wix.com/obsolescence#!pidp-8 [dt2]: https://github.com/VentureKing/Deeper-Thought-2 [program]: https://tangentsoft.com/pidp8i/doc/trunk/src/test.c ADDED doc/uwfocal-manual-supp.md Index: doc/uwfocal-manual-supp.md ================================================================== --- /dev/null +++ doc/uwfocal-manual-supp.md @@ -0,0 +1,905 @@ +# U/W FOCAL Manual Supplement for the PiDP-8/I + +## Introduction + +This document is a supplement to the [U/W FOCAL Manual][uwfm]. ("The +Manual") Although it is not a complete FOCAL tutorial — much less a +reference guide! — we suggest that you start learning about our +distribution of FOCAL by skimming through this document *first*, then +proceeding to [the Manual][uwfm], since this document will alert you to +the areas of the Manual that are simply incorrect for the PiDP-8/I +distribution of U/W FOCAL. Having gotten through the Manual, come back +here and re-read this supplement more carefully; you will get more out +of this supplement on that second pass with the context from the Manual. + +Other helpful sources are the [U/W FOCAL reference cards][uwfr], the +[U/W FOCAL DECUS submission][uwfd], the [DECUS and OMSI manuals for PS/8 +FOCAL, 1971][f71], and the [DEC FOCAL-8 Manual][df8]. To a first +approximation, those are ordered in decreasing degree of application to +our distribution of U/W FOCAL. The final document in that series is +still quite useful for understanding U/W FOCAL, however. + +See [below](#rationale) for the reasons why we felt it was necessary to +write this document. + + +## Starting and Stopping U/W FOCAL + +The section "Starting the Program" in [the Manual][uwfm] is entirely +concerned with loading U/W FOCAL from paper tape using the front panel +and the BIN loader. + +The PiDP-8/I software project does not currently ship U/W FOCAL in SIMH +paper tape image form. Instead, it's installed by default on the OS/8 +system disk, which greatly simplifies starting it: + + .R UWF16K + +Yes, that's all. You're welcome. `:)` + +To get back to OS/8, just hit Ctrl-C. + + +## Loading and Saving Programs + +There are many ways to get program text into U/W FOCAL other than simply +typing it in. This section gives several methods, because each may be of +use to you in different circumstances. Some of them may not be of direct +use to you, but may open your eyes to techniques that may be useful to +you in other contexts, so we encourage you to read this entire section. + + +### Pasting Text in from a Terminal Emulator + +#### The Naïve Way + +If you are SSHing into your PiDP-8/I, you might think to write your +FOCAL programs in your favorite text editor on your client PC then copy +and paste that text into U/W FOCAL over SSH. Currently, that won't work. +(2017.10.05) We believe it is because of the way U/W FOCAL handles +terminal I/O and interrupts. If you try, the input ends up trashed in +FOCAL. + + +#### The Way That Works + +"But I really really want to write my FOCAL programs in [my favorite +text editor][mfte] and paste them into my PiDP-8/I," I hear you say. +Dispair not. There is a path. Follow. + +The problem affecting U/W FOCAL which prevents it from handling input at +modern paste-through-SSH speeds doesn't affect OS/8 itself, so we'll use +it as an intermediary: + + .R PIP + *HELLO.DAThe `PUNCH` Command + +When [the Manual][uwfm] talks about loading and saving programs, it is +in terms of the `PUNCH` command, which punches the current program out +on paper tape, because the Manual was written for the paper tape based +version of U/W FOCAL. + +The PiDP-8/I software project ships the OS/8 version of U/W FOCAL +instead, which doesn't even have a `PUNCH` command. (We get the [`PLOT` +command](#miss-hw) instead.) + +Even if it did work, mounting and unmounting simulated paper tapes under +SIMH is a bit of a hassle. We can do better. + + +### The `LIBRARY` Command + +The effective replacement for `PUNCH` in the OS/8 version of U/W FOCAL +is the `LIBRARY` command. + +If you've read [the Manual][uwfm], you may be wondering if it's +overloaded with `LINK` and `LOOK`, but no: those commands are apparently +missing from the OS/8 version. (Open question: how do you use multiple +fields of core for program code with the OS/8 version, then?) + +Briefly, then, I'll show how to use some of these commands: + + .R UWF16K ⇠ start fresh + *1.10 TYPE "Hello, world!"! ⇠ input a simple one-line program + *L S HELLO ⇠ write program to disk with LIBRARY SAVE + *L O HELLO ⇠ verify that it's really there + HELLO .FD 1 ⇠ yup, there it is! + *E ⇠ ERASE all our hard work so far + *W ⇠ is it gone? + C U/W-FOCAL: 16K-V4 NO/DA/TE ⇠ goneski + *L C HELLO ⇠ load it back in with LIBRARY CALL + *W ⇠ did it come back? + C U/W-FOCAL: HELLO NO/DA/TE + + 01.10 TYPE "Hello, world!"! ⇠ yay, there it is! + *L D HELLO ⇠ nuke it on disk; it's the only way to + *L O HELLO ⇠ ...be sure + * ⇠ Houston, we have no program + +See the [DECUS submission][uwfd] and `CARD2.DA` in the [refcards][uwfr] +for more examples. + + +### The `WRITE` Command + +U/W FOCAL's `LIBRARY` command saves programs as core images, which are +a) non-relocatable; and b) non-portable to other versions of FOCAL. We +can fix both of these problems by saving the program to an ASCII text +file instead. + +With a program already typed in or loaded from disk: + + *O O HELLO; W; O C + +All of that has to be on a single line, with the semicolons. If you give +these three commands separately, you end up with the `WRITE` command as +the first line in the output file and the `OUTPUT CLOSE` command as the +last; you must then either edit those commands out of the output file or +tolerate having FOCAL run those two commands again every time you load +the program from disk. + +What this does is opens a data output file (extension `.DA`) and makes +it the output destination, so that the following `WRITE` command sends +its text there, and then it is immediately closed with `O C`, returning +control back to the terminal. + +You can then load that program back into U/W FOCAL with the same command +we used above with the `PIP` solution: + + *O I HELLO + +If you `TYPE` that file from OS/8, you might be wondering why the banner +line doesn't cause a problem on loading the file back in: + + C U/W-FOCAL: HELLO NO/DA/TE + +That leading `C` causes U/W FOCAL to treat it as a comment. Since we're +in "direct mode" at that point, the comment is simply eaten. + + +### The Paper Tape Reader + +Above, I warned you off trying to save programs to simulated paper tape +via the `PUNCH` command, but what about *reading* programs back in? You +can do that, but it's trickier than you might guess. + +First, if you've read [the Manual][uwfm], you may think to attach a +paper tape to SIMH then use U/W FOCAL's `OPEN READER` command, but as +with `PUNCH`, that command has been replaced in this version of U/W +FOCAL. With the removal of paper tape support in U/W FOCAL proper, they +felt free to reassign `O R` to `OPEN RESTART/RESUME`. + +Thus, we again have to pop back out to OS/8 and use PIP to pull this +off. + +First we must create that paper tape. If you place your FOCAL source +code in `examples/*.fc`, you can simply type `make` at the top level of +the PiDP-8/I source tree to have it translated to `bin/*-focal.pt` with +the same base name. + +(This is done by Bill Cattey's `txt2ptp` program; there is also the +inverse filter, `ptp2txt`. We include the `-focal` tag to distinguish +these files from `*-pal.pt` files produced from `*.pal` source files by +a similar process.) + +We'll work with the provided `examples/tratbl.fc` example program, +which got translated to `bin/tratbl-focal.pt` when the PiDP-8/I software +was built. + +To attach that paper tape to SIMH's paper tape reader device, hit +Ctrl-E to get to the SIMH command prompt, then: + + sim> att ptr bin/tratbl-focal.pt + sim> c + +On re-entering the simulator with the `c` ("continue") command, we can +read that tape into OS/8: + + .R PIP + *TRATBL.DAEnter at the end of the PIP command, either + instead of Esc or *before* hitting Esc. + + If you do it right, it should appear on screen as: + + *TRATBL.DAEsc, signifying that + it has read the paper tape and hit the end. Hitting Enter + should then drop you back to the OS/8 prompt, not leave you in + `PIP`. If you get another `*` prompt from `PIP` on hitting + Enter, you fat-fingered something. Try again. + +2. Every time you cause the PDP-8 to read the paper tape, you must + re-attach it to SIMH to read it again. Neither SIMH nor OS/8 warns + you if you try to read from the paper tape reader with nothing + attached; you just get no input. + + This mimics what happens with real paper tapes: once the reader has + read the tape, it falls out of the machine and needs to be fed back + in to be read again. The difference between the real paper tape + reader and SIMH is that that repeated sequence is much more of a + hassle than just sticking the tape back in the reader: + + . + sim> ATT PTR ... + sim> C + + That `TTY:` based `PIP` method above will start to look awfully + attractive after a while... + +3. You saved the FOCAL text out on the host side with Unix line + endings, so on `TYPE`ing it at the OS/8 command prompt to check it, + you got stair-stepped output. OS/8 expects CR+LF line endings, + also called DOS line endings, though OS/8 long precedes MS-DOS, + and the teletype based design of ASCII that made CR+LF a sensible + way of ending lines in a text file long precedes *OS/8*. Fix the + line endings, then say "make" to rebuild the `*-focal.pt` file, + then reattach the tape, and try again. + +Once you make it through that gauntlet, loading the ASCII program text +into U/W FOCAL is just as above: `O I TRATBL`. + + +## Lowercase Input + +The version of U/W FOCAL we include by default on the PiDP-8/I's OS/8 +system disk copes with lowercase input only within a fairly narrow +scope. The fact that it copes with lowercase input at all is likely due +to the fact that the version we ship was released late in the commercial +life of OS/8, by which time lowercase terminals were much more common +than at the beginning of OS/8's lifetime. + +The examples in [the Manual][uwfm] are given in all-uppercase, which +means there is no reason you would immediately understand how U/W FOCAL +deals with lowercase input, having no examples to build a mental model +from. If you just guess, chances are that you will be wrong sooner or +later, because U/W FOCAL's behavior in this area can be surprising! + +The two main rules to keep in mind are: + +1. U/W FOCAL is case-sensitive for variable and built-in function + names, but it is case-insensitive for command names. + +2. U/W FOCAL doesn't support lowercase variable and function names. It + may sometimes appear to work, but internally, U/W FOCAL isn't doing + what you want it to. + +The following gives incorrect output because of a violation of rule 1: + + *type fsin(pi/2)! + 0.000000000E+00* + +The correct answer is 1. It fails because there is no built-in function +called `fsin` nor a built-in constant `pi`. + +FOCAL gives an answer here instead of detecting our failure to call +things by their right names because it is falling back to its rule to +use a value of 0 where no value or function is available to do what you +asked. Zero divided by 2 is 0; then it tries to subscript a nonexistent +`fsin` variable with index 0, so it punts and gives the answer you see +above, zero. + +A better language would have detected your errors and given a +diagnostic, but U/W FOCAL is implemented in less than a page of PDP-8 +core memory, roughly the same number of bytes as +[Clang](http://clang.llvm.org/) gives when compiling an empty C program +on the machine I'm typing this on. The fact that U/W FOCAL detects +errors *at all* is somewhat impressive. + +To get the expected result, call the `FSIN` function and use the `PI` +constant, which are very much not the same thing as `fsin` and `pi` to +FOCAL: + + *type FSIN(PI/2)! + 1.000000000E+00 + +U/W FOCAL doesn't care that you gave the `type` command in lowercase, +but it *does* care about the case of the function and variable names. + +U/W FOCAL's tolerance of lowercase in command names doesn't extend to +arguments. In particular, the `OPEN` command's argument must be +uppercase: `o o` doesn't work, nor does `O o`, but `o O` does. + +Violating rule 2 can be even more surprising: + + .R UWF16K ⇠ We need a fresh environment for this demo. + *s a=1 ⇠ What, no error? I thought you said... + *s b=2 + *s c=3 + *type $ ! + * + +No, that transcript isn't cut off at the end: the `TYPE` command simply +doesn't give any output! Why? + +The reason is that U/W FOCAL can't \[currently\] cope with lowercase +variable names. + +But wait, it gets weird: + + *s A=1 + *s foo=42 + *type $ ! + A (+00) 1.000000000E+00 + &/(+00) 4.200000001E+01 + +We now see output for our uppercased `A` variable, but what is that `&/` +noise? Apparently "`foo`" somehow gets mangled into `&/` by FOCAL's +command parser. + +We have not yet tried to investigate the reason `foo` gets saved into a +mangled variable name and `a`, `b`, and `c` above do not, because the +workaround is simple: keep CAPS LOCK engaged while typing +FOCAL programs except when typing text you want FOCAL to send back out +to the terminal: + + *1.1 TYPE "Hello, world!"! + *G + Hello, world! + +See the [Variables section][vars] of [`CARD2.DA`][card2] for more +information on variable naming. + +[card2]: uwfocal-refcards.md#card2 +[vars]: uwfocal-refcards.md#variables + + +## Default Output Format + +FOCAL is primarily a scientific programming language. That, coupled with +the small memory size of the PDP-8 family and the slow terminals of the +day mean its default output format might not be what you initially +expect. Consider these two examples pulled from the [U/W FOCAL +Manual][uwfm]: + + *TYPE FSGN(PI), FSGN(PI-PI), FSGN(-PI) ! + 1.000000000E+00 0.000000000E+00-1.000000000E+00 + *TYPE 180*FATN(-1)/PI ! + -4.500000000E+01 + +This may raise several questions in your mind, such as: + +1. Why is there no space between the second and third outputs of the + first command? + +2. Why does the ouptut of the first command begin in the second column + and the second begin at the left margin? + +3. Is the second command giving an answer of -4.5°? + +If you've read the U/W FOCAL Manual carefully, you know the answer to +all three of these questions, but those used to modern programming +environments might have skimmed those sections and thus be surprised by +the above outputs. + +The first two questions have the same answer: U/W FOCAL reserves space +for the sign in its numeric outputs even if it doesn't end up being +needed. This was done, no doubt, so that columns of positive and +negative numbers line up nicely. It might help to see what's going on if +you mentally replace the spaces in that first output line above with `+` +signs. + +This then explains the apparent discrepancy between the first and second +commands' outputs: the first output of the first command is positive, +while the second command's output is negative, so there is a space at +the beginning of the first output for the implicit `+` sign. + +As for the third question, the default output format is in scientific +notation with full precision displayed: 4.5×10¹ = 45 degrees, the +correct answer. + + +### Improving the Output Format + +The following changes to the examples as given in [the Manual][uwfm] +show how you can get output more suitable to your purposes: + + *TYPE %1, FSGN(PI):5, FSGN(PI-PI):5, FSGN(-PI)! + 1 0 -1 + +That sets the output precision to 1 significant digit, which is all we +need for the expected {-1, 0, -1} ouptut set. The tabstop formatting +options (`:5`) put space between the answers, but there is a trick which +gives similar output with a shorter command: + + *TYPE %5.0, FSGN(PI), FSGN(PI-PI), FSGN(-PI)! + 1 0 -1 + +That tells it to use 5 significant digits with zero decimal digits. +Since the answers have only one significant digit, FOCAL right-justifies +each output with 4 spaces. There are 5 spaces between the `1` and `0` +outputs because of that pesky implicit `+` sign, though. + +The second example above can be improved thus: + + *TYPE %3.2, 180*FATN(-1)/PI ! + -45.0 + +That tells FOCAL to display 3 significant digits, and to include up to 2 +decimal places even if the traling one(s) would be 0, thus showing all 3 +significant digits in an answer expected in degrees. If you'd wanted 4 +significant digits with any trailing zeroes instead, you'd give `%4.3` +instead. If you'd given `%3`, the output would be `-45`, the trailing +zero being deemed unnecessary. + + +## ASCII Character & Key Names + +Many of the common names for keys and their ASCII character equivalents +have shifted over the years, and indeed they shifted considerably even +during the time when the PDP-8 was a commercially viable machine. The +following table maps names used in [the Manual][uwfm] to their decimal +ASCII codes and their common meaning today. + +| Old Name | ASCII | Current Name | +| ----------- | ----- | ------------- | +| `RUBOUT` | 127 | Delete or Del | +| `DELETE` | 127 | Delete or Del | +| `BACKARROW` | 95 | Underscore | +| `UNDERLINE` | 95 | Underscore | + +Beware that the ASCII values above differ from the values given in the +U/W FOCAL Manual Appendix "Decimal Values for All Character Codes." +FOCAL sets the 8th bit on ASCII characters for reasons unimportant here. +Just add 128 to the values above if you need to get the FOCAL +equivalent. + +Some terminals and terminal emulator software may remap Backspace and +Delete, either making one equivalent to the other or swapping them. +Without such remapping, if you hit the key most commonly marked +Backspace on modern keyboards, U/W FOCAL will just insert an ASCII +character 8 at that point in the program entry, almost certainly not +what you want. You either need to remap Backspace to Delete or hit the +key most commonly marked Del. + +The U/W FOCAL Manual also references keys that used to appear on some +terminals, most especially teletypes, which no longer appear on modern +keyboards: + +| Teletype Key | Modern Equivalent | +| ------------ | ----------------- | +| `LINE FEED` | Ctrl-J | +| `FORM FEED` | Ctrl-L | + + +## Command Overloading + +[The Manual][uwfm] tells you right up front that U/W FOCAL only pays +attention to the first letter of each command when trying to decide what +you're asking it to do, which is why we have strange commands like +`YNCREMENT`: `I` was already taken by `IF`. + +What it *doesn't* make as clear is how the creators of U/W FOCAL began +adding a second layer of overloading to this scheme after they began +running out of letters in the English alphabet. + +Early examples of FOCAL's command overloading scheme are the `ON`, +`OPEN` and `OUTPUT` commands. By looking at the first argument to the +command, FOCAL can tell which of the three you mean. If you give a valid +FOCAL expression in parentheses, it is clearly an `ON` command. If you +give anything beginning with a letter, FOCAL decides whether it's an +`OPEN` or `OUTPUT` command based on which letter that is; you will +notice that the first letter of no `OPEN` sub-command is the same as the +first letter of any `OUTPUT` sub-command. + +In later versions of U/W FOCAL, they added a third level to some +commands. We have `OPEN RESTART READ` and `OPEN RESUME INPUT`, for +example. It may help to abbreviate the commands, as the first letter of +each word is all that really matter: `O R R` is clearly not the same as +`O R I`. + +There are other examples of this, such as `LIBRARY` and `LIST`. It is +the first letter of the first argument to these commands that determines +what FOCAL does. + +In at least one case, you can see this feature of FOCAL used to talk +about a single command under different names. Some FOCAL documents talk +about a `ZVR` command, meaning Zero VaRiable. It's just another way of +spelling the `ZERO` command: it does the same thing. + +FOCAL *only* checks the first letter of the command and any necessary +disambiguating arguments. The following is therefore a perfectly legal +FOCAL program: + + 01.10 SPEW I = 0 + 01.20 YGGDRASIL I + 01.30 LOOGIE BRAIN 1.2 ; TARPIT I ! + +It does exactly the same thing as a program we will encounter +[shortly](#lbranch). + + +## `LIBRARY` + +The OS/8 version of U/W FOCAL includes a "library" feature modeled on a +similar feature in OMSI PS/8 FOCAL. These features collectively allow +the user access to FOCAL program files stored in the OS/8 file system +from within a U/W FOCAL program or as immediate commands to U/W FOCAL. + +We showed how you can use some of these commands to save and load +programs to disk [above](#ls-library), but there's a lot more to it. +Coverage of this begins on page 34 of [the DECUS submission][uwfd]. + + +## `LIST ALL`, `LIST ONLY`, `ONLY LIST` + +These commands allow you to get OS/8 directory listings from within U/W +FOCAL: + +* **LIST ALL** — Same as `DIR` under OS/8. Accepts an + optional OS/8 device name, e.g. `L A SYS:` to show the contents of the + `SYS:` device. + +* **LIST ONLY** — Same as `DIR *.FC` under OS/8. You can + also give a device name, a file name, or both to be more specific. For + example, you could check for the existence of a `FOO.FC` file on + the first half of the second RK05 device with `L O RKA1:FOO`. + +* **ONLY LIST** — Same as `LIST ONLY` except for FOCAL + data files, `*.DA`. The [DECUS submission][uwfd] says it looks for + `*.FD`, but that does not appear to be correct in my testing. + + +## `LOGICAL BRANCH` + +Our distribution of U/W FOCAL includes the `LOGICAL BRANCH` feature, +which is not documented in [the Manual][uwfm], but it *is* documented on +page 37 of [the DECUS submission][uwfd]. Briefly, it acts like a `GOTO` +command where the jump is only made if keyboard input is detected. + +The following program counts upward continuously until a key is pressed, +then it prints how many iterations it ran: + + 01.10 SET I = 0 + 01.20 YNCR I + 01.30 LOGICAL BRANCH 01.20 ; TYPE I ! + +The DECUS document suggests using this feature to turn the keyboard into +a giant "switch register," allowing the user to control the behavior of +a long-running program without stopping to wait for user input. Can you +say "video games"? + + +## `LOGICAL EXIT` + +Another addition to our version of U/W FOCAL as compared to the version +documented in [the Manual][uwfm] is `LOGICAL EXIT` which immediately +exits U/W FOCAL and returns you to OS/8, just as if you had hit +Ctrl-C. + + +## FOCAL Statement Functions + +This appears to be the same thing [the Manual][uwfm] calls Program +Defined Functions. Therefore, you may look on the material beginning on +page 63 of [the DECUS submission][uwfd] as just another take on the same +issue. Some of the examples are more enlightening than the one in the +manual. The first example in the DECUS submission, `F(EXP)`, is more +robustly coded than the same function in the Manual; comparing the two +is instructive. + + +## `FRA` Built-In Function + +This function is not documented in [the Manual][uwfm], but it is +documented on page 60 of [the DECUS submission][uwfd]. It allows you to +set up random access to a file, storing numbers in various raw binary +formats directly to the file as if it were a large in-memory array. + +If you've been trying to use the `FCOM` function but have run into the +limit on the size of a PDP-8 core memory field, switching to `FRA` is +the logical next step. + + +## Front Panel Differences + +Whenever [the Manual][uwfm] refers to the PDP-8's front panel, it is +speaking generically of all the models it ran on as of October 1978. The +PDP-8 models introduced in the decade following the introduction of the +PDP-8/I differ in many ways, and one of the greatest areas of difference +is in their front panel controls and indicators. We do not intend to +fully document all of the differences here, but only to clarify the +differences brought up by the U/W FOCAL Manual. + +You normally will not need to use the front panel with the OS/8 version +of U/W FOCAL we distribute with the PiDP-8/I software distribution since +you start and stop U/W FOCAL through OS/8 rather than the front panel. +However, we thought these matters could use clarification anyway. + +Beyond this point, when we refer to the PDP-8/e, we also mean the 8/f, +which shared the same front panel design. We also include the 8/m, which +normally came with a minimal front panel, but there was an optional +upgrade for an 8/e/f style front panel. These three models are therefore +interchangeable for our purposes here. + + +### `START` vs. `CLEAR` + `CONTINUE` vs. `RESET` + +With the PDP-8/e, DEC replaced the `START` front panel switch of the +preceding PDP-8/I with a `CLEAR` switch. Why did they do this? + +On a PDP-8/I, the difference between `START` and `CONTINUE` is sometimes +confusing to end users, since in many cases they appear to do the same +thing. Why have both? The difference is that `CONTINUE` simply resumes +operation from the current point in the program where it is stopped, +whereas `START` resets several key registers and *then* continues. + +The PDP-8/e change splits this operation up to avoid the confusion: the +old `START` keypress is equivalent to `CLEAR` followed by `CONTINUE`. +(This pair of switches also has a `START` label above them, a clear +functional grouping.) + +The U/W FOCAL Manual also speaks of a `RESET` switch in conjunction with +the FOCAL starting and restarting the computer. I haven't been able to +track down which PDP-8 model has such a switch yet, but for our purposes +here, I can say that it just means to load the starting address and hit +`START` on a PDP-8/I. + + +### `EXTD. ADDR LOAD` + +The PDP-8/e has many fewer switches on its front panel than the PDP-8/I, +yet it is a more functional machine. One of the ways DEC achieved this +is by removing the `IF` and `DF` switch groups and adding the +`EXTD. ADDR LOAD` switch, which lets you set the `IF` and `DF` registers +using the same 12-bit switch register used by the `ADDR LOAD` switch. + +The `ADDR LOAD` switch on a PDP-8/e does the same thing as the +`Load Add` switch on a PDP-8/I. + + +### Switch Direction + +DEC reversed the meaning of switch direction between the PDP-8/I and the +PDP-8/e, and [the Manual][uwfm] follows the 8/e convention: on the 8/I, +up=0=off, whereas on the 8/e, up=1=on. Keep this in mind when reading +the U/W FOCAL Manual's references to front panel switch settings. + + +### Switch Ordering + +When [the Manual][uwfm] talks about the switch register (SR), it numbers +the switches left to right, not by their logical bit number in the +switch register. That is, "Switch 0" is the leftmost (high order bit) SR +switch, not "bit 0" in the SR, which would be the rightmost SR switch. + + +## Error Codes + +The [U/W FOCAL Manual][uwfm] gives a somewhat different error code table +than the one on `CARD4.DA` of the [U/W FOCAL reference cards][uwfr]. For +the most part, the latter is just a simple superset of the former, and +both apply. In some cases, though, the two tables differ, or one of them +differs from the `UWF16K` program we ship on the OS/8 system disk. + + +### `?18.32` vs `?18.42` — `FCOM` index out of range + +The two error code tables give different error codes for this condition. +However, since I have not been able to get this error to happen, I do +not know which code is correct for our current version of FOCAL. + + +### `?31.<7` — Non-existent program area called by `LOOK` or `LINK` + +Our current implementation of U/W FOCAL removed those commands in favor +of `LIBRARY`, so you can't make this one happen. An error in a `LIBRARY` +command is most likely to give `?26.07` instead. + + +### Irreproducible Errors + +There are some errors listed in one or both tables that I have been +unable to cause, though I have tried: + +| Code | Meaning +| ------ | ------- +| ?07.44 | Operator missing or illegal use of an equal sign +| ?18.32 | `FCOM` index out of range (value given in the manual) +| ?18.42 | `FCOM` index out of range (value given on the refcard) +| ?27.90 | Zero divisor + + +### Untested Error Cases + +I have not yet created programs large enough to test the "out of space" +codes `?06.41` (too many variables), `?10.50` (program too large), +`?13.65` (insufficient memory for `BATCH` operation), `?23.18` (too much +space requested in `OUTPUT ABORT` or `CLOSE`), `?23.37` (output file +overflow), and `?25.02` (stack overflow). + +There are also some errors I simply have not yet tried to cause: +`?01.03`, `?01.11`, `?12.10`, `?12.40`. + + +## Missing Hardware Support + +The [U/W FOCAL reference cards][uwfr] and the [DECUS submission][uwfd] +talk about features for hardware we don't have. Either the +command/feature doesn't exist at all in the version of U/W FOCAL we +distribute or it doesn't do anything useful, lacking support within the +version of SIMH we distribute. + +Broadly, these features are for the PDP-12, the LAB-8/e, Tektronix +terminals, and pen plotters. Should anyone extend SIMH with a way to +control such hardware (or emulations of it) we may consider putting +these features back into our distribution of U/W FOCAL. + +In the meantime, the following facilities do not work: + +* The `FADC`, `FJOY`, `FLS`, `FRS`, and `FXL` functions don't exist + +* There is no plotter support in SIMH, so the `PLOT` command doesn't + exist + +* Although support for the VC8E point-plot display exists in SIMH, the + `VIEW` command to drive it is not present in our version of U/W + FOCAL. + +* Error code `?14.15` can't happen; we have no "display buffer" + +* Error codes `?14.50` and `?14.56` can't happen; SIMH doesn't + simulate a PDP-12 or a LAB-8/e + + +## Differences Between U/W FOCAL and Other FOCALs + +The [DECUS submission for U/W FOCAL][uwfd] lists the following +advantages for the version of U/W FOCAL included with the PiDP-8/I +software distribution as compared to FOCAL,1969, FOCAL-8, and OMSI PS/8 +FOCAL: + +1. Extended library features with device-independent chaining and + subroutine calls between programs. + +2. File reading and writing commands, 10 digit precision, full 32k + memory support, 36 possible functions, 26 possible command letters. + +3. Computed line numbers and unlimited line lengths. + +4. Tabulation on output, format control for scientific notation. + +5. Double subscripting allowed. + +6. Negative exponentiation operators permitted. + +7. `FLOG`, `FEXP`, `FATN`, `FSIN`, `FCOS`, `FITR`, and `FSQT` rewritten + for 10-digit accuracy. + +8. Character manipulations handled with `FIN`, `FOUT`, and `FIND`. + +9. Function return improvements: + + * `FSGN(0)=0` in U/W FOCAL; `=1` in FOCAL,1969 + * `FOUT(A)=0` in U/W FOCAL; `=A` in PS/8 FOCAL + +10. n/a; see [above](#miss-hw) + +11. 6 special variables are protected from the `ZERO` command: `PI`, + `!`, `"`, `$`, `%`, and `#`. + + `PI` is initialized as 3.141592654. + +12. The limit on the number of variables is 676 + +13. Text buffer expanded to 15 blocks + +14. Two-page handlers permitted + +15. Program and file names are wholly programmable. File size may be + specified. OS/8 block numbers may be used in place of file names. + +16. The `OPEN` and `DELETE` commands can have programmed error returns. + +17. Improved distribution and random initialization of `FRAN`. + +18. `ERASE`, `MODIFY`, and `MOVE` do not erase variables. + +19. `WRITE` doesn't terminate a line; `MODIFY` doesn't echo form feed. + +20. U/W FOCAL's starting address is 100 (field 0) or 10200 (field 1). + + +## Converting Programs from Other Versions of FOCAL + +Programs saved by other versions of FOCAL generally don't have the same +format as the core images used by U/W FOCAL. You must therefore use one +of the [text based loading methods](#loading) to save your program text +out of the other FOCAL and load it into U/W FOCAL. + +Also, while the `ERASE` command may be used to zero variables in other +FOCALs, you must use `ZERO` for that in U/W FOCAL. If your program +starts off with `ERASE` commands to initialize its variables, there's a +pretty good chance your program will just erase itself under U/W FOCAL. + + +## Why Did We Write This? + +[The Manual][uwfm] is well written as far as it goes, but there are +gaps: + +1. It is written somewhat generically for the whole PDP-8 family as of + late 1978, whereas the PiDP-8/I project is focused on a single model + from 1968. Those not familiar with the differences can therefore be + confused by some of its directions. + +1. There are considerations in our simulated PiDP-8/I world that simply + did not apply to those running U/W FOCAL on the real hardware. + +1. There are multiple versions of U/W FOCAL; the version covered by + [the Manual][uwfm] isn't the one we actually ship. Our two + [other][uwfr] primary [sources][uwfd] also do not cover exactly the + version of U/W FOCAL we ship. + +1. It inspires questions in the reader's mind without providing an + answer. Whether this was intentional — with the author intending + that the user answer these questions on his own — or otherwise, some + of these questions we felt needed answering here within the PiDP-8/I + U/W FOCAL documentation. + +This document is our attempt to fill these gaps and to supplement those +other documents. [Extensions and corrections][hack] are welcome. + + +## References + +The primary sources for this supplement are: + +* [U/W FOCAL Manual][uwfm], October 1978, by Jim van Zee of the + University of Washington. + +* [U/W FOCAL reference cards][uwfr] from the U/W FOCAL distribution, + approximately contemporaneous with the Manual, but clearly for a + different version of U/W FOCAL than is documented in the Manual. + +* [DECUS Submission for U/W FOCAL][uwfd], also by van Zee, from August + 1978. + + This document describes the OS/8 version of U/W FOCAL rather than + the paper tape version described by [the Manual][uwfm] we use as our + primary document here within the PiDP-8/I project. We chose to + convert the Manual to Markdown rather than this DECUS submission + because the DECUS document's scan is terrible, resulting in nearly + worthless OCR output; we *really* did not want to retype the whole + thing! Additionally, we think the Manual is a better tutorial than + the DECUS submission, though the DECUS submission is perhaps a + better reference text. + +[df8]: http://www.ibiblio.org/pub/academic/computer-science/history/pdp-8/FOCL69%20Files/DEC-08-AJAD-D.pdf +[f71]: http://svn.so-much-stuff.com/svn/trunk/pdp8/src/decus/focal8-177/ +[hack]: https://tangentsoft.com/pidp8i/doc/trunk/HACKERS.md#patches +[uwfd]: http://www.pdp8.net/pdp8cgi/query_docs/view.pl?id=191 +[uwfm]: https://tangentsoft.com/pidp8i/doc/trunk/doc/uwfocal-manual.md +[uwfr]: https://tangentsoft.com/pidp8i/doc/trunk/doc/uwfocal-refcards.md + + +### License + +Copyright © 2017 by Warren Young and Bill Cattey. Licensed under the +terms of [the SIMH license][sl]. + +[sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md ADDED doc/uwfocal-manual.md Index: doc/uwfocal-manual.md ================================================================== --- /dev/null +++ doc/uwfocal-manual.md @@ -0,0 +1,3056 @@ +# U/W FOCAL Manual V4E, October 1978 + + +UWFUWFU           UWFUWFU  UWFUWFU           UWFUWFU  UWFUWFUWFUWFUwFUWFUWFUWF +WFUWFUW           WFUWFUW  WFUWFUW           WFUWFUW  WFUWFUWFUWFUWFUWFUWFUWFU +FUWFUWF           FUWFUWF  FUWFUWF           FUWFUWF  FUWFUWFUWFUWFUWFUWFUWFUW +UWFUWFU           UWFUWFU  UWFUWFU           UWFUWFU  UWFUWFUWFUWFUwFUWFUWFUWF +WFUWFUW           WFUWFUW  WFUWFUW           WFUWFUW  WFUWFUW +FUWFUWF           FUWFUWF  FWUFWUF           FUWFUWF  FUWFUWF +UWFUWFU           UWFUWFU  UWFUWFU     U     UWFUWFU  UWFUWFU +WFUWFUW           WFUWFUW  WFUWFUW    UWF    WFUWFUW  WFUWFUW +FUWFUWF           FUWFUWF  FUWFUWF   UWFUF   FWUFWUF  FUWFUWFUWFUWFUWFUW +UWFUWFU           UWFUWFU  UWFUWFU  WWFUWFU  UWFUWFU  UWFUWFUWFUWFUWFUWF +WFUWFUW           WFUWFUW  WFUWFUW UWFUWFUWF WFUWFUW  WFUWFUWFUWFUWFUWFU +FUWFUWF           FUWFUWF  FUWFUWFUWFUWFUWFUWFUWFUWF  FUWFUWFUWFUWFUWFUW +UWFUWFU           UWFUWFU  UWFUWFUWFUWFUWFUWFUWFUWFU  UWFUWFU +WFUWFUW           WFUWFUW  WFUWFUWFUWFU FUWFUWFUWFUW  WFUWFUW +FUWFUWFW         WFUWFUWF  FUWFUWFUWFU   WFUWFUWFUWF  FUWFUWF +UWFUWFUWFUWFUWFUWFUWFUWFU  UWFUWFUWFU     UWFUWFUWFU  UWFUWFU + FUWFUWFUWFUWFUWFUWFUWFU   WFUWFUWFU       FUWFUWFUW  WFUWFUW +  WFUWFUWFUWFUWFUWFUWFU    FUWFUWFU         WFUWFUWF  FUWFUWF +      UWFUWFUWFUWFU        UWFWUFU           UWFUWFU  UWFUWFU + + + +## Index to Major Topics in This Manual + +* [Abbreviations](#abbreviations) +* [Arithmetic operators](#operators) +* [Break key](#ctrl-keys) +* [Character codes](#character-codes) +* Commands + * [Summary](#commands) + * [Direct, indirect](#dir-ind-cmd) + * [`ASK`](#io-cmds) + * [`BREAK`](#break) + * [`COMMENT`](#comment) + * [`DO`](#do) + * [`ERASE`](#erase) + * [`FOR`](#for) + * [`GOTO`](#goto) + * [`HESITATE`](#hesitate) + * [`IF`](#if) + * [`JUMP`](#jump) + * [`KONTROL`](#kontrol) + * [`LINK`](#link) + * [`LOOK`](#look) + * [`MODIFY`/`MOVE`](#modify) + * [`NEXT`](#next) + * [`ON`](#on) + * [`OPEN`](#open) + * [`PUNCH`](#punch) + * [`QUIT`](#quit) + * [`RETURN`](#return) + * [`SET`](#set) + * [`TYPE`](#io-cmds) + * [`WRITE`](#write) + * [`XECUTE`](#xecute) + * [`YNCREMENT`](#yncrement) + * [`ZERO`](#zero) + * [Editing](#editing) + * [Enclosures](#enclosures) + * [Error messages](#error-messages) + * [Formatting](#formatting) + * Functions + * [Summary](#function-summary) + * [Program Defined](#pdfs) + * [`FABS`](#fabs) + * [`FATN`](#fatn) + * [`FBUF`, `FCOM`](#fbuf) + * [`FCOS`](#fcos) + * [`FDIN`](#fdin) + * [`FEXP`](#fexp) + * [`FIN`](#fin) + * [`FIND`](#find) + * [`FITR`](#fitr) + * [`FLOG`](#flog) + * [`FMIN`, `FMAX`](#fmin) + * [`FMQ`](#fmq) + * [`FOUT`](#fout) + * [`FRAC`](#frac) + * [`FRAN`](#fran) + * [`FSGN`](#fsgn) + * [`FSIN`](#fsin) + * [`FSQT`](#fsqt) + * [`FSR`](#fsr) + * [`FTRM`](#ftrm) + * [Input echo](#input-echo) + * [Input terminators](#input-terminators) + * [I/O operators](#io-operators) + * [Line numbers](#line-numbers) + * [Loops](#loops) + * [Numbers and variables](#numbers) + * [Patches](#patches) + * [Punctuation](#punctuation) + * [Reading in programs](#punch) + * [Symbol table dump](#symbol-table-dump) + * [Trace feature](#trace) + * [Variables](#variables) + + +## Introduction to U/W-FOCAL for the PDP-8 + +UWF is a powerful interactive language for the PDP-8¹ which combines +ease of use with an extensive set of features, allowing one to perform +complex calculations or control laboratory experiments with a minimum of +effort. UWF is an advanced version of FOCAL,¹ a stack processing +language which is somewhat similar to BASIC and FORTRAN but much easier +to use! Programmers who are familiar with either of these languages +should be able to write programs for UWF almost immediately, while those +who are just learning will find that UWF's simplified commands and +built-in editor make their progress very rapid. + + +### Hardware Requirements + +The minimum system configuration for running UWF is an 8K machine +capable of executing the PDP-8 instruction set and some sort of +terminal. UWF will automatically take advantage of any additional memory +present as well as permitting the use of high-speed I/O devices such as +punched paper tape or an audio tape recorder for program and data +storage. There is also a much more elaborate version available for +systems licensed to use the OS/8 operating system¹ which provides +complete device-independent file support. + + +### Loading UWF + +To load the binary tape containing UWF into your machine, proceed as +follows: + +1. Make sure that the BIN loader is resident in Field 0. + +2. Set the Switch Register to 7777 and hit `ADDR LOAD`, then reset + Switch 0 if input is from the High Speed reader (leaving 3777), + otherwise go to the next step. + +3. Place the tape in the reader so that the read-head is positioned + over the leader section and hit the `START` (or `CLEAR` and + `CONTINUE`) switches. + +The run light will go off when the tape has finished loading; check to +be sure the display is zero, indicating a successful load. If not, +repeat steps 1-3 above to see if you get the same checksum error each +time. If you do, the tape you are using has probably been damaged and +you should try again with another copy. + + +### Starting the Program + +In order to start UWF for the first time, do the following: + +1. Set the Switch Register to 0100 (UWF's Starting Address) + +2. Press the `ADDR LOAD` and `EXTD. ADDR LOAD` switches + +3. Set switches 0-2 and 6-11 for any [options desired](#opt-switch) + +4. Now set the `RUN/HALT` switch to `RUN`, and hit `CONTINUE` + +UWF should respond immediately with a congratulatory message and +indicate the amount of memory available in your system. For +installations with more than 8K, here is how the additional memory space +is used: + +| Core | Features Allowed | +| ---: | -------------------------------------------- | +| 12K | Expanded symbol storage or `FCOM` | +| 16K | One additional program area or `FCOM` | +| ... | ... | +| 32K | Five more program areas, or four plus `FCOM` | + +If you wish to limit the amount of memory available for any reason, you +should set switches 9-11 in the switch register before proceeding with +Step 4: + +> Switches 9-11 (octal): 0=All, 1=28K, 2=24K, 3=20K, 4=16K, 5=12K and 6 or 7=8K + +There are a number of other 'custom' features which can be installed +automatically when the program is first started up. These options are +controlled by the setting of bits 0-2 and 6-8 in the switch register. +The first group (0-2) selects various terminal options while the second +group (6-8) controls additional features. Switches 3-5 are ignored and +may be set to any value. Once UWF has been started the first time, step +3 is unnecessary and the switches may remain set to '100'. + + + +| Switch | Function | +| -----: | -------------------------------------------------- | +| 0 | Use 'CRT-style' rubouts instead of 'backslashes' | +| 1 | Output four 'nulls' after every Carriage Return | +| 2 | Print error messages on a new line | +| 6 | Add three extra 'secret variables' (`&`, `:`, `\`) | +| 7 | Add the `KONTROL` command and the `FDIN` function | +| 8 | Add the `FCOM` and `FBUF` functions (requires 12K) | + +Example: A switch setting of '4134' limits the program to 16K, adds +`FCOM`, `FBUF` and the digital I/O routines, and installs 'scope +rubouts'. The '100' bit is ignored. + +Some of these patches can also be installed (or removed) after the +program has been started; see [the Patches section below](#patches) for +further details. Note that adding the `FCOM` function reduces the +effective memory size by 1 field, hence users with 16K who add this +option will normally lose the first additional program area. Since it +might be more desirable in this particular case to have `FCOM` replace the +extra variable storage, there is a 'magic location' which can be changed +(*before* you start things up!) to effect this arrangement. (16K +configurations only; see [the Patches section below](#patches) for +details.) + +Note that UWF runs with the interrupt system *ON* which allows program +execution to overlap certain I/O operations. The result is faster run +times, a 'live' keyboard and the possibility of adding 'background' +tasks which can be controlled by the program or 'high-level' interrupts +in which an external event causes the execution of a specific group of +statements within the program. + +With the interrupt system enabled, however, it is possible that an +'unknown' device will create a continuous interrupt and thus prevent UWF +from running. If the `RUN` light goes on but there is no output as soon +as you hit the `CONTINUE` switch, halt the machine, hit the `RESET` or +`CLEAR` switch a few times, and then restart at location 100. If UWF +still appears to be stuck in an endless loop, you will probably have to +add an appropriate 'clear flag' instruction to the interrupt routine. +See [the Patches section below](#patches) for the proper location. + + +### UWF's Control Keys + +UWF recognizes the following control keys: + +1. CTRL/F is the master program break key: it will restart + UWF at any time, assuming that the program is still running. + +2. CTRL/C is the monitor break key. It will eventually trap + to a resident 'ODT' package which is not yet implemented. + +3. CTRL/S (`XOFF`) stops output to the terminal. This + provides users with video terminals time to inspect their results. + +4. CTRL/Q (`XON`) resumes output to the terminal. Some + terminals issue `XON`/`XOFF` codes automatically when the screen + fills up. + +5. The RETURN key is used to terminate all command lines. + UWF will not recognize a command until the RETURN key is typed. + +6. The RUBOUT or DELETE key is used to cancel the + previous character. On hard-copy terminals, a `\` is echoed for each + character deleted. On video terminals they just disappear! + +7. The LINE FEED key is used to retype a command line + *before* typing RETURN in order to verify that all + corrections were made properly. This is mostly for hard-copy + terminals. + +Remember: UWF can be interrupted at any time simply by typing +CTRL/F. This is accomplished by holding down the +CTRL key and then typing the letter `F`. UWF will respond by +typing a question mark (`?`) followed by the line number where the +program was interrupted and then print an asterisk to indicate that it +is ready for further instructions: + + ?@ 05.13 UWF was interrupted at line 5.13 + + +## Direct and Indirect Commands + +UWF prints an asterisk (`*`) whenever it is in command mode waiting for +new instructions. You can then type in either 'direct commands' which +are executed immediately, or 'indirect commands' which are saved for +execution at a later time. To use UWF as a fancy calculator simply give +it a 'direct command' and hit the RETURN key. For example, if you enter +the command: + + *TYPE PI + +UWF will print the value '3.141592654E+00', correct to 10 significant +figures. The Direct Command feature is the essence of interactive +programming since it permits one to work through a long calculation a +step at a time, or to try out several different ways of doing something. +You can experiment with any of UWF's commands by simply typing them in +as you read through this manual. + +Indirect Commands always begin with a line number which indicates the +order in which they are to be executed. They may be entered in *any* +order, however, and can be examined or changed at any time. Changes to +indirect commands are facilitated by the use of UWF's built-in editor +which allows lines to be modified, moved to a different part of the +program, or deleted. Since indirect commands can be selectively executed +by direct commands it is possible to build a very powerful set of +'macros' which can then be called with just a few keystrokes. + +Line numbers in UWF have a 'dollars and cents' format: `XX.YY` where +`XX` may range from 00-31 (the 'group' number) and `YY` may have any +value from 00-99 (the 'step' number). Group and Step both have special +meanings in some commands, so the first line of the program is usually +labeled `1.1`. Notice that leading and trailing zeros are not necessary, +but one must always include a space after the line number to separate it +from the commands on the rest of the line. Here are some sample indirect +commands: + + *3.61 TYPE ! + *12.7 COMMENT + *1.99 QUIT + +A standard programming practice is to index sequential commands by an +increment of either '.05' or '.1'. Thus line '1.2' would be used for the +statement following line '1.1' rather than line '1.11'. This leaves room +to insert up to 9 additional lines in case changes to the program are +necessary. Of course lines can always be moved to make room, but it is a +nuisance to have to do this and such changes might require alteration of +other parts of the program as well. + + +### Group and Relative Line Numbers + +Several UWF commands are capable of operating on all statements with the +same group number. To reference an entire group of lines one simply +specifies the group number without designating any particular program +step: `WRITE 1`, for example, will list all the program steps in 'Group +1'. Since the number '1' and the number '1.00' are indistinguishable to +UWF, it is not possible to write just line 1.00 without writing the rest +of the lines in the same group as well. For this reason the first line +in a group is generally reserved for comments in order to avoid any +complications with group operations. + +UWF can also designate a 'sub-group' operation consisting of all the +lines following a specified line in the same group. Such operations are +indicated by a 'negative line number': 'WRITE -1.5', for instance, will +list all of the lines in Group 1 starting from line 1.5 (if it exists). + +Line numbers in the range '.01-. 99" are termed 'relative line numbers', +i.e. they refer to lines in the *current group*, rather than to lines in +'Group 0'. The use of such line numbers is encouraged because it makes +the program more compact and also allows subroutines to be moved easily +from one part of the program to another without having to worry about +internal references. Lines with numbers less than 1.00 *can* be saved as +part of the indirect program, but they can only be executed when the +program is started from the beginning since there is no way to branch to +them at a later time. + +Finally, references to line '0' also have a special meaning. A few +commands interpret such references to mean 'the entire program', while +most others regard 'line 0' as a reference to 'the next command'. Line 0 +itself is the permanent comment line (the 'Header Line') at the +beginning of the program. + + +### Punctuation + +It is a common practice to put several commands on the same line in +order to reduce the amount of paper required for listing the program as +well as to consolidate related operations. A 'semicolon' (`;`) is used +to separate such commands: + + *SET A=5; TYPE A + +Commands which operate on more than one expression use a comma to +separate the values. Thus the command `TYPE A,B` is equivalent to `TYPE +A; TYPE B`. Spaces may be included to improve the readability of a +program, but one must remember that 'space' is a terminator, equivalent +to a comma, so the command `TYPE A B` is interpreted as `TYPE A,B`, not +as `TYPE AB`. + + +## Numbers and Variables + +UWF can handle up to 10-digit numbers with a magnitude range of +10615. Numbers may be written as signed or unsigned +quantities and may include a decimal fraction as well as a +'power-of-ten' exponent indicated by the letter `E`. All numbers are +stored internally in a 'floating-point' format with 35 bits of mantissa +and 11 bits of exponent. This is equivalent to more than 10-digit +accuracy. UWF will respond with an error message if a user attempts to +enter a number with too many digits. The following all represent the +value 'sixty': + +* 60 +* 60.00 +* 6E1 +* 600.0E-1 + +UWF also allows letters to be treated as numbers so that questions may +be answered with a 'YES' or 'NO' response rather than with a numeric +reply. When decoded in this manner, the letters 'A-Z' have the values +'1-26', except that the letter 'E' always means 'power-of-ten'. Thus the +answer 'NO' would have the numerical value '155' and the number 'sixty' +could also be written as `0DT` or `0FEA`. Note that the leading '0' is +only required when incorporating such 'numbers' into a program. It is +not required as part of a user response. + + +### Variable Names + +Variables are used to store input values or to save intermediate +results. Variables are thus like the storage registers on a calculator +except that the programmer may make up his own names to designate the +values stored. UWF allows variable names of any length, but only the +first two characters are retained internally. Thus the names JOHN and +JOE would both refer to the variable `JO`. The first character of a +variable name must obviously not be a number, nor can it be the letter +`F` since that letter is used to designate functions. However UWF does +allow symbols such as `$` and `"` to be used as part of a variable name +so you can have quantities such as `A$`, `A`, and `A"`. Variables are +always stored as numeric quantities; UWF does not currently have +'string' variables. + + +### The Symbol Table + +The names of the variables which have been used by the program are saved +(along with their values) in a region of memory called the 'Symbol +Table'. This area is completely independent of the area used to store +the program so changes to the text buffer do not affect any of the +values stored in the symbol table. This is extremely convenient since it +allows a program to be interrupted, modified, and then restarted +somewhere in the middle, knowing that any intermediate results obtained +will still be available. Of course the programmer may examine any such +values simply by `TYPE`ing them out, or he may change a few values with +a direct command before restarting the program. Variables are always +assumed to have the value 'zero' until another value has been assigned. +The `TYPE $` command can be used to list all the values in the Symbol +Table in the order they were defined by the program. The ZERO command is +used to clear the table or to selectively set some of the variables to +zero. Variables with the value '0' may be replaced by other non-zero +variables when the symbol table fills up. This is transparent to the +programmer since 'undefined' variables are always zero anyway. + + +### Protected Variables + +The symbols {`!`, `"`, `#`, `$`, `%`} and optionally {`&`, `:`, `\`}, +along with the value of `PI`, are 'protected' variables which cannot be +replaced or removed by a ZERO command. This makes them useful for saving +results which are needed by a second program. Since they cannot be input +or output directly and do not appear in a symbol table dump, they are +also sometimes called 'secret' variables. Note that UWF automatically +sets `PI` equal to 3.141592654, so users should not use `PI---` as a +variable name or this value will be lost. The variable `!` ('bang') is +used as the dimension constant for [double subscripting](#dsub) and many +of the remaining 'secret variables' serve as dummy arguments for +[Program Defined Functions](#pdfs). To `TYPE` the values of these +variables, you must prefix a `+` sign or enclose them in parentheses: +`TYPE +!` or `TYPE (!)` will output the value of the first one. + + +### Subscripted Variables + +Variables may be further identified by attaching a subscript enclosed in +parentheses immediately after the name, e.g. `A(I)`. Such subscripts may +consist of arithmetic expressions involving other subscripted variables, +so quite intricate relations can be developed. Unlike many other +high-level languages, UWF does not require any 'dimension' statements +for processing subscripted variables, nor are the subscripts limited to +only positive integers. (They are limited to 12 bits, however.) A +variable such as `APPLE(-PIE)` is thus perfectly acceptable although UWF +will view this more prosaically as simply `AP(-3)`. Non-subscripted +variables are the same as those with a subscript of zero, i.e. `A` = +`A(O)`. + + + +To handle double subscripting, UWF *does* require a small amount of +additional information. Before using a double subscripted variable the +programmer must store the maximum value of the first subscript in the +protected variable `!`. This value may be greater than the actual +maximum without incurring any storage penalty, but if it is too small +more than one array element will be stored in the same location. Since +this single 'dimension constant' is used for all arrays it should be +chosen for the largest array in cases where the program uses several +different sizes. + +To illustrate: suppose that operations on a 5x5 array were necessary. +Then `!` ('bang') should be set to 5. If 3x3 arrays were also needed +simultaneously (which is not very likely) their elements would all be +unique and only 9 storage locations would be used, not 25. Non-square +arrays are handled just as easily: a 5x20 array would still only require +that `!` be set to 5 since that is the maximum value of the *first* +subscript. This method of storing two-dimensional arrays proves very +convenient for a wide range of linear algebra problems. The value of `!` +is generally used as a loop limit so that the routines can be used with +any size array. + + +## Arithmetic Operators + +UWF recognizes 6 basic arithmetic, and 1 special 'character value' +operator: + +1. `+` Addition +2. `-` Subtraction +3. `*` Multiplication +4. `/` Division +5. `^` Signed Integer Powers +6. `=` Replacement +7. `'` Value of next character + +These 7 operators may be combined with explicit numbers and function or +variable names to create 'Arithmetic Expressions' such as: + + X= -B/2*A + Y= FSQT(B^2-4*A*C) + +Such expressions can be used *anywhere* that explicit numbers appear in +this writeup. In particular, they may be used to compute line numbers. +All expressions are evaluated to 10-digit accuracy independent of the +format used for output. Intermediate results are generally rounded off +rather than being truncated. Most commands use, or operate on, +arithmetic expressions. If such expressions are *omitted*, a value of +'zero' is always assumed. This occurs frequently when evaluating line +numbers, hence you should recall the comments about line '00.00' +mentioned [above](#line-numbers). + + +### Priority of Arithmetic Operations + +Arithmetic operations are performed in the following sequence: + +| Priority | Op | Description | +| -------: | ------------------------------ | +| 1st | `^` | integer power | +| 2nd | `*` | multiplication | +| 3rd | `/` | division | +| 4th | `-` | subtraction and negation | +| 5th | `+` | addition | +| Last | `=` | replacement | + +When UWF evaluates an expression which includes several operations, the +order above is followed. For example, UWF evaluates the expression: + + X=5^2/5*2-Z=5/2 + +leaving `X` equal to 'zero' and `Z` equal to 2.5. + +Notice that multiplication has a higher priority than division. This is +different from the convention in many other languages where these +operations have equal priority. In most cases this difference is of no +consequence. The effect of embedding replacement operators is to cause +portions of the expression to be evaluated in a somewhat different order +than would be the case if they were not included. In the example above, +for instance, the quantity `5*2` is divided by the quantity `5*2` and +then the quantity `Z` which is equal to `5/2` is subtracted from the +result. However, if one were to add a `Y=` operator after the first `/` +then the quantity `5*2` would be divided by `Y` which would be equal to +`5*2-Z`. + + +### Enclosures + +The order of evaluation can also be changed by the use of enclosures. +Three different kinds are allowed by UWF: Parentheses `()`, Square +Brackets `[]`, and Angle Brackets `<>`. Subscripts and function +arguments are common examples of expressions contained in enclosures. +UWF treats all sets identically — they must be matched, of course! — +except in some of the monitor commands in the OS/8 version. If the +expression contains nested enclosures, UWF will evaluate it starting +with the innermost set and working outward. For example: + + [A=5*-5]^2 + +is evaluated as 'four hundred' with `A`=20 and `B`=5. + + +## Abbreviations + +UWF doesn't care whether you tell it to `TYPE` or `TAKEOFF`! The reason +is that only the *first* letter of the command is recognized, just as +only the first *two* letters of a variable name have significance. So +while we have been carefully spelling out all the commands in the +examples so far, we could just as well have abbreviated them to their +first letters. + +This feature of the language is both good and bad. On the one hand it +greatly reduces the amount of typing required and at the same time +increases the number of program steps possible. But on the other hand, a +program containing hundreds of single letter commands looks more like a +sheet of hieroglyphics than anything else. This makes it quite difficult +for the beginner to understand the program logic until he himself has +become more familiar with the meaning of all the symbols. For maximum +clarity the examples in this writeup will generally be spelled out, but +you should realize that the commands `T PI` and `TYPE PI` will produce +*exactly* the same result. + +We will now turn to a detailed examination of all the commands available +to the UWF programmer, beginning with the editing commands since they +are required for further program development. + + +## Command Mode Editing + +When UWF is in command mode you can use the `RUBOUT` or `DELETE` key to +correct any typing errors. Each time that you hit this key UWF will +delete the preceding character and echo a `\` on the terminal. If you +have a video terminal, and you set switch 0 up when you started UWF for +the first time — or made the appropriate patch yourself — hitting +DELETE will actually remove the character from the screen. +This is obviously much nicer since 'what you see is what you've got'. On +the other hand, users with a hard-copy terminal can always just hit the +LINE FEED key to have the current input line retyped so that +they can see just how it 'really' looks to UWF. There is no limit to the +length of input lines; however, if your terminal does not handle +'wrap-around' automatically, the practical limit is the width of paper. + +In addition to RUBOUT, the BACKARROW (or +UNDERLINE key as it is identified on newer terminals) may be +used to delete all characters to the left, including the line number of +an indirect command. You may then start over again. It is not necessary +to hit RETURN although you may wish to do so to get back to +the left margin again. Note that LINE FEED will not echo a +blank link and RUBOUT will stop echoing a `\` when it reaches +the beginning of the line. + +The use of BACKARROW as a 'line-kill' character necessarily +means that this character (and RUBOUT, of course) cannot be +part of the program, but all remaining ASCII characters, both upper and +lower case, can be used. Control codes can also be used, but they should +be scrupulously avoided since they are non-printing and are therefore +impossible to find when they are embedded in a program. In fact, if you +ever have a mysterious error in what appears to be a perfectly good +command, just try retyping it in its entirety to eliminate any 'ghosts'. + +Once you hit the RETURN key, UWF will digest whatever you +have typed, so subsequent changes require the use of the editing +commands. The text buffer can hold approximately 7000 (decimal) +characters — typically 3-4 pages of printout. To list any or all of this +material you use the `WRITE` command; to eliminate some of it you use +`ERASE`, and to make changes without having to retype the unchanged +part, you use the `MODIFY` command. This command can also be used to +`MOVE` parts of the program to a different location. + + +### `WRITE` + +The `WRITE` command, without any modifier, will list all of the indirect +commands currently saved in the text buffer. Lines are typed out in +numerical sequence, no matter in what order they were entered, and are +separated into the groups you have specified. For this reason it is very +convenient to use a different group number for each major part of the +program even if such a section only has a few program steps. Using the +first line (line `XX.00`) for a `COMMENT` to describe the purpose of +that section is also highly recommended. + +The `WRITE` command can also be qualified by a string of numbers to +limit the listing to selected portions of the program. `WRITE 1`, for +example, will print out just the commands belonging to Group 1, while +`WRITE 2.2` will list only that single line. A command such as `WRITE +1,2,3.3,4.9,5` will list 3 groups and 2 single lines, in the order +specified. Of course you should try to plan your program so that it +executes smoothly 'from top to bottom', but if you do need to add a +major section at the end, the `WRITE` command can be used to at least +make a listing showing the logical program flow. Another convenient +feature of the `WRITE` command is the ability to list a specific line +and all lines following it within the same group. This is done by +specifying a *negative* line number. Thus `WRITE -1.5` will list line +1.5 (if it exists) plus the remainder of Group 1. The `WRITE` command +will not produce an error if the line or group you specified is missing +— it will simply not list it: What you see is what you've got! + + +### `ERASE` + +The `ERASE` command is used to delete parts of the program. `ERASE` +without a qualifier deletes the entire program, while `ERASE 2` will +delete just Group 2. Other possibilities are `ERASE 9.1` which will only +remove that single line, and `ERASE -4.5` which will eliminate the +second half of Group 4. Since `ERASE 0` erases everything, you must use +an `ERASE -.01` command to erase all of Group 0. There is no way to +erase lines such as `2.00` without erasing the entire group at the same +time; this is one restriction on the use of such lines. Unlike the +`WRITE` command, only a single qualifier may be used with `ERASE`, and +UWF will return to command mode immediately after executing the command. +Typing in a new line with the same number as an old one will effectively +erase the previous version. Entry of just a line number by itself will +result in a 'blank line' which may be used to separate sub-sections of a +program. Note that this treatment of blank lines differs from that used +by BASIC. Blank lines will be ignored during program execution. + + +### `MODIFY`/`MOVE` + +To change a program line or to move it to a different part of the +program, you must use the `MODIFY` or `MOVE` commands. `MODIFY` without +a qualifier can be used to examine the header line, but it cannot be +used to change this line. `MODIFY` with a single line number permits +changes to the line specified while a `MODIFY` (or `MOVE`) with *two* +line numbers allows similar changes, but saves the modified line with +the new number. The old line, in this case, remains just as it was. + +`MODIFY` only operates on single lines at the moment, so a command such +as `MODIFY 1` will allow changes to line `1.00`, not to all of Group 1. +Similarly, `MOVE 2,3` will move line `2.00` to line `3.00`; it will not +move all the lines in Group 2. Since UWF does not have a 're-number' +command, moving lines and then erasing the old copy is the only way to +add additional lines when you forget to leave enough room between +sequential line numbers. + +After you have entered a `MODIFY` (or `MOVE`) command, UWF will +optionally print out the line number and then pause until you enter a +search character. As soon as you have done so, the line specified will +be typed out through the first occurrence of this character. If you want +to insert material at this point, just type it in; if you want to delete +a few characters, simply use the `RUBOUT` or `DELETE` key. Other editing +options may be invoked by typing one of the following control keys. +Note: mistakes made while trying to modify a line often lead to embedded +control codes, so if you do get confused, just type CTRL/F and try +again. + +| Keystroke | Name | Description | +| ----------------------------------------- | ---- | ------------------------------------------------- | +| CTRL/F | — | Aborts the command — the line is unchanged | +| CTRL/G | BELL | Rings bell and waits for a new search character | +| CTRL/J | LF | Copies the rest of the line without changes | +| CTRL/L | FF | Looks for the next occurrence of search character | +| CTRL/M | CR | Terminates the line at this point | +| BACKARROW/UNDERLINE | — | Deletes all chars to the left, except line number | +| RUBOUT/DELETE | — | Deletes previous character, as in command mode | + +The last two operations are similar to those available during command +mode except that BACKARROW or UNDERLINE does not +delete the line number. To remove the first command on a line containing +several commands, just enter a semicolon (`;`) as the search character, +wait for the first command to be typed out, hit BACKARROW or +UNDERLINE and then hit the LINE FEED key. + +CTRL/G and CTRL/L may be used to skip quickly to +the part of the line requiring changes. If the change(s) you wish to +make involve frequently used characters (such as an `=`), you can +initially select a different symbol which occurs less frequently and +then use BELL to change to the character you really wish to +find. Or you can simply keep hitting the FORM FEED key to +advance through the line. In case your terminal happens to respond to a +FF, you will be pleased to know that UWF does not echo this +character! + +If you just want to move a line from one location to another, type a +LF as the initial search character. If you are adding new +commands in the middle of a line, be sure to use the LF key — +not the RETURN key — to finish copying the rest of the line. +Otherwise you will lose the commands at the end of the line and you will +have to `MODIFY` the line a second time in order to re-enter them! If +you have a hard-copy terminal you may wish to `WRITE` out the line after +you have modified it to check for additional errors. With a video +terminal, on the other hand, the corrected line will be displayed just +as it is. + +If you have many lines to move (say all the lines in Group 5), and you +have a slow terminal, you can disable the printout during the Move in +order to speed things up. To do this, simply disable the keyboard echo +by using [the `O I` command](#echo-option). A disadvantage to this +method is that not even the `MOVE` commands will be printed so you have +to operate 'in the dark', but this is still the best way to make such a +major program change. To restore the keyboard echo just hit CTRL/F. + +On video terminals, the number of the line being modified is printed out +at the beginning so that the changes will be properly positioned on the +screen. With a hard-copy terminal, however, the line number is not +normally printed in order to leave as much room as possible for rubouts +and insertions. [The Patches section below](#patches) indicates the +location to change if you wish to add the line number printout in this +case. + + +## Expanded Text Storage + +If your machine has more than 12K of memory, UWF will automatically use +Fields 3-7 for additional text buffers. This allows such systems to keep +several different programs in memory at the same time, which is obviously +a very convenient thing to do. The `LOOK` command is then used to select +the desired 'area' for editing, program execution, etc. Programs in +different areas are essentially independent and may use the same line +numbers, but the symbol table and the 'stack' are shared by all areas. + +The `LOOK` command has the form: `LOOK Area`, where `Area` has the value +`0` for the main text buffer and `1`, `2`, `3`, etc. (up to `5`) for the +additional fields. `LOOK` always returns to command mode and is normally +only used as a direct command. `L 1` will switch to Area 1 while `L 0` +(or just `L`) will return to Area 0. For calls between program areas, +see [the `LINK` command](#link). + + +## Input/Output Commands + +UWF's I/O commands are called `ASK` and `TYPE`, respectively. The `TYPE` +command has appeared previously in a few of the examples; basically, it +converts the value of an arithmetic expression to a string of ASCII +characters which are then sent to the terminal, or to whatever output +device has been selected as a result of an appropriate [`OPEN` +command](#open). Similarly, the `ASK` command is used to input numeric +values, either from the keyboard or from another input device. Both of +these commands recognize 6 special operators for controlling the format +of I/O operations. These operators are, in fact, just the symbols +previously identified as 'protected variables', and it is because of +their special significance in `ASK` / `TYPE` commands that they cannot +be input or output directly. These operators and their meanings are as +follows: + +| Op | Description | +| --- | ----------------------------------------------------------- | +| `!` | Generate a new line by printing a CR/LF | +| `"` | Enclose character strings for labeling | +| `#` | Generate a RETURN without a LINE FEED | +| `$` | Print the contents of the Symbol Table | +| `%` | Change the output format | +| `:` | Tabulate to a given column or ignore input | + +You will notice that these are mostly 'output' operations. Nevertheless, +they perform the same function during an `ASK` command that they do in a +`TYPE` command. The `#` operator does not work on all I/O devices and is +therefore seldom used. It was originally intended for overprinting on +the same line, but may be [easily patched](#patches) to generate a +FORM FEED, should that be desirable. The remaining operators +will now be discussed in greater detail. + + +### The New Line `!` (Bang) Operator + +The `!` operator is used to advance to a new line. UWF never performs +this function automatically, so output on a single line may actually be +the result of several `ASK` or `TYPE` commands. 'Bang' operators can be +'piled together' to produce multiple blank lines: `TYPE !!!!!`, for +example, would advance 5 lines. Note that to produce a single blank +line, you may require either 1 or 2 `!`s depending upon whether anything +has been written on the first line. + + +### The Quote `"` Operator + +UWF uses the `"` operator to enclose strings which are output just as +they appear in the program. Thus the command: `TYPE "HELLO THERE, HOW +ARE YOU TODAY?"` would simply print the message enclosed by the quote +marks. The `ASK` command uses such output for prompting: `ASK "HOW OLD +ARE YOU? ",AGE` will print the question and then wait for a response. In +some cases the [`TRACE` operator (`?`)](#trace) is also useful for +printing labels during an `ASK` or `TYPE` command. + + +### The Symbol Table Dump `$` Operator + +The Symbol Table Dump operator (`$`) has already been [mentioned +briefly](#symbol-table). It prints all the symbols defined by the user's +program in the order in which they were encountered. It does not print +the values of the 'secret variables'. To conserve paper and to permit as +many symbols as possible to be listed on a video terminal, the listing +normally has three values per line. This format can be changed simply by +specifying a different number after the `$`. Thus, `TYPE $5` will change +the default value to 5, which is convenient on terminals which can print +up to 132 characters per line. The total number of symbols possible +depends upon the amount of memory available. In an 8K machine there will +only be room for about 120 variables, while in a 12K machine one can +have approximately 675. For internal reasons, a Symbol Table Dump always +terminates execution of the command line it is on, hence commands +following it on the same line will not be executed. + + +### The Format `%` Operator + +The format operator (`%`) allows UWF to print numeric results in any of +three standard formats: integer, mixed decimal, or 'floating-point' +(scientific notation). A format remains in effect until another one is +selected. Initially UWF is set to print all results in full-precision +scientific notation so that all digits of a result will be output. +However for many calculations a 'decimal' or 'integer' style of output +is more desirable. Such formats are selected by the value of an +arithmetic expression following the `%` operator which has the form: + + %ND.DP + +where `ND` is the Number of Digits to be printed (the result will be +rounded off to this precision), and `OP` is the requested number of +Decimal Places. `DP` should be smaller than `ND` unless `ND` is zero; if +`DP` is zero the result will be an 'integer' format and no decimal point +will be printed. Thus the command `TYPE %2,PI` will produce the result +'   `3`'. + +Notice that the form of the format specification is similar to that used +for line numbers. This may help to explain why it is necessary to use +`%5.03`, rather than `%5.3`, when you wish to have 5 digits printed with +up to 3 decimal places. The number of decimal places actually printed +may not be exactly what you have requested. If UWF finds that the number +being output is too big to fit the format you specified it will reduce +the number of decimal places. For example, if you try the command: + + TYPE %5.04, 123.456 + +you will actually get the value '  `123.46` printed since it is not +possible to show 4 decimal places with only 5 digits. Note however that +UWF *did* print the 5 most significant digits in a format approximately +like the one requested. Programmers accustomed to dealing with large +powerful computers which print only a string of `*****`s under similar +circumstances should find UWF's approach quite sensible. + +What happens if the number is so large that even the most significant +part overflows the field specified? In that case UWF automatically +switches to floating-point format for that value so you will be able to +see an unexpected result without having to re-run the entire program! +You can try this out simply by typing the value `123456` without +changing the format from the previous setting. UWF will print: +'  `1.2346E+05`. + +To purposefully select a floating-point format you should specify one +with `ND` equal to 0. Thus the format `%.05` will print 5-digit numbers +in proper scientific notation (1 digit before the decimal point). The +default format when UWF is first loaded is `%.1` which prints all 10 +digits. To return to this format you can simply specify `%`, since the +value '0' is treated the same as `%.1`. Note that using an arithmetic +expression for the format specification, rather than just a fixed +number, permits the format to be changed dynamically while the program +is running: `%VF` would select a format using the value of the variable +`VF`. + +Finally, note that UWF will never print more than 10 significant digits, +the limit of its internal accuracy. If the quantity `ND` is larger than +this, spaces will be used to fill out the number. If the quantity `DP` +is larger, zeros will be added. In any case, if the number is negative +UWF will print a minus sign just ahead of the first digit. A plus sign +is never printed — except as part of the exponent — but a space is +reserved for it anyway. An additional space is also printed at the +beginning in order to separate the number from any previous output. This +space may be omitted (or changed to an `=` sign) by making the patch +shown in [the Patches section below](#patches). + +To summarize the various output format settings: + +| Format | Description | +| ------ | ---------------------------------------- | +| `%N` | `N` digit integer format | +| `%N.D` | `N` digits with up to `D` decimal places | +| `%.D` | `D` digits in scientific (F.P.) notation | +| `%` | the same as `%.1` — full precision F.P. | + + +### The Tab `:` Operator + +The tab (`:`) operator provides a convenient way to create column +output. The expression following the colon is used to set the column, +i.e. `:10` specifies column 10. The tab routines do not attempt to go +backward if the column specified is to the left of the current print +position; the command is simply ignored in this case. 'Tabs' are +recommended in place of a string of spaces so that changes in the output +format will not affect subsequent columns. + +There are two special cases: tabbing to column 0 and tabbing to a +negative column. Neither is possible since columns are numbered from 1 +to 2047, but both are useful operations. Expressions which have the +value zero can be evaluated by the tab operator within a `TYPE` command +*without* producing any output. This is convenient occasionally, +especially for calling the [`FOUT` function](#fout). Tabbing to a +negative column has been given a quite different interpretation, +however. + +Since the current version of UWF can only input numeric values with the +`ASK` command, there is a need for a method to skip over label fields +when re-reading output produced by another program. This facility is +provided by 'tabbing' to a negative column number which causes no +output, but instead reads and ignores the specified number of +characters. Thus the command `TYPE :-1` will *read* 1 character from the +input device. This may well appear confusing, since we have an 'output' +command waiting for input, so the `ASK` command may be used instead: +`ASK :-1` performs the same function. This feature provides a simple way +to get the program to wait for operator intervention. For example, the +command `TYPE "TURN ON THE PUNCH":-1` would print the message and then +wait for any keyboard character to be typed. An `ASK :-2000` command +will let a visitor type almost anything s/he likes into the computer +without danger of destroying a valuable program. + + +### `ASK`/`TYPE` Summary + +Having discussed all the `ASK`/`TYPE` operators, there is really very +little more to explain about the commands themselves. `TYPE` can +evaluate a whole series of arithmetic expressions which are generally +separated by commas or spaces or one of the above operators, while `ASK` +can input values for a whole list of variables, again separated by +commas or spaces. Here are a few examples: + + TYPE !!:10"TODAY IS"%2,15 %4" OCTOBER"1978! + ASK "TYPE A KEY WHEN YOU ARE READY TO G0":-1 + TYPE !"THE ROOTS ARE:" %5.02, R1 :20 R2 !! + ASK !"WHAT IS THE INITIAL VALUE OF X? " IX + +Notice that the tab (`:`) and new line (`!`) operators can be included +in an `ASK` command to help format the input. Thus `ASK X :10 Y !` would +keep the input responses in two nicely aligned columns. It is quite +convenient to be able to output the necessary prompting information with +the `ASK` command; other languages frequently require separate commands +(such as `PRINT` followed by `INPUT`) for these operations. The [trace +operator](#trace) is also useful in `ASK` and `TYPE` commands when one +is interested in a 'minimal effort' I/O structure. + +One other feature of a `TYPE` command should be noted: it is possible to +save the value of a quantity being `TYPE`d just by including a +replacement operator in the expression. Thus `TYPE X=5` will output the +value '5' and also save it as the value of the variable `X`. + +Numeric input for the `ASK` command can take any of the forms [listed +above](#numbers-variables); specifically: signed integers, alphabetic +responses, decimal values or numbers containing a power-of-ten exponent. +Because such numbers are processed as they are being input, it is not +possible to use the RUBOUT key to delete an erroneous +character. Rather, one must effectively hit the 'clear key' (as on a +calculator) and then re-enter the entire number. The 'clear' function is +indicated by typing a BACKARROW or UNDERLINE just +as it is during command input. If you do attempt to use +RUBOUT, no `\` will be echoed which serves as a reminder that +this key is ignored during an `ASK` command. + + +### Input Terminators + +UWF allows a variety of characters to serve as input terminators. In +addition to the RETURN key, one may use a SPACE — +spaces in front of a number are ignored, but may be used to format the +input as desired; spaces following the number always act as a terminator +— a COMMA, SEMICOLON, or other punctuation marks +such as a QUESTION MARK or COLON. A +PERIOD is, of course, recognized as a decimal point, but a +second period also works as a terminator. Any of the arithmetic +operators also serve as terminators; in particular, the / and +- characters are often convenient. This allows responses such +as `1/2` or `1-5` for the values of *two* different variables. + +In fact, any character *except* `0`-`9`, `A`-`Z`, RUBOUT and +LINE FEED or FORM FEED can be used to terminate +the response to an `ASK` command. More to the point, however, is the +fact that the program can test to see which terminator was used. This +allows a very simple input loop to read an indefinite number of items +until a specific terminator — a `?`, for instance — is found. See the +discussion of the [FTRM function](#ftrm). + +The ALTMODE or ESCAPE key is a special case: +typing either of these keys leaves the previous value of the variable +unchanged. This allows quick responses to repeated requests for the same +value. The program, of course, can pre-set the value of the variable so +that an ALTMODE response will merely confirm the expected +value. + + +## Arithmetic Processing Commands + +There are four commands in this group: `SET`, `XECUTE`, `YNCREMENT` and +`ZERO`. + + +### `SET` + +The most frequently used command in the UWF language is the `SET` +command. This command evaluates arithmetic expressions without producing +any output (except when the [trace feature](#trace) is enabled). Such +expressions typically have the form: + + SET Variable Name = Arithmetic Expression + +But more general expressions, particularly those containing +sub-expressions, are perfectly acceptable. Thus a command such as `SET +A=B=C=5` could be used to set all three variables to the same value +while `SET I=J+K=1` would initialize the value of `K` to 1 as well as +set `I` to `J+1`. Expressions used with the SET command do not need to +contain replacement operators: the command `SET FIN()` could be used, +for instance, to input a single character. The value of the function +would not be saved, however; this is sometimes useful when calling 'I/O' +functions for their 'side-effects'. + +Note that the word `SET` (or its abbreviation `S`) is not optional as it +is in some other languages. The flexible syntax employed by UWF makes it +mandatory that every command begin with a command letter. One SET +command, however, will process as many expressions as can fit on a +single line. The expressions should be separated by commas or spaces; +for instance: + + SET A=1,B=2,C=A+B + +which is equivalent to: + + SET C=(A=1)+B=2 + +Another point to remember is that the same variable may appear on both +sides of an `=` sign. Thus `SET X=X+5` has the effect of redefining the +value of `X` to be 5 more than the initial value. This can get to be +tricky if the same variable appears several times in a single expression +on both sides of replacement operators. The rule here is that in each +instance the variable will have its current value until the entire +expression to the *right* has been evaluated; then it will be replaced +with the new value. To give a fairly simple yet intriguing example: + + SET A=B+A-B=A + +which is equivalent to: + + SET C=B+A,B=A,A=C-B + +This will interchange the values of `A` and `B`. Another expression +which does the same thing is: `SET A=B+0*B=A`. Notice that the +processing of this expression involves two different values of `B`: The +first time `B` is encountered it is on the right side of an `=`, so its +current value is used; the second time it is on the left side, so the +rest of the expression is evaluated, the substitution made, and then +processing of the first part is resumed. Thus `A` retains its original +value until the very end (when it is replaced by the initial value of +`B`, which was saved on the stack). + + +### `ZERO` + +The special case of 'SET Var=0' is conveniently handled by the `ZERO` +command. A single `ZERO` command may be used to set several variables to +zero, making it very convenient for initializing sums and 'flags': `ZERO +A,#,C` will set those three variables to zero. As a special case, if no +variables are specified, the `ZERO` command clears the entire symbol +table. This effectively sets *all* the variables to zero since this is +the default value for 'undefined' quantities. + +One other use of the `ZERO` command should be mentioned. When the Symbol +Table fills up, UWF tries to replace any variables which have the value +'0' with new variables. This procedure succeeds as long as there is at +least 1 variable with this value, since that one will simply be renamed, +and no matter what the name, it will always be zero. As a result of this +scheme, programmers may regain symbol table space by `ZERO`ing unneeded +variables when they are finished with them. + + +### `YNCREMENT` + +Another special case of the `SET` command — `SET Var = Var + 1` is +handled by the `YNCREMENT` command. This command allows a list of +variables to be either incremented or decremented by the value '1'. The +command `Y K`, for example, is equivalent to `SET K=K+1` while `Y -J` is +the same as `SET J=J-1`. Of course commands such as `Y N,O-P` are +permitted; this one increments the variables `N` and `O` and decrements +`P`. Either commas, spaces or minus signs may be used to separate the +variable names. + + +### `XECUTE` + +The `XECUTE` command has been included for compatibility with earlier +versions of UWF. Its purpose was to evaluate arithmetic expressions +without setting useless 'dummy' variables. This is now accomplished by +the `SET` command itself simply by omitting the replacement operator. +Thus `SET FOUT(7)` may be used to ring the bell on the terminal. +Internally, `SET` and `XECUTE` are identical; it is recommended that +`SET` be used in new programs. + + +## Branch and Control Commands + +This class of commands is used to test arithmetic results, set up loops +and otherwise control the sequence of command execution. There are 11 +commands in this category — UWF has a very rich control structure built +around two fundamentally different types of transfers: the `GOTO` branch +and the `DO` call. Both interrupt the normal sequence of command +execution, but the `GOTO` is an unconditional branch while a `DO` call +eventually returns to the next command following the call. The `DO` +command is similar to the `GOSUB` in BASIC, but is considerably more +flexible. + + +### `GOTO` + +This command has the form `GOTO line number`. It causes an immediate +transfer to the line specified. The `GO` command is the usual way of +starting the indirect program at the lowest numbered line; it may be +used to start the program at any other line as well: `G 2.1` will start +at line '2.1'. An explicit line number may be replaced by an arithmetic +expression to create what FORTRAN calls an 'Assigned Goto': `SET X=5.1 . +. . GOTO X`. + + +### `DO` + +The `DO` command is effectively a subroutine call. A `DO` command +without a modifier (or equivalently, a `DO 0` command) calls the entire +stored program. This may be used as a Direct Command in cases where you +wish to follow such action with additional commands, e.g. `DO;TYPE +FTIM()` might be used to check the running time of a benchmark program. + +`DO` also accepts a list of line and group numbers such as `DO +-.7,8,9.1`, which would call the subroutine starting at line XX.70 in +the current group, then Group 8, and finally line 9.1. `DO` is +completely recursive: a DO may thus 'do' itself! Note that the commands +called by a DO are not designated anywhere as subroutines — they may be, +and usually are, just ordinary commands somewhere in the main program. +This is one of the major differences between `DO` calls in UWF and +`GOSUB`s in BASIC. Suppose, for example, that the program had a line +such as: + + 1.3 ZERO A,B,C; SET D=5, E=6 + +which occurred in Group 1 as part of an initialization sequence. If the +same set of commands were needed later in Group 12, one would only need +to write `DO 1.3`. This facility for re-using common parts of the +program is akin to writing 'macros' and is generally considered to be a +good programming practice. The one feature missing from the `DO` command +is the ability to explicitly pass arguments to the 'subroutine'; this +must be handled by the use of 'common' variables. As you will see later +on, [Program Defined Function calls](#pdfs) provide this capability in a +somewhat limited form. + +A DO call may be terminated in one of four ways: + +1. There are no more lines to execute in the range specified +2. A `RETURN` command is encountered +3. A loop containing a `DO` is terminated by a `NEXT` or `BREAK` +4. A `GOTO` transfers to a line outside the range of the `DO` + +The first condition is the most common, especially for single line +calls. The second condition is explained below, while the third is +explored in the discussion of the `NEXT` command. That leaves only the +fourth possibility: `GOTO` branches can be used to terminate a `DO` call +simply by transferring to a line outside of the range; however the line +transferred to will be executed first, which can lead to slightly +unexpected results. For instance, if the line branched to happens to +immediately precede the group, no exit will occur because UWF will find +itself back in the proper group again when it finishes the line. Another +somewhat similar case occurs when calling a 'sub-group': `GOTO` +transfers anywhere in the same group will be honored without causing a +return. Thus if you wish to force a return from a `DO` call, do it with +the [`RETURN` command](#return), not with a `GOTO`. + + +### `RETURN` + +The `RETURN` command provides a way to selectively exit from a `DO` call +in cases where the entire subroutine is not required. Since a `DO` call +always specifies the implied range of the subroutine (a single line or +an entire group), a `RETURN` command is normally not required. There are +cases, however, especially when calling a 'sub-group', in which a +`RETURN` is necessary to force an early exit. If there is no subroutine +call to return from, `RETURN` will go back to command mode instead, i.e. +it behaves just like a `QUIT` command. This is a useful feature, since +programs which end with a `RETURN` can be run normally, but can also be +called as subroutines via the [`LINK` command](#link). + +`RETURN` can also designate a line number, for example: `RETURN 5.3`. In +this case the normal return to the calling point is aborted (except for +[PDF calls](#pdfs)) and the program continues from the line specified. +This is a very important feature since it effectively transforms a `DO` +call into a `GOTO` branch. It is all the more useful since it can be +'turned on and off simply by making the return point an arithmetic +expression which, when zero, indicates a normal return, but otherwise +causes a branch to the line specified. This gives UWF a 'multiple +return' feature which is found in only a few high-level languages. + + +### `IF` + +The form of the `IF` command is: + + IF (Arithmetic Expression) negative, zero, positive + +where 'negative', 'zero', and 'positive' are line number expressions not +containing commas. Depending upon the sign of the value being tested, +the program will perform a `GOTO` branch to one of the three +possibilities. The expression being tested must be enclosed in +parentheses and must be separated from the command word by a space. + +Not all of the branch options need to be specified, and relative line +numbers are especially useful for those which are. Here are some +examples of `IF` commands: + +| Example | Meaning | +| --------------------------- | ---------------------------------------- | +| `IF (D=B^2-4*A*C) .2,.3,.4` | Tests for all 3 possibilities | +| `IF (A-5) 5.1, 5.1` | Branches if A is less than or equal to 5 | +| `IF (-X) .9 or IF (X),,.9` | Branches if X is greater than 0 | +| `IF [I-J] , .2` | Branches only if I equals J | +| `IF .4,,.4` | Branches only if W is non-zero | + +These examples illustrate the flexible nature of the `IF` command. In +commands with only 1 or 2 branch options, if the branch is *not* taken, +the next sequential command will be executed — whether this command is +on the same line or on the next line unless the `IF` is in a [`FOR` +loop](#for). Here, then, is a case where 'line 0' is interpreted as the +'next command'. Also note (example 1 above) that the expression being +tested may contain replacement operators so that the value may be saved +for use elsewhere in the program. + + +### `ON` + +The `ON` command is identical in form to the `IF` command: `ON (exp) +N,Z,P`. The difference is that `DO` calls are used in place of `GOTO` +transfers, so upon completion of the subroutine, the program will +continue with the next command following the `ON` test.² This is often a +very convenient thing to do since it allows additional processing for +specific cases. As with the `IF` command, not all 3 calls need to be +specified, so one can test just for equality (zero), or for some other +condition. Notice that an entire group can be called by the `ON` +command. + + +### `JUMP` + +The `JUMP` command has two distinct forms which have been designed to +serve the needs of interactive programs: + + JUMP line number + +or: + + JUMP (expression) SI, S2, S3, S4, S5, . . . + +The first form is a conditional `GOTO` in which the branch is taken +*unless* there is a character waiting in the input buffer. This form is +used to test the keyboard for input without interrupting the program if +there isn't any. This feature is essential in interactive programs which +allow program flow to be controlled dynamically from operator response. +For example: + + 1.1 YNCR I; JUMP .1; TYPE I + +will hang in a loop incrementing the variable `I` until a key is struck, +then type the number of cycles. The character used to interrupt the +program can be read with the [`FIN` function](#fin) and so used to +further control program flow. If the example above simply called `FIN` +to read the character directly, the program would hang in the input wait +loop and nothing further could be accomplished until the operator struck +a key. + +The second form of the `JUMP` command provides a computed subroutine +(`DO`) call which is essentially similar in form to the `ON` command +except that the actual *value* of the arithmetic expression being tested +is used (rather than just the *sign* bit) to determine which subroutine +to call. The call list is indexed from 1 to N, and any number of +subroutines may be specified. Values of the expression which do not +match up with a specified call are ignored. In the example shown above, +Subroutine No. 4 will be called if the expression has the value 4.5, +whereas if the expression has the value -1, 0, or 12.3, no subroutine at +all will be called. As with the `IF` and `ON` commands, line numbers may +be omitted (or set to zero) to avoid a call for certain values of the +expression. + +Typically the expression is simply the ASCII value of a keyboard +character which is used to select an appropriate subroutine. For +example: + + JUMP (FIN()-'@> A,B,C,,E + +will call subroutine `A` if the letter `A` is typed, etc. Notice that +typing the letter `D` is treated as a `NOP` by this particular command. +As with the `ON` command, the program normally continues with the next +sequential command following the subroutine call unless a `RETURN` +command is employed to transfer elsewhere. + + +### `LINK` + +The `LINK` command allows systems with more than 12K to call subroutines +stored in different text 'areas',³ thus 'linking' such areas together as +part of a 'main' program. The command has the form: + + LINK Area, Subroutine Pointer + +where 'Area' may have the values '0' or '1' in a 16K system, and up to +'5' if sufficient memory is available. The 'Subroutine Pointer' is a +line or group (or sub-group) number as described for the `DO`, `ON` and +`JUMP` commands. A value of '0' specifies that the entire area is to be +used as a subroutine. Examples: + +| Command | Meaning | +| --------------: | ----------------------------------------------- | +| `L,4` | Calls group 4 in Area 0 | +| `L 1,-8.5` | Calls sub-group starting at line 8.5 in Area 1 | +| `L,.3` | Calls line XX.30 in the same group in Area 0 | +| `L 2,;T "DONE"` | Executes all of Area 2, then types `DONE` | + +Notice that the comma is required punctuation even when the second +parameter is zero, as in the last example.⁴ To avoid returning to the +calling area at the end of the subroutine, use a RETURN command with a +non-zero line number, such as `R .9` to abort the normal return +sequence. By using a computed line number in such a command the calling +program can control the return. A [`QUIT` command](#quit) can also be +used to cancel all returns. + +The variables created or used by a program in one area are shared by all +areas, so be careful to avoid conflicts. Also, since each `LINK` saves +its return on the 'stack', watch out for calls which never return, but +simply chain from one area to another. This will eventually lead to a +'stack overflow' which can be cured by using a `QUIT X` command to +cancel all pending returns. + +The `LINK` command functions properly for calls from within the same +area, but the `DO` command is clearly preferable since, for one thing, +it can handle multiple calls which the `LINK` command cannot. `LINK` can +be used in direct commands; it is somewhat similar to the `LIBRARY +GOSUB` command in the OS/8 version. + + +### `QUIT` + +The QUIT command stops program execution and resets the 'stack' pointers +so that all pending operations (such as subroutine returns) are +destroyed. CTRL/F as well as any execution error performs an effective +`QUIT`, thereby returning to command mode. There are rare occasions, +however, when it is desirable to be able to 'quit' and then simulate a +keyboard restart so that the program will continue running without +actually returning to command mode. This is accomplished by specifying a +non-zero line number as a 'restart' point. Thus, `QUIT 1.1` will stop +execution, clear the stacks, and then restart at line 1.1. To restart at +the lowest numbered line of the program, use a `Q .001` command. `QUIT +0` or just `Q` will stop the program and return to command mode. + +It is also possible to use `QUIT` to specify a restart point for any +error condition. This is accomplished by specifying a *negative* line +number, i.e. something like `QUIT -9.1`. This command will not stop the +program when it is executed; it will merely remember the line number and +then continue with the next command. If an error subsequently occurs, +however, the program will be automatically restarted at line 9.1 instead +of returning to command mode. + +This provides UWF with a somewhat limited error recovery procedure, but +one which can be used to take care of certain 'unexpected' conditions +which might develop while the program was running unattended, Note that +it is up to the user to determine which error caused the restart. One +way that this could be accomplished is to select different restart +points for different sections of the program where specific errors might +be expected. This feature should be considered somewhat 'experimental' +in the sense that it may not be included in later releases of UWF if +other features appear to be more important. + +The error trap is automatically reset every time UWF returns to command +mode in order to prevent conditions set by one program from causing an +unexpected restart at a later time. + + +## Loop Commands + +UWF has 3 commands for constructing program loops. The `FOR` command +sets up the loop, the `NEXT` command serves as an optional terminator, +and the `BREAK` command provides a way to exit from a loop before it +runs to completion. UWF's loops are slightly different from those in +other languages, but the differences, once recognized, are easy to +accommodate. Basically, UWF uses 'horizontal' loops which consist of +only the statements following the `FOR` command on the same line. Most +other algebraic languages use 'vertical' loops which consist of a number +of contiguous program steps with some way to designate the end of the +loop. + +UWF's approach is convenient for short loops since the commands to be +repeated are simply coded on the same line with the `FOR` command and no +'end-of-loop' designation is required. Loops which require several lines +of code are handled just as easily by putting a `DO` command in the main +loop to call all the statements which cannot be placed on the same line. +A `NEXT` command at the end of those statements then serves to designate +both the end of the loop as well as the continuation point of the +program. Symbolically, UWF's loops may thus have either of these two +forms: + + FOR * * *; loop commands + +or: + + FOR * * *; DO -.YY + XX.YY first loop command + second loop command + . . . + last loop command; NEXT + +The latter form is practically identical to that used by BASIC or +FORTRAN, with the mere addition of the `DO` call on the first line. + + +### `FOR` + +This command initializes a loop, assigning a 'loop variable' to count +the number of iterations. The form is: + + FOR Var = Initial Value, Increment, Final Value ; + +or, more generally: + + FOR expression 1, expression 2, expression 3 ; + +where the first variable to the left of a replacement operator in +expression 1 will be used as the loop counter. The semicolon after +expression 3 is required punctuation. An increment of +1 is assumed if +only the initial and final values are given. Notice that the increment, +if specified, is the *second* expression! This is different from the +convention used by BASIC and FORTRAN. There are no restrictions on any +of the expressions: they may be positive, negative or non-integer. Thus +one can increment either 'forward' or 'backward', using any step size. +The execution of the FOR command is such, however, that one pass will +always occur even if the 'initial value' is greater than the 'final +value'. In any case, the exit value of the loop variable will be one +increment more than the value it had in the final iteration. + +Here are some examples: + +1. `FOR I=J=1,10;` +2. `FOR L=1,N; FOR M=-L,L;` +3. `FOR X(I)= 10, -1, 1;` +4. `FOR A=0, PI/180, 2*PI;` +5. `FOR Q= P/2, Q, 5*Q;` + +Notice that loops may contain other loops (Ex. 2). Such 'nesting' is +permitted to a depth of 15 or so, but in practice, loops are rarely +nested more than 5 deep. Another point, illustrated in example 5, is +that the initial value of the loop variable can be used by the +expression for the increment and the final values; also notice that +subscripted variables are permissible as loop indices (Ex. 3). + +In example 1, it may appear that both `I` and `J` will be used as +control variables. This is not the case: only the first variable (in +this case `I`) will be incremented. Other variables (such as `J`) may be +given values by replacement operators in any of the three expressions, +but these values will not change during the loop (unless commands within +the loop change them). It is often quite convenient to use the `FOR` +command to initialize several variables used in the loop along with the +value of the loop index. + +The novice programmer who wishes to try writing a simple loop might +begin with the following direct command: + + FOR I=1,10;TYPE I,I^2,! + +which will print out the first 10 squares in some (unspecified) format. +The more experienced programmer will quickly appreciate UWF's loop +structure; for one thing, no rules regarding branches into the middle of +a loop are necessary since there is no way to branch to the middle of a +line! + + +### `NEXT` + +The normal termination for a loop is at the end of the line containing +the `FOR` command. If the loop contains a `GOTO` branch, however, the +end of the line branched to becomes the terminator. It is convenient at +times, especially in direct commands, to terminate the loop in the +middle of a line so that other commands which logically follow the loop +can be placed on the same line. The `NEXT` command serves this purpose, +as shown in the following example: + + FOR * * *; loop commands; NEXT; other commands + +which excludes 'other commands' from the loop. + +This construction also works in 'vertical' loops: + + FOR * * *; DO -.# + # commands + commands + NEXT + +The commands executed by the `DO` will be terminated upon encountering +the `NEXT` command. But more importantly, when the loop is finished, UWF +will continue with the first command following the `NEXT`, thus skipping +over the commands in the body of the loop. If this `NEXT` command were +to be omitted or replaced by a `RETURN`, the program would simply 'fall +through' to the first statement in the loop. (The one indicated by `#` +in the example above.) + +Notice that the `NEXT` command contains no references to the loop +variable. This is a little different from the way most versions of BASIC +implement this command, but the effect is quite similar and since only +the first letter of the command word is decoded, variations such as `NI` +or `NEXT-J` may prove helpful to some programmers. Nested loops, of +course, may require 'nested `NEXT`s': `N;N`. Here is an example which +types out all the elements of a 5×5 array a row at a time with a CR/LF +printed at the end of each row: + + FOR I=1,5; FOR J=1,5; TYPE A(I,J); NEXT; TYPE ! + +`NEXT` has one other feature: it may be used with a line number to +specify a continuation point other than the next sequential command. +Thus: `FOR * * *; commands; NEXT .8` will branch to line XX.80 when the +loop runs to completion. + +**Note 1:** A `NEXT` command which is executed outside of a FOR loop is +ignored unless it specifies a line number, in which case the branch will +always be taken. A `NEXT` command may thus be placed at the beginning of +any line and used as a target for a 'do nothing' branch from within a +loop without affecting the normal execution of that line. + +**Note 2:** Loops which contain conditional branches (i.e. `IF` +commands) should be careful that all paths end with an appropriate +`NEXT` if it is desired to skip over the statements in the loop under +all conditions. Whichever `NEXT` is executed on the final iteration will +determine the program flow. + + +### `BREAK` + +Once a loop has been initiated it must normally run to completion. +Branching to a line outside of the loop is not effective: that line will +simply be treated as a continuation of the main loop. (See +[above](#next) comments about `GOTO`s in a loop.) One way to force an +exit from a loop would be to set the loop variable to a value greater +than the final value. This is obviously not very 'elegant', to say the +least, so the `BREAK` command has been provided to solve this +difficulty. A `BREAK` causes an immediate exit from the loop, preserving +the current value of the loop index, and the program then continues with +the next sequential command following the `BREAK`. As you might expect, +`BREAK` may also specify a line number so you can branch to a different +part of the program at the same time that you leave the loop. A `BREAK` +without a line number is ignored (just like the `NEXT` command) if it is +encountered outside of a loop, so lines containing `BREAK`s can be used +by other parts of the program. Each `BREAK` exits from only a single +loop, so to exit from nested loops it would be necessary to use multiple +`BREAK` commands: `B;B 15.1` will exit from 2 loops and then transfer to +line 15.1. + + +## Miscellaneous Commands + +We will now finish the alphabet: `C`, `H`, `K`, `P`, `U`, and `V` are +the remaining command letters. `U` and `V` are not implemented in this +version and may be used for whatever purpose the user desires. The '@' +command is also available for expansion purposes. + + +### `COMMENT` + +Any command beginning with a `C` causes the rest of the line to be +ignored. Such lines may thus be used for comments describing the +operation of the program. In particular, line XX.00 (the first line in a +group) should generally be reserved for comments. Branching to a comment +line from within a loop will terminate that cycle of the loop. In this +way a `COMMENT` is equivalent to the Fortran `CONTINUE` statement. A +`NEXT` command performs the same function and in addition may be used to +designate the continuation of the program. + + +### `HESITATE` + +The `HESITATE` command delays the program for a time specified by the +command. The argument (which must be an integer) is nominally in +milliseconds, so `H 1000` will generate approximately a 1 second delay. +However, the exact delay is directly dependent upon the cycle time of +the machine, so some calibration is necessary. Here is an example using +the `H` command: + + FOR T=1,9; TYPE "*"; HESITATE 250*T + + +### `PUNCH` + +The `PUNCH` command allows the programmer to save a copy of the text +buffer and the symbol table in binary format, ready to reload with the +standard BIN loader. This command requires either a High Speed punch or +an audio (cassette) recorder. Tapes created with the `PUNCH` command can +only be read with the BIN loader since they are not punched as ASCII +characters. The advantage to punching tapes in this format is that they +tend to be somewhat shorter than ASCII tapes and they also contain a +checksum so there is less probability of an error going undetected. The +disadvantage, however, is that they are absolute memory dumps and so are +not necessarily transportable between different versions of UWF. They +also cannot be loaded by UWF itself from a remote location, but require +access to the front panel of the computer in order to activate the BIN +loader as well as to restart UWF once the tape is loaded. + +To use this command (assuming that you have a cassette recorder, but the +same procedure applies to a HS paper tape punch), advance the tape to an +unused area, turn on the recorder and then type `P` followed by a +RETURN. Approximately 5 seconds of leader code will be +punched, followed by the contents of the text buffer and then the symbol +table. To restore a program (and any symbols in use at the time it was +dumped), position the tape at the start of the leader, and while this +section is being read, start the BIN loader from the front panel. If you +start the loader before reaching the leader section, the computer will +halt with a checksum error (`AC` not zero); hit the `CONTINUE` switch +quickly, and if you are still in the leader, all will be well. After +reading in the program tape you must manually [restart UWF at location +100](#starting). + +The `PUNCH` command always returns to command mode (like `MODIFY` and +`ERASE`) so it cannot be followed by other commands, and should not be +included in the program itself. In systems with more than 12K the +`PUNCH` command will dump the contents of the *current* program area, so +to save the program in Area 3, for example, use a `L 3` command to get +to it and then type `P` to punch it out. A program can only be reloaded +into the area that it came from, so if you wish to move a program to a +different area, you must `WRITE` it out (rather than `PUNCH`ing it), and +then read it in again as explained [below](#write-ascii-tape). + + +### The `OPEN` Commands + +In addition to the `PUNCH` command described above, UWF has a series of +`OPEN` commands which allow `ASK` and `TYPE` (or other I/O operations) +to use something other than the terminal. These commands consists of +*two* words (or two single-letter abbreviations) separated by a space. +You may recall that the letter `O` has already been used for the `ON` +command and wonder how it could also be used for `OPEN`. `OPEN` and `ON` +can be distinguished, however, since `ON` must always be followed by an +arithmetic expression. Here is a short summary of the `OPEN` commands +currently available. The mnemonics, which were chosen in part to be +compatible with the OS/8 version, are somewhat less than perfect! + +| Mnemonic | Command | Description | +| -------------- | ------- | ----------------------------------------- | +| OPEN INPUT | `O I` | Selects the Terminal as the Input device | +| OPEN OUTPUT | `O O` | Selects the Terminal as the Output device | +| OPEN READER | `O R` | Selects the High Speed Reader for Input | +| OPEN PUNCH | `O P` | Selects the High Speed Punch for Output | +| OUTPUT TRAILER | `O T` | Punches leader/trailer code (ASCII 200) | +| OPEN ----,ECHO | `O -,E` | Connects the Input device to the Output | + +Only the first two commands (`O I` and `O O`) and the ECHO option are +useful unless you have a high speed reader/punch (or an audio tape +recorder). The list of `OPEN` commands could also be expanded to include +things like `O L` (for selecting a line printer) or `O S` to send output +to a 'scope display, etc. Such expansion is, however, entirely up to the +user. + + +### I/O Device Selection + +The Input and Output devices are always reset to the terminal when you +hit CTRL/F. To select a different device, use the appropriate +`OPEN` command. For example, to read in a program from the High Speed +Reader, simply type in an `O R` command, and henceforth, until this +assignment is changed, all input to UWF will come from the reader rather +than from the keyboard. In particular, even direct commands will be +taken from the reader, so you can set up a program tape to run the +machine while you are gone. Also, if the tape contains a listing of a +program it will be read into the text buffer just as though you were +typing it in yourself. This is an alternative method for saving programs +which has the advantage that they are available as ASCII tapes which can +be edited or processed by other programs. A 'time-out' trap in the +reader routine normally senses when the end of the tape has been reached +and then restores the terminal as the input device. A +BACKARROW or UNDERLINE is printed on the terminal +to indicate that it is the active input (and output) device once more. +If you need to manually restore the terminal to its usual status, just +hit CTRL/F. + + + +Similarly, to select the High Speed Punch (or Cassette Recorder) for use +as the output device, just use an `O P` command. To dump the text buffer +on tape, for example, enter the commands: + + O P,T; W; O T,O (do not hit RETURN) + +and then start the punch or recorder. Hit RETURN and then +wait for the asterisk (`*`) to reappear on the terminal. + +To re-read such a tape at a later time, position it in the reader +somewhere in the leader section, use the `ERASE` command to clear the +program area, and then type `O R` followed by the RETURN key. +If input is from a paper tape reader, the reader will now begin to read +the tape. If input is from an audio recorder you should actually start +the tape moving (in the leader section) before hitting the +RETURN key, otherwise the first few characters are likely to +be 'garbage' as the tape comes up to speed and UWF may well conclude +that you have run out of the tape before you have even begun! + +It is also possible to use the reader/punch for data storage purposes. +This works best with paper tape since the audio recorder lacks a +'stop-on-character' capability, making it difficult for UWF to keep up +with the data once the tape has started moving. By way of an example, +the following command will read in 50 numbers from the high-speed +reader: + + O R; FOR I=1,50; ASK DATA(I); NEXT; O I,E + +Notice that an `O I,E` command is used at the end of the loop to restore +input to the keyboard. If this command were omitted the H.S. reader +would continue to be used for input, probably causing an error to occur +since it is unlikely that the next data value on the tape would +correspond to anything expected from the keyboard. The `,E` part of this +command is explained more fully in the next section. + + +### The ECHO Option + +The `,E` option may be added to either an `O I` or `O R` command to +specify that the input characters are to be 'echoed' to the output +device. Generally this option is *always* used with `O I` and *never* +used with `O R`. The echo option may at first appear slightly confusing +since UWF normally runs with the keyboard echo *on* and thus one comes +to expect that whatever is typed will be printed on the terminal. This +makes the terminal appear much like a simple typewriter and tends to +obscure the fact that if UWF were not sending back each character it +received, *nothing* would be printed! The `ECHO` option must be +specified when selecting the input device, or *no echo* will be +assumed. Thus an `O I` command will select the keyboard for input (it +may already *be* selected) and effectively turn the echo off. An `O I,E` +command is necessary to restore the echo under program control. Of +course any program error or typing CTRL/F, will also restore the echo. + +The ability to disable the input echo is convenient at times since it +allows a program to read one thing and possibly print something else. An +example of this mode of operation occurs during command input: when you +type the RUBOUT key you do not get this character printed, +but rather a backslash (`\`), or on a video terminal, a three character +sequence: Backspace, Space, Backspace, +which effectively removes the character from the screen. UWF programs +can also be written to use the keyboard for program control, and in such +cases it is often desirable to have 'silent' input. You can try this out +quickly by using a direct `O I` command to disable the echo. Now type in +O, Space, I, Comma +E and hit RETURN and the echo will return again. + +Another time when you will want to disable the echo is when reading in a +program tape on the 'low-speed' reader. If you turn off the echo in this +case you can avoid getting an unwanted listing while you relax to the +rhythm of a quiet little 'burp, burp, burp' instead of a 'clackety clack +clack'. Just hit CTRL/F at the end of the tape to turn on the +echo again. + +Similarly, when reading a data tape from the high-speed reader it is +generally undesirable to have it all printed on the terminal. Thus the +`O R` command automatically disables the echo; but if you wanted to see +what some of the data looked like, you could use an `O R,E` command. To +make a copy of a program or data tape you would first switch output to +the punch and then turn on the echo to 'print' each character received +on the output tape, e.g. + + O P;O R,E;S FIND() + +The [`FIND` function](#find) keeps reading from the input device, +looking for the character code specified. In this case a 'null' was +used, which will never be found, so the effect of this command is to +continue reading until the end of the tape is reached at which point the +terminal will automatically be restored as the I/O device with the echo +enabled. If only portions of a tape were to be copied, you could use the +`FIND` function to search for an appropriate character and then switch +I/O back to the terminal yourself. You can use the `ECHO` option to skip +sections of the tape by disabling the echo until you 'find' the right +character and then turning it back on to copy some more. + + +### The Leader/Trailer Option + +The `T` option punches leader/trailer code (ASCII 200). This is +convenient (but not essential) for separating output on paper tape, and +somewhat more important when using an audio recorder since there is no +visual indication of breaks in the data. Blank tape may also be used as +'leader' and both are ignored each time the reader is selected as the +input device. However, after the first valid input character has been +read these same codes are interpreted as the 'end-of-tape' and cause +both input and output to be restored to the terminal. A +BACKARROW or UNDERLINE is also printed to indicate +that the EOT was detected. This character serves the dual purpose of +also removing any 'garbage' characters which might have been read after +the last valid input. + +The `T` option can be used alone (`O T`) or in conjunction with another +`OPEN` command. + +The number of L/T codes punched is determined by an optional arithmetic +expression following the letter `T` (and separated by a space from it) +with the previous specification being used as the default. The initial +value is 512, which is about right for use with an audio recorder, but +somewhat ridiculous for paper tape. (Over 4 feet of leader!) A value of +70 or so is more appropriate in this case. You can always just repeat +the `T` option to get a slightly longer leader if you want to: +`O T 100,T` will punch out 200 L/T codes but leave the default set at +100. Notice how this option was used in the [example +above](#write-ascii-tape) for writing out all of the program buffer. The +length specified by the `T` option is also used by the [`PUNCH` +command](#punch). + + +### `KONTROL` + +This is an optional command which may be used to program the DR8-EA +parallel I/O module. The `K` command is used to set and clear individual +bits in the output register while the [`FDIN` function](#fdin) is used +to read the corresponding bits in the input register. These options are +added by the initialization routine if [Switch 7 is *UP*](#opt-switch). + +The `KONTROL` command uses *positive* numbers to turn bits on and +*negative* numbers to turn them off. Each bit is directly controllable, +independent of the setting of any of the others. Thus a `K 1` command, +for example, will turn on bit '1' without changing the state of any of +the other 11 bits, while a `K -1` command will turn it off again. In +order for this scheme to work successfully, the bits must be numbered +from 1-12 rather than from 1-11, which is the usual convention. This is +because '-0' is not distinguishable from '+0'. In fact, '0' is +interpreted to mean 'clear all bits', so a `K 0` command (or just `K` +since '0' is the default for all arithmetic expressions) can be used to +quickly initialize this register. + +More than one bit position can be set at a time, e.g. a command such as: + + K 1,-2,3 + +will set bit 1, clear bit 2, and finally set bit 3. + +In this form, each operation occurs sequentially with perhaps 10 +milliseconds or so between operations. This allows a command such as +`K 1,-1` to be used to generate a short pulse on line 1. If it is +necessary for several signals to occur simultaneously, those operations +can be enclosed in parentheses: + + K 1,(2,3,A),-1 + +will set bit 1, then bits 2, 3, and 4, then clear bit 1. + +Since for some purposes it is more convenient to be able to specify +various bit combinations with a single arithmetic expression rather than +setting and clearing each bit individually, a third mode of operation is +also available. In this mode, the last 4 bits (bits 9-12) are set to the +value of an expression preceded by an `=` sign. The remaining 8 bits are +not changed. Thus a `K,=5` command would first clear all bits — the +comma indicates a missing argument which is the same as '0' — then set +bits 10 and 12 while clearing bits 9 and 11 (which were already clear in +this case). + +To summarize the 3 different forms of the `KONTROL` command: + +| Command | Meaning | +| ------------ | ----------------------------------------------------------------------------------------------------------- | +| `K N,-N` | Turns a single bit on or off; N=0 turns *all* bits off | +| `K (L,M,-N)` | Performs all operations in parentheses simultaneously instead of sequentially | +| `K =N` | Sets the 4 least-significant bits to the binary value of `N`; this form may not be used inside parentheses. | + + +## Error Messages + +UWF traps any 'illegal' operation such as division by zero or an unknown +command and prints a peculiar little message to tell you what the +problem was and where in the program it occurred. If you type in the +command: `SET 2=3` for example, UWF will reply with: + + '?07.44' + +which is its way of telling you that you have something besides a +variable on the left side of an `=` sign. To decode the error message, +you should look at the [error code table](#error-codes) below (or the +Summary card) which lists all of the error diagnostics and their +probable cause. + +If this same error had occurred while the program was running (i.e. not +from a direct command), the error message would also indicate the line +in the program containing the erroneous statement: + + ?07.44 @ 15.13 + +indicates 'operator missing or illegal use of an equal sign' in line +15.13. + +The program `QUIT`s whenever an error occurs, thus all pending +operations are canceled, and in general it is impossible to resume +*precisely* at the point of interruption, but it is often possible to +make the necessary changes, perhaps update a few variables with direct +commands, and then restart from a point close to where the error +occurred. + +This version also has an 'auto-restart' feature which allows the program +to continue after an error instead of returning to command mode. This +feature is selected by [an option in the `QUIT` command](#quit). + + +## The `TRACE` Feature + +To further assist in finding the source of an error, UWF has a facility +for printing out each program step as it tries to execute it. Thus, when +an error occurs you can see exactly where the problem is. The 'trace' +feature is turned on by the occurrence of a `?` in the program (not one +which is preceded by a single quote or enclosed in double quotes, +however) and turned off again by another `?`. Thus only the portion of +the program between pairs of question marks will be output while the +program is running. The `?` may be given in a direct command, so to +trace the entire program, just use a `GO?` command to start it. +Similarly, a `DO 5.2?` command will selectively trace that one line. + +As a further aid to finding logical errors (as opposed to simple +programing mistakes), when the trace is on, UWF will print out the +result of all expressions appearing in `SET` commands. Thus you can see +the values of all the variables as well as the program steps which +created those values. A video terminal is obviously preferable for +program traces since rather voluminous output can be generated in quite +a short time. + +A somewhat secondary use of the `TRACE` feature is for simplified +input/output prompting. Whenever variables have names closely resembling +their usage, it is a bit of a waste to have commands such as: + + ASK "AGE? "AGE + +or + + TYPE "COST="COST + +when, with only a small sacrifice in the punctuation, the following will +do just as well: + + ASK ?AGE ? + +or + + TYPE ?COST? + +UWF will print out just the characters enclosed by the `?`s. For this +reason it is preferable to use 'spaces' as separators rather than +'commas', i.e. + + ASK ?A B(I) C(J,K) ? + +will print out each variable name followed by a space and then wait for +its value to be input. One small disadvantage to this 'trick' is that +when such statements are *actually* being traced, the text enclosed by +`?` marks will *not* be printed due to the 'toggling' nature of the +trace switch. + +There is one other small anomaly associated with the trace feature: A +command such as `SET !=5,$=10` will not set those two 'secret variables' +when it is traced, but will instead first perform a CR/LF and then dump +the symbol table! This is because during a program trace all `SET` +commands are treated internally as though they were `TYPE`s and hence +the secret variables take on their special roles as operators. There is +a simple solution to this problem, however, and that is to simply prefix +a `+` sign or otherwise embed such variables in the midst of an +arithmetic expression so that they are no longer recognized as +`ASK`/`TYPE` operators. Thus the command `SET +!=5,+$=10` would be +traced properly. + + +## Command Summary + +The following table provides a quick review of UWF's entire command +repertoire. + +| Command | Form | Example | +| ------------------ | ------------------------------------------------ | -------------------- | +| `@` | a not implemented in this version |  | +| `ASK` | list of variables, "prompts", formatting options | `A X,Y(I),Z(J,K)` | +| `BREAK` | line number | `B` or `B 11.45` | +| `COMMENT` | your programs whenever possible | `C FOR COMMENTS` | +| `DO` | list of lines, groups, or sub-groups | `D .7, -9.5, 10` | +| `ERASE` | line, group, sub-group, or 'all' | `E 5 or E 9.1` | +| `FOR` | var = start, increment, finish | `F I=1,5;F J=I,-1,0` | +| `GOTO` | line number | `G 11.8 or G .3` | +| `HESTATE` | time delay desired | `H 1000` | +| `IF` | (arithmetic expression) negative, zero, positive | `I (K=I-J), .5` | +| `JUMP` | line number | `J .3;C WAIT LOOP` | +| `JUMP` | (arithmetic expression) one, two, three, four... | `J (N) 1, .2, -3.4` | +| `KONTROL` | bit positions | `K 1,(-1,2,3),=X` | +| `LOOK` | program area | `L 1` | +| `LOOK` | program area, subroutine pointer | `L 2,4.1 or L,10` | +| `MODIFY` | line number | `M 5.1` | +| `MOVE` | old line number, new line number | `M 3.3,6.3` | +| `NEXT` | line number | `F I=1,10;N;T PI` | +| `ON` | (arithmetic expression) negative, zero, positive | `O (A-5) -9.2, 9` | +| `PUNCH` | punches program and variables in binary format | `P` | +| `QUIT` | line number | `Q or Q 5.1` | +| `RETURN` | line number | `R or R .2` | +| `SET` | list of arithmetic expressions | `S A=5, B=C=A/2` | +| `TYPE` | arithmetic expressions, "labels", formatting | `T !?A ?:10"B="B` | +| `U` | available for user expansion |  | +| `V` | available for user expansion |  | +| `WRITE` | list of lines, groups, sub-groups, or 'all' | `W or W -1.5,2,3.1` | +| `XECUTE` | list of arithmetic expressions (same as `SET`) | `X FSIN(#)/FCOS(#)` | +| `YNCR` | list of variables | `Y I-J,K L` | +| `ZERO` | list of variables or 'all' | `Z,#,A,B(I),C(J,K)` | +| `OPEN INPUT, ECHO` | normal terminal input | `O I,E` | +| `OPEN READER` | selects high-speed reader | `O R` | +| `OPEN PUNCH` | selects high-speed punch | `O P` | +| `OPEN OUTPUT` | selects terminal for output | `O O` | +| `OUTPUT TRAILER` | punches leader/trailer code | `O T` | + + +## Internal Functions + +In spite of the fact that only about 3.3K words have been used to +implement UWF, there are nearly 20 built-in functions and a facility for +adding a limitless number of Program Defined Functions. + +The 'internal' functions provide the user with full-accuracy +('10-digit') approximations for commonly used relations such as log, +exponential, sine, cosine, square root, etc. Also included are simple +numerical functions such as absolute value, integer, sign and fractional +parts, maximum/minimum, etc. And finally, there are a few functions for +character processing and special I/O operations such as reading the +Switch Register and loading the MQ register. All function names in UWF +begin with the letter `F`; thus, variables names may not begin with this +letter. + + +### Transcendental Functions + +This class of functions, so named because the relations they represent +can only be expressed as infinite series, includes the natural log and +exponential functions and the three most common trigonometric functions. +The series approximations used by UWF have been optimized by a +constrained least-squares procedure to reduce the error over the +principal argument range to at worst a few parts in 10 billion. + +The transcendental functions can be removed if you wish to increase the +number of variables available in the 8K version. Removing them creates +space for another 55 variables — a total of 175 instead of only 120. +Program Defined Functions can be incorporated in their place at the +expense of greater execution time and slightly poorer accuracy. See [PDF +example 6](#pdf-transcendentals) and [the Patches section](#patches) +below for details. + + +#### `FLOG` + +`FLOG(X)` returns the natural logarithm of the absolute value of the +argument. An error occurs if `X` is zero since the theoretical result is +infinite. No error occurs if `X` is negative, although the log function +is, in fact, only defined for positive arguments. This implementation +facilitates the use of `FLOG` for extracting roots and raising values to +non-integer powers. The common (base-10) logarithm is easily obtained +from the `FLOG` function just by dividing by `FLOG(10)`. Example: + + *TYPE %,"NATURAL LN(PI)="FLOG(PI) :45"COMMON LOG(PI)="FLOG(PI)/FLOG(10)! + NATURAL LN(PI)= 1.144729886E+00 COMMON LOG(PI)= 4.971498727E-01 + + +#### `FEXP` + +`FEXP(x)` returns the value of *ex* where *e*=2.718281828... +The value of 'e' is always available as `FEXP(1)`. This function is +often used to extract roots and compute non-integer powers. For example, +X3.5 is found from the expression: `FEXP(3.5*FLOG(X))`. +Similarly, the cube root of 27 may be found from the expression: +`FEXP(FLOG(27)/3)`. The absolute value of the argument must be less than +approximately 1400 in order to avoid numeric overflow. + + +#### `FSIN`/`FCOS` + +`FSIN(A)` and `FCOS(A)` return the value of the sine and cosine of the +angle `A` when `A` is measured in *radians*. A radian is a unit of +angular measure preferred for scientific and engineering work because it +eliminates factors of π in many formulæ. One radian is +1/ of a full circle, or approximately 60°. + +To convert angles from degrees to radians you simply multiply by +`PI/180`. The value of `PI` is a protected variable which is always +available. Here is a short table of the values of `FSIN` and `FCOS` over +the first quadrant as produced by the command shown. Notice how the +radian value was saved for use in the second function call: + + *FOR A=0,10,90; TYPE %2,A %15.1, FSIN(R=A*PI/180), FCOS(R)! + 0 0.0000000000 1.0000000000 + 10 0.1736481776 0.9848077530 + 20 0.3420201433 0.9396926207 + 30 0.5000000001 0.8660254037 + 40 0.6427876096 0.7660444431 + 50 0.7660444431 0.6427876096 + 60 0.8660254037 0.5000000001 + 70 0.9396926207 0.3420201433 + 80 0.9848077530 0.1736481778 + 90 1.0000000000 0.0000000000 + + +#### `FTAN`/`FATN` + +The tangent function is not provided as an internal function since it is +just the ratio of `FSIN`/`FCOS` and is thus easy enough to compute. The +user may implement his own FTAN function, however, as described in the +discussion of [Program Defined Functions](#pdfs). + +The inverse tangent function (a.k.a arctan) is available, however. +`FATN` accepts values of any magnitude and returns the *angle* (in +radians) which would give that tangent. The range of answers is from +/2 (-90°) to /2 (+90°). +To convert from radians to degrees, just multiply by `180/PI`. For +example, to check that the angle whose tangent is -1 is, in fact, -45°: + + *TYPE 180*FATN(-1)/PI ! + -4.500000000E+01 + + +#### Trigonometric Identities + +All other trig functions can be derived from these primary functions. +For example: + +| Function | Identity | +| ----------------- | --------------------- | +| Cotangent |`FCOS(A)/FSIN(A)` | +| Arcsine | `FATN(A/FSQT(1-A*A))` | +| Arccosine |`FATN(FSQT(1-A*A)/A)` | +| Hyperbolic sine |`(FEXP(A)-FEXP(-A))/2` | +| Hyperbolic cosine |`(FEXP(A)+FEXP(-A))/2` | + +Consult any advanced algebra book for other such identities. + + +### Other Built-In Functions + +#### `FSQT` + +The `FSQT` function computes the square root of the argument using an +iterative approximation which guarantees that no more than the last bit +will be in error. Example: + + *TYPE FSQT(2), FSQT(2)^2! + 1.414213562E+00 2.000000000E+00 + + +#### `FABS` + +`FABS` returns the absolute value of the argument: + + *TYPE FABS(-1), FABS(1)! + 1.000000000E+00 1.000000000E+00 + + +#### `FSGN` + +`FSGN` returns -1, 0, or +1 depending upon whether the argument was +negative, zero or positive. Example: + + *TYPE FSGN(PI), FSGN(PI-PI), FSGN(-PI) ! + 1.000000000E+00 0.000000000E+00-1.000000000E+00 + + +#### `FITR` + +`FITR` returns the InTegeR part of the argument. Thus `FITR(PI)` is `3` +and `FITR(-5.5)` is `-5`. Note that some languages have an +[entier][entier] function which is the 'integer less than or equal to +the argument'. For positive numbers this produces the same result as +UWF's `FITR` function, but for negative values it gives the next lowest +number. If you are converting a program which was originally written in +another language, be sure to watch for this subtlety! It should be noted +that many functions and commands in UWF convert values to an integer +form internally without requiring the programer to do so. Subscripts, +for example, are always used in integer form, meaning that `A(1.5)` is +legal, but is no different from `A(1)`. In general, a value which is +used as an index or is stored in a hardware register is always converted +to an integer before use. + +[entier]: https://en.wikipedia.org/wiki/Floor_and_ceiling_functions#Notation + + +#### `FRAC` + +`FRAC` returns the fractional part of a number — the part which `FITR` +discards! This may be used to do "modulo-N' arithmetic or to check for a +remainder. The user is cautioned, however, that the value returned by +`FRAC` may have only limited accuracy and hence checks for 'exact' +values computed from expressions containing the `FRAC` function should +generally be avoided. To illustrate, the fractional value of '.002' is +.002, but the fractional value of 1.002 is off in the 8th place while +that of 1000000.002 is only correct to 3 digits. This is simply the +result of taking the difference between two large numbers. + + +#### `FMIN`/`FMAX` + +These functions compare two arguments, returning the algebraically +smallest or largest value. Thus `FMIN(+1,-2)` would return -2 while +`FMAX` would return +1. These functions have several uses. A simple +example in connection with the `FLOG` function allows one to avoid the +'log-of-zero' error with a call such as `FLOG(FMAX(1E-10,X))`. +Similarly, the `FMIN` function can be used to avoid typing nonexistent +values when dumping an array in a multi-column format. In this example, +`C` is the number of columns and `N` the number of data values in the +array: + + FOR I=1,C,N; FOR J=I,FMIN(N,C+I-1); TYPE Q(J); NEXT; TYPE ! + +As a final example, an entire array can be scanned for its extrema +simply by comparing each element with the previous best estimates: + + SET MIN=MAX=A(1); FOR I=2,N; SET MIN=FMIN(A(I),MIN), MAX=FMAX(A(I),MAX) + +A disadvantage of this method for locating the extremes is that no +information is available as to which element is biggest or smallest, +only the values are returned. + + +#### `FRAN` + +The `FRAN` function returns a different pseudo-random number each time +it is called. The numbers are limited to the range 0-1 and have an +approximately 'flat' distribution. Other distributions, for instance +Gaussian or Lorentzian functions, can be created as Program Defined +Functions by using `FRAN` in an appropriate expression. The function is +initialized by the input wait loop so the values you observe will appear +to be truly random. The pair-wise and higher-order groupings do have a +small correlation coefficient, but even so, a reasonable value of `PI` +can be obtained using `FRAN` to generate a 2-dimensional scatter +pattern. The principal use of `FRAN` appears to be for games. + + +## Character and I/O Functions + +The remaining internal functions handle character manipulation and other +special-purpose I/O operations. The character functions include `FIN`, +`POUT`, `FIND` and `FTRM`, while `FSR`, `FMQ` and `FDIN` are 'I/O-type' +functions. `FBUF` and `FCOM` provide access to extended memory for +storing large data arrays. + + +### `FIN` + +The `FIN` function reads a single character from the Input Device and +returns the numerical value of that character. A list of character +values may be found in Appendix I, and the value of any character can be +obtained within the program simply by preceding it with a single quote +mark. Thus the expression `('A)` will have the value of the letter `A` +(193) while `('A-'Z`)` will be the difference of the codes for `A` and +`Z`. Character strings can be read with the `FIN` function and later +output with `FOUT`; this is a bit slow, but it does provide UWF with a +limited string-handling facility. + + +### `FOUT` + +The `FOUT` function generates a single character from the value of an +arithmetic expression. It will thus output what `FIN` has input: +`FOUT(193)` will generate the letter `A`. More commonly, however, `FOUT` +is used to output special control characters which would otherwise be +invisible if they were simply included in a `TYPE "..."` command. For +instance, `FOUT(7)` is used to ring the 'bell', while `FOUT(140)` +outputs a 'form-feed' character. `FOUT(27)` generates an +ESCAPE code which is used by many terminals to initiate +special functions such as reverse video, cursor movement, etc. + +`FOUT` expects arguments in the range 0-255; values beyond this range +will be output, but should be avoided. Most terminals respond in the +same way to values in the range 0-127 and 128-255. UWF's input routines, +however, always return values in the higher range (128-255) in keeping +with the standard established for the PCM-12. + +The value returned by `FOUT` is always *zero*, not the value of the +character code! This was done to simplify calling the function as part +of a command. For instance, you can output a form feed ahead of a +program listing by using a `WRITE FOUT(12)` command instead of just +`WRITE`. Similarly, since 'tabbing' to column zero is ignored, you can +include `FOUT`s in `ASK` or `TYPE` commands just by putting them in 'tab +expressions'. To print a 'double quote' mark, for instance, you could +use the following: + + TYPE "THIS IS A ":FOUT('")" MARK!" + +which will produce: + + THIS IS A " MARK! + + +### `FIND` + +`FIND` searches for a character equal to its argument, reading and +echoing all characters it encounters until it finds a match. The echo is +controlled by the setting of the input echo switch, as [described +earlier](#echo-option). The character which matches is *not* echoed, +however, but is returned as the value of the function. To output this +character too, you may use a call such as `S FOUT(FIND('A))` where `A` +is the search character. To read in a comment line, just search for a +Carriage Return: `SET FIND(141)`. To read the same line in from a paper +tape, however, you should search for the line feed following the CR: +`SET FIND(138)`. This is due to different conventions for the +'end-of-line' character. `FIND` also checks continually for a +CTRL/Z. This is recognized as an 'End-of-File' mark and +causes `FIND` to return with the value 'zero' instead of with the value +of the search character. + + +### `FTRM` + +As discussed [earlier](#input-terminators), the `ASK` command treats any +input other than `0`-`9` and `A`-`Z` as a terminator, which means that +data values may be conveniently 'flagged' by the use of a special +terminating character. The purpose of the `FTRM` function is to then +pass this information back to the program so that special action may be +taken if necessary. For instance, a program might need to be able to +work with either metric or English measurements, using an appropriate +terminator to differentiate between them. Similarly one can devise a +'pocket calculator' program which accepts numbers terminated by one of +the arithmetic operators and then performs the indicated function. One +of the more common uses for this feature is to permit an indefinite +number of data values to be read in, sensing a special terminator for +the last value. A loop like the one in the example below (which checks +for a `?`) is all that is required: + + 4.1 ZERO N;TYPE "ENTER QUIZ GRADES, TERMINATE THE LAST ONE WITH A '?'"! + 4.2 ASK G(N=N+1); IF (FTRM()-'?) .2,,.2; TYPE %2"THERE WERE"N "GRADES"! + + +### `FBUF`/`FCOM` + +These functions allow UWF to use extra memory for data storage and are +thus of interest only for systems with more than 12K. They may be added +by setting Switch 8 *UP* when UWF is started for the first time. (See +[above](#opt-switch).) FBUF is designed to handle 12-bit (signed) +integer data while `FCOM` may be used for storing either 24-bit integers +or 48-bit floating-point values. Both functions are called in the same +manner: the first argument specifies the relative location in the +storage area and the second argument (if any) is the value to be stored +at that location. The function always returns the value at the location +specified. Thus: + +| Function | Description | +| ----------- | --------------------------------------------- | +| `FCOM(I)` | returns the `I`th value in the `FCOM` area | +| `FBUF(I,V)` | stores the value of `V` in the `I`th location | + +The range of the index is typically 0-4095 for FBUF and 0-1023 for +`FCOM`. `FCOM` has another mode however, in which data is stored as +two-word integers (rather than four-word floating point values) thereby +doubling the amount of storage available but limiting the range of the +data to ±223. To use `FCOM` in this mode, specify a +*negative* index. (The legal range is -1 to -2048.) Here is a loop which +stores the square root of all numbers from 0-1023: + + FOR I=0,1023; SET FCOM(I,FSQT(I)) + +Although `FBUF` and `FCOM` share the same field, FBUF starts from the +'bottom up' while `FCOM` stores from the 'top down', so both functions +may be used simultaneously. Furthermore, both functions are fully +recursive, so calls such as `FCOM(I,FCOM(J))` may be used to move data +from one location to another. + + +### `FSR` + +The FSR function reads the value of the Switch Register. This may be +used to control program options. The value is treated as a signed +number, so the range is from -2048 (4000 octal) to +2047 (3777 octal). + + +### `FMQ` + +The FMQ function displays the integer part of the argument in the MQ +register. This is quite handy for 'spying' on the progress of a long +calculation simply by displaying the value of a loop index. Since FMQ +returns the integer part of the argument, it can be included in a +subscript expression, such as 'A(FMQ(I))' which is functionally the same +as 'A(I)' but also displays the index in the MQ. + + +### `FDIN` + +This is an optional function for reading the input register of a DR8-EA +parallel I/O module. It may be added (along with the `KONTROL` command) +by setting Switch 7 *UP* the first time UWF is started. The interface +may be wired to respond to either levels or pulses, the difference being +that it will 'remember' a pulse, but 'forget' when a level changes. Each +bit is separately addressable, and each may be wired for pulse or level +sensing. For use with the `FDIN` ('Digital INput') function, the bits +are considered to be numbered from 1-12 (rather than from 0-11), just as +they are for the [`KONTROL` command](#kontrol). + +The value of `FDIN(0)` (or just `FDIN()` since 'zero' is always the +default value of an argument) is simply the weighted sum of all input +bits which have been 'set'. Bit '1' has the value 2048, bit '2' 'weighs' +1024, etc. The maximum value is thus '4095' if all the bits are turned +on. Any bits which are read by the `FDIN` function will be reset if they +are resettable, i.e. if they are wired for 'pulse' input. This ensures +that only one occurrence of an event will be detected by the program. + +`FDIN` can be made to respond to only a single bit — or to a collection +of bits — by including various arguments as the programmer desires. For +instance, `FDIN(1)` will only sense the state of bit '1'. If bit 1 is +on, `FDIN` will have the value 2048, while if it is off, the value 0 +will be returned, regardless of the setting of any other bits. +Furthermore, only bit 1 will be reset. The value of `FDIN(-1)` on the +other hand, will be the status of all bits *except* bit 1, i.e. bits +2-12. Any bits which are read will be reset as described above. + +More complicated masks can be constructed by specifying multiple bits. +Thus `FDIN(1,3)` will only look at bits '1' and '3', while `FDIN(-2,-5)` +will look at *all but* bits 2 and 5, etc. + + +## Program Defined Functions + +UWF allows the user to define his own set of special functions within +the program. Such 'Program Defined Functions' ('PDFs') may consist of +any set of UWF commands, ranging from a single program step to as much +as an entire group. A PDF is very similar to an ordinary subroutine +(`DO`) call, but with 3 important differences: + +1. a PDF may pass arguments to the subroutine + +2. a PDF returns a numeric value: the value of the function + +3. a PDF may occur in any command, not just `DO`, `ON`, `LINK`, etc. + +The last difference is especially important since it allows subroutine +calls in some circumstances when they might not otherwise be possible. + +The form of a PDF call is: + + F( line number, argument list ) + +where the letter `F` identifies this as a function call and the line (or +group) number identifies the function. This number can be replaced by a +suitably chosen variable so that one may use a ['named' function +call](#named-pdf) rather than a 'numeric' one. The argument list is not +required, but may contain several arguments. Typically, only 1 or 2 are +used although this is not a fundamental restriction. The arguments may +consist of other PDF calls which do not themselves have arguments, or +any other internal functions, with or without arguments. The use of +nested PDF calls containing an argument list is restricted since the +arguments are not stored recursively. Here are few examples of Program +Defined Functions: + +| Call | Description | +| ----------: | --------------------------------------------- | +| `F(2,A*B)` | Calls Group 2, passing `A*B` as the argument | +| `F(.9,X,Y)` | Calls line XX.90 in the current group | +| `F(-9.5)` | Calls sub-group at line 9.5 with no arguments | + +Coding a PDF is no different from writing an ordinary subroutine, but +the mechanism for passing argument values and returning the function +result needs to be explained. The value of each arithmetic expression +appearing in the argument list is saved in a specific 'protected +variable'. The first argument is saved in the variable `#`, the second +one in the variable `$`, and the third in the variable `%`. Additional +arguments are possible, and if necessary more protected variables should +be defined when [initializing UWF](#initializing). The ordinary +variables created by the program may also be used as 'common' variables +(those appearing in both the 'main' program and the definition of the +function) for passing information to the subroutine. + +PDF calls are not required to always have the same number of arguments, +so infrequently used parameters can be placed after frequently used +ones. These will not be changed unless they are modified by the +subroutine itself. In the first example, the value of `A-times-B` is +placed in the variable `#`. In the second example, `X` is placed in `#`, +and `Y` goes into `$`. If this function were called subsequently with +only a single argument, the value placed in `$` would not be disturbed. +No arguments are used in the third example, but any variables defined by +the program may be used by the subroutine. This is the only reasonable +way to handle arrays. + +The subroutine must then be written to use the appropriate protected +variable whenever it needs the value of the corresponding argument. A +routine to compute the length of a vector, for instance, might use an +expression such as `FSQT(#*#+$*$)`. + +The value returned by the function is just the result of the last +arithmetic expression processed by the subroutine. This expression may +be evaluated by any suitable command, but typically the `SET` command is +employed. To begin with a very simple example, here is how you could +code the tangent function: + + 9.9 SET FSIN(#)/FCOS(#); COMMENT: THIS IS THE TANGENT FUNCTION + +You could also include a replacement operator to save the result in a +variable, or you could use the `TYPE` command to output the result of +the expression, or whatever. Since it is the *last* result which is +returned as the value of the function, however, if other calculations +are necessary for checking the result or performing ancillary +calculations, the value desired must be saved and then `SET` again just +before returning. + +There are a number of UWF commands which do not disturb a PDF result and +so may be used without caution in the definition of the function. These +are `COMMENT`, `RETURN`, `YNCREMENT` and `ZERO`. On the other hand, +branching commands always evaluate a line number (which may be zero), +and so cannot be used to terminate a PDF without destroying the +(expected) function result. It should also be pointed out that the line +number option in a [`RETURN` command](#return) will be ignored by a PDF +call. This is necessary to ensure that the program returns to complete +the function call. + +While most PDF calls just use an explicit line or +group number to identify the function, it is possible to be somewhat +more elegant! By using a variable with a nicely selected name you can +specify the `F(TAN,X)` function rather than the `F(9.9)` function. To do +this, just set the variable `TAN` to the value 9.9. This has the +additional advantage that you can easily move the subroutine to a +different part of the program without having to change all the function +calls. + + +## Examples of Program Defined Functions + +Here are a few interesting PDFs which illustrate some of the things you +can do. A symbolic name has been used in most cases; it must be set to +the value of the line or group actually used to code the function. + +1. `F(PWR,X,Y)` — raises `X` to the `Y` power when `Y` is non-integer: + + SET FEXP($*FLOG(#)) + + Sample call: `TYPE F(PWR,27,1/3)    3.000000000` + +2. `F(!,N)` — computes the Nth factorial (maximum value of N is about 300) + + FOR I=$=1,#; SET $=$*I + + Sample call: `TYPE F(!,5)           120.0000000` + +3. `F(SUM)` — computes the sum of the subscripted array `G(I)` + + ZERO $; FOR I=1,N; SET $=$+G(I) + + Sample call: `SET AVE=F(SUM)/N` + +4. `F(PN,X)` — evaluates the polynomial + `Y=C(0)+C(1)*X+C(2)*X^2+...+C(N)*X^N` + + FOR I=N,-1,$=0; SET $=$*#+C(I) + + This function is useful for computing series approximations + +5. `F(OCTAL,VALUE)` — converts a number from decimal to octal + + FOR I=N=0,4,SET N=N+(#-8*#=FITR(#/8))*10^I + + Sample call: `TYPE F(OCTAL,1000)     1750` + + This is the most interesting of the functions shown so far, if for + no other reason than that it uses all the arithmetic operators in a + single SET command as well as some fancy redefinitions within the + loop. The technique employed is quite general for changing from one + number base to another, so simply by interchanging the 8s and 10s in + the definition you can construct a function to give you the decimal + equivalent of an octal number: + + TYPE F(DECIMAL,1000)            512 + + To be still more elegant you can rewrite the function to use the + value of `$` in place of the number 8 shown above and thus have a + general-purpose routine for converting to any number base less than + or equal to 10. A fun thing to do once you have made this change is + to try it out with a direct command such as: + + FOR J=2,10; TYPE F(BASE, 99, J)! + + which will then type out the value of 'ninty-nine' in all number + bases from 2-10. The loop limit represents the maximum number of + digits required to represent the number, so if you try this with + large numbers and small number bases you will probably need to + increase the limit to something more than '4'. + +6. PDF replacements for the + transcendental functions: + + These functions may be used in place of the internal functions in + the event that you wish to delete some of them to increase the + number of variables available on an 8K machine. + + F(EXP)=     25.1 IF (#*#-.01).2; SET #=F(EXP,#/2)^2 + EXP=25.1    25.2 SET #=1+#+#*#/2+#^3/6+#^4/24+#^5/120 + + F(LOG)=     26.1 IF (#*#-2.04*#+1).2; SET #=2*F(LOG,FSQT(#)) + LOG=26.1    26.2 SET #=(#-1)/(#+1), #=2*(#+#^3/3+#^5/5+#^7/7) + + F(ATN)=     27.1 IF (#*#-.01).2; SET #=2*F(ATN,#/(1+FSQT(1+#*#))) + ATN=27.1    27.2 SET #=#-#^3/3+#^5/5-#^7/7 + + F(SIN)=     28.1 IF(#*#-0.1).2; SET #=F(SIN(#/3), #=3*#-4*#^3 + SIN=28.1    28.2 SET #=#-#^3/6+#^5/120 + F(COS)=     28.3 SET F(SIN, PI/2-#) + + F(TAN)=     29.1 IF (#*#-.01).2;S #=F(TAN,#/2), #=2*#/(1-#*#+1E-99) + TAN=29.1    29.2 SET #=#+#^3+#^5/7.5+#^7/315 + + F(ASIN)=    30.1 IF (#*#-.01).2;S #=2*F(.1,#/(FSQT(1+#)+FSQT(1-#))) + ASIN=30.1   30.2 SET #=#+#^3/6+.075*#^5+#^7/22.4 + F(ACOS)=    30.3 SET PI/2-F(ASIN) + + F(HSIN)=    31.1 IF (#*#-.01).2; SET #=F(HSIN,#/3), #=3*#+4*#^3 + HSIN=31.1   31.2 SET #=#+#^3/6+#^5/120 + F(HCOS)=    31.3 SET FSQT(F(HSIN)*#+1) + + The method used in these functions is to recursively reduce the + argument to a value typically less than .1, evaluate a series + approximation which is reasonably accurate for an argument of this + magnitude, and then 'bootstrap' back using an identity such as + e2X=(eX)2. Thus the approximation + for `F(EXP)` is evaluated after reducing the argument to the proper + range and then the result is squared enough times to return to the + original value. This clever method was devised by A.K. Head. + +7. In many cases a PDF call is preferable to a simple `DO` because it + can pass a parameter or two to the subroutine at the same time and + can also return a 'status' value. As an example of such a use, + consider a subroutine for finding the roots of a quadratic equation. + There are three possible cases: the roots are equal, the roots are + real but unequal, or the roots are complex numbers. If the values + produced by the subroutine are stored in `R1` and `R2`, then after + calling the routine one must still decide how to interpret the + results. If the subroutine were to return the value of the + 'discriminant' this could be accomplished as follows: + + ON (F(QR)) complex, equal, unequal + + where `QR` is the group number of the Quadratic Root subroutine and + 'complex', 'equal', 'unequal' are line or group numbers associated + with the `ON` command which serves both to call the subroutine and + to test the result at the end. Other such examples will undoubtedly + occur to the reader. + + +## Function Summary + +Here is a list of all the functions implemented in the standard version +of UWF. Since up to 36 internal functions are possible, it should be +clear that this list is not exhaustive. + +| Function | Description | +| ------ | ------------------------------------------------------------ | +| `FABS` | Returns the absolute value of the argument | +| `FATN` | Returns the angle in radians whose tangent is given | +| `FBUF` | Optional: stores or retrieves 12-bit signed integers | +| `FCOM` | Optional: accesses additional memory for data storage | +| `FCOS` | Returns the cosine of an angle measured in radians | +| `FDIN` | Optional: returns value of digital input register | +| `FEXP` | Returns value of eX where \|X\| is less than 1418 | +| `FIN` | Reads and returns the value of a single character | +| `FIND` | Searches for a given character code | +| `FITR` | Returns integer value of the argument | +| `FLOG` | Returns the natural logarithm of the argument | +| `FKAX` | Returns the maximum value of two arguments | +| `FMIN` | Returns the minimum value of two arguments | +| `FMQ` | Displays the argument in the MQ register, returns same | +| `FOUT` | Outputs a single character value | +| `FRAC` | Returns the fractional part of the argument | +| `FRAN` | Returns a random number in the range 0-1 | +| `FSGN` | Returns the sign value of the argument: -1,0,+1 | +| `FSIN` | Returns the sine of an angle measured in radians | +| `FSQT` | Returns the square root of a positive number | +| `FSR` | Returns the signed value of the switch register | +| `FIRM` | Returns the value of the last `ASK` terminator | +| `F` | Program Defined Functions | + + +## Appendix Ⅰ + +### Decimal Values for All Character Codes + +| Code | Character | Name | Code | Char | Code | Char | Code | Char | +| ---- | --------- | ---- | ---- | ------- | ---- | ---- | ---- | ----------- | +| 128 | `Ctrl/@` | NULL | 160 | `SPACE` | 192 | `@` | 224 | \` | +| 129 | `Ctrl/A` | SOH | 161 | `!` | 193 | `A` | 225 | `a` | +| 130 | `Ctrl/B` | STX | 162 | `"` | 194 | `B` | 226 | `b` | +| 131 | `Ctrl/C` | ETX | 163 | `#` | 195 | `C` | 227 | `c` | +| 132 | `Ctrl/D` | EOT | 164 | `$` | 196 | `D` | 228 | `d` | +| 133 | `Ctrl/E` | ENQ | 165 | `%` | 197 | `E` | 229 | `e` | +| 134 | `Ctrl/F` | ACK | 166 | `&` | 198 | `F` | 230 | `f` | +| 135 | `Ctrl/G` | BELL | 167 | `'` | 199 | `G` | 231 | `g` | +| 136 | `Ctrl/H` | B.S. | 168 | `(` | 200 | `H` | 232 | `h` | +| 137 | `Ctrl/I` | TAB | 169 | `)` | 201 | `I` | 233 | `i` | +| 138 | `Ctrl/J` | L.F. | 170 | `*` | 202 | `J` | 234 | `j` | +| 139 | `Ctrl/K` | V.T. | 171 | `+` | 203 | `K` | 235 | `k` | +| 140 | `Ctrl-L` | F.F. | 172 | `,` | 204 | `L` | 236 | `l` | +| 141 | `Ctrl-M` | C.R. | 173 | `-` | 205 | `M` | 237 | `m` | +| 142 | `Ctrl/N` | SO | 174 | `.` | 206 | `N` | 238 | `n` | +| 143 | `Ctrl/O` | SI | 175 | `/` | 207 | `O` | 239 | `o` | +| 144 | `Ctrl/P` | DLE | 176 | `0` | 208 | `P` | 240 | `p` | +| 145 | `Ctrl/Q` | XON | 177 | `1` | 209 | `Q` | 241 | `q` | +| 146 | `Ctrl/R` | DC2 | 178 | `2` | 210 | `R` | 242 | `r` | +| 147 | `Ctrl/S` | XOFF | 179 | `3` | 211 | `S` | 243 | `s` | +| 148 | `Ctrl/T` | DC4 | 180 | `4` | 212 | `T` | 244 | `t` | +| 149 | `Ctrl/U` | NAK | 181 | `5` | 213 | `U` | 245 | `u` | +| 150 | `Ctrl/V` | SYNC | 182 | `6` | 214 | `V` | 246 | `v` | +| 151 | `Ctrl/W` | ETB | 183 | `7` | 215 | `W` | 247 | `w` | +| 152 | `Ctrl/X` | CAN | 184 | `8` | 216 | `X` | 248 | `x` | +| 153 | `Ctrl/Y` | EM | 185 | `9` | 217 | `Y` | 249 | `y` | +| 154 | `Ctrl/Z` | SUB | 186 | `:` | 218 | `Z` | 250 | `z` | +| 155 | `Ctrl/[` | ESC | 187 | `;` | 219 | `[` | 251 | `{` | +| 156 | `Ctrl/\` | FS | 188 | `<` | 220 | `\` | 252 | \| | +| 157 | `Ctrl/]` | GS | 189 | `=` | 221 | `]` | 253 | `} ALTMODE` | +| 158 | `Ctrl/^` | RS | 190 | `>` | 222 | `^` | 254 | `~ PREFIX` | +| 159 | `Ctrl/_` | US | 191 | `?` | 223 | `_` | 255 | `⌑ DELETE` | + +`FOUT(141)` will output a RETURN/LINE FEED while +`FOUT(13)` will just do a RETURN. Codes 225 through 255 are +lower case letters, some of which serve other functions on keyboards +without lower case. Many keyboards use SHIFT/K for `[`, +SHIFT/L for `\`, and SHIFT/M for `]` and +corresponding combinations for the control codes following +CTRL/Z. These symbols are often not printed on the key tops. +Codes 0-127 are the same as codes 128-255 except for the parity bit. UWF +always forces the parity bit during input. + + +## U/W FOCAL V4E Patches + +Here is a list of patches for adding a number of special features to +UWF. They are shown in the format: `FLLLL/ CCCC PPPP; QQQQ` where +`FLLLL` is the Field + Memory location, `CCCC` is the original contents, +and `PPPP` is the patch. In cases where several successive locations are +to be changed, a semicolon is shown, followed by the next patch `QQQQ`. +Note that the `FCOM` patch shown below is for 16K versions only and must +be added *before* UWF is started the first time. + + +### Field 0 + +`00045/ 4463 4442` — Replace extra variable storage with `FCOM` ([16K only](#starting)) + +`00061/ 7610 6213` — Print a CR/LF before printing an error message + + +### Field 1 + +`10402/ 4547 0000` — Eliminate the line number printout in `MODIFY` + +`11216/ 7000 4533` — Make the `ASK` command print a `:` each time + +`11241/ 1377 7040` — Use the `#` operator to output a Form Feed + +`12471/ 1000 1177; 4533` — Change 'rubout' for video terminals + +`13070/ 7106 7107` — Increase the delay after a Carriage Return + +`13134/ 7000 6xxx` — Clear an unwanted interrupt (next 3 locations too) + +`15665/ 1103 1213` — Make `TYPE` print an `=` ahead of each value + +`15666/ 4534 7200` — Remove the initial space (or `=`) printed by `TYPE` + +`14503/ 62X1 62Y1` — Change the data field used by `FCOM` (`X`,`Y` may be 2-7) + +`14545/ 62X1 62Y1` — Ditto for the `FBUF` function (`X` is set at startup) + +`10033/ 4566 5200` — Remove the `FLOG`, `FEXP` and `FATN` functions to increase the
+`12371/ 5020 1754; 1754; 1754` — size of the symbol table in the 8K version + +`10033/ 5200 5303` — Remove `FSIN` and `FCOS` to increase the symbol table size a
+`12367/ 5205 1754; 1754` — little bit more (8K only) + + +## Error Codes for UWF (V4E) October 1978 + +| Code | Meaning | +| -----: | --------------------------------------------------------------------- | +| ? | Keyboard interrupt (CTRL/F) or restart from location 10200 | +| ?01.50 | Group number greater than 31 | +| ?01.93 | Non-existent line number in a `MODIFY` or `MOVE` command | +| ?03.10 | Non-existent line called by `GOTO`, `IF`, `NEXT`, `BREAK` or `QUIT` | +| ?03.30 | Illegal command | +| ?03.47 | Non-existent line or group: `DO`, `ON`, `JUMP`, `LINK` or `PDF` call | +| ?04.35 | Missing or illegal terminator in a `FOR` command | +| ?06.03 | Illegal use of a function or number: `ASK`, `YNCR`, or `ZERO` | +| ?06.41 | Too many variables (`ZERO` unnecessary ones to recover space) | +| ?07.44 | Operator missing or illegal use of an equal sign | +| ?07.67 | Variable name begins with `F` or improper function call | +| ?07.76 | Double operators or an unknown function | +| ?08.10 | Parentheses don't match | +| ?10.50 | Program too large (sorry, you'll have to erase some of it) | +| ?18.32 | `FCOM` index out of range | +| ?19.72 | Logarithm of zero | +| ?21.57 | Square root of a negative number | +| ?22.65 | More than 10 digits in a number | +| ?25.02 | Stack overflow: reduce nested subroutines and expressions | +| ?27.90 | Zero divisor | +| ?31.<7 | Non-existent program area called by `LOOK` or `LINK` | +| ← or \_ | End of input sensed, I/O switched back to the terminal + + +## `FPAL` + +`FPAL` allows the user to code short 'machine language' functions +directly into his program. This provides 'keyboard control' of special +devices which are not supported by any of the normal functions or +commands, and also permits operations requiring only 12-bit arithmetic +to proceed at full machine speed. Routines as long as 3210 +instructions can (in theory) be incorporated, but in practice, `FPAL` +routines are seldom longer than about 5-10 instructions — just enough to +execute a short sequence of `IOT`s to pulse a control line, for +instance. + +The form of the function call is: `FPAL(AC,inst,inst,inst...)` where +`AC` is an arithmetic expression, the value of which will be placed in +the AC prior to calling the routine, and the remaining arguments are +construed as a list of *octal* numbers which represent the desired +machine instructions. These are stored in Field 3 such that the first +instruction is at 'page+1' , the second at 'page+2', etc. After the last +instruction has been tucked away, `FPAL` loads the `AC` with the integer +part of the first argument, clears the `Link`, and calls the routine. +The final value of the `AC` is then returned to the program as the value +of the function. Note that the user does not have to worry about any of +the 'calling' instructions — he only has to write the essential machine +code. + +Here are a few examples which may help clarify how the `FPAL` function +works and illustrate some of the things it can do: + +1. UWF has an `FMQ` function for loading a number into the `MQ` + register (where it is preserved by all internal arithmetic + operations), but no corresponding function for finding out what is + already there. The following `FPAL` function will not only do this, + but will also increment the value in the `MQ` at the same time: + + TYPE MQ=FPAL(,7501,7001,7521) + + Note that the first argument has been omitted in this example, since + no information is being passed *to* the function. The first + instruction (`7501=MQA`) reads the `MQ`, the next (`7001=IAC`) + increments this value and the third (`7521=SWP`) interchanges the + new and old values, saving the new value for a subsequent call and + returning the old value to the program. Machines based on the 6100 + microprocessor may not be able to display the `MQ` while UWF is + running. Using this function however, the value of the hardware + register can be saved in the variable `MQ` and output by the `TYPE` + command as well. So being able to actually 'see' this register is + not a necessity. + +2. Several variations of this routine come to mind almost immediately. + For instance, we could use the hardware `SWP` instruction to + interchange two values: + + SET MQ=FPAL(AC,7521) + + or we could take advantage of the `MQA` instruction to perform an + 'inclusive OR' between a value in the `MQ` and one in the `AC`: + + SET FMQ(A),AB=FPAL(B,7501) + +3. As a final example, suppose that we have constructed an A/D + converter interface which uses the same instruction set as the + AD8-EA. In order to test it out we can use the following `FPAL` + routine to implement the `FADC` function: + + SET CH(N)=FPAL(N,6531,6532,6534,5203,6533) + +The channel number (`N`) will be placed in the `AC` at the beginning and +can be used to control the multiplexer via a `6531=ADLM` instruction. +The converter is then started (`6532=ADST`) and we begin testing the +'done' flag (`6534=ADSD`) to see when it is finished. This involves a +`JMP .-1` instruction which means that the location of the `ADSD` +instruction (relative to a page boundary) must be known. Since `FPAL` +routines always start at 'page+1', (location 'page+0' can be used as a +'temporary'), a jump to the *third* instruction becomes `5203`. When the +conversion is finally done, the result is read into the `AC` +(`6533=ADRD`) and returned to the program. + +It goes almost without saying that such easy access to machine-level +code is both powerful *and* dangerous! No error checking can be +performed, so a single 'typo' can lead to instant disaster! Always be +sure, therefore, to save a copy of a valuable program *before* you try +out any sort of 'wild' `FPAL` function, and be especially careful with +`ISZ`s, `DCA`s, `JMP`s and `JMS`es since they can modify memory or send +the program off 'into the wild blue yonder'. + +Similarly, give special consideration to any `IOT` which might cause a +hardware interrupt since UWF runs with the interrupt system enabled! +Most interfaces have an 'interrupt disable' instruction, but if it is +necessary to use an `IOF` in order to protect UWF from a spurious +interrupt, be sure to clear the flag and then issue an `ION` before +exiting from the function — otherwise it may be necessary to [restart +the interpreter](#starting) in order to activate the interrupt system +again. + + +### Advanced Considerations + +While it is clearly possible to use `FPAL` to implement patches to UWF +itself, this practice is *strongly* discouraged — and no help with such +folly will be offered — since this makes programs 'version dependent'. +On the other hand, there *are* a few 'tricks' which could prove useful +at various times: + +1. The value of the first parameter is actually converted into a 24-bit + integer, of which only the lower 12-bits are loaded into the `AC` at + the beginning of the routine. This means that the values `4095` and + `-1` will both load 77778 into the AC. The high-order + part of the number can be accessed with a `TAD 45` (1045) + instruction, while the low-order half can always be recalled with a + `TAD 46` (1046) if it is needed later on in the function. + +2. The value of the AC is normally returned as a signed number; if it + is more desirable to have an 'unsigned' result you can simply code an + `ISZ .+1` instruction as the last step of the routine. Thus: + `TYPE FPAL (4095)` will return `-1`, whereas `TYPE FPAL (4095,2202)` + will return `4095`. The `2202` instruction is `ISZ .+1` when located + at 'page+1'. + + Notice that numbers appearing in the *first* argument of an `FPAL` + call are treated as 'decimal' values and can be replaced by + variables and/or other functions. The remaining arguments, however, + are processed as character strings and so cannot be replaced by + arithmetic expressions. + + +--------------------------------- + +**End Notes** + +1. FOCAL, PDP-8 and OS/8 are trademarks of Digital Equipment Corp., + Maynard, Mass. + +2. The automatic return can be aborted if desired — see the + [`RETURN` command](#return) for further details. + +3. For more information on storing programs in different areas, see the + [expanded text storage](#etext) discussion. + +4. `LINK` and `LOOK` differ only in the presence or absence of a second + parameter. If only the area is specified, UWF returns to command + mode (`LOOK`), otherwise it executes a subroutine call (`LINK`). + +----------------------------- + +**Formatter's Note** + +This document is based on the [OCR'd text][ocr] of the [scanned U/W +FOCAL Manual V4E from October 1978][scan]. The following edits have been +made to the original text as presented here: + +1. Fixed some grammar, OCR, and heading nesting level errors. Some new + errors have doubtless been added in this process. [Bug reports][tkt] + and [patches][hack] are welcome. + +2. "PDP8" and "PDP/8" are stylized as "PDP-8", and "Binary loader" + changed to "BIN loader" to match DEC documentation. + +3. Asterisk-marked page notes are now numbered end-notes. + +4. Removed page numbers and replaced prose page references with + internal hyperlinks. This version is intended to be read on a + computer screen, not on paper; even if printed, the original + pagination would no longer apply. + +5. Replaced instances of the obscure Latin initialism [v.i.][videf] + (*vide infra*, meaning "see below") with hyperlinks to the + referenced section. + +6. The original document used typewriter formatting. Where sensible, we + use the slightly upgraded formatting possible with Markdown. + + Examples: + + - Replaced emphasis indicated via `-dashes-` with *italics* + + - Headlines are rendered with larger and bolder fonts rather than + CENTERED ALL CAPS TEXT + + - FOCAL program text, keywords within running prose, and computer + output are marked up appropriately + + - Removed hard hyphenation and primitive line justification; that is + done by the Markdown renderer, if at all + +7. Due to the differences between documents made on and transmitted in + paper form — as compared to Markdown with HTML rendering — several + of the input commands and their resulting outputs are presented + differently in this version of the manual. + + A good example is the documentation for the [`FATN`](#fatn) + function, where in the original, the example input was given inline + in the prose, and the output was shown centered on the page. I've + chosen instead to present it as a terminal transcript, with the + command and its output shown just as you'd see it on your terminal. + + This in turn means that in some places I've added the `*` FOCAL + prompt to make it clearer which line(s) are input and which are + output. I've also added `!` operators where necessary to avoid + questions about why you get `*` prompts at the end of program ouput. + +— [Warren Young][wy], September & October 2017, Aztec, NM, USA + +[hack]: https://tangentsoft.com/pidp8i/doc/trunk/HACKERS.md +[ocr]: https://archive.org/stream/bitsavers_decpdp8focct78_4144912/UW_FOCAL_Manual_V4E_Oct78_djvu.txt +[scan]: https://archive.org/details/bitsavers_decpdp8focct78_4144912 +[tkt]: https://tangentsoft.com/pidp8i/tktnew +[videf]: https://en.wiktionary.org/wiki/vide_infra#Latin) +[wy]: https://tangentsoft.com/ + +--------------------------- + +Original document © 1978 by LAB DATA SYSTEMS
+Seattle, Washington 98125
+All rights reserved (JvZ) + +Edits © 2017 by Warren Young
+Released under the terms of the [SIMH License](https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md) ADDED doc/uwfocal-refcards.md Index: doc/uwfocal-refcards.md ================================================================== --- /dev/null +++ doc/uwfocal-refcards.md @@ -0,0 +1,414 @@ +# U/W FOCAL V4E Reference Cards + +### Introduction + +The following material is reformatted from the `CARD[1-4].DA` files +contained within the U/W FOCAL V4E distribution which we used in +creating the PiDP-8/I software project's U/W FOCAL feature. + +Some minimal effort has been made to make this document print well, +though it doesn't paginate the same as the original material. + +Since these files were likely created [before 1978][cl78] and probably +did not have their copyright renewed — if it was in fact applied for, +not an automatic thing at the time in the United States — we believe +this text to be in the public domain. If the authors of the text below +request it, we will remove this file from the PiDP-8/I software +distribution. + +[cl78]: https://en.wikipedia.org/wiki/Copyright_law_of_the_United_States#Works_created_before_1978 + + + + +## U/W FOCAL Quick Reference Card (`CARD1.DA`) + + +### Single Letter Commands + +| `A` | Ask [`"QUERY"`,`X`,`:`,`!`] | Accepts value of `X` from input device | +| `B` | Break [_L1_]% | Exits from a FOR loop, continuing at _L1_ | +| `C` | Comment | Ignores the rest of the line | +| `D` | Do [_G1_,_G2_,_G3_,etc.] | Calls a line or a group as a subroutine | +| `E` | Erase [_G1_] | Deletes all or part of the program | +| `F` | `For X=`_E1_`,`[_E2_`,`] _E3_`;`(_commands_) | Executes line `1+(_E3_-_E1_)/_E2_` times | +| `G` | Goto [_L1_] | Branches to line _L1_ | +| `H` | Hesitate [_E1_]\* | Delays (or synchronizes) the program | +| `I` | If `(E1)` [_L1_,_L2_,_L3_]% | Transfers to _L1_,_L2_,_L3_ on sign of _E1_ | +| `J` | Jump `(`_E1_`)` [_G1_,_G2_,_G3_,_G4_...]% | Calls the subroutine selected by _E1_ | +| `K` | Kontrol [_E1_,_E2_,etc]\* | Controls relays or other digital output | +| `L` | Library/List | Two-letter commands, see the next page | +| `M` | Modify [_L1_,_L2_] | Edits and/or Moves line L1 - see below | +| `N` | Next [_L1_]% | Ends a `FOR` loop, branches to _L1_ when finished | +| `O` | On `(`_E1_`)` [_G1_,_G2_,_G3_]% | Calls subroutine selected by sign of _E1_ | +| `P` | Plot [_X_,_Y_,_L_,_M_]\* | Controls an analog or digital plotter | +| `Q` | Quit [_L1_]% | Stops program, allows restart at _L1_ | +| `R` | Return [_L1_]% | Exits from a subroutine call, continuing at _L1_ | +| `S` | Set [_E1_,_E2_,_E3_,etc.] | Evaluates arithmetic expressions | +| `T` | Type [_E1_,`"TEXT"`,`!`,`#`,`:`,`%`,`$`] | Generates alphanumeric output | +| `U` | User | | +| `V` | View [_X_,_Y_,_Z_]\* | Generates graphic output on a CRT | +| `W` | Write [_G1_,_G2_,_G3_,etc.] | Lists all or part of a program | +| `X` | Xecute | Equivalent to SET | +| `Y` | Yncrement [_X_,_Y_`-`_Z_] | Increments or decrements variables | +| `Z` | Zero [_X_,_Y_,...] | Sets some or all of the variables to zero | + +\* Indicates a non-standard (installation dependent) feature + +% If the line number is omitted (or=0) no branch will occur + +_En_ are Arithmetic Expressions - - [] Enclose optional items + +_Ln_ are Line Numbers from `0.01` to `31.99` - excluding integers + +_Gn_ are Line or Group Numbers from `0` to `+31` (`0` = next or all) + +Line numbers `.01` to `.99` refer to lines in the current group Negative +or Integer line numbers denote a 'Group' operation. Arithmetic +expressions may be used as Line or Group numbers + + +### Arithmetic Operators + +| | ( ) [ ] < > | Three equivalent sets of enclosures | +| ' | Character value | `'A` is the value of the letter `A` | +| ^ | Exponentiation | Positive or negative integer powers | +| * | Multiplication | Note especially that multiplication | +| / | Division | has a higher priority than division | +| - | Subtraction or Negation | Example: (to illustrate priorities) | +| + | Addition | `-5^4/3*A=2+1` is `0-<5^4>/[3*(A=2+1)]` | +| = | Replacement | May be used anywhere in expressions | + + +### Ask/Type Operators + +| , | COMMA or SPACE | Separates variables and/or expressions | +| ! | Carriage return/linefeed | Starts a new line for input or output | +| " | String delimiter | Case shift option uses `\`: `"A\B\C"=AbC` | +| # | Return or Clear Screen | Used for plotting or overprinting | +| $ | Symbol table listing | `TYPE $4` prints 4 variables per line | +| : | Tabulation | `ASK :-15` skips over the next 15 characters | +| | (:0 is ignored) | `TYPE :15` spaces to column 15 if not beyond | +| % | Format control | `%3` Produces 3 Digits in an integer format | +| | (for output only) | `%0.04` = 4 Digits using scientific notation | +| | (input is unformatted) | `%5.02` = 5 Digits, 2 decimal places maximum | + +Letters (but only one E) are legal numeric input: `YES=25E19`. `ALTMODE` +or `ESCAPE` aborts input, with the variable unchanged. `_` deletes all +digits during input — `RUBOUT` is ignored. + + +### Modify / Move Operators + +| `CTRL/F` | Aborts the command leaving the line unchanged | +| `CTRL/G` (bell) | Selects a new search character | +| `CTRL/L` (does not echo) | Searches for next occurrence of character | +| `_` (backarrow or underline) | Deletes all characters to the left | +| `RETURN` | Terminates the line at the current position | +| `LINEFEED` | Copies the remainder of the line unchanged | +| `RUBOUT`/`DELETE` | Removes the previous character, echos a `\` | + +`RUBOUT` or `DELETE` and `_` also work during command input + +`LINEFEED` retypes the corrected input line for verification + + +## Command Summary (`CARD2.DA`) + +In the descriptions below, arguments in square brackets are optional. +Specify the argument, but don't include the square brackets. If a space +is in the square brackets, a space is required to provide the argument. + + +### Miscellaneous Commands + +| `O D` | Output Date | Prints system date in the form `DD.MM.YY` | +| `L E` | Logical Exit | Returns to the OS/8 keyboard monitor | +| `L B` | Logical Branch _L1_ | Branches to _L1_ if -no- input from TTY | +| `J ` | Jump _L1_. | Equivalent to the Logical Branch command | + + +### Filesystem Directory Commands + +| `L A,E` | List All [name][`,E`] | Lists all files after the one specified | +| `L O` | List Only [_name_]\* | Verifies the existence of one `.FC` file | +| `O L` | Only List [_name_]\* | Verifies the existence of one `.DA` file | +| `L L` | Library List [_name_]% | Shows files with the same extension | +| `L D` | Library Delete name [ _L1_] | Removes a name from the directory | +| `L Z` | Library Zero dev:[_length_] | Zeros directory using length given | + +Notes on Directory Commands: + +`E` Adding the phrase `,E` will list all of the 'empties' too + +* Omitting the name lists all files with the same extension + +% A null extension will list all files having the same name + + +### Program Commands + +| `L C` | Library Call name | Loads a program, then Quits | +| `L G` | Library Gosub name [ _G1_] | Calls a program as a subroutine | +| `L R` | Library Run name [ _L1_] | Loads a program and starts at _L1_ | +| `L N` | Library Name [_name_] | Changes the program header | +| `L S` | Library Save name [ _L1_] | Saves the current program | + +[ _G1_] indicates which line or group will be called by `L G` + +[ _L1_] specifies an error return, except for the `L R` command + + +### Input / Output Commands + +| `O A` | Output Abort [_E1_] | Terminates output file with length _E1_ | +| `O B` | Output Buffer | Dumps buffer without closing the file | +| `O C` | Output Close [_E1_] | Ends output, saves file with length _E1_ | +| `O I,E` | Open Input [`,Echo`] | Selects the terminal for input | +| `O O` | Open Output | Selects the terminal for output | +| `O S` | Output Scope | Selects CRT for output (if available) | +| `O I -` | Open Input name [`,E`] [ _L1_] | Switches input to an OS/8 device | +| `O S -` | Open Second name [`,Echo`] [ _L1_] | Selects a second input file | +| `O O -` | Open Output name [`,Echo`] [ _L1_] | Initiates OS/8 (file) output | +| `O E -` | Output Everything device [`,Echo`] | Changes error/echo device | +| `O R R` | Open Restart Read [`,Echo`] | Restarts from the beginning | +| `O R I` | Open Resume Input [`,Echo`] [ _L1_] | Returns to file input | +| `O R O` | Open Resume Output [`,Echo`] [ _L1_] | Returns to file output | +| `O R S` | Open Resume Second [`,Echo`] [ _L1_] | Returns to second input file | + +The `INPUT ECHO` sends characters to the current `OUTPUT` device + +The `OUTPUT ECHO` sends characters to the current 'O E' device + + +### Filename Expressions + +Device and filenames may be written explicitly: `RXA1:`, `MYSTUF`, +`0123.45`. Numeric parts can be computed from (expressions): +`DTA(N):PROG(X).(A+B)`. Negative values specify single characters: +`F(-201)L(-197,.5,PI)=FILE03`. An \ can be +substituted for the name: `LTA1:<20*BN+7>`. Expressions in square +brackets indicate the size: `TINY[1]`, `[SIZE]`. + + +### Variables + +Variable names may be any length, but only the first two characters are +stored; the first character may not be an `F`. Both single and double +subscripts are allowed - a subscript of 0 is assumed if none is given. +The variables `!`, `"`, `#`, `$`, `%` and `PI` are protected from the +`ZERO` command and do not appear in table dumps. `!` is used for double +subscripting and should be set to the number of rows in the array. `#`, +`$`, `%` are used by [FOCAL Statement Functions](#fsf). The `ZVR` +feature permits non-zero variables to replace any which are zero. This +includes `FOR` loop indices, so use a protected variable if the index +runs through zero. Undefined or replaced variables are automatically +set to zero before their first use. + + +### FOCAL Statement Functions + +`F(G1,E1,E2,E3)` executes line or group `G1` after first setting the +variables `#`,`$`,`%` to the values of `E1`,`E2`,`E3` (if any). The +function returun with the value of the last arithmetic expression +processed by the sub routine, including line number & subscript +evaluations. For example: + + 8.1 S FSIN(#)/FCOS(#) is the TANGENT function = F(TAN,A) if 'TA' = 8.1 + 9.1 S FEXP($*FLOG(#)) computes X^Y for any value of Y using F(9.1,X,Y) + + +## Miscellaneous Material (`CARD3.DA`) + +### Internal Functions + +| `FABS(`_E1_`)` | Returns the absolute value of the argument | +| `FADC(`_N_`)` | Reads A/D converter channel N (LAB/8e or PDP12`)` | +| `FATN(`_A_`)` | Computes the arctangent of _A_, result in radians | +| `FBLK(``)` | OS/8 block number of the current input file | +| `FBUF(`_I_`,`_V_`)` | Display buffer storage (single-precision) | +| `FCOM(`_I_`,`_V_`)` | Extended data storage in Fields 2 and 4-7 | +| `FCOS(`_A_`)` | Computes the cosine of _A_ (_A_ is in radians) | +| `FCTR(`_N_`)` | Reads a frequency counter using timebase _N_ | +| `FDAC(`_N_`,`_V_`)` | Sets D/A converter channel _N_ to the value _V_ | +| `FDAY(`_MONTH*256+DAY*8+YEAR-78_`)` | Reads/Sets the OS/8 system date | +| `FDIN(`_B1_`,`_B2_`,`...`,`_Bn_`)` | Reads selected bits from the input register | +| `FDVM(`_N_`,`_R_`)` | Reads a digital voltmeter, channel _N_, range _R_ | +| `FEXP(`_E1_`)` | Base 'e' exponential function `\|`_E1_`\|<1420` | +| `FIN()` | Reads a single character, returns the ASCII value | +| `FIND(`_C_`)` | Searches for code _C_, returning _C_ if found, 0 if `EOF` | +| `FITR(`_E1_`)` | Returns the integer part of the argument | +| `FJOY(`_I_`)` | Places the cursor (joystick) coordinates in _XJ_,_YJ_ | +| `FLEN(`_I_`)` | File length: _I_=`0` for `O`utput, _I_=`1` for `I`nput | +| `FLOG(`_E1_`)` | Natural logarithm of the absolute value of _E1_ | +| `FLS()` | Returns unsigned value of the Left Switches (PDP12) | +| `FMIN(`A_`,`_B`)` | Returns the minimum or argument | +| `FMAX(`A_`,`_B`)` | Returns the maximum argument | +| `FMQ(`_N_`)` | Displays the lower 12 bits of _N_ in the `MQ` register | +| `FOUT(`_C_`)` | Outputs character code _C_, returns the value `0` | +| `FRA(`_I_`,`_V_`)` | Reads or writes in a binary file at location I | +| `FRAC(`_E1_`)` | Returns the fractional part of the argument | +| `FRAN(``)` | Pseudo-random number function, range 0-1 | +| `FSAM(`_N_`)` | Samples _N_ channels and stores results in buffer | +| `FSGN(`_E1_`)` | Returns `-1`,`0`,`+1` for _E1_ negative, zero, positive | +| `FSIN(`_A_`)` | Computes the sine of _A_ (_A_ is in radians) | +| `FSQT(`_E1_`)` | Finds the square root using Newton's method | +| `FSR()` | Reads the Switch Register | +| `FRS()` | Reads the Right Switches on a PDP-12 | +| `FSS(`_N_`)` | Tests Sense Switch _N_: `-1` = `OFF`, `+1` = `ON` | +| `FTIM(`_N_`)` | Reads, sets or clears the elapsed time counter | +| `FTRG(`_N_`)` | Returns status and clears Schmitt trigger _N_ | +| `FTRM(``)` | Returns the last input terminator | +| `FXL(`_N_`)` | Tests external level _N_ (PDP12) returning `-1` or `+1` | + +And others. There are a total of 36 possible function names + +Functions indicated by a * are not available in all versions. The +functions `FBLK` & `FLEN` are useful in filename expressions. `FIN`, +`FOUT`, `FIND` and `FTRM` use decimal ASCII codes - see below. + + +### Decimal ASCII Character Codes + +| Code | Character | Code | Char | Code | Char | Code | Char | +| ---- | -------------------- | ----- | ---------------- | ----- |------- | ----- | --------- | +| 128 | `CTRL/@` (leader/ | 152 | `CTRL/X` | 176 | `0` | 201 | `I` | +| | trailer-ignored) | 153 | `CTRL/Y` | 177 | `1` | 202 | `J` | +| 129 | `CTRL/A` | 154 | `CTRL/Z` (`EOF`) | 178 | `2` | 203 | `K` | +| 130 | `CTRL/B` | 155 | `ESCAPE` or | 179 | `3` | 204 | `L` | +| 131 | `CTRL/C` (OS/8) | | `CTRL/[` | 180 | `4` | 205 | `M` | +| 132 | `CTRL/D` | 156 | `CTRL/\` | 181 | `5` | 206 | `N` | +| 133 | `CTRL/E` | 157 | `CTRL/]` | 182 | `6` | 207 | `O` | +| 134 | `CTRL/F` (`BREAK`) | 158 | `CTRL/^` | 183 | `7` | 208 | `P` | +| 135 | `CTRL/G` (`BELL`) | 159 | `CTRL/_` | 184 | `8` | 209 | `Q` | +| 136 | `CTRL/H` (`BACKSP`) | 160 | `SPACE` | 185 | `9` | 210 | `R` | +| 137 | `CTRL/I` (`TAB`) | 161 | `!` | 186 | `:` | 211 | `S` | +| 138 | `LINEFEED` | 162 | `"` | 187 | `;` | 212 | `T` | +| 139 | `CTRL/K` (`LINEUP`) | 163 | `#` | 188 | `<` | 213 | `U` | +| 140 | FORMFEED | 164 | `$` | 189 | `=` | 214 | `V` | +| 141 | RETURN | 165 | `%` | 190 | `>` | 215 | `W` | +| 142 | `CTRL/N` | 166 | `&` | 191 | `?` | 216 | `X` | +| 143 | `CTRL/O` | 167 | `'` (`APOST`) | 192 | `@` | 217 | `Y` | +| 144 | `CTRL/P` | 168 | `(` | 193 | `A` | 218 | `Z` | +| 145 | `CTRL/Q` (`X-ON`) | 169 | `)` | 194 | `B` | 219 | `[` | +| 146 | `CTRL/R` | 170 | `*` | 195 | `C` | 220 | `\` | +| 147 | `CTRL/S` (`X-OFF`) | 171 | `+` | 196 | `D` | 221 | `]` | +| 148 | `CTRL/T` | 172 | `,` (comma) | 197 | `E` | 222 | `^` | +| 149 | `CTRL/U` | 173 | `-` (minus) | 198 | `F` | 223 | `_` | +| 150 | `CTRL/V` | 174 | `.` (period) | 199 | `G` | 253 | `ALTMODE` | +| 151 | `CTRL/W` | 175 | `/` | 200 | `H` | 255 | `RUBOUT` | + +Codes 224-250 are lower case letters. Codes 000-127 are similar +to codes 128-255 except that the parity bit has been eliminated. + +Many keyboards use `SHIFT/K`, `/L`, `/M`, `/N`, `/O` for `[`, `\`, `]`, `^` and `_` + +A single quote before a character indicates the-value-of: `'A=193` +Use `CTRL/@` to page the TV display to avoid getting error `12.40` + +To erase the screen on a Tektronix terminal: `S FOUT(27) FOUT(12)` + +To make a copy: `S FOUT(27) FOUT(23)`. Note: `FOUT(27)` = `ESCAPE` + +To make bold letters on a Centronics printer: `T :FOUT(14) "text"` + +To set 'Hold Screen' mode (VT50 terminals): `S FOUT(27) FOUT(91)` + +To rubout the last character on the PDP12/LAB8e display `FOUT(92)` + + +## Error Code Table (`CARD4.DA`) + +For extreme economy of memory, FOCAL does not print error message strings. +Instead, an error routine prints a question mark followed by a four digit +fixed point number corresponding to where in the FOCAL runtime executable +the error was encountered. + +I.E. If an error was encountered in the FOCAL interpreter's parsing +of a variable name, the error message prints out the error message +traceable to that parser within FOCAL. + +This means that an error table must be produced, and every time code shifts +around, the error table must be updated. + +The U/W FOCAL manual contains an error table, but it is incomplete. +Here is a complete one which comes from the file CARD4.DA in the +U/W FOCAL archive from which this distribution is taken. + +Errors appearing in bold face denotes an error from a command with an +optional error return. + +| Error | Meaning | +| ------------- | ------------------------------------------------------------- | +| `?` | Keyboard interrupt or restart from location 10200 | +| __`?01.03`__ | Secondary input file missing | +| __`?01.11`__ | No secondary input file to resume | +| `?01.50` | Group number greater than 31 | +| `?01.93` | Non-existent line number in a MODIFY or MOVE command | +| `?03.10` | Line called by `GO`, `IF`, `J`, `R`, `Q`, `L` `B`, or `L R` is missing | +| `?03.30` | Illegal command | +| `?03.47` | Line or group missing in `DO`, `ON`, `JUMP`, `L GOSUB` or a `FSF` | +| | | +| `?04.35` | Bad syntax in a `FOR` command (missing semicolon?) | +| `?06.03` | Illegal use of a function or number: `ASK`, `YNCREMENT`, `ZERO` | +| `?06.41` | Too many variables (ZERO unnecessary ones) | +| `?07.44` | Operator missing or illegal use of an equal sign | +| `?07.67` | Variable name begins with `F` or improper function call | +| `?07.76` | Double operators or an unknown function | +| `?08.10` | Parentheses don't match | +| `?10.50` | Program too large | +| | | +| `?12.10` | Error detected in the `BATCH` input file | +| `?12.40` | Keyboard buffer overflow (eliminated in 8/e versions) | +| `?13.65` | Insufficient memory for `BATCH` operation | +| `?14.15` | Display buffer overflow | +| `?14.50` | Bad Sense Switch number on a PDP12 (range is 0-5) | +| `?14.56` | Illegal external sense line (PDP12 range is 0-11) | +| `?17.22` | `FRA` not initialized | +| `?17.33` | `FRA` index too large (exceeds file area) | +| `?17.62` | `FRA` mode error: only modes 0,1,2,4 allowed | +| | | +| `?18.42` | `FCOM` index too large: reduce program size | +| `?19.72` | Logarithm of zero | +| `?21.57` | Square root of a negative number | +| `?22.65` | Numeric overflow: too many digits in a string | +| `?23.18` | `OUTPUT` `ABORT` or `CLOSE` requested too much space | +| `?23.37` | Output file overflow: recover with: `O O name;O A FLEN()` | +| __`?23.82`__ | Cannot open output file (file open, too big or no name) | +| __`?24.05`__ | No output file to resume | +| | | +| `?24.25` | Illegal `OPEN` command | +| `?24.35` | Illegal `RESUME` command | +| __`?24.40`__ | Input file not found (wrong name? wrong device?) | +| `?24.47` | No input file to restart | +| __`?24.52`__ | No input file to resume | +| `?25.02` | Stack overflow: reduce nested subroutines and expressions | +| __`?25.60`__ | Device does not exist or illegal 2-page handler | +| `?26.07` | Illegal `LIBRARY` command | +| `?26.32` | File specified is already deleted (wrong extension?) | +| | | +| `?26.39` | File loaded is not a FOCAL program - __better reload UWF!__ | +| `?26.56` | Program requested is missing (wrong device?) | +| `?26.66` | `LIBRARY SAVE` error: no name, device full, or no directory | +| `?27.18` | Attempted `LIBRARY` operation on a device without a directory | +| `?27.75` | No length specified in a `LIBRARY ZERO` command | +| `?27.90` | Zero divisor | +| `?29.25` | Cannot use the '<>' construction with `OPEN OUTPUT` | +| `?29.38` | Device error (write-lock, bad checksum or illegal request) | + +`_` Indicates EOF detected in input - I/O continues from terminal + +`?....?` TRACE feature: Text enclosed by `?` marks is typed during execution +to help find the source of an error. The value of each expression in a SET +command is also printed Index: examples/README.md ================================================================== --- examples/README.md +++ examples/README.md @@ -30,35 +30,35 @@ ## How to Use the BASIC Examples To use the example BASIC program, simply transcribe it into OS/8 BASIC: .R BASIC - NEW OR OLD--NEW - FILE NAME--PAL001.BA - - READY - 10 FOR I = 1 TO 999 - 10 FOR I = 1 TO 999 - 20 A = I / 3 \ B = I / 5 - 30 IF INT(A) = A GOTO 60 - 40 IF INT(B) = B GOTO 60 - 50 GOTO 70 - 60 T = T + I - 70 NEXT I - 80 PRINT "TOTAL: "; T - 90 END - SAVE - - READY - RUN - - PAL001 BA 4A - - TOTAL: xxxxxxx - - READY - BYE + NEW OR OLD--NEW + FILE NAME--PEP001.BA + + READY + 10 FOR I = 1 TO 999 + 10 FOR I = 1 TO 999 + 20 A = I / 3 \ B = I / 5 + 30 IF INT(A) = A GOTO 60 + 40 IF INT(B) = B GOTO 60 + 50 GOTO 70 + 60 T = T + I + 70 NEXT I + 80 PRINT "TOTAL: "; T + 90 END + SAVE + + READY + RUN + + PEP001 BA 5A + + TOTAL: xxxxxxx + + READY + BYE If you're SSH'd into the PiDP-8/I, "transcribing" is simply a matter of cut-and-paste into the terminal window. I've obscured the output on purpose, since I don't want this page to be @@ -69,36 +69,39 @@ OS/8 Handbook for a decoding guide. ## How to Use the Assembly Language Examples -For each PAL8 assembly program in `examples/*.pal`, there are two -additional files: - -| Extension | Meaning ------------------------------ -| `*.pal` | the PAL8 assembly source code for the program -| `*.lst` | the human-readable assembler output -| `*.pt` | the machine-readable assembler output (RIM format) - -There are three ways to run these on your PiDP-8/I, each starting with -one of the above three files: +For each PAL8 assembly program in `src/asm/*.pal` or `examples/*.pal`, +the build process produces several output files: + +| Extension | Meaning +| --------------- | --------------- +| `*.pal` | the PAL8 assembly source code for the program; input to the process +| `obj/*.lst` | the human-readable assembler output +| `bin/*-pal.pt` | the machine-readable assembler output (RIM format) +| `boot/*.script` | a SIMH-readable version of the assembled code + +Each of those files has a corresponding way of getting the example +running in the simulator: 1. Transcribe the assembly program text to a file within a PDP-8 operating system and assemble it inside the simulator. 2. Toggle the program in from the front panel. I can recommend this method only for very short programs. -3. Copy the `*.pt` file to a USB stick and use the PiDP-8/I's +3. Copy the `*-pal.pt` file to a USB stick and use the PiDP-8/I's [automatic media mounting feature][howto]. This is the fastest method. + +4. Boot SIMH with the example in core, running the program immediately. I cover each of these options below, in the same order as the list above. -## Option 1: Transcribing the Assembly Code into an OS/8 Session +### Option 1: Transcribing the Assembly Code into an OS/8 Session To transcribe [`examples/add.pal`][pal] into the OS/8 simulation on a PiDP-8/I: .R EDIT @@ -166,21 +169,22 @@ If you have the finished assembly code already on your computer and are SSH'd into the PiDP-8/I machine, there is a shortcut for all of the above. At the OS/8 command line, say: .R PIP - *ADD.PA +#include + +ire0 (n, d) +{ + while (n > 0) n = n - d; + return n == 0; +} + +main () +{ + int i, st; + st = 0; + + for (i = 3; i < 1000; i++) { + if (ire0 (i, 3) | ire0 (i, 5)) st = st + i; + + if (st > 1000) { + printf("%d + ", st); + st = 0; + } + } + + printf("%d\n", st); +} ADDED examples/pep001-mod.c Index: examples/pep001-mod.c ================================================================== --- /dev/null +++ examples/pep001-mod.c @@ -0,0 +1,19 @@ +#include +#include + +main () +{ + int i, st; + st = 0; + + for (i = 3; i < 1000; i++) { + if ((i % 3 == 0) | (i % 5 == 0)) st = st + i; + + if (st > 1000) { + printf("%d + ", st); + st = 0; + } + } + + printf("%d\n", st); +} ADDED examples/pep001.fc Index: examples/pep001.fc ================================================================== --- /dev/null +++ examples/pep001.fc @@ -0,0 +1,7 @@ +01.10 SET TOTAL = 0 +01.20 FOR I = 3, 999 ; DO -.3 +01.30 IF (FRAC(I / 3)) , 1.5 +01.40 IF (FRAC(I / 5)) , 1.5, 1.6 +01.50 SET TOTAL = TOTAL + I +01.60 NEXT +01.70 TYPE "TOTAL: ", %6, TOTAL, ! ADDED examples/tratbl.fc Index: examples/tratbl.fc ================================================================== --- /dev/null +++ examples/tratbl.fc @@ -0,0 +1,6 @@ +C Print transcendental function tables. +C Example from DEC FOCAL-8 Manual, July 1969, page 4-2. +01.05 T " I SINE COSINE LOG E" ! +01.10 FOR I=1,.00001,1.0001; DO 2.05 +01.20 QUIT +02.05 T %7.06,I," ",FSIN(I)," ",FCOS," ",%,FLOG[I]," ",FEXP(I),!! ADDED lib/mkos8/__init__.py Index: lib/mkos8/__init__.py ================================================================== --- /dev/null +++ lib/mkos8/__init__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +######################################################################## +# __init__.py - mkos8 module initialization +# +# Copyright © 2017 by Warren Young +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +__all__ = [ 'argparser' ] ADDED lib/mkos8/argparser.py Index: lib/mkos8/argparser.py ================================================================== --- /dev/null +++ lib/mkos8/argparser.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +######################################################################## +# argparse.py - Extend ArgumentParser to add mkos8 bits. +# +# Copyright © 2017 by Warren Young +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +import argparse +import opts + +class ArgParser (argparse.ArgumentParser): + def __init__ (self, allowed_acts): + # Call parent class ctor to initialize the arg parser + argparse.ArgumentParser.__init__ (self, + description = 'Build OS/8 RK05 disk images') + + # Add general-purpose args + self.add_bool ('-d', '--debug', + help = 'add extra debugging output, normally suppressed') + self.add_bool ('-v', '--verbose', + help = 'verbose SIMH output instead of progress messages') + + # Add arguments corresponding to --*-os8-* configure script options + for obn, vals in opts.opts.iteritems(): + od = 'dis' if vals[0] else 'en' + self.add_bool ('--' + od + 'able-' + obn, help = vals[1]) + + # Add options that do not exactly mirror configuration options + self.add_bool ('--disable-lcmod', + help = 'disable the OS/8 command upcasing patch; best set ' + + 'when SIMH is set to tti ksr mode') + + # Add trailing "what do do" argument + self.add_argument ( + 'what', + choices = allowed_acts, + help = 'select which RK05 media gets built; default is "all"', + nargs = argparse.REMAINDER) + + # Finish initializing + self.args = self.parse_args() + if len (self.args.what) == 0: self.args.what = [ 'all' ] + + + def add_bool (self, *args, **kwargs): + kwargs['action'] = 'store_true' + kwargs['default'] = False + self.add_argument (*args, **kwargs) ADDED lib/pidp8i/__init__.py.in Index: lib/pidp8i/__init__.py.in ================================================================== --- /dev/null +++ lib/pidp8i/__init__.py.in @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +######################################################################## +# __init__.py.in - pidp8i module initialization +# +# This file is a *.in file only because dirs.py.in is in this directory, +# and both *.in outputs need to land in the same directory for Python +# to find it. That happens naturally when building in-tree, but we +# need this bit of *.in trickery when building out-of-tree. +# +# Copyright © 2017 by Warren Young +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +__all__ = [ 'dirs', 'ips' ] ADDED lib/pidp8i/dirs.py.in Index: lib/pidp8i/dirs.py.in ================================================================== --- /dev/null +++ lib/pidp8i/dirs.py.in @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +######################################################################## +# dirs.py.in - Declare constants for directory names subbed in by +# autosetup, partly so we don't have to do this in multiple modules +# but also so that those files don't keep getting touched whenever +# other *.in files get touched, thus forcing an OS/8 RK05 rebuild. +# +# Copyright © 2017 by Warren Young +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +import os + +# Anchor directories. These have to be statically defined for the +# development vs installation cases because chances are high that both +# trees exist on the disk, and these are absolute paths, so we can't +# just do a "path exists" test to determine which we should be using. +# So, autosetup subs in the proper values into the .py version of this +# file in the development tree and the install script overwrites these +# for the installation tree. +build = "@builddir@" +src = "@abs_top_srcdir@" + +# Derived directories. Where it matters, these are the development tree +# paths, overridden or adjusted below if we're installed. +bin = os.path.join (build, "bin/") +log = os.path.join (build, "obj/") +media = os.path.join (src, "media/") +os8mi = os.path.join (media, "os8/") # mkos8 inputs +os8mo = bin # mkos8 outputs + +# Adjust paths for the "installed" case +if not os.path.exists(log): + # The obj/ dir doesn't exist in the install tree + log = "/tmp/" + +if not os.path.exists(media): + # We bury media one extra level deeper in the install tree + media = media.replace('/media/', '/share/media/') + + # mkos8 outputs *.rk05 into bin/ at build time, but then they're + # copied next to the os8/*.tu56 source tapes for installation. + # If we're building or modifying the OS/8 media in the installation + # tree, though, we need to write straight to the media directory. + os8 = media ADDED lib/pidp8i/ips.py.in Index: lib/pidp8i/ips.py.in ================================================================== --- /dev/null +++ lib/pidp8i/ips.py.in @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +######################################################################## +# ips.py.in - Defines the default instructions per second value. +# +# This file is a .in file purely to provide the default version of +# ips.py which can be appended to by running bin/teco-pi-demo -b on +# the target hardware to provide a better local IPS value. +# +# Copyright © 2017 by Warren Young +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +# Approximate IPS rate for a PDP-8/I based on one fact: +# +# 1. The core cycle time of a PDP-8/I is 1.5 µs. +# +# ...and one assumption: +# +# 2. PDP-8 systems execute instructions at varying rates from roughly 1 +# core cycle time up to many core cycle times. Most of the core +# PDP-8 instructions not involving I/O take 1-2 cycles, with I/O +# taking longer, so we assume that the average for typical real +# world PDP-8 code will average to 2 cycles per instruction. +# +# Thus, this value is the inverted cycle time divided by 2. +pdp8i = 333333 + +# A Raspberry Pi B+ running pidp8i-sim unthrottled executes PDP-8 code +# faster than a real PDP-8/I by the factor given, so that running the +# teco-pi-demo benchmkark (-b) it shows a factor of about 1.0. This +# gives us the lowest possible IPS value short of throttling the sim, +# underclocking the Pi, or starving the simulator of CPU power. +raspberry_pi_b_plus = pdp8i * 5.6 + +# Initial value; teco-pi-demo -b appends overrides for this +current = raspberry_pi_b_plus + +# Benchmark results: ADDED lib/simh.py Index: lib/simh.py ================================================================== --- /dev/null +++ lib/simh.py @@ -0,0 +1,350 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +######################################################################## +# simh/__init__.py - A wrapper class around pexpect for communicating +# with an instance of the PiDP-8/I SIMH simulator running OS/8. +# +# See ../doc/class-simh.md for a usage tutorial. +# +# Copyright © 2017 by Jonathan Trites, William Cattey, and Warren Young. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +import os.path +import pexpect +import pkg_resources +import subprocess +import tempfile +import time + +import pidp8i + +class simh: + # pexpect object instance, set by ctor + _child = None + + # Constant used by os8_kbd_delay, assembled in stages: + # + # 1. PDP-8 RS-232 bits per character: 7-bit ASCII plus necessary + # start, stop, and parity bits. + # + # 2. The ratio of the instructions per second ratios of a PDP-8/I to + # that of the host hardware running the simulator. The former is + # an approximate value; see lib/pidp8i/ips.py.in for the value and + # its defense. The latter is either the IPS rate for: + # + # a) a Raspberry Pi Model B+, that being the slowest host system + # we run this simulator on; or + # + # b) the IPS rate of the actual host hardware if you have run the + # "bin/teco-pi-demo -b" benchmark on it. + # + # 2. The fact that real PDP-8s ran OS/8 reliably at 300 bps, and have + # been claimed to get flaky as early as 600 bps by some. (Others + # claim to have run them up to 9,600 bps.) + # + # 3. The "safe BPS" value is the fastest bit per second speed actual + # PDP-8 hardware was known to run OS/8 terminal I/O at. In this + # case, it is the high-speed tape reader. + # + # TODO: We may be able to increase this. + # + # We have one report that OS/8 was tested with terminals up to + # about ~600 bps before becoming unreliable. + # + # We have another report that OS/8 could run under ETOS with + # 9,600 bps terminals, but we don't know if that tells us anything + # about OS/8 running without the ETOS multitasking hardware. + # + # 4. Given above, calculate safe characters per second for host HW. + # + # 5. Invert to get seconds per character, that being the delay value. + _bpc = 7 + 1 + 1 + 1 # [1] + _ips_ratio = float (pidp8i.ips.current) / pidp8i.ips.pdp8i # [2] + _pdp8i_safe_bps = 300 # [3] + _host_safe_cps = _pdp8i_safe_bps * _ips_ratio / _bpc # [4] + _os8_kbd_delay = 1 / _host_safe_cps # [5] + + + #### ctor ############################################################ + # The first parameter must be given as the parent of bin/pidp8i-sim. + # + # The second defaults to false, meaning that a failure to lock the + # GPIO for the caller's exclusive use is a fatal error. If you pass + # True instead, we just complain if the GPIO is already locked and + # move on. This tolerant mode is appropriate for programs that need + # the simulator alone, not actually the PiDP-8/I front panel display. + + def __init__ (self, basedir, ignore_gpio_lock = False): + # Start the simulator instance + self._child = pexpect.spawn(basedir + '/bin/pidp8i-sim') + + # Turn off pexpect's default inter-send() delay. We add our own as + # necessary. The conditional tracks an API change between 3 and 4. + pev4 = (pkg_resources.get_distribution("pexpect").parsed_version > + pkg_resources.parse_version("4.0")) + self._child.delaybeforesend = None if pev4 else 0 + + # Wait for either an error or the simulator's configuration line. + if not self.try_wait (\ + '^PiDP-8/I [a-z].*\[.*\]', \ + 'Failed to lock /dev/gpiomem', timeout = 3): + if ignore_gpio_lock: + print "WARNING: Failed to lock GPIO for exclusive use. Won't use front panel." + else: + raise RuntimeError ('GPIO locked') + + + #### back_to_cmd ###################################################### + # Pause the simulation and return to the SIMH command prompt when the + # simulated software emits the given prompt string. Typically used to + # wait for OS/8 to finish running a command so we can do something + # down at the SIMH layer instead. + + def back_to_cmd (self, prompt): + self._child.expect ("\n%s$" % prompt) + self.os8_kbd_delay () + self._child.sendcontrol ('e') + + + #### os8_get_file #################################################### + # Rough inverse of os8_send_file. + # + # Both paths must be given and are used literally. (Contrast our + # inverse, where the destinatinon file name is produced from the + # source if not given.) + # + # When this function is called to pull a file sent by our inverse, the + # conversion should be lossless except for the transforms done by our + # underlying utility tools, such as the LF -> CR+LF done by txt2ptp + # but not undone by ptp2txt. + # + # Entry context should be inside OS/8. Exit context is inside OS/8. + + def os8_get_file (self, intname, extname): + # Attach a blank paper tape to the simulator. + ptf = tempfile.NamedTemporaryFile (suffix = '.pt', delete = False) + ptf.close () + ptn = ptf.name + self.back_to_cmd ('\\.') + self.send_cmd ('attach ptp ' + ptn) + + # Punch internal file to external paper tape image + self.os8_restart () + self.os8_send_cmd ('\\.', 'PUNCH ' + intname); + self.back_to_cmd ('\\.') # wait for transfer to finish + + # Convert text file from SIMH paper tape format + tool = os.path.join (pidp8i.dirs.build, 'bin', 'ptp2txt') + self.send_cmd ('detach ptp') + subprocess.call (tool + ' < ' + ptn + ' > ' + extname, shell = True) + + # Return to OS/8, just because that's where we were on entry, so we + # should not change that. + self.os8_restart () + + + #### os8_kbd_delay ################################################### + # Artificially delay the media generation process to account for the + # fact that OS/8 lacks a modern multi-character keyboard input buffer. + # It is unsafe to send text faster than a contemporary terminal could, + # though we can scale it based on how much faster this host is than a + # real PDP-8. See the constants above for the calculation. + def os8_kbd_delay (self): + time.sleep (self._os8_kbd_delay) + + + #### os8_send_cmd #################################################### + # Wait for an OS/8 command prompt running within SIMH, then send the + # given line. + # + # The prompt string is passed in because OS/8 has several different + # prompt types. + + def os8_send_cmd (self, prompt, line): + self._child.expect ("\n%s$" % prompt) + self.os8_send_line (line) + + + #### os8_send_ctrl ################################################### + # Send a control character to OS/8 corresponding to the ASCII letter + # given. We precede it with the OS/8 keyboard delay, since we're + # probably following a call to os8_send_line or os8_send_cmd. + + def os8_send_ctrl (self, char): + self.os8_kbd_delay () + self._child.sendcontrol (char[0].lower ()) + + + #### os8_send_file ################################################### + # Send a copy of a local text file to OS/8. The local path may + # contain directory components, but the remote must not, of course. + # + # If the destination file name is not uppercase, it will be so forced. + # + # If the destination file name is not given, it is taken as the + # basename of the source file name. + # + # The file is sent via the SIMH paper tape device through PIP in its + # default ASCII mode, rather than character by character for two reasons: + # + # 1. It's faster. It runs as fast as the simulator can process the + # I/O instructions, without any os8_kbd_delay() hooey. + # + # 2. It allows lowercase input regardless of the way the simulator is + # configured. ASCII is ASCII. + # + # Entry context should be inside OS/8. Exit context is inside OS/8. + + def os8_send_file (self, source, dest = None): + # Create path and file names not given + bns = os.path.basename (source) + if dest == None: dest = bns + dest = dest.upper () + + # Convert text file to SIMH paper tape format + bdir = pidp8i.dirs.build + pt = os.path.join (bdir, 'obj', bns + '.pt') + tool = os.path.join (bdir, 'bin', 'txt2ptp') + subprocess.call (tool + ' < ' + source + ' > ' + pt, shell = True) + + # Paper tape created, so attach it read-only and copy it in. We're + # relying on txt2ptp to insert the Ctrl-Z EOF marker at the end of + # the file. + self.back_to_cmd ('\\.') + self.send_cmd ('attach -r ptr ' + pt) + self.os8_restart () + self.os8_send_cmd ('\\.', 'R PIP') + self.os8_send_cmd ('\*', dest + ' $") + self._child.sendline (cmd) + + + #### send_line ####################################################### + # Sends the given line "blind", without waiting for a prompt. + + def send_line (self, line): + self._child.sendline (line) + + + #### set_logfile ##################################################### + + def set_logfile (self, lf): + self._child.logfile = lf + + + #### spin ############################################################ + # Let child run without asking anything more from it, with an optional + # timeout value. If no value is given, lets child run indefinitely. + + def spin (self, timeout = None): + self._child.expect (pexpect.EOF, timeout = timeout) + + + #### try_wait ######################################################## + # Wait for one of two strings to come back from SIMH, returning true + # if we get the first, else false. + + def try_wait (self, success, failure, timeout = -1): + return self._child.expect ([success, failure], timeout = timeout) == 0 ADDED libexec/mkos8 Index: libexec/mkos8 ================================================================== --- /dev/null +++ libexec/mkos8 @@ -0,0 +1,838 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +######################################################################## +# mkos8 - Build bin/os8v3d-*.rk05 from media/*/*.tu56 by scripting +# commands to SIMH and OS/8. +# +# Copyright © 2017 by Jonathan Trites, William Cattey, and Warren Young. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +# Bring in just the basics so we can bring in our local modules +import os +import sys +sys.path.insert (0, os.path.dirname (__file__) + '/../lib') +sys.path.insert (0, os.getcwd () + '/lib') + +# Remaining Python core modules +import re +import shutil +import subprocess + +# Our local modules +from mkos8 import * +from pidp8i import * +from simh import * + + +#### globals and constants ############################################# + +# Flag set when -v is *not* given. Causes make_*() and the functions +# called thereby to print progress messages to the console since SIMH +# and OS/8 output is not being sent there to clue the user into the +# script's progress. +progmsg = True + +# Name of the RK05 disk image files we create +_bin_rk05 = "os8v3d-bin.rk05" +_src_rk05 = "os8v3d-src.rk05" +_patched_rk05 = "os8v3d-patched.rk05" + +# Parser regexps used in patcher +_com_os8_parse_str = "^\.([a-zA-Z]+)\s?(.*)$" +_com_os8_parse = re.compile(_com_os8_parse_str) +_com_split_str = "^([a-zA-Z]+)\s?(.*)$" +_com_split_parse = re.compile(_com_split_str) +_odt_parse_str = "^([0-7]+)\s?/\s?(\S+)\s+([0-7;]+)" +_odt_parse = re.compile(_odt_parse_str) + +#### mkos8_abort ###################################################### +# Fatal error. Abort mkos8 + +def mkos8_abort (s): + print "Fatal Error. Cannot proceed." + s.quit () + exit (-1) + + +#### check_exists ###################################################### +# Check existence of all files needed + +def check_exists (s, image_copyins): + for copyin in image_copyins: + image = copyin[1] + image_path = dirs.os8mi + image + if (not os.path.isfile(image_path)): + print "Required file: " + image_path + " not found." + mkos8_abort(s) + # else: print "Found " + image_path + + +#### Data Structures ################################################## +# +# The make procedures use helper procedures +# to confirm that the relevant input image file exists and +# to perform the file copies. +# +# A data structure called "image copyin" +# describes the image file pathname relative to an implied root, +# provides a message string when the action is run, +# names a default destination device for whole image content copies, +# offers an optional array of specific file copy actions. +# +# FUTURE: Parse source path for ".tu56" vs. ".rk05" for more general use. +# Currently all code assumes a copyin comes from a DECtape image. +# +# Example: We Install all files for ADVENT, the Adventure game: +# +# advent_copyin = ['RKB0:', 'subsys/advent.tu56', "Installing ADVENT...", None] +# +# A DECtape device is chosen for attachment in SIMH and +# a 'COPY *.*' command is filled in with the Destination device, and the chosen DECtape. +# +# A data structer called "file copyin" +# provides override destination to allow renames or varied destinations. +# names individual files within a copyin to use +# +# Example: To copy the C compiler we want all .SV files on SYS +# but everything else to RKB0: +# (Note the useful /V option to invert the match.) +# +# cc8_sv_file_copyin = ['SYS:', '*.SV'] +# cc8_rest_file_copyin = ['RKB0:', '*.SV/V'] +# +# A 'COPY' command is filled in with the override destination and +# The file spec is used with the chosen dectape instead of "*.*" +# + +#### copyin_pair ####################################################### +# Copy two images into two destinations with two messages +# +# Assumes our context is "in simh". +# Assumes dt0 and dt1 are free. +# Assumes rk0 is the boot device +# Detaches dt0 and dt1 after using them. +# copyin0 mounts on dt0. copyin1 mounts on dt1. +# Either copyin or both can be None + +def copyin_pair (s, copyin0, copyin1, debug): + if debug: + if copyin0: + print "Copying: " + copyin0[1] + " to: " + copyin0[0] + "from dt0" + else: print "copyin0 is empty." + if copyin1: + print "Copying: " + copyin1[1] + " to: " + copyin1[0] + "from dt1" + else: print "copyin1 is empty." + + if not copyin0 and not copyin1: return # Nothing to do. + + # The order of events here is a bit funky because we want + # to use both DECtape drives but also + # switch between SIMH and OS/8 as infrequently as possible. + + if copyin0: s.send_cmd ("attach -r dt0 " + dirs.os8mi + copyin0[1]) + if copyin1: s.send_cmd ("attach -r dt1 " + dirs.os8mi + copyin1[1]) + + s.os8_restart() + + if copyin0: + if progmsg: print copyin0[2] + if copyin0[3]: # We have specific files to do. + for file_copyin in copyin0[3]: + s.os8_send_cmd ("\\.", "COPY " + file_copyin[0] + "1 + +/ + LDX 0,0 + FLDA XRDAL + FSTA F + FCLA + FSTA CUM +DALB, FLDA F + FMUL D16 /16 REAL PARTS + FSUB D8 /NEED JUST 8 + ATX 1 + FLDA A0,1 /GET MULTIPLIER + FMULM F + FLDA LA0,1 /ADD LOG(A(K)) TO SUM + FADDM CUM + FLDA F + FSUB DAL1 + JLT DALB +/NOW F>1. USE TAYLOR SERIES +/LOG(T)=Z-(Z^2)/2+(Z^3)/3+... WHERE Z=T-1 + FLDA F + FSUB DAL1 /F-1.0 + FSTA F + FMUL DT7 + FADD DT6 + FMUL F + FADD DT5 + FMUL F + FADD DT4 + FMUL F + FADD DT3 + FMUL F + FADD DT2 + FMUL F + FADD DAL1 + FMUL F + FSUB CUM + FADD N + JA DALRTN + ADDED media/os8/patches/EDIT-21.17.1M-v12B.patch8 Index: media/os8/patches/EDIT-21.17.1M-v12B.patch8 ================================================================== --- /dev/null +++ media/os8/patches/EDIT-21.17.1M-v12B.patch8 @@ -0,0 +1,12 @@ +# Problem: EDIT sometimes loses its TAB characters in multi-page files +# if file is exited on other than the last page. +# +# Solution: Make the following patch to OS/8 V3D of EDIT.SV. +# This raises the patch level to V12B + +.GET SYS:EDIT.SV +.ODT +2014/1301 1357 +2372/0301 0302 +\c +.SAVE SYS:EDIT.SV ADDED media/os8/patches/EDIT-21.17.2M-v12C.patch8 Index: media/os8/patches/EDIT-21.17.2M-v12C.patch8 ================================================================== --- /dev/null +++ media/os8/patches/EDIT-21.17.2M-v12C.patch8 @@ -0,0 +1,19 @@ +# EDIT V12B 21.17.2 M +# EDIT Q COMMAND AFTER L COMMAND (RY) +# +# Problem: When a "Q" command is issued after an "L" command, the output +# is sent to the terminal instead of the file. +# +# Diagnosis: The "Q" command has bypassed the code that resets the +# variable 'OUTDEV in its attempt to clear the variable +# 'TABIND' +# +# Solution: Install the following patch, that upgrades EDIT to Version +# 12C. +.GET SYS:EDIT +.ODT +2372/0302 0303 +2014/nnnn 2774 +2774/nnnn 3112;5776;1301 +\c +.SAVE SYS:EDIT ADDED media/os8/patches/EDIT-21.17.3M-v12D.patch8 Index: media/os8/patches/EDIT-21.17.3M-v12D.patch8 ================================================================== --- /dev/null +++ media/os8/patches/EDIT-21.17.3M-v12D.patch8 @@ -0,0 +1,13 @@ +# EDIT V12C Seq 21.17.3 M +# EDIT Q COMMAND PATCH (RY) +# +# Problem: The "Q" command does not output the first page of a file. +# +# Solution: Install the following patch and EDIT is upgraded to Version +# 12D. +.GET SYS:EDIT +.ODT +1360/5304 5301 +2372/0303 0304 +\c +.SAVE SYS:EDIT ADDED media/os8/patches/EDIT-21.17.4M-V12C.patch8 Index: media/os8/patches/EDIT-21.17.4M-V12C.patch8 ================================================================== --- /dev/null +++ media/os8/patches/EDIT-21.17.4M-V12C.patch8 @@ -0,0 +1,16 @@ +# EDIT.SV "V" OPTION WILL NOT WORK WITH LPT (RY) +# +# The "V" option of EDIT.SV will not work when the LPT interface is the +# parallel port of the DKC8-AA I/O option board. Install the following patch +# and EDIT will be upgraded from V12B to V12C. +# Correction: This is the 4th patch in the sequence, and upgrades EDIT +# from V12D to V12E + +.GET SYS EDIT +.ODT +2713/6666 5374 +2774/xxxx 7040;6574;5314 +2714/6661 6570 +2372/0304 0305 +\c +.SAVE SYS EDIT.SV ADDED media/os8/patches/F4-21.1.2M-v4B.patch8 Index: media/os8/patches/F4-21.1.2M-v4B.patch8 ================================================================== --- /dev/null +++ media/os8/patches/F4-21.1.2M-v4B.patch8 @@ -0,0 +1,23 @@ +# Updated in Dec/Jan 1978 SEQ 21.1.2 N to correct referencee to F4 not FRTS. +# F4.SV V4A Seq 2 M +# EQUIVALENCE STATEMENT (MH) +# The EQUIVALENCE statement does not always work correctly in OS/8 FORTRAN IV V3D. +# Install the following patch to correct the problem. +.GET SYS F4.SV +.ODT +2067/1471 1367 +2070/1071 5363 +2163/**** 2071 +2164/**** 7000 +2165/**** 1071 +2166/**** 5271 +2167/**** 2 +1130/6401 6402 +\c +.SAVE SYS F4.SV +.GET SYS PASS3.SV +.ODT +712/6401 6402 +\c +.SAVE SYS PASS3.SV +# This patch upgrades F4.SV to V4B. ADDED media/os8/patches/F4-51.3.1M-v4C.patch8 Index: media/os8/patches/F4-51.3.1M-v4C.patch8 ================================================================== --- /dev/null +++ media/os8/patches/F4-51.3.1M-v4C.patch8 @@ -0,0 +1,14 @@ +# Supersedes article dated Mar 78 +# FORTRAN COMPILER FAILS TO RECOGNIZE " AS AN ERROR (SPR 8-2428 JB) +# Problem: The F4 compiler fails to recognize the double quotes (") as +# an incorrect character in a subroutine call argument. +# Instead, it generates an argueless call. +# Solution: The following patch corrects this problem and should be +# installed: +.GET SYS F4 +.ODT +3343/ 7440 7640 +1130/ 6402 6403 +\c +.SAVE SYS F4 +# This patch corrects this problem and upgrades the F4.SV to V4C. ADDED media/os8/patches/F4-51.3.2M-v4x.patch8 Index: media/os8/patches/F4-51.3.2M-v4x.patch8 ================================================================== --- /dev/null +++ media/os8/patches/F4-51.3.2M-v4x.patch8 @@ -0,0 +1,13 @@ +# F4 V4A +# FORTRAN COMPILER NOT RECOGNIZING SYNTAX ERROR (JB) +# F4 compiler does not recognize syntax error in type declaration +# statement. +# The following patch will resolve this situation. +.GET SYS F4.SV +.ODT +2520/4557 5326 +2521/2200 7000 +2522/5600 7000 +2541/5320 5326 +\c +.SAVE SYS F4.SV ADDED media/os8/patches/FORLIB-51.10.1M.patch8 Index: media/os8/patches/FORLIB-51.10.1M.patch8 ================================================================== --- /dev/null +++ media/os8/patches/FORLIB-51.10.1M.patch8 @@ -0,0 +1,16 @@ +# FORTRAN IV DLOG PATCH (JR) +# +# There is a problem with DLOG where it could not handle numbers smaller than +# 1.E-018 correctly. The following patch fixes this problem. +# +# Make a source change to DLOG.RA using either EDIT or TECO. Replace +# this line: +# +# Published patch has a typo. There is no "EADD" function. It is "FADD". +# DALA, EADD DAL1 /ADD BACK +# with : +# DALA, FLDA% BPDAL /GET ARGUMENT BACK +# .R RALF +# *DLOG.RL +R,1\r +O,111\r +0340/ 440\r +W\r + +R,3\r +O,352 \r +5356/ 5364\r +O,355\r +1443/ 6211\n +5747/ 5363\r +O,363\r +0000/ 1443 \n +0000/ 5747\r +W\r + +R,24\r +O,222\r +2441/ 7000\r +W\r + +R,25\r +O,305\r +3162/ 3055\r +O,336\r +1562/ 1455 \n +3562/ 3455\r +W\r + +R,46\r +O,254\r +7001/ 5271\r +O,271\r +0000/ 7450 \n +0000/ 1370\n +0000/ 7001\n +0000/ 5255\r +W\r + +\c ADDED media/os8/patches/LINK-40.2.2M-v1E.patch8 Index: media/os8/patches/LINK-40.2.2M-v1E.patch8 ================================================================== --- /dev/null +++ media/os8/patches/LINK-40.2.2M-v1E.patch8 @@ -0,0 +1,118 @@ +# THIS PATCH FIXES THE FOLLOWING BUGS: +# 1. ERROR IN COMPUTING JOB STATUS WORD FOR MEMORY IMAGE +# 2. EAD TECHNIQUE IN COMBINING MULTIPLE NULL PAGES +# 3. BAD FIELD NUMBER IN OVERLAY DATA TABLE FOR OVRDRV +# 4. BAD OVERLAY/LEVEL LENGTH IN OVERLAY DATA TABLE FOR OVRDRV +# +# THIS PATCH UPGRADES LINK TO VIE +.R EPIC +*LINK.SV +R,1\r +O,111\r +0440/ 0540\r +W\r + +R,24\r +O,31\r +1227/ 1775\n +3776/ 7450\n +1775/ 5257\n +7450/ 7041\n +5257/ 3244\n +7041/ 7344\n +3244/ 3137\n +7344/ 1227\n +3137/ 3776\n +O,56\r +5242/ 5240\r +O,314\r +1035/ 5715\n +3035/ 5065\r +W\r + +R,25\r +O,57\r +7450/ 1043\n +5265/ 7450\n +1043/ 5271\r +O,64\r +7100/ 5246\r +O,70\r +7630/ 400 \n +5305/ 7100\n +1341/ 1043\n +1371/ 1270\n +7041/ 3043\n +3043/ 7430\n +7100/ 5337\r +O,100\r +7041/ 7161\r +O,102\r +7620/ 7670\r +O,111\r +5332/ 5246\r +O,117\r +7100/ 1341\n +1341/ 7041\n +1044/ 7100\n +7620/ 5273\r +W\r + +R,31\r +O,2\r +7041/ 376\n +1063/ 7041\n +0376/ 1063\r +O,65 \r +0000/ 3275\n +0000/ 1275\n +0000/ 7040\n +0000/ 35\n +0000/ 1275\n +0000/ 3035\n +0000/ 5674\n +0000/ 2716\r +W\r + +R,42\r +O,50\r +1042/ 1542\n +7041/ 7001\n +1542/ 5360\n +7710/ 7700\r +O,160\r +0000/ 7110\n +0000/ 7041\n +0000/ 1042\n +0000/ 5253\r +W\r + +R,46\r +O,111\r +7112/ 7000\n +7010/ 7000\r +O,125\r +7001/ 7000\n +7110/ 7000\r +O,264 \r +1041/ 1042\r +O,273\r +7001/ 3042\n +5255/ 1042\n +0000/ 7001\n +0000/ 5255\r +W\r + +R,50\r +O,214\r +1411/ 5243\r +O,243\r +0000/ 1411\n +0000/ 7001\n +0000/ 7110\n +0000/ 5215\r +W\r + +? +# (Question mark, if output by EPIC here, may be ignored) +\c ADDED media/os8/patches/LINK-40.2.3M-v1F.patch8 Index: media/os8/patches/LINK-40.2.3M-v1F.patch8 ================================================================== --- /dev/null +++ media/os8/patches/LINK-40.2.3M-v1F.patch8 @@ -0,0 +1,80 @@ +# THIS PATCH FIXES THE FOLLOWING BUGS: +# 1. LOSS OF DATA IN THE .SV IMAGE +# 2. BUG IN COMPUTATION OF RSECTS ON THE SAME PAGE AS ANOTHER PROGRAM +# 3. BAD MEMORY CONTROL BLOCKS WHEN USING /M OPTION +# 4. NEW SYSTEM ERROR (2760) FOR BAD SYMBOL TYPE +# THIS PATCH UPGRADES LINK TO VlF +.R EPIC +*LINK.SV +R,1\r +O,111\r +0540/ 640\r +W\r + +R,24\r +O,357\r +0000/4514\n +0000/5357\r +W\r + +R,25\r +O,147(CP) +1101/2757\n +1101/2757\n +W\r + +R,26\r +O,74\r +5235/5250\r +O,301\r +7041/7161\r +O,303\r +7740/7660\r +W\r + +R,46\r +O,35 \r +0374/7200\r +W\r + +R,47\r +O,326 \r +7041/5342\n +7100/7141\r +O,340\r +1411/5741\n +7041/5524\n +7100/7440\n +1410/5327\n +7640/5765\n +7420/7670\r +0,365 \r +0000/5537(DR) +0,367 \r +1401/1201\r +W\r + +R,50\r +O,124\r +0000/7320\n +0000/1411\n +0000/7440\n +0000/7041\n +0000/3013\n +0000/1410\n +0000/7450\n +0000/7020\n +0000/1013\n +0000/5736\n +0000/5345\n +0000/2010\n +0000/2010\n +0000/5742\n +0000/5355\n +W\r + +? +# (Question mark, if output by EPIC here, may be ignored) +\c + + ADDED media/os8/patches/LQP-21.49.1M-vB.patch8 Index: media/os8/patches/LQP-21.49.1M-vB.patch8 ================================================================== --- /dev/null +++ media/os8/patches/LQP-21.49.1M-vB.patch8 @@ -0,0 +1,18 @@ +# Supersedes article dated March 1978 +# +# LQP01 HANDLER FAILS TO RECOGNIZE TABS (SPR 8-2441 JM) +# +# The LQP.BN handler as distributed does not recognize the TABS character. Any +# listing or text that uses TABS will not be printed correctly. +# +# The method to patch this problem is through the BUILD procedure. This will +# fix this problem and maintain the correct version in the saved copy of BUILD. +# This is done as follows: + +.R BUILD +LOAD DSK:LQP.BN # (OR DEVICE THAT DISTRIBUTED LQP.BN IS ON) +$ALTER LQP,324=7640 +$BOOT +.SAVE SYS BUILD + +# This patch corrects this problem and upgrades the LQP.BN to VB. ADDED media/os8/patches/MACREL-40.5.1M-v1D.patch8 Index: media/os8/patches/MACREL-40.5.1M-v1D.patch8 ================================================================== --- /dev/null +++ media/os8/patches/MACREL-40.5.1M-v1D.patch8 @@ -0,0 +1,19 @@ +# IN MACREL V1C, SECT LENGTHS ARE WRONG FOR SECTS WHICH CONTAIN RELOC +# STATEMENTS AND CURRENT PAGE LITERALS. THE FOLLOWING PATCH CORRECTS +# THIS PROBLEM AND UPGRADES MACREL TO VlD. +.GET SYS:MACREL +.ODT +305/3303 5325 +320/4772 5772 +372/5546 5547 +322/3036 1044;1106;5721 +356/1044 4321 +363/1044 4321 +5455/0000 303 +5546/0000 364 +5552/5746 5356 +5555/1044 5762;1655;3036;1655;5746;363 +325/0000 1106;3303;5306 +13136/0303 304 +\c +.SAVE SYS:MACREL ADDED media/os8/patches/MACREL-40.5.2M-v1E.patch8 Index: media/os8/patches/MACREL-40.5.2M-v1E.patch8 ================================================================== --- /dev/null +++ media/os8/patches/MACREL-40.5.2M-v1E.patch8 @@ -0,0 +1,47 @@ +# PATCH VIE TO MACREL (SR) +# THIS PATCH FIXES THE FOLLOWING BUGS: +# 1. THE 'DEVICE' DIRECTIVE SOMETIMES PRODUCES RELOCATABLE TEXT. THE +# TEXT PRODUCED SHOULD ALWAYS BE ABSOLUTE. +# 2. AN UNKNOWN KEYWORD AFTER A .LIST DIRECTIVE FAILS TO PRODUCE THE +# ERROR MESSAGE: "UNKNOWN LIST CONDITION". +# 3. > AT THE END OF A DECLARATION CAUSES AN ERRONEOUS ERROR MESSAGE TO +# BE PRINTED. +# 4. CERTAIN DIRECTIVES DO NOT PRINT IN THE LISTING WHEN THEY CONTAIN A +# SYNTACTIC ERROR. +# 5. DATES LATER THAN DECEMBER 31, 1977 DO NOT PRINT CORRECTLY IN THE +# HEADER LINE. +# 6. THE THIRD CHARACTER IN OS/8 TEXT PACKING IS COMING OUT AS THE FIRST +# CHARACTER OF THE TRIPLE. +# 7. EVERY THIRD CHARACTER IN OS/8 TEXT PACKING IS IGNORING THE +# 7BIT/8BIT ENABLE CONDITION. +# THIS PATCH UPGRADES MACREL TO VIE. +# +.GET SYS:MACOVR +.ODT +11531/1033 5345 +11545/xxxx 3030;1033;5332 +12541/xxxx 1026;1347;7650;5763;1026;5740;7702 +12637/1026 4763 +12763/xxxx 2140 +10763/1302 1333 +10553/7775 7774 +10751/7006 0173;4767 +10767/5762 2156 +10557/1137 7440;1764;7006;7006;5756;2332 +\c +.SAVE SYS:MACOVR + +.GET SYS:MACREL +.ODT +13136/0304 305 +0242/5566 5301 +7220/0177 4621;2112 +2113/xxxx 0177;3725;1726;7012;7012;0327;1725;3725;2312;5712;7342;7777;30 +\c +.SAVE SYS:MACREL + +.GET SYS:MACERR +.ODT +21335/5544 5542 +\c +.SAVE SYS:MACERR ADDED media/os8/patches/MCPIP-21.21M-v6B.patch8 Index: media/os8/patches/MCPIP-21.21M-v6B.patch8 ================================================================== --- /dev/null +++ media/os8/patches/MCPIP-21.21M-v6B.patch8 @@ -0,0 +1,16 @@ +# Problem: New files created by MCPIP on a magtape or cassette after +# January 1, 1978 will be entered with the wrong creation date. +# (New files created on other devices will have their dates correct.) +# MCPIP fails to look at the date extension bits when writing a magtape +# or cassette header record. +# +# The following patch fixes this problem and upgrades MCPIP to V6B: + +.GET SYS MCPIP +.ODT +14031/ 6601 6602 +07464/ 1360 5342 +07542/ xxxx 3302;4747;1302;1360;5265;6137 +06140/ xxxx 1745;7012;7012;0346;5737;7777;0030 +\c +.SAVE SYS MCPIP ADDED media/os8/patches/MSBAT-31.22.1M-v3B.patch8 Index: media/os8/patches/MSBAT-31.22.1M-v3B.patch8 ================================================================== --- /dev/null +++ media/os8/patches/MSBAT-31.22.1M-v3B.patch8 @@ -0,0 +1,18 @@ +# MSBAT V3A SEQ. 31.22.1 M +# DIM STATEMENT NOT WORKING IN MSBAT (SR) +# PROBLEM: +# MSBAT V3A is converting a 'DIM' punch in a mark sense +# card into a dimension statement. This statement is not +# recognized by BASIC. +# DIAGNOSOS: +# The punch should be translated to 'DIM', not +# 'DIMENSION'. +# SOLUTION: +# Apply the following patch: +.GET SYS:MSBAT +.ODT +4015/1505 1500 +3671/6301 6302 +\c +.SAVE SYS:MSBAT +# This patch upgrades MSBAT to V3B. ADDED media/os8/patches/OVRDRV-40.6.1M-v1B-8srccom Index: media/os8/patches/OVRDRV-40.6.1M-v1B-8srccom ================================================================== --- /dev/null +++ media/os8/patches/OVRDRV-40.6.1M-v1B-8srccom @@ -0,0 +1,74 @@ +PATCH V1B TO OVRDRV.MA (ES) +THE FOLLOWING IS A SOURCE COMPARE OF THE DISTRIBUTED OVERLAY-DRIVER +(OVRDRV.MA) VERSES THE CHANGES NECESSARY TO MAKE IT COMPATIBLE WITH +THE BUG FIXES CORRECTED IN PATCH IE OF LINK.SV. THE CHANGES TO +OVRDRV.MA ONLY HAVE TO BE MADE IF YOU ARE USING THE LINK OVERLAY +STRUCTURE. + +SRCCOM V4A +1) /OVRDRV - OVERLAY DRIVER +2) /OVRDRV - OVERLAY DRIVER +1)001 /COPYRIGHT (C) 1977 BY DIGITAL EQUIPMENT CORPORATION +1) / +**** +2)001 /COPYRIGHT (C) 1977,1978 BY DIGITAL EQUIPMENT CORPORATION +2) / +******** +1)002 /VIA +1) /THIS SECT IS TWO LOCATIONS AND CONTAINS THE TRANSFER VECTOR + TO SWAPER +**** +2)002 /V1B +2) /THIS SECT IS TWO LOCATIONS AND CONTAINS THE TRANSFER VECTOR + TO SWAPER +******** +1)002 SWAP, 6101 /VERSION NUMBER +1) DCA AC /SAVE CALLING AC +**** +2*002 SWAP, 6102 /VERSION NUMBER +2) DCA AC /SAVE CALLING AC +******** +1)003 ISZ TEMP /TIMES (THE NUMBER OF THE + OVERLAY) + +1) JMP .-2 +1) LOAD2, TAD I RELBLK /PLUS (RELATIVE BLOCK OF + LEVEL) +**** +2)003 JMP I .+1 /TIMES (THE NUMBER OF THE + OVERLAY) + + +2) PATCH +2) LOAD2, TAD I RELBLK /PLUS (RELATIVE BLOCK OF + LEVEL) +******** +1)003 RTR /POSITION +1)004 TAD I LENGTH /GET LENGTH +1) RTR +1) RTR +1) RTR +1) DCA REDCNT /FORM CONTROL WORD +**** +2)003 RAR /POSITION +2)004 TAD I LENGTH /GET LENGTH +2) RTL +2) RTL +2) RTL +2) DCA REDCNT /FORM CONTROL WORD +******** +1)006 /THIS AREA CONTAINS OVERLAY DATA FOR MAIN AND THE 7 LEVELS +**** +2)006 /PATCH TO FIX BLOCK POSITION CALCULATION +2) PATCH, IAC /CONVERT PAGES TO BLOCKS +2) CLL RAF +2) DCA PTEMP +2) TAD PTEMP /MULTIPLY BLOCK LENGTH +2) ISZ I PPNT /BY OVEFLAY NUMBER +2) JMP .-2 +2) JMP I .+1 +2) LOAD 2 +2) PPNT, TEMP +2) PTEMP, 0 +2) /THIS AREA CONTAINS OVERLAY DATA FOR MAIN AND THE 7 LEVELS +******** ADDED media/os8/patches/PAL8-21.22.1M-v10B.patch8 Index: media/os8/patches/PAL8-21.22.1M-v10B.patch8 ================================================================== --- /dev/null +++ media/os8/patches/PAL8-21.22.1M-v10B.patch8 @@ -0,0 +1,17 @@ +# PAL8 V10A Seq 21. 22. 1 M +# INCORRECT CORE SIZE ROUTINE (SR) +# Problem: PALS's core size routine fails on certain machines such as +# an 8K PDP-8L. +# Diagnosis: There was no room in PAL8 for the standard size routine. +# The routine used by PAL8 includes a typo. +# TAD I (FLD4 should read TAD I FLD4 and, +# DCA I (FLD4 should read DCA I FLD4. +# Solution: Apply the following patch: +.GET SYS:PAL8 +.ODT +5675/3755 3715 +5677/1755 1715 +1533/0301 302 +\c +.SAVE SYS:PAL8 +# This patch upgrades PAL8 to V10B. ADDED media/os8/patches/PAL8-21.22.2M-v10C.patch8 Index: media/os8/patches/PAL8-21.22.2M-v10C.patch8 ================================================================== --- /dev/null +++ media/os8/patches/PAL8-21.22.2M-v10C.patch8 @@ -0,0 +1,21 @@ +# PAL8 V10B Seq 21. 22. 2 M +# ERRONEOUS LINK GENERATION NOTED ON PAGE DIRECTIVE (SR) +# Problem: +# An apostrophe (') is sometimes printed to the right of +# the binary column on the listing line for a PAGE +# directive (pseudo-op). Such a symbol is meaningless +# in this case. +# Diagnosis: +# This occurs if the previous line had a link generated +# in it. The PAGE directive code fails to reset the +# links generated flag (LININD). +# Cure: +# Install the following patch which upgrades PAL8 to V10C: + +.GET SYS:PAL8 +.ODT +0463/5550 5367 +0567/xxxx 3070;5550 +1533/0302 0303 +\c +.SAVE SYS PAL8 ADDED media/os8/patches/PAL8-21.22.3M-v10D.patch8 Index: media/os8/patches/PAL8-21.22.3M-v10D.patch8 ================================================================== --- /dev/null +++ media/os8/patches/PAL8-21.22.3M-v10D.patch8 @@ -0,0 +1,18 @@ +# EXPUNGE PATCH TO PAL8 (DBB) +# +# Problem: A symbol definition following an EXPUNGE directive causes a +# symbol table exceeded (SE) error in some cases. +# +# Diagnosis: The EXPUNGE directive code in PAL8 improperly counts the +# number of symbols that it deletes from the symbol table. +# +# Solution: Install the following patch which upgrades PAL8 to V10D. + +.GET SYS PAL8 +.ODT +1471/4572 5373;7106;7650;5307 +1573/xxxx 4572;1020;5272 +1533/0303 0304 +\c +.SAVE SYS PAL8 + ADDED media/os8/patches/PAL8-21.22.4M.patch8 Index: media/os8/patches/PAL8-21.22.4M.patch8 ================================================================== --- /dev/null +++ media/os8/patches/PAL8-21.22.4M.patch8 @@ -0,0 +1,32 @@ +# Supersedes article dated June/July 1980 +# TABS ARE TRANSLATED INCORRECTLY (KW) +# +# Problem: Tabs following a label (PAL8) that are 6 characters long +# are translated into spaces incorrectly. +# +# Diagnosis: This problem is evident only when CREF listings are +# requested during a PAL8 assembly. +# +# Solution: The following optional patch will correct this problem, +# improving the appearance of CREF output listings. Since +# this patch is optional, no change has been made to the +# version number of PAL8.SV. + +.R FUTIL +SET DEV SYS +SET MODE SAVE +FILE PAL8.SV +1363/7440 5764 +1364/4527 4321 +4321/XXXX 7200 +4322/XXXX 1363 +4323/XXXX 4762 +4324/XXXX 7200 +4325/XXXX 4761 +4326/XXXX 5760 +4360/XXXX 1372 +4361/XXXX 0735 +4362/XXXX 1000 +4363/XXXX 0211 +WRITE +EXIT ADDED media/os8/patches/PIP-21.23.1M-v12B.patch8 Index: media/os8/patches/PIP-21.23.1M-v12B.patch8 ================================================================== --- /dev/null +++ media/os8/patches/PIP-21.23.1M-v12B.patch8 @@ -0,0 +1,29 @@ +# PIP /Y OPTION DOES NOT WORK PROPERLY WHEN TRANSFERRING A SYSTEM HEAD FROM A +# DEVICE WHICH IS NOT CO-RESIDENT WITH SYS. (ES) +# Problem: PIP /Y option always reads absolute block 7 of the input devices +# even if the input is from a file. +# The following patch: +# 1) Fixes /Y problems in PIP +# 2) Adds RL01 Disk to PIP device tables +# 3) Upgrades patch level to V12B (from V11A - an incorrect revision code) +.GET SYS PIP +.ODT +13626/0000 17 +13631/0000 4027 +16012/1373 7001 +16051/4764 7410 +16112/3400 1600 +16134/3243 7200 +16151/7400 5600 +16152/0016 0007 +16153/7774 7770 +16167/6254 6210 +16210/1377 3032 +16211/3020 1766 +16212/1376 3024 +16213/3344 5763 +16622/6161 6162 +16623/0100 0200 +\c +.SAVE SYS PIP + ADDED media/os8/patches/PIP10-21.24.1M-v3B.patch8 Index: media/os8/patches/PIP10-21.24.1M-v3B.patch8 ================================================================== --- /dev/null +++ media/os8/patches/PIP10-21.24.1M-v3B.patch8 @@ -0,0 +1,20 @@ +PIP10 V3A Seq 21.24.1 M +Supersedes article dated Dec 78/Jan 79 +DATE '78 PATCH TO PIP10 (RY) +Problem: When PIP10 creates a new file on a PDP-10 DECtape, the file +gets the wrong date. +Diagnois: PIP10 does not understand about the new OS/8 extended date +bits for today's date. +Cure: Install the following patch which fixes this problem until 1984. +.GET SYS:PIP10 +.ODT +2612/1023 4760;1116 +2760/nnnn 1554 +1555/nnnn 1765;0176;7112;7012;3116;1023;0156;5754;7777 +4320/6301 6302 +\c +.SAVE SYS:PIP10 +# This patch corrects the above problem and upgrades PIP10 to +# V3B. +# This article replaces and supercedes the same sequence number +# published in the Dec 78-Jan 79 DSN. ADDED media/os8/patches/SABR-21.91.1M-v18B.patch8 Index: media/os8/patches/SABR-21.91.1M-v18B.patch8 ================================================================== --- /dev/null +++ media/os8/patches/SABR-21.91.1M-v18B.patch8 @@ -0,0 +1,18 @@ +# SABR V18A Seq 21.91.1 M +# LINE BUFFER PROBLEM IN SABR (DBB) +# Problem: When a SABR input line generates code across a page boundary, +# SABR puts out an altered version of the line next to the code +# at the beginning of the next page in the listing file. +# Diagnosis: The routine to enable the buffer to be placed in the listing +# file is in error. +# Solution: Install the following patch. +.GET SYS SABR +.ODT +14755/6201 1362;5757;5375;3362;5754 +15375/0000 6201;5777;6173; +16173/0000 3777;6211;5776;4760;1646 +17033/7001 7002 +\c +.SAVE SYS SABR +# The underlined text is computer generated. This patch +# corrects the problem and upgrades SABR to Version 18B. ADDED media/os8/patches/SET-21.26.1M-v1C.patch8 Index: media/os8/patches/SET-21.26.1M-v1C.patch8 ================================================================== --- /dev/null +++ media/os8/patches/SET-21.26.1M-v1C.patch8 @@ -0,0 +1,20 @@ +# The commands SET TTY SCOPE and SET SYS INIT +# ruin systems which use a 2-page system +# handler. +# SET modifies block 0 of the system device to +# handle these commands. However, in the case +# of two-page system handlers, the correct +# image is stored in block 66 instead. +# Install the following patch which creates +# once-only code in SET VlB to check for a +# 2-page system handler and modify itself +# accordingly. +.GET SYS:SET +.ODT +0507/6102 6103 +0240/5632 5357 +0357/xxxx 1765;1366;7650;4767;3362;5632;7612;7775;4400 +4401/0000 1207;3460;2202;2210;5201;5600;0066;7774 +0060/xxxx 0713;0725;3444;3453 +\c +.SAVE SYS:SET ADDED media/os8/patches/SET-21.26.2M-v1D.patch8 Index: media/os8/patches/SET-21.26.2M-v1D.patch8 ================================================================== --- /dev/null +++ media/os8/patches/SET-21.26.2M-v1D.patch8 @@ -0,0 +1,16 @@ +# If SET is run directly ( .R SET) and TTY SCOPE +# had previously been SET, then rubouts to a SET +# command fail to properly erase characters from +# the screen. +# The scope rubout code is failing to send the +# initial backspace character to the display +# terminal. +# Install the following patch to SET VIC: +# This patch upgrades SET to VlD. +.GET SYS:SET +.ODT +0507/6103 6104 +2337/5274 5370 +2370/xxxx 1056;7650;5274;5271 +\c +.SAVE SYS:SET ADDED media/os8/patches/SET-21.26.3M-v1E.patch8 Index: media/os8/patches/SET-21.26.3M-v1E.patch8 ================================================================== --- /dev/null +++ media/os8/patches/SET-21.26.3M-v1E.patch8 @@ -0,0 +1,21 @@ +# SET VlD +# Seq 21. 26. 3 M +# 1 of 1 +# PARSING OF - IN TTY WIDTH OPTION (SR) +# Problem: The valid command SET TTY WIDTH=80 results in a syntax error +# Diagnosis: The code that checks for an optional equals sign is failing to +# advance the character scan pointer. +# Solution: Install the following patch: +.GET SYS:SET +.ODT +5763/5754 4564;7200;5754 +0507/6104 6105 +\c +.SAVE SYS:SET +# This patch upgrades SET to Version VIE. In both the commands +# SET TTY WIDTH=n +# and +# SET CDR CODE=02x +# the equals sign is optional, and may be replaced by one or more spaces. +# If the equals sign is specified, it may also be optionally preceded or +# followed by spaces. ADDED media/os8/patches/TECO-31.20.01O.patch8 Index: media/os8/patches/TECO-31.20.01O.patch8 ================================================================== --- /dev/null +++ media/os8/patches/TECO-31.20.01O.patch8 @@ -0,0 +1,13 @@ +# In OS/8 TECO VS, the default value for the EU flag is 0 +# (unless .SET TTY SCOPE has been specified to the KBM +# in which case the default value is -1). +# +# Users who wish to permanently change the default value to be -1 +# (no case flagging) may install the following patch: + +.GET SYS TECO +.ODT +4576/ 0000 7777 +2245/ 7650 7200 +\c +.SAVE SYS TECO ADDED media/os8/patches/TECO-31.20.02O.patch8 Index: media/os8/patches/TECO-31.20.02O.patch8 ================================================================== --- /dev/null +++ media/os8/patches/TECO-31.20.02O.patch8 @@ -0,0 +1,18 @@ +# CHANGING THE DEFAULT EH VALUE for one-line error printouts. +# +# In OS/8 TECO V5, as on the PDP-10 and PDP-11, the default value of the +# EH flag is 0, which is the same as 2. This value causes the one-line +# form of an error message to print upon encountering an error. +# This could be annoying to experienced users with slow terminals. +# Naturally, you can change this at run-time by executing the +# 1EH command which causes only the 3-character error message cod +# to print thereafter. +# +# Users who wish to cause the default value of the EH flag +# to be permanently set to 1, can install the following patch: + +.GET SYS TECO +.ODT +4572/0000 1 +\c +.SAVE SYS TECO ADDED media/os8/patches/TECO-31.20.03O.patch8 Index: media/os8/patches/TECO-31.20.03O.patch8 ================================================================== --- /dev/null +++ media/os8/patches/TECO-31.20.03O.patch8 @@ -0,0 +1,28 @@ +# REMOVING YANK PROTECTION (SR) +# +# Probably the most common way to lose data in TECO is to accidentally +# type 'Y' when you meant to type 'T'. OS/8 TECO VS has included +# what is known as Yank protection. Yank protection causes the error message +# +# ?YCA Y Command Aborted +# +# to be produced any time that the following 3 conditions are met: +# (a) The current command is Y or +# (b) There is text in the text buffer +# (c) There is an output file open. +# +# If all these conditions are met, it is presumed that you are about +# to lose some important data, and so the command is rejected. +# Note that the Yank is always legal if the text buffer is empty +# or if no output file is open. If you really want to do the Yank, +# you can always type HKY, a command which will always succeed. +# +# Users who do not wish to have Yank protection can patch it out of TECO +# by installiing the following patch: + +.GET SYS TECO +.ODT +2032/ 7640 7610 + +\c +.SAVE SYS TECO ADDED media/os8/patches/TECO-31.20.04O.patch8 Index: media/os8/patches/TECO-31.20.04O.patch8 ================================================================== --- /dev/null +++ media/os8/patches/TECO-31.20.04O.patch8 @@ -0,0 +1,20 @@ +# SCOPE SUPPORT FOR VT05 USERS +# +# Version 5 of TECO supports scope terminals (such as VT50, VT52, VT78, etc.) +# by sending appropriate escape sequences to the terminal. +# For example, when typing the immediate mode command ^U during a multi-line +# command string, TECO will physically erase the current line from the screen. +# This support will not work properly on terminals which do not support these +# escape sequences. +# +# For our users with VT05's, we offer the following patch which will +# permit TECO to work properly on a VT05 terminal: + +.GET SYS TECO +.ODT +2771/ 4552 7200 +1446/ 0101 0032 +1437/ 0113 0036 +5405/ 0113 0036 +\c +.SAVE SYS TECO ADDED media/os8/patches/TECO-31.20.05M-v5A.patch8 Index: media/os8/patches/TECO-31.20.05M-v5A.patch8 ================================================================== --- /dev/null +++ media/os8/patches/TECO-31.20.05M-v5A.patch8 @@ -0,0 +1,17 @@ +# PROBLEM WITH AY COMMAND (SR) +# +# Problem: On rare occasions, the Y command will fail, giving the error message +# +# ?NAY Numeric Argument with Y +# +# even though no numeric argument was specified. +# For example, the AY command (Append then Yank) consistently fails in this manner. +# The test for a numeric argument is incorrect. +# The following patch corrects this problem and upgrades TECO to 5A: + +.GET SYS TECO +.ODT +2022/ 1024 2024;7610 +4573/ 0005 0765 +\c +.SAVE SYS TECO ADDED media/os8/patches/TECO-31.20.06M-v5B.patch8 Index: media/os8/patches/TECO-31.20.06M-v5B.patch8 ================================================================== --- /dev/null +++ media/os8/patches/TECO-31.20.06M-v5B.patch8 @@ -0,0 +1,25 @@ +# CONDITIONALS INSIDE ITERATIONS +# +# Problem: TECO does not properly handle unsatisfied conditionals +# if other conditionals are encountered within an inner iteration +# while scanning for the terminating single quote. +# +# While scanning for the matching single quote, TECO keeps an iteration count +# for each level of nested iterations which it finds. +# TECO then ignores any single quotes which occur at a 'nest' level greater than 0. +# (All conditionals must end at the same macro level that they begin.) +# The problem is that TECO incorrectly bumps the conditional count +# whenever it sees a double quote. +# This should not be done for double quotes occurring at a non-zero level. +# +# The following patch fixes this problem by causing TECO to +# ignore double quotes within iterations while scanning for +# a terminating single quote. This ?atch upgrades TECO to VSB: + +.GET SYS TECO +.ODT +6077/ 7240 5356 +6156/ xxxx 7200;1763;7650;7240;5300;3331 +4573/ 0765 0766 +\c +.SAVE SYS TECO ADDED media/os8/patches/TECO-31.20.07M.v5C.patch8 Index: media/os8/patches/TECO-31.20.07M.v5C.patch8 ================================================================== --- /dev/null +++ media/os8/patches/TECO-31.20.07M.v5C.patch8 @@ -0,0 +1,25 @@ +# ECHOING OF WARNING BELLS (SR) +# +# Problem: When typing in command characters to TECO, +# if you come within 10 characters of running out of memory, TECO warns you by +# ringing the terminal bell after each character input. +# (This gives you a small margin in which to clean up your command, +# e.g. by typing two altmodes.) +# Should you persist in typing after the ten warnings, +# upon typing the 11th character, TECO prints the error message +# +# ?QMO Q-Register Memory Overflow. +# +# This mechanism works correctly except that the ringing of the bell +# is accompanied by the printing of the (unwanted) two-character sequence "^G". +# TECO is ringing the bell by calling TYPTCV instead of TPUT. +# +# The following patch corrects this problem, by causing +# only a warning bell to ring. This patch upgrades TECO to V5C. + +.GET SYS TECO +.ODT +0365/ 4521 4552 +4573/ 0766 0767 +\c +.SAVE SYS TECO ADDED media/os8/patches/TECO-31.20.08M-v5.04.patch8 Index: media/os8/patches/TECO-31.20.08M-v5.04.patch8 ================================================================== --- /dev/null +++ media/os8/patches/TECO-31.20.08M-v5.04.patch8 @@ -0,0 +1,44 @@ +# CTRL/U SOMETIMES FAILS AFTER * (SR) +# PROBLEMS: +# (I) If a command line contains the character '*', then a subsequent +# use of the immediate mode command, ^U, will reprint the entire +# command string as well as erasing the current line. (This will +# not hurt you - but it is annoying.) +# (II) If on a scope terminal, a command line contains the character +# '*', then rubbing out a tab, line feed, vertical tab, or form +# feed will cause the entire command string to be reprinted. +# (III) The bell-space and bell-star (^G and ^G* ) commands were +# not documented because they did not work properly. +# The immediate mode command, ^G causes the current line of +# the commmand string to be retyped. +# The immediate mode command, ^G* causes the entire command string +# to be retyped. +# Note that the ^G (bell) character cannot be entered in up-arrow +# mode. +# (IV) The ^G* command incorrectly prints out the contents of all your +# Q-Pegisters. +# (V) When in scope mode, if you rub-out back to the first line of the +# command string, and if there is text in some Q-register, the '*' +# representing TECO's last prompt vanishes from the screen. +# (VI) The ^G command works improperly on 12K machines when +# there are more than 2900 characters stored away in Q-registers. +# ANALYSIS: +# Poltergeists in TECO. +# DISPOSITION: +# The following patch fixes all these bugs in TECO. It also makes +# the ^G and ^G* commands work properly. This patch +# upgrades TECO to version 5.04. +.GET SYS:TECO +.ODT +1341/1435 1464;1464 +1431/5235 5264 +1435/4265 7510;5313;1072;5304 +1500/7240 1072;7040;1050;5235 +1463/4265 5266;1175;3331 +1524/1175 6032;5775;1175;5253 +4570/1454 1526 +0255/5772 5004 +0004/xxxx 1577;4540;5407;1464 +4573/0767 0770 +\c +.SAVE SYS:TECO ADDED media/os8/patches/TECO-31.20.10M-5.05.patch8 Index: media/os8/patches/TECO-31.20.10M-5.05.patch8 ================================================================== --- /dev/null +++ media/os8/patches/TECO-31.20.10M-5.05.patch8 @@ -0,0 +1,15 @@ +# TECO computes the product n*0 incorrectly. +# Complementing a 13-bit 0 sets the link. +# TECO fails to account for this. +# The following patch to TECO V5.04 fixes this bug by +# zeroing the link before starting the multiply. +# This patch upgrades TECO to V5.05 +# Just as in V3C TECO (Version 4), multiplication by +# negative numbers is not supported and unpredictable +# results will occur if a multiplicand is less than 0 +.GET SYS:TECO +.ODT +1311/7010 7110 +4573/0770 0771 +\c +.SAVE SYS:TECO ADDED media/os8/patches/TECO-31.20.11M-v5.06.patch8 Index: media/os8/patches/TECO-31.20.11M-v5.06.patch8 ================================================================== --- /dev/null +++ media/os8/patches/TECO-31.20.11M-v5.06.patch8 @@ -0,0 +1,14 @@ +# Q-REGISTERS DON'T WORK IN 8K (SR) +# TECO doesn't work properly on 8K machines. +# The code which changes the handling of Q-register +# storage in the 8K case is faulty. +# This patch upgrades TECO to Version 5.06. +.GET SYS:TECO +.ODT +5762/0122 7777 +5771/xxxx 122;127;102;107;7777 +5710/3362 3371;24 +# Correction: Original patch had version change wrong. +4573/0771 772 +\c +.SAVE SYS:TECO ADDED media/os8/patches/TECO-31.20.12M-v5.07.patch8 Index: media/os8/patches/TECO-31.20.12M-v5.07.patch8 ================================================================== --- /dev/null +++ media/os8/patches/TECO-31.20.12M-v5.07.patch8 @@ -0,0 +1,31 @@ +# CAN'T SKIP OVER A "W" (SR) +# PROBLEM: +# If the letter w (as in PW) occurs inside a piece of +# TECO code which is being skipped (say because it is +# part of an unsatisfied conditional), TECO V5.06 +# will blow up. +# DIAGNOSIS: +# The appropriate skip table does not end with the +# required negative number. This table flows into +# the skip table for skipping the second letter of an +# E command (R, w, B, or G). The corresponding +# entries in the dispatch table are all harmless +# (positive) except for 'W' which causes SORT to +# branch to 'death'. +# SOLUTION: +# The following patch inserts a -1 indicator to +# properly terminate the table: +# This patch upgrades TECO to V5.07. +.GET SYS:TECO +.ODT +5264/7240 1360 +5461/7346 7344 +6250/7346 7344 +# Correction: original patch had version wrong. +4573/0772 0773 +5227/1760 1642 +5242/6201 7777 +5360/7777 7776 +5331/5266 5264 +\c +.SAVE SYS:TECO ADDED media/os8/patches/TECO-31.20.13M-v5.08.patch8 Index: media/os8/patches/TECO-31.20.13M-v5.08.patch8 ================================================================== --- /dev/null +++ media/os8/patches/TECO-31.20.13M-v5.08.patch8 @@ -0,0 +1,24 @@ +# UNSPECIFIED ITERATIONS AFTER INSERTS (SR) +# Problem: +# If an iteration has no iteration count specified, and the +# previous command was "on insert, then the iteration is +# skipped. For example, the command IA$ will not work +# properly. +# Diagnosis: +# The insert code destructively tests the number flag (NFLG) +# and if it was 0, sets it to a 1. It is never reset to 0. +# The iteration code sees a 1 and thinks a number is +# present. Looking for one, it finds 0 and thinks the +# iteration count is 0 meaning skip this iteration. +# Cure: +# The insert code should reset (zero) the number flag. This +# was not a problem in TECO V4 because 0<> was the same as +# <> then. Apply the following patch (which upgrades TECO +# to version 5.08): +.GET SYS:TECO +.ODT +2616/6032 5776;3167 +2674/5565 5617 +4573/0773 0774 +\c +.SAVE SYS:TECO ADDED media/os8/patches/patch_list.txt Index: media/os8/patches/patch_list.txt ================================================================== --- /dev/null +++ media/os8/patches/patch_list.txt @@ -0,0 +1,96 @@ +# Most of these patches have been verified +# by reading the source code. +# The patches that remain commented out are not recommended +# and the reason why appears in the line above it. +# +# ABSLDR-21.29.1M is against v6A but we have v4A. +# v6A came with OS/8 Devices Extension kit QF026 +# Binary DECtape: AL-H525A-BA, Source not on DECtape +# DO NOT APPLY THIS PATCH. +## ABSLDR-21.29.1M-v6C.patch8 +BASIC.UF-31.5.1M-V5B.patch8 +BATCH-31.23.1M-v7B.patch8 +BLOAD-31.10.1M-v5B.patch8 +BRTS-31.11.1M-v5B.patch8 +# BRTS 31.11.2O disables 8th bit parity. Recommended. +BRTS-31.11.2-O.patch8 +# BRTS 31.11.3O enables 132 column output. Recommended. +BRTS-31.11.3-O.patch8 +BRTS-31.11.5-x.patch8 +CREF-21.15.1M-v5B.patch8 +CREF-21.15.2M-v5C.patch8 +EDIT-21.17.1M-v12B.patch8 +EDIT-21.17.2M-v12C.patch8 +EDIT-21.17.3M-v12D.patch8 +# EDIT 21.17.4 overwrites patch in 21.17.2. DO NOT APPLY THIS PATCH +# EDIT-21.17.4M-V12C.patch8 +F4-21.1.2M-v4B.patch8 +F4-51.3.1M-v4C.patch8 +F4-51.3.2M-v4x.patch8 +# FORLIB 51.10.1M is hard-coded into mkos8 to copy a new +# FORLIB.RL made with instructions from the patch. +# So the patch will not apply but is listed here for completeness. +## FORLIB-51.10.1M.patch8 +FOTP-21.19.1M-V9B.patch8 +# FRTS-51.3.3-O is to enable FRTS to work with 2-page system +# handlers. I've read the code but do not fully understand it. +# It is plausable that it generalizes on the code that makes +# the 2-page TD8E handler work. But it could also be a +# patch tha tONLY works with the OS/8 Devices Extension kit QF026. +# We are enabling the patch for not. If further testing shows +# that it breaks TD8E support, we will turn it off. +FRTS-51.3.3-O.patch8 +# The two FUTIL patches only get applied to FUTIL V7 which comes with +# OS/8 V3D to bring it up to V7D. MACREL V2 comes with FUTIL V8B, so +# these patches are skipped by mkos8 using an RE match on the file name +# when the user does not pass --disable-os8-macrel to configure. +FUTIL-31.21.1M-v7B.patch8 +FUTIL-31.21.2M-v7D.patch8 +# FUTIL 31.21.3O switches XS format. Recommend to leave it out. +# FUTIL-31.21.3O.patch8 +# LQP 21.49.1 consists of commands run in BUILD. +# The auto-apply system won't apply it. +# It has not been validated. It uses hardware we don't have. +## LQP-21.49.1M-vB.patch8 +MCPIP-21.21M-v6B.patch8 +MSBAT-31.22.1M-v3B.patch8 +PAL8-21.22.1M-v10B.patch8 +PAL8-21.22.2M-v10C.patch8 +PAL8-21.22.3M-v10D.patch8 +# PAL8 21.22.4M is for V12 PAL8. It BREAKS LS output in V10! +# DO NOT APPLY THIS PATCH! +# PAL8-21.22.4M.patch8 +PIP-21.23.1M-v12B.patch8 +PIP10-21.24.1M-v3B.patch8 +SABR-21.91.1M-v18B.patch8 +SET-21.26.1M-v1C.patch8 +SET-21.26.2M-v1D.patch8 +SET-21.26.3M-v1E.patch8 +# TECO 31.20.1 Unconditional no case flagging. Not recommended +# TECO-31.20.01O.patch8 +# TECO 31.20.2 Turns off verbose errors. Not recommended. +# TECO-31.20.02O.patch8 +# TECO 31.20.3 Turns off Yank overwrite warning. Not recommended. +# TECO-31.20.03O.patch8 +# TECO 31.20.4 Special support for VT05. Not recommended. +# TECO-31.20.04O.patch8 +TECO-31.20.05M-v5A.patch8 +TECO-31.20.06M-v5B.patch8 +TECO-31.20.07M.v5C.patch8 +TECO-31.20.08M-v5.04.patch8 +TECO-31.20.10M-5.05.patch8 +TECO-31.20.11M-v5.06.patch8 +TECO-31.20.12M-v5.07.patch8 +TECO-31.20.13M-v5.08.patch8 +# +# MACREL, LINK, and OVDRV patches have not been validated. +# The Version numbers dont all match. Some wont apply. +# More work is needed before they are deemed safe. +# NOT Recommended. +## MACREL-40.5.1M-v1D.patch8 +## MACREL-40.5.2M-v1E.patch8 +## LINK-40.2.1M-v1D.patch8 +## LINK-40.2.2M-v1E.patch8 +## LINK-40.2.3M-v1F.patch8 +# OVRDRV 40.6.1 is a source level patch +## OVRDRV-40.6.1M-v1B-8srccom Index: media/os8/subsys/advent.tu56 ================================================================== --- media/os8/subsys/advent.tu56 +++ media/os8/subsys/advent.tu56 cannot compute difference between binary files ADDED media/os8/subsys/al-4545d-sa-fr4-v3d-1.tu56 Index: media/os8/subsys/al-4545d-sa-fr4-v3d-1.tu56 ================================================================== --- /dev/null +++ media/os8/subsys/al-4545d-sa-fr4-v3d-1.tu56 cannot compute difference between binary files ADDED media/os8/subsys/al-4546d-sa-fr4-v3d-2.tu56 Index: media/os8/subsys/al-4546d-sa-fr4-v3d-2.tu56 ================================================================== --- /dev/null +++ media/os8/subsys/al-4546d-sa-fr4-v3d-2.tu56 cannot compute difference between binary files ADDED media/os8/subsys/al-4547d-sa-fr4-v3d-3.tu56 Index: media/os8/subsys/al-4547d-sa-fr4-v3d-3.tu56 ================================================================== --- /dev/null +++ media/os8/subsys/al-4547d-sa-fr4-v3d-3.tu56 cannot compute difference between binary files ADDED media/os8/subsys/al-4549d-ba-fr4-v3d-1.1978.tu56 Index: media/os8/subsys/al-4549d-ba-fr4-v3d-1.1978.tu56 ================================================================== --- /dev/null +++ media/os8/subsys/al-4549d-ba-fr4-v3d-1.1978.tu56 cannot compute difference between binary files ADDED media/os8/subsys/al-5596d-ba-fr4-v3d-2.1978.tu56 Index: media/os8/subsys/al-5596d-ba-fr4-v3d-2.1978.tu56 ================================================================== --- /dev/null +++ media/os8/subsys/al-5596d-ba-fr4-v3d-2.1978.tu56 cannot compute difference between binary files Index: media/os8/subsys/cc8.tu56 ================================================================== --- media/os8/subsys/cc8.tu56 +++ media/os8/subsys/cc8.tu56 cannot compute difference between binary files ADDED media/os8/subsys/focal69.tu56 Index: media/os8/subsys/focal69.tu56 ================================================================== --- /dev/null +++ media/os8/subsys/focal69.tu56 cannot compute difference between binary files ADDED media/os8/subsys/uwfocal-v4e-1.tu56 Index: media/os8/subsys/uwfocal-v4e-1.tu56 ================================================================== --- /dev/null +++ media/os8/subsys/uwfocal-v4e-1.tu56 cannot compute difference between binary files ADDED media/os8/subsys/uwfocal-v4e-2.tu56 Index: media/os8/subsys/uwfocal-v4e-2.tu56 ================================================================== --- /dev/null +++ media/os8/subsys/uwfocal-v4e-2.tu56 cannot compute difference between binary files DELETED palbart/LICENSE.md Index: palbart/LICENSE.md ================================================================== --- palbart/LICENSE.md +++ /dev/null @@ -1,16 +0,0 @@ -# palbart License - -The following was extracted from the top of [`palbart.c`][1] in this -directory: - ---------- - -This is free software. There is no fee for using it. You may make any -changes that you wish and also give it away. If you can make commercial -product out of it, fine, but do not put any limits on the purchaser's -right to do the same. If you improve it or fix any bugs, it would be -nice if you told me and offered me a copy of the new version. - ---------- - -[1]: https://tangentsoft.com/pidp8i/doc/trunk/palbart/palbart.c DELETED palbart/palbart.1 Index: palbart/palbart.1 ================================================================== --- palbart/palbart.1 +++ /dev/null @@ -1,200 +0,0 @@ -.\" Hey, EMACS: -*- nroff -*- -.\" First parameter, NAME, should be all caps -.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection -.\" other parameters are allowed: see man(7), man(1) -.TH PALBART 1 "January 16, 2000" -.\" Please adjust this date whenever revising the manpage. -.\" -.\" Some roff macros, for reference: -.\" .nh disable hyphenation -.\" .hy enable hyphenation -.\" .ad l left justify -.\" .ad b justify to both left and right margins -.\" .nf disable filling -.\" .fi enable filling -.\" .br insert line break -.\" .sp insert n+1 empty lines -.\" for manpage-specific macros, see man(7) -.SH NAME -palbart \- BART enhanced PDP8 crossassembler -.SH SYNOPSIS -.B palbart -.RI [options] inputfile -.br -.SH DESCRIPTION -This manual page documents briefly the -.B palbart -command. -It is a cross-assembler to for PDP/8 assembly language programs. -It will produce an output file in bin format, rim format, and using the -appropriate pseudo-ops, a combination of rim and bin formats. -A listing file is always produced and with an optional symbol table -and/or a symbol cross-reference (concordance). The permanent symbol -table can be output in a form that may be read back in so a customized -permanent symbol table can be produced. Any detected errors are output -to a separate file giving the filename in which they were detected -along with the line number, column number and error message as well as -marking the error in the listing file. -.PP -The following file name extensions are used: -.PP - .pal source code (input) -.PP - .lst assembly listing (output) -.PP - .bin assembly output in DEC's bin format (output) -.PP - .rim assembly output in DEC's rim format (output) -.PP - .err assembly errors detected (if any) (output) -.PP - .prm permanent symbol table in form suitable for reading after the EXPUNGE pseudo-op. - -.PP -.SH OPTIONS -A summary of options is included below. -.TP -.B \-d -Show symbol table at end of assembly -.TP -.B \-h -Display help. -.TP -.B \-l -Allow generation of literals (default is no literal generation) -Show version of program. -.TP -.B \-p -Generate a file with the permanent symbols in it. -(To get the current symbol table, assemble a file than has only -a $ in it.) -.TP -.B \-r -Produce output in rim format (default is bin format) -.TP -.B \-v -Display version information. -.TP -.B \-x -Generate a cross-reference (concordance) of user symbols. - -.SH DIAGNOSTICS -Assembler error diagnostics are output to an error file and inserted -in the listing file. Each line in the error file has the form -.PP -(:) : error: at Loc = -.PP -An example error message is: -.br -bintst.pal(17:9) : error: undefined symbol "UNDEF" at Loc = 07616 -.PP -The error diagnostics put in the listing start with a two character -error code (if appropriate) and a short message. A carat '^' is -placed under the item in error if appropriate. -An example error message is: -.PP - 17 07616 3000 DCA UNDEF -.br - UD undefined ^ -.br - 18 07617 1777 TAD I DUMMY -.PP -When an indirect is generated, an at character '@' is placed after the -the instruction value in the listing as an indicator as follows: -.PP - 14 03716 1777@ TAD OFFPAG -.PP -Undefined symbols are marked in the symbol table listing by prepending -a '?' to the symbol. Redefined symbols are marked in the symbol table -listing by prepending a '#' to the symbol. Examples are: -.PP - #REDEF 04567 -.br - SWITCH 07612 -.br - ?UNDEF 00000 -.PP -Refer to the code for the diagnostic messages generated. - -.SH BUGS -Only a minimal effort has been made to keep the listing format -anything like the PAL-8 listing format. -The operation of the conditional assembly pseudo-ops may not function -exactly as the DEC versions. I did not have any examples of these so -the implementation is my interpretation of how they should work. -.PP -The RIMPUNch and BINPUNch pseudo-ops do not change the binary output -file type that was specified on startup. This was intentional and -and allows rim formatted data to be output prior to the actual binary -formatted data. On UN*X style systems, the same effect can be achieved -ing the "cat" command, but on DOS/Windows systems, doing this was -a major chore. -.PP -The floating point input does not generate values exactly as the DEC -compiler does. I worked out several examples by hand and believe that -this implementation is slightly more accurate. If I am mistaken, -let me know and, if possible, a better method of generating the values. -.br - -.SH HISTORICAL NOTE -This assembler was written to support the fleet of PDP-8 systems -used by the Bay Area Rapid Transit System. As of early 1997, -this includes about 40 PDP-8/E systems driving the train destination -signs in passenger stations. - -.SH REFERENCES -This assembler is based on the pal assember by: -.br -Douglas Jones and -.br -Rich Coon - -.SH DISCLAIMER -See the symbol table for the set of pseudo-ops supported. -.PP -See the code for pseudo-ops that are not standard for PDP/8 assembly. -.PP -Refer to DEC's "Programming Languages (for the PDP/8)" for complete -documentation of pseudo-ops. -.PP -Refer to DEC's "Introduction to Programming (for the PDP/8)" or a -lower level introduction to the assembly language. - -.SH WARRANTY -If you don't like it the way it works or if it doesn't work, that's -tough. You're welcome to fix it yourself. That's what you get for -using free software. - -.SH COPYRIGHT NOTICE -This is free software. There is no fee for using it. You may make -any changes that you wish and also give it away. If you can make -a commercial product out of it, fine, but do not put any limits on -the purchaser's right to do the same. If you improve it or fix any -bugs, it would be nice if you told me and offered me a copy of the -new version. -Gary Messenbrink - -.SH VERSIONS - Version Date by Comments -.br - v1.0 12Apr96 GAM Original -.br - v1.1 18Nov96 GAM Permanent symbol table initialization error. -.br - v1.2 20Nov96 GAM Added BINPUNch and RIMPUNch pseudo-operators. -.br - v1.3 24Nov96 GAM Added DUBL pseudo-op (24 bit integer constants). -.br - v1.4 29Nov96 GAM Fixed bug in checksum generation. -.br - v2.1 08Dec96 GAM Added concordance processing (cross reference). -.br - v2.2 10Dec96 GAM Added FLTG psuedo-op (floating point constants). -.br - v2.3 2Feb97 GAM Fixed paging problem in cross reference output. -.br - v2.4 11Apr97 GAM Fixed problem with some labels being put in cross reference multiple times. - -.SH AUTHOR -This manual page was written by Vince Mulhollon , -for the Debian GNU/Linux system (but may be used by others). DELETED palbart/palbart.c Index: palbart/palbart.c ================================================================== --- palbart/palbart.c +++ /dev/null @@ -1,4352 +0,0 @@ -/******************************************************************************/ -/* */ -/* Program: PAL (BART version) */ -/* File: pal.c */ -/* Author: Gary A. Messenbrink */ -/* gam@rahul.net */ -/* */ -/* Purpose: A 2 pass PDP-8 pal-like assembler. */ -/* */ -/* PAL(1) */ -/* */ -/* NAME */ -/* pal - a PDP/8 pal-like assembler. */ -/* */ -/* SYNOPSIS: */ -/* pal [ -$ -d -h -e -l -p -r -t -v -x ] inputfile */ -/* */ -/* DESCRIPTION */ -/* This is a cross-assembler to for PDP/8 assembly language programs. */ -/* It will produce an output file in bin format, rim format, and using the */ -/* appropriate pseudo-ops, a combination of rim and bin formats. */ -/* A listing file is always produced and with an optional symbol table */ -/* and/or a symbol cross-reference (concordance). The permanent symbol */ -/* table can be output in a form that may be read back in so a customized */ -/* permanent symbol table can be produced. Any detected errors are output */ -/* to a separate file giving the filename in which they were detected */ -/* along with the line number, column number and error message as well as */ -/* marking the error in the listing file. */ -/* The following file name extensions are used: */ -/* .pal source code (input) */ -/* .lst assembly listing (output) */ -/* .bin assembly output in DEC's bin format (output) */ -/* .rim assembly output in DEC's rim format (output) */ -/* .err assembly errors detected (if any) (output) */ -/* .prm permanent symbol table in form suitable for reading after */ -/* the EXPUNGE pseudo-op. */ -/* */ -/* OPTIONS */ -/* -$ Allow files to not end with $ */ -/* -d Dump the symbol table at end of assembly */ -/* -h Show help */ -/* -e Don't allow generation of links */ -/* -l Allow generation of links (default is link generation) */ -/* -n No redefinition of permanent symbols with labels */ -/* -p Generate a file with the permanent symbols in it. */ -/* (To get the current symbol table, assemble a file than has only */ -/* a $ in it.) */ -/* -r Produce output in rim format (default is bin format) */ -/* -tN Set tab stops to N spaces (default is 8) */ -/* -v Display program version. */ -/* -x Generate a cross-reference (concordance) of user symbols. */ -/* */ -/* DIAGNOSTICS */ -/* Assembler error diagnostics are output to an error file and inserted */ -/* in the listing file. Each line in the error file has the form */ -/* */ -/* (:) : error: at Loc = */ -/* */ -/* An example error message is: */ -/* */ -/* bintst.pal(17:9) : error: undefined symbol "UNDEF" at Loc = 07616 */ -/* */ -/* The error diagnostics put in the listing start with a two character */ -/* error code (if appropriate) and a short message. A carat '^' is */ -/* placed under the item in error if appropriate. */ -/* An example error message is: */ -/* */ -/* 17 07616 3000 DCA UNDEF */ -/* UD undefined ^ */ -/* 18 07617 1777 TAD I DUMMY */ -/* */ -/* When an indirect is generated, an at character '@' is placed after the */ -/* the instruction value in the listing as an indicator as follows: */ -/* */ -/* 14 03716 1777@ TAD OFFPAG */ -/* */ -/* Undefined symbols are marked in the symbol table listing by prepending */ -/* a '?' to the symbol. Redefined symbols are marked in the symbol table */ -/* listing by prepending a '#' to the symbol. Examples are: */ -/* */ -/* #REDEF 04567 */ -/* SWITCH 07612 */ -/* ?UNDEF 00000 */ -/* */ -/* Refer to the code for the diagnostic messages generated. */ -/* */ -/* BUGS */ -/* This program will accept source that real PAL will not. To ensure */ -/* valid source assemble on real or simulated PDP-8. */ -/* Different PAL versions have different permanent symbols defined. This */ -/* program define more than and PAL version. By default redefining them */ -/* as a label is not an error. It is for normal PAL. The -n flag will */ -/* make redefining an error. */ -/* */ -/* Only a minimal effort has been made to keep the listing format */ -/* anything like the PAL-8 listing format. */ -/* The operation of the conditional assembly pseudo-ops may not function */ -/* exactly as the DEC versions. I did not have any examples of these so */ -/* the implementation is my interpretation of how they should work. */ -/* */ -/* The RIMPUNch and BINPUNch pseudo-ops do not change the binary output */ -/* file type that was specified on startup. This was intentional and */ -/* and allows rim formatted data to be output prior to the actual binary */ -/* formatted data. On UN*X style systems, the same effect can be achieved */ -/* by using the "cat" command, but on DOS/Windows systems, doing this was */ -/* a major chore. */ -/* */ -/* The floating point input does not generate values exactly as the DEC */ -/* compiler does. I worked out several examples by hand and believe that */ -/* this implementation is slightly more accurate. If I am mistaken, */ -/* let me know and, if possible, a better method of generating the values. */ -/* */ -/* CDF .-. */ -/* Generates 2201 when assembled at 5000. This looks like a bug in OS/8 */ -/* PAL */ -/* */ -/* BUILD and INSTALLATION */ -/* The current version has only been built under Linux. */ -/* Earlier versions have been built and successfully executed on: */ -/* a. Linux (80486 CPU)using gcc */ -/* b. RS/6000 (AIX 3.2.5) */ -/* c. Borland C++ version 3.1 (large memory model) */ -/* d. Borland C++ version 4.52 (large memory model) */ -/* with no modifications to the source code. */ -/* */ -/* On UNIX type systems, store the the program as the pal command */ -/* and on PC type systems, store it as pal.exe */ -/* */ -/* HISTORICAL NOTE: */ -/* This assembler was written to support the fleet of PDP-8 systems */ -/* used by the Bay Area Rapid Transit System. As of early 1997, */ -/* this includes about 40 PDP-8/E systems driving the train destination */ -/* signs in passenger stations. */ -/* */ -/* REFERENCES: */ -/* This assembler is based on the pal assembler by: */ -/* Douglas Jones and */ -/* Rich Coon */ -/* */ -/* DISCLAIMER: */ -/* See the symbol table for the set of pseudo-ops supported. */ -/* See the code for pseudo-ops that are not standard for PDP/8 assembly. */ -/* Refer to DEC's "Programming Languages (for the PDP/8)" for complete */ -/* documentation of pseudo-ops. */ -/* Refer to DEC's "Introduction to Programming (for the PDP/8)" or a */ -/* lower level introduction to the assembly language. */ -/* */ -/* WARRANTY: */ -/* If you don't like it the way it works or if it doesn't work, that's */ -/* tough. You're welcome to fix it yourself. That's what you get for */ -/* using free software. */ -/* */ -/* COPYRIGHT NOTICE: */ -/* This is free software. There is no fee for using it. You may make */ -/* any changes that you wish and also give it away. If you can make */ -/* a commercial product out of it, fine, but do not put any limits on */ -/* the purchaser's right to do the same. If you improve it or fix any */ -/* bugs, it would be nice if you told me and offered me a copy of the */ -/* new version. */ -/* */ -/* */ -/* Amendments Record: */ -/* Version Date by Comments */ -/* ------- ------- --- --------------------------------------------------- */ -/* v1.0 12Apr96 GAM Original */ -/* v1.1 18Nov96 GAM Permanent symbol table initialization error. */ -/* v1.2 20Nov96 GAM Added BINPUNch and RIMPUNch pseudo-operators. */ -/* v1.3 24Nov96 GAM Added DUBL pseudo-op (24 bit integer constants). */ -/* v1.4 29Nov96 GAM Fixed bug in checksum generation. */ -/* v2.1 08Dec96 GAM Added concordance processing (cross reference). */ -/* v2.2 10Dec96 GAM Added FLTG psuedo-op (floating point constants). */ -/* v2.3 2Feb97 GAM Fixed paging problem in cross reference output. */ -/* DJG: I started with the 2.5 RK version but found when looking on the net */ -/* later that multiple diverging version existed. I have tried to combine */ -/* the fixed into one version. I took the version info below from the versions*/ -/* I pulled from. */ -/* http://dustyoldcomputers.com/pdp-common/reference/host/index.html */ -/* http://www.dunnington.u-net.com/public/PDP-8/palbart.c */ -/* http://sourcecodebrowser.com/palbart/2.4/palbart-2_84_8c_source.html */ -/* http://packages.qa.debian.org/p/palbart.html */ -/* v2.4 11Apr97 GAM Fixed problem with some labels being put in cross */ -/* reference multiple times. */ -/* Started with RK version, Attempted to merge */ -/* GAM V2.4 and PNT change DJG */ -/* v2.4 29Oct07 RK Added 4 character tabstop; IOTs for TA8/E. */ -/* v2.4 19Jan03 PNT Added ASCII pseudo-op, like TEXT but not packed. */ -/* v2.5 03Nov07 RK Fixed buffer overflow problem in readLine and */ -/* increased symbol table size */ -/* v2.6 14Jul03 PNT Added missing TTY symbols, and "1st TTY" symbols. */ -/* v2.7 14Jun13 DJG David Gesswein djg@pdp8online.com */ -/* Merged other changes found online giving duplicate */ -/* Versions in the history */ -/* Didn't copy over deleting -l literal flag */ -/* All fixes to make it match OS/8 PAL8 better */ -/* Fixed handling of IFDEF type conditionals */ -/* Fixed excessive redefined symbol errors */ -/* PAL8 uses 12 bit symbols and this program label */ -/* symbols are 15 bit. */ -/* Added FILENAME and DEVNAME psuedo ops */ -/* Added OPR and KCF instructions. Fixed RMF */ -/* Allowed space after = */ -/* Prevented I and D from being deleted by EXPUNGE */ -/* Allowed permanent symbols to be redefined with error*/ -/* PAL8 updates without message. Error is just warning*/ -/* Fixed certain cases of memory reference generation */ -/* Allowed unary + */ -/* Fixed " character literal at end of line */ -/* Fixed errors in reloc handling */ -/* Fixed [CDF CIF type expressions */ -/* Made title default to first line */ -/* Fixed checksum when nopunch used */ -/* Fixed FIXTAB */ -/* Probably added more subtle bugs */ -/* v2.8 15Jun13 DJG Merged versions found on net. See above */ -/* Added * to RELOC addresses in listing */ -/* Changed default to literal/links on. Added -e to */ -/* turn off */ -/* Fixed PAGE when RELOC used */ -/* Changed SPF to TFL and SPI to TSK */ -/* Make error when changing permanent symbol to label */ -/* if -e flag is used */ -/* Allow space oring in IFZERO etc */ -/* Fixed handling of page zero overflow */ -/* v2.9 23Jun13 DJG Fixed properly all pages literal handling */ -/* changing page doesn't cause loss of last literal */ -/* location used. */ -/* Fixed bin generation if no origin set */ -/* v2.9a 01Jul13 DJG Fixed Comment. Binaries not updated */ -/* v2.10 08Feb14 DJG Changed trailer to 8 bytes since pip didn't like */ -/* trailer of one 0x80 */ -/* v2.11 19Apr15 DPI Fixed incorrect link generation with impled 0200 */ -/* starting address. Patch from Doug Ingrams */ -/* v2.12 28Apr15 DJG Fixed incorrect handling of reloc, expressions with */ -/* undefined symbols. Fixed conditional assembly with */ -/* undefined symbols. Added new flag to allow file to */ -/* not end with $ */ -/* v2.13 02May15 DPI Fixed bug in readLine when removing \r from a blank */ -/* line. Changed -s to -$ in -h display. Corrected */ -/* version comment. */ -/* v2.13 03May15 DJG Moved TITLE, BANK to new additional option. */ -/* Change release variable below when you update. Send changes back to */ -/* David Gesswein, djg@pdp8online.com. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include - -char *release = "pal-2.13, 03 May 2015"; - -/* Set to 1 and use -e flag to make ( and [ literals errors */ -#define LITERAL_ERROR 0 - -#define LINELEN 132 -#define LIST_LINES_PER_PAGE 55 /* Includes 5 line page header. */ -#define NAMELEN 128 -#define SYMBOL_COLUMNS 5 -#define SYMLEN 7 -#define SYMBOL_TABLE_SIZE 4096 -#define TITLELEN 63 -#define XREF_COLUMNS 8 - -#define ADDRESS_FIELD 00177 -#define FIELD_FIELD 070000 -#define INDIRECT_BIT 00400 -#define LAST_PAGE_LOC 00177 -#define OP_CODE 07000 -#define PAGE_BIT 00200 - -#ifdef PAGE_SIZE -#undef PAGE_SIZE -#endif -#define PAGE_SIZE 00200 - -#define PAGE_FIELD 07600 -#define PAGE_ZERO_END 00200 - -/* Macro to get the number of elements in an array. */ -#define DIM(a) (sizeof(a)/sizeof(a[0])) - -/* Macro to get the address plus one of the end of an array. */ -#define BEYOND(a) ((a) + DIM(A)) - -#define is_blank(c) ((c==' ') || (c=='\t') || (c=='\f') || (c=='>')) -#define isend(c) ((c=='\0')|| (c=='\n')) -#define isdone(c) ((c=='/') || (isend(c)) || (c==';')) - -/* Macros for testing symbol attributes. Each macro evaluates to non-zero */ -/* (true) if the stated condition is met. */ -/* Use these to test attributes. The proper bits are extracted and then */ -/* tested. */ -#define M_CONDITIONAL(s) ((s & CONDITION) == CONDITION) -#define M_DEFINED(s) ((s & DEFINED) == DEFINED) -#define M_DUPLICATE(s) ((s & DUPLICATE) == DUPLICATE) -#define M_FIXED(s) ((s & FIXED) == FIXED) -#define M_LABEL(s) ((s & LABEL) == LABEL) -#define M_MRI(s) ((s & MRI) == MRI) -#define M_MRIFIX(s) ((s & MRIFIX) == MRIFIX) -#define M_PSEUDO(s) ((s & PSEUDO) == PSEUDO) -#define M_REDEFINED(s) ((s & REDEFINED) == REDEFINED) -#define M_UNDEFINED(s) (!M_DEFINED(s)) -#define M_PERM_REDEFINED(s) ((s & PERM_REDEFINED) == PERM_REDEFINED) - -/* This macro is used to test symbols by the conditional assembly pseudo-ops. */ -#define M_DEF(s) (M_DEFINED(s)) -#define M_COND(s) (M_CONDITIONAL(s)) -#define M_DEFINED_CONDITIONALLY(t) (M_DEF(t) && ((pass==1) ||!M_COND(t))) - -typedef unsigned char BOOL; -typedef unsigned char BYTE; -typedef short int WORD16; -typedef long int WORD32; - -#ifndef FALSE - #define FALSE 0 - #define TRUE (!FALSE) -#endif - -/* Line listing styles. Used to control listing of lines. */ -enum linestyle_t -{ - LINE, LINE_VAL, LINE_LOC_VAL, LOC_VAL -}; -typedef enum linestyle_t LINESTYLE_T; - -/* Symbol Types. */ -/* Note that the names that have FIX as the suffix contain the FIXED bit */ -/* included in the value. */ -/* */ -/* The CONDITION bit is used when processing the conditional assembly PSEUDO- */ -/* OPs (e.g., IFDEF). During pass 1 of the assembly, the symbol is either */ -/* defined or undefined. The condition bit is set when the symbol is defined */ -/* during pass 1 and reset on pass 2 at the location the symbol was defined */ -/* during pass 1. When processing conditionals during pass 2, if the symbol */ -/* is defined and the condition bit is set, the symbol is treated as if it */ -/* were undefined. This gives consistent behavior of the conditional */ -/* pseudo-ops during both pass 1 and pass 2. */ -enum symtyp -{ - UNDEFINED = 0000, - DEFINED = 0001, - FIXED = 0002, - MRI = 0004 | DEFINED, - LABEL = 0010 | DEFINED, - REDEFINED = 0020 | DEFINED, - DUPLICATE = 0040 | DEFINED, - PSEUDO = 0100 | FIXED | DEFINED, - CONDITION = 0200 | DEFINED, - PERM_REDEFINED = 0400, - MRIFIX = MRI | FIXED | DEFINED, - DEFFIX = DEFINED | FIXED -}; -typedef enum symtyp SYMTYP; - -enum pseudo_t -{ - BANK, BINPUNCH, DECIMAL, DUBL, EJECT, ENPUNCH, EXPUNGE, FIELD, - FIXMRI, FIXTAB, FLTG, IFDEF, IFNDEF, IFNZERO, IFZERO, NOPUNCH, - OCTAL, PAGE, PAUSE, RELOC, RIMPUNCH, SEGMNT, TEXT, TITLE, - XLIST, ZBLOCK, FILENAME, DEVICE, ASCII -}; -typedef enum pseudo_t PSEUDO_T; - -struct sym_t -{ - SYMTYP type; - char name[SYMLEN]; - WORD16 val; - int xref_index; - int xref_count; -}; -typedef struct sym_t SYM_T; - -struct lpool_t -{ - WORD16 loc; - WORD16 last_punched; - WORD16 pool[PAGE_SIZE]; -}; -typedef struct lpool_t LPOOL_T; - -struct emsg_t -{ - char *list; - char *file; -}; -typedef struct emsg_t EMSG_T; - -struct errsave_t -{ - char *mesg; - int col; -}; -typedef struct errsave_t ERRSAVE_T; - -struct fltg_ -{ - WORD16 exponent; - WORD32 mantissa; -}; -typedef struct fltg_ FLTG_T; - -/*----------------------------------------------------------------------------*/ - -/* Function Prototypes */ - -int binarySearch( char *name, int start, int symbol_count ); -int compareSymbols( const void *a, const void *b ); -void conditionFalse( void ); -void conditionTrue( void ); -SYM_T *defineLexeme( int start, int term, WORD16 val, SYMTYP type ); -SYM_T *defineSymbol( char *name, WORD16 val, SYMTYP type, WORD16 start); -void endOfBinary( void ); -void errorLexeme( EMSG_T *mesg, int col ); -void errorMessage( EMSG_T *mesg, int col ); -void errorSymbol( EMSG_T *mesg, char *name, int col ); -SYM_T *eval( void ); -WORD32 evalDubl( WORD32 initial_value ); -FLTG_T *evalFltg( void ); -SYM_T *evalSymbol( void ); -void getArgs( int argc, char *argv[] ); -WORD32 getDublExpr( void ); -WORD32 getDublExprs( void ); -FLTG_T *getFltgExpr( void ); -FLTG_T *getFltgExprs( void ); -SYM_T *getExpr( void ); -WORD16 getExprs( void ); -WORD16 incrementClc( void ); -void inputDubl( void ); -void inputFltg( void ); -WORD16 insertLiteral( LPOOL_T *pool, WORD16 value, int fieldpage_index ); -char *lexemeToName( char *name, int from, int term ); -void listLine( void ); -SYM_T *lookup( char *name ); -void moveToEndOfLine( void ); -void nextLexBlank( void ); -void nextLexeme( void ); -void normalizeFltg( FLTG_T *fltg ); -void onePass( void ); -void printCrossReference( void ); -void printErrorMessages( void ); -void printLine(char *line, WORD16 loc, WORD16 val, LINESTYLE_T linestyle); -void printPageBreak( void ); -void printPermanentSymbolTable( void ); -void printSymbolTable( void ); -BOOL pseudoOperators( PSEUDO_T val ); -void punchChecksum( void ); -void punchLocObject( WORD16 loc, WORD16 val ); -void punchLiteralPool( LPOOL_T *p, BOOL punch_page0 ); -void punchOutObject( WORD16 loc, WORD16 val ); -void punchLeader( int count ); -void punchObject( WORD16 val ); -void punchOrigin( WORD16 loc ); -void readLine( void ); -void saveError( char *mesg, int cc ); -BOOL testForLiteralCollision( WORD16 loc ); -void topOfForm( char *title, char *sub_title ); - -/*----------------------------------------------------------------------------*/ - -/* Table of pseudo-ops (directives) which are used to setup the symbol */ -/* table on startup and when the EXPUNGE pseudo-op is executed. */ -SYM_T pseudo[] = -{ - { PSEUDO, "ASCII", ASCII }, /* Put 8-bit ASCII into memory (see TEXT) */ - { PSEUDO, "BINPUN", BINPUNCH }, /* Output in Binary Loader format. */ - { PSEUDO, "DECIMA", DECIMAL }, /* Read literal constants in base 10. */ - { PSEUDO, "DEVICE", DEVICE }, /* Pack 6 bit device name into memory */ - { PSEUDO, "DUBL", DUBL }, /* Ignored (unsupported). */ - { PSEUDO, "EJECT", EJECT }, /* Eject a page in the listing. */ - { PSEUDO, "ENPUNC", ENPUNCH }, /* Turn on object code generation. */ - { PSEUDO, "EXPUNG", EXPUNGE }, /* Remove all symbols from symbol table. */ - { PSEUDO, "FIELD", FIELD }, /* Set origin to memory field. */ - { PSEUDO, "FILENA", FILENAME }, /* Pack 6 bit filename into memory. */ - { PSEUDO, "FIXMRI", FIXMRI }, /* Like =, but creates mem ref instruction*/ - { PSEUDO, "FIXTAB", FIXTAB }, /* Mark current symbols as permanent. */ - { PSEUDO, "FLTG", FLTG }, /* Ignored (unsupported). */ - { PSEUDO, "IFDEF", IFDEF }, /* Assemble if symbol is defined. */ - { PSEUDO, "IFNDEF", IFNDEF }, /* Assemble if symbol is not defined. */ - { PSEUDO, "IFNZER", IFNZERO }, /* Assemble if symbol value is not 0. */ - { PSEUDO, "IFNZRO", IFNZERO }, /* Assemble if symbol value is not 0. */ - { PSEUDO, "IFZERO", IFZERO }, /* Assemble if symbol value is 0. */ - { PSEUDO, "NOPUNC", NOPUNCH }, /* Turn off object code generation. */ - { PSEUDO, "OCTAL", OCTAL }, /* Read literal constants in base 8. */ - { PSEUDO, "PAGE", PAGE }, /* Set orign to page +1 or page n (0..37).*/ - { PSEUDO, "PAUSE", PAUSE }, /* Ignored */ - { PSEUDO, "RELOC", RELOC }, /* Assemble to run at a different address.*/ - { PSEUDO, "RIMPUN", RIMPUNCH }, /* Output in Read In Mode format. */ - { PSEUDO, "SEGMNT", SEGMNT }, /* Like page, but with page size=1K words.*/ - { PSEUDO, "TEXT", TEXT }, /* Pack 6 bit trimmed ASCII into memory. */ - { PSEUDO, "XLIST", XLIST }, /* Toggle listing generation. */ - { PSEUDO, "ZBLOCK", ZBLOCK }, /* Zero a block of memory. */ - { PSEUDO, "TITLE", TITLE }, /* Use the text string as a listing title.*/ - { PSEUDO, "BANK", BANK } /* Like field, select some 32K out of 128K*/ -}; -/* Number o extended pseudo operators to ignore unless command option specified - * to enable */ -#define NUMBER_ADDITIONAL_PSEUDO 2 - -/* Symbol Table */ -/* The table is put in lexical order on startup, so symbols can be */ -/* inserted as desired into the initial table. */ -/* really_permanent_symbols aren't removed by EXPUNGE */ -SYM_T really_permanent_symbols[] = -{ - { MRIFIX, "I", 00400 }, /* INDIRECT ADDRESSING */ - { MRIFIX, "Z", 00000 } /* PAGE ZERO ADDRESS */ -}; - -SYM_T permanent_symbols[] = -{ - /* Memory Reference Instructions */ - { MRIFIX, "AND", 00000 }, /* LOGICAL AND */ - { MRIFIX, "TAD", 01000 }, /* TWO'S COMPLEMENT ADD */ - { MRIFIX, "ISZ", 02000 }, /* INCREMENT AND SKIP IF ZERO */ - { MRIFIX, "DCA", 03000 }, /* DEPOSIT AND CLEAR ACC */ - { MRIFIX, "JMP", 05000 }, /* JUMP */ - { MRIFIX, "JMS", 04000 }, /* JUMP TO SUBROUTINE */ - /* Floating Point Interpreter Instructions */ - { MRIFIX, "FEXT", 00000 }, /* FLOATING EXIT */ - { MRIFIX, "FADD", 01000 }, /* FLOATING ADD */ - { MRIFIX, "FSUB", 02000 }, /* FLOATING SUBTRACT */ - { MRIFIX, "FMPY", 03000 }, /* FLOATING MULTIPLY */ - { MRIFIX, "FDIV", 04000 }, /* FLOATING DIVIDE */ - { MRIFIX, "FGET", 05000 }, /* FLOATING GET */ - { MRIFIX, "FPUT", 06000 }, /* FLOATING PUT */ - { FIXED, "FNOR", 07000 }, /* FLOATING NORMALIZE */ - { FIXED, "FEXT", 00000 }, /* EXIT FROM FLOATING POINT INTERPRETER */ - { FIXED, "SQUARE", 00001 }, /* SQUARE C(FAC) */ - { FIXED, "SQROOT", 00002 }, /* TAKE SQUARE ROOT OF C(FAC) */ - /* Group 1 Operate Microinstrcutions */ - { FIXED, "OPR", 07000 }, /* NO OPERATION */ - { FIXED, "NOP", 07000 }, /* NO OPERATION */ - { FIXED, "IAC", 07001 }, /* INCREMENT AC */ - { FIXED, "RAL", 07004 }, /* ROTATE AC AND LINK LEFT ONE */ - { FIXED, "RTL", 07006 }, /* ROTATE AC AND LINK LEFT TWO */ - { FIXED, "RAR", 07010 }, /* ROTATE AC AND LINK RIGHT ONE */ - { FIXED, "RTR", 07012 }, /* ROTATE AC AND LINK RIGHT TWO */ - { FIXED, "CML", 07020 }, /* COMPLEMENT LINK */ - { FIXED, "CMA", 07040 }, /* COMPLEMEMNT AC */ - { FIXED, "CLL", 07100 }, /* CLEAR LINK */ - { FIXED, "CLA", 07200 }, /* CLEAR AC */ - /* Group 2 Operate Microinstructions */ - { FIXED, "BSW", 07002 }, /* Swap bytes in AC (PDP/8e) */ - { FIXED, "HLT", 07402 }, /* HALT THE COMPUTER */ - { FIXED, "OSR", 07404 }, /* INCLUSIVE OR SR WITH AC */ - { FIXED, "SKP", 07410 }, /* SKIP UNCONDITIONALLY */ - { FIXED, "SNL", 07420 }, /* SKIP ON NON-ZERO LINK */ - { FIXED, "SZL", 07430 }, /* SKIP ON ZERO LINK */ - { FIXED, "SZA", 07440 }, /* SKIP ON ZERO AC */ - { FIXED, "SNA", 07450 }, /* SKIP ON NON=ZERO AC */ - { FIXED, "SMA", 07500 }, /* SKIP MINUS AC */ - { FIXED, "SPA", 07510 }, /* SKIP ON POSITIVE AC (ZERO IS POSITIVE) */ - /* Combined Operate Microinstructions */ - { FIXED, "CIA", 07041 }, /* COMPLEMENT AND INCREMENT AC */ - { FIXED, "STL", 07120 }, /* SET LINK TO 1 */ - { FIXED, "GLK", 07204 }, /* GET LINK (PUT LINK IN AC BIT 11) */ - { FIXED, "STA", 07240 }, /* SET AC TO -1 */ - { FIXED, "LAS", 07604 }, /* LOAD ACC WITH SR */ - /* MQ Instructions (PDP/8e) */ - { FIXED, "MQL", 07421 }, /* Load MQ from AC, then clear AC. */ - { FIXED, "MQA", 07501 }, /* Inclusive OR MQ with AC */ - { FIXED, "SWP", 07521 }, /* Swap AC and MQ */ - { FIXED, "ACL", 07701 }, /* Load MQ into AC */ - /* Program Interrupt */ - { FIXED, "IOT", 06000 }, - { FIXED, "ION", 06001 }, /* TURN INTERRUPT PROCESSOR ON */ - { FIXED, "IOF", 06002 }, /* TURN INTERRUPT PROCESSOR OFF */ - /* Program Interrupt, PDP-8/e */ - { FIXED, "SKON", 06000 }, /* Skip if interrupt on and turn int off. */ - { FIXED, "SRQ", 06003 }, /* Skip on interrupt request. */ - { FIXED, "GTF", 06004 }, /* Get interrupt flags. */ - { FIXED, "RTF", 06005 }, /* Restore interrupt flags. */ - { FIXED, "SGT", 06006 }, /* Skip on greater than flag. */ - { FIXED, "CAF", 06007 }, /* Clear all flags. */ - /* Keyboard/Reader */ - { FIXED, "KCF", 06030 }, /* CLEAR KEYBOAR FLAG */ - { FIXED, "KSF", 06031 }, /* SKIP ON KEYBOARD FLAG */ - { FIXED, "KCC", 06032 }, /* CLEAR KEYBOARD FLAG & READ CHAR */ - { FIXED, "KRS", 06034 }, /* READ KEYBOARD BUFFER (STATIC) */ - { FIXED, "KIE", 06035 }, /* AC11 TO KEYBD/RDR INT ENABLE F/F */ - { FIXED, "KRB", 06036 }, /* READ KEYBOARD BUFFER & CLEAR FLAG */ - /* Teleprinter/Punch */ - { FIXED, "TFL", 06040 }, /* SET TELEPRINTER/PUNCH FLAG */ - { FIXED, "TSF", 06041 }, /* SKIP ON TELEPRINTER FLAG */ - { FIXED, "TCF", 06042 }, /* CLEAR TELEPRINTER FLAG */ - { FIXED, "TPC", 06044 }, /* LOAD TELEPRINTER & PRINT */ - { FIXED, "TSK", 06045 }, /* SKIP IF TELETYPE INTERRUPT */ - { FIXED, "TLS", 06046 }, /* LOAD TELPRINTER & CLEAR FLAG */ - /* High Speed Paper Tape Reader */ - { FIXED, "RSF", 06011 }, /* SKIP ON READER FLAG */ - { FIXED, "RRB", 06012 }, /* READ READER BUFFER AND CLEAR FLAG */ - { FIXED, "RFC", 06014 }, /* READER FETCH CHARACTER */ - /* PC8-E High Speed Paper Tape Reader & Punch */ - { FIXED, "RPE", 06010 }, /* Set interrupt enable for reader/punch */ - { FIXED, "PCE", 06020 }, /* Clear interrupt enable for rdr/punch */ - { FIXED, "RCC", 06016 }, /* Read reader buffer, clear flags & buf, */ - /* and fetch character. */ - /* High Speed Paper Tape Punch */ - { FIXED, "PSF", 06021 }, /* SKIP ON PUNCH FLAG */ - { FIXED, "PCF", 06022 }, /* CLEAR ON PUNCH FLAG */ - { FIXED, "PPC", 06024 }, /* LOAD PUNCH BUFFER AND PUNCH CHARACTER* */ - { FIXED, "PLS", 06026 }, /* LOAD PUNCH BUFFER AND CLEAR FLAG */ - - /* DECassette TU60 (RK 20071008) */ - { FIXED, "KCLR", 06700 }, /* Clear all (clear A and B) */ - { FIXED, "KSDR", 06701 }, /* Skip if data flag set */ - { FIXED, "KSEN", 06702 }, /* Skip if EOT/BOT, not ready, or empty */ - { FIXED, "KSBF", 06703 }, /* Skip if ready flag set */ - { FIXED, "KLSA", 06704 }, /* AC4-11 -> A, clear A, -(AC4-11) -> A */ - { FIXED, "KSAF", 06705 }, /* Skip on any flag or error */ - { FIXED, "KGOA", 06706 }, /* Assert status A and transfer data to AC*/ - { FIXED, "KRSB", 06707 }, /* Transfer B -> AC4-11 */ - - /* DECtape Transport Type TU55 and DECtape Control Type TC01 */ - { FIXED, "DTRA", 06761 }, /* Contents of status register is ORed */ - /* into AC bits 0-9 */ - { FIXED, "DTCA", 06762 }, /* Clear status register A, all flags */ - /* undisturbed */ - { FIXED, "DTXA", 06764 }, /* Status register A loaded by exclusive */ - /* OR from AC. If AC bit 10=0, clear */ - /* error flags; if AC bit 11=0, DECtape */ - /* control flag is cleared. */ - { FIXED, "DTLA", 06766 }, /* Combination of DTCA and DTXA */ - { FIXED, "DTSF", 06771 }, /* Skip if error flag is 1 or if DECtape */ - /* control flag is 1 */ - { FIXED, "DTRB", 06772 }, /* Contents of status register B is */ - /* ORed into AC */ - { FIXED, "DTLB", 06774 }, /* Memory field portion of status */ - /* register B loaded from AC bits 6-8 */ - /* Disk File and Control, Type DF32 */ - { FIXED, "DCMA", 06601 }, /* CLEAR DISK MEMORY REQUEST AND */ - /* INTERRUPT FLAGS */ - { FIXED, "DMAR", 06603 }, /* LOAD DISK FROM AC, CLEAR AC READ */ - /* INTO CORE, CLEAR INTERRUPT FLAG */ - { FIXED, "DMAW", 06605 }, /* LOAD DISK FROM AC, WRITE ONTO DISK */ - /* FROM CORE, CLEAR INTERRUPT FLAG */ - { FIXED, "DCEA", 06611 }, /* CLEAR DISK EXTENDED ADDRESS AND */ - { FIXED, "DSAC", 06612 }, /* SKIP IF ADDRESS CONFIRMED FLAG = 1 */ - /* MEMORY ADDRESS EXTENSION REGISTER */ - { FIXED, "DEAL", 06615 }, /* CLEAR DISK EXTENDED ADDRESS AND */ - /* MEMORY ADDRESS EXTENSION REGISTER */ - /* AND LOAD SAME FROM AC */ - { FIXED, "DEAC", 06616 }, /* CLEAR AC, LOAD AC FROM DISK EXTENDED */ - /* ADDRESS REGISTER, SKIP IF ADDRESS */ - /* CONFIRMED FLAG = 1 */ - { FIXED, "DFSE", 06621 }, /* SKIP IF PARITY ERROR, DATA REQUEST */ - /* LATE, OR WRITE LOCK SWITCH FLAG = 0 */ - /* (NO ERROR) */ - { FIXED, "DFSC", 06622 }, /* SKIP IF COMPLETION FLAG = 1 (DATA */ - /* TRANSFER COMPLETE) */ - { FIXED, "DMAC", 06626 }, /* CLEAR AC, LOAD AC FROM DISK MEMORY */ - /* ADDRESS REGISTER */ - /* Disk File and Control, Type RF08 */ - { FIXED, "DCIM", 06611 }, - { FIXED, "DIML", 06615 }, - { FIXED, "DIMA", 06616 }, - { FIXED, "DISK", 06623 }, - { FIXED, "DCXA", 06641 }, - { FIXED, "DXAL", 06643 }, - { FIXED, "DXAC", 06645 }, - { FIXED, "DMMT", 06646 }, - /* Memory Extension Control, Type 183 */ - { FIXED, "CDF", 06201 }, /* CHANGE DATA FIELD */ - { FIXED, "CIF", 06202 }, /* CHANGE INSTRUCTION FIELD */ - { FIXED, "CDI", 06203 }, /* Change data & instrution field. */ - { FIXED, "RDF", 06214 }, /* READ DATA FIELD */ - { FIXED, "RIF", 06224 }, /* READ INSTRUCTION FIELD */ - { FIXED, "RIB", 06234 }, /* READ INTERRUPT BUFFER */ - { FIXED, "RMF", 06244 }, /* RESTORE MEMORY FIELD */ - /* Memory Parity, Type MP8/I (MP8/L) */ - { FIXED, "SMP", 06101 }, /* SKIP IF MEMORY PARITY FLAG = 0 */ - { FIXED, "CMP", 06104 }, /* CLEAR MEMORY PAIRTY FLAG */ - /* Memory Parity, Type MP8-E (PDP8/e) */ - { FIXED, "DPI", 06100 }, /* Disable parity interrupt. */ - { FIXED, "SNP", 06101 }, /* Skip if no parity error. */ - { FIXED, "EPI", 06103 }, /* Enable parity interrupt. */ - { FIXED, "CNP", 06104 }, /* Clear parity error flag. */ - { FIXED, "CEP", 06106 }, /* Check for even parity. */ - { FIXED, "SPO", 06107 }, /* Skip on parity option. */ - /* Data Communications Systems, Type 680I */ - { FIXED, "TTINCR", 06401 }, /* The content of the line select */ - /* register is incremented by one. */ - { FIXED, "TTI", 06402 }, /* The line status word is read and */ - /* sampled. If the line is active for */ - /* the fourth time, the line bit is */ - /* shifted into the character assembly */ - /* word. If the line bit is active for */ - /* a number of times less than four, */ - /* the count is incremented. If the */ - /* line is not active, the active/inac- */ - /* tive status of the line is recorded */ - { FIXED, "TTO", 06404 }, /* The character in the AC is shifted */ - /* right one position, zeros are shifted */ - /* into vacated positions, and the orig- */ - /* inal content of AC11 is transferred */ - /* out of the computer on the TTY line. */ - { FIXED, "TTCL", 06411 }, /* The line select register is cleared. */ - { FIXED, "TTSL", 06412 }, /* The line select register is loaded by */ - /* an OR transfer from the content of */ - /* of AC5-11, the the AC is cleared. */ - { FIXED, "TTRL", 06414 }, /* The content of the line select regis- */ - /* ter is read into AC5-11 by an OR */ - /* transfer. */ - { FIXED, "TTSKP", 06421 }, /* Skip if clock flag is a 1. */ - { FIXED, "TTXON", 06424 }, /* Clock 1 is enabled to request a prog- */ - /* ram interrupt and clock 1 flag is */ - /* cleared. */ - { FIXED, "TTXOF", 06422 }, /* Clock 1 is disabled from causing a */ - /* program interrupt and clock 1 flag */ - /* is cleared. */ -}; /* End-of-Symbols for Permanent Symbol Table */ - -/* Global variables */ -SYM_T *symtab; /* Symbol Table */ -int symbol_top; /* Number of entries in symbol table. */ - -SYM_T *fixed_symbols; /* Start of the fixed symbol table entries. */ -int number_of_fixed_symbols; - -/*----------------------------------------------------------------------------*/ - -WORD16 *xreftab; /* Start of the concordance table. */ - -ERRSAVE_T error_list[20]; -int save_error_count; - -#define GET_PAGE_INDEX(x) (((x) & 07600) >> 7) -#define MAX_PAGES 32 -LPOOL_T cp[MAX_PAGES]; /* Storage for page constants. */ -int max_page_used[MAX_PAGES]; - -char s_detected[] = "detected"; -char s_error[] = "error"; -char s_errors[] = "errors"; -char s_no[] = "No"; -char s_page[] = "Page"; -char s_symtable[] = "Symbol Table"; -char s_xref[] = "Cross Reference"; -char s_generated[] = "generated"; -char s_link[] = "link"; -char s_links[] = "links"; - -/* Assembler diagnostic messages. */ -/* Some attempt has been made to keep continuity with the PAL-III and */ -/* MACRO-8 diagnostic messages. If a diagnostic indicator, (e.g., IC) */ -/* exists, then the indicator is put in the listing as the first two */ -/* characters of the diagnostic message. The PAL-III indicators where used */ -/* when there was a choice between using MACRO-8 and PAL-III indicators. */ -/* The character pairs and their meanings are: */ -/* DT Duplicate Tag (symbol) */ -/* IC Illegal Character */ -/* ID Illegal Redefinition of a symbol. An attempt was made to give */ -/* a symbol a new value not via =. */ -/* IE Illegal Equals An equal sign was used in the wrong context, */ -/* (e.g., A+B=C, or TAD A+=B) */ -/* II Illegal Indirect An off page reference was made, but a literal */ -/* could not be generated because the indirect bit was already set. */ -/* IR Illegal Reference (address is not on current page or page zero) */ -/* ND No $ (the program terminator) at end of file. */ -/* PE Current, Non-Zero Page Exceeded (literal table flowed into code) */ -/* RD ReDefintion of a symbol */ -/* ST Symbol Table full */ -/* UA Undefined Address (undefined symbol) */ -/* ZE Zero Page Exceeded (see above, or out of space) */ -EMSG_T duplicate_label = { "DT duplicate", "duplicate label" }; -EMSG_T illegal_blank = { "IC illegal blank", "illegal blank" }; -EMSG_T illegal_character = { "IC illegal char", "illegal character" }; -EMSG_T illegal_expression = { "IC in expression", "illegal expression" }; -EMSG_T label_syntax = { "IC label syntax", "label syntax" }; -EMSG_T not_a_number = { "IC numeric syntax", "numeric syntax of" }; -EMSG_T number_not_radix = { "IC radix", "number not in current radix"}; -EMSG_T symbol_syntax = { "IC symbol syntax", "symbol syntax" }; -EMSG_T illegal_equals = { "IE illegal =", "illegal equals" }; -EMSG_T illegal_indirect = { "II off page", "illegal indirect" }; -EMSG_T illegal_reference = { "IR off page", "illegal reference" }; -EMSG_T undefined_symbol = { "UD undefined", "undefined symbol" }; -EMSG_T redefined_symbol = { "RD redefined", "redefined symbol" }; -EMSG_T illegal_redefine = { "ID redefined", "Illegal redefine of symbol" }; -EMSG_T literal_overflow = { "PE page exceeded", - "current page literal capacity exceeded" }; -EMSG_T pz_literal_overflow = { "ZE page exceeded", - "page zero capacity exceeded" }; -EMSG_T dubl_overflow = { "dubl overflow", "DUBL value overflow" }; -EMSG_T fltg_overflow = { "fltg overflow", "FLTG value overflow" }; -EMSG_T zblock_too_small = { "expr too small", "ZBLOCK value too small" }; -EMSG_T zblock_too_large = { "expr too large", "ZBLOCK value too large" }; -EMSG_T end_of_file = { "ND no $ at EOF", "No $ at End-of-File" }; -EMSG_T no_pseudo_op = { "not implemented", - "not implemented pseudo-op" }; -EMSG_T illegal_field_value = { "expr out of range", - "field value not in range of 0 through 7" }; -EMSG_T literal_gen_off = { "literals off", - "literal generation is off" }; -EMSG_T no_literal_value = { "no value", "no literal value" }; -EMSG_T text_string = { "no delimiter", - "text string delimiters not matched" }; -EMSG_T in_rim_mode = { "not OK in rim mode" - "FIELD pseudo-op not valid in RIM mode" }; -EMSG_T lt_expected = { "'<' expected", "'<' expected" }; -EMSG_T symbol_table_full = { "ST Symbol Tbl Full", - "Symbol Table Full" }; -/*----------------------------------------------------------------------------*/ - -FILE *errorfile; -FILE *infile; -FILE *listfile; -FILE *listsave; -FILE *objectfile; -FILE *objectsave; - -char errorpathname[NAMELEN]; -char filename[NAMELEN]; -char listpathname[NAMELEN]; -char objectpathname[NAMELEN]; -char *pathname; -char permpathname[NAMELEN]; - -int tabstops; /* number of characters to expand a tab to */ -int list_lineno; -int list_pageno; -char list_title[LINELEN]; -BOOL list_title_set; /* Set if TITLE pseudo-op used. */ -char line[LINELEN]; /* Input line. */ -int lineno; /* Current line number. */ -int page_lineno; /* print line number on current page. */ -BOOL listed; /* Listed flag. */ -BOOL listedsave; - -int cc; /* Column Counter (char position in line). */ -WORD16 checksum; /* Generated checksum */ -BOOL binary_data_output; /* Set true when data has been output. */ -WORD16 clc; /* Location counter */ -WORD16 cplc; /* Current page literal counter. */ -char delimiter; /* Character immediately after eval'd term. */ -int errors; /* Number of errors found so far. */ -int links; /* Number of links generated so far. */ -BOOL error_in_line; /* TRUE if error on current line. */ -int errors_pass_1; /* Number of errors on pass 1. */ -WORD16 field; /* Current field */ -WORD16 fieldlc; /* location counter without field portion. */ -BOOL fltg_input; /* TRUE when doing floating point input. */ -BOOL indirect_generated; /* TRUE if an off page address generated. */ -int last_xref_lexstart; /* Column where last xref symbol was located. */ -int last_xref_lineno; /* Line where last xref symbol was located. */ -int lexstartprev; /* Where previous lexeme started. */ -int lextermprev; /* Where previous lexeme ended. */ -int lexstart; /* Index of current lexeme on line. */ -int lexterm; /* Index of character after current lexeme. */ -BOOL literals_ok; /* Generate literals, ignore ID redefine err */ -BOOL perm_redef_error; /* Make redefining perm sym with labels error */ -int maxcc; /* Current line length. */ -BOOL overflow; /* Overflow flag for math routines. */ -int pass; /* Number of current pass. */ -BOOL print_permanent_symbols; -WORD16 pzlc; /* Page Zero literal counter. */ -WORD16 radix; /* Default number radix. */ -WORD16 reloc; /* The relocation distance. */ -BOOL rim_mode; /* Generate rim format, defaults to bin */ -BOOL dollar_not_required; /* $ not required at end of file */ -BOOL additional_enabled; /* True if extended functions over PAL8 */ - /* enabled */ -BOOL symtab_print; /* Print symbol table flag */ -BOOL xref; - -FLTG_T fltg_ac; /* Value holder for evalFltg() */ -SYM_T sym_eval = { DEFINED, "", 0 }; /* Value holder for eval() */ -SYM_T sym_getexpr = { DEFINED, "", 0 }; /* Value holder for getexpr() */ -SYM_T sym_undefined = { UNDEFINED, "", 0 };/* Symbol Table Terminator */ - - -/******************************************************************************/ -/* */ -/* Function: main */ -/* */ -/* Synopsis: Starting point. Controls order of assembly. */ -/* */ -/******************************************************************************/ -int main( int argc, char *argv[] ) -{ - int ix; - int space; - - /* Set the default values for global symbols. */ - binary_data_output = FALSE; - fltg_input = FALSE; - literals_ok = TRUE; - perm_redef_error = FALSE; - print_permanent_symbols = FALSE; - rim_mode = FALSE; - dollar_not_required = FALSE; - additional_enabled = FALSE; - symtab_print = FALSE; - xref = FALSE; - pathname = NULL; - - /* Get the options and pathnames */ - getArgs( argc, argv ); - - /* Setup the error file in case symbol table overflows while installing the */ - /* permanent symbols. */ - errorfile = fopen( errorpathname, "w" ); - if (errorfile == NULL) { - fprintf( stderr, "Could not open error file %s: %s\n", errorpathname, strerror(errno)); - exit( -1 ); - - } - errors = 0; - save_error_count = 0; - pass = 0; /* This is required for symbol table initialization. */ - symtab = (SYM_T *) malloc( sizeof( SYM_T ) * SYMBOL_TABLE_SIZE ); - - if( symtab == NULL ) - { - fprintf( stderr, "Could not allocate memory for symbol table.\n"); - exit( -1 ); - } - - /* Place end marker in symbol table. */ - symtab[0] = sym_undefined; - symbol_top = 0; - number_of_fixed_symbols = symbol_top; - fixed_symbols = &symtab[symbol_top - 1]; - - /* Enter the pseudo-ops into the symbol table */ - for( ix = 0; ix < DIM( pseudo ) - - (additional_enabled ? 0 : NUMBER_ADDITIONAL_PSEUDO) ; ix++ ) - { - defineSymbol( pseudo[ix].name, pseudo[ix].val, pseudo[ix].type, 0 ); - } - - /* Enter the predefined symbols into the table. */ - /* Also make them part of the permanent symbol table. */ - for( ix = 0; ix < DIM( really_permanent_symbols ); ix++ ) - { - defineSymbol( really_permanent_symbols[ix].name, - really_permanent_symbols[ix].val, - really_permanent_symbols[ix].type | DEFFIX , 0 ); - } - - /* Enter the predefined symbols into the table. */ - /* Also make them part of the permanent symbol table. */ - for( ix = 0; ix < DIM( permanent_symbols ); ix++ ) - { - defineSymbol( permanent_symbols[ix].name, - permanent_symbols[ix].val, - permanent_symbols[ix].type | DEFFIX , 0 ); - } - - number_of_fixed_symbols = symbol_top; - fixed_symbols = &symtab[symbol_top - 1]; - - /* Do pass one of the assembly */ - checksum = 0; - pass = 1; - page_lineno = LIST_LINES_PER_PAGE; - onePass(); - errors_pass_1 = errors; - - /* Set up for pass two */ - rewind( infile ); - /*Opened in main errorfile = fopen( errorpathname, "w" );*/ - objectfile = fopen( objectpathname, "wb" ); - if (objectfile == NULL) { - fprintf( stderr, "Could not open object file %s: %s\n", objectpathname, strerror(errno)); - exit( -1 ); - - } - objectsave = objectfile; - - listfile = fopen( listpathname, "w" ); - if (listfile == NULL) { - fprintf( stderr, "Could not open list file %s: %s\n", listpathname, strerror(errno)); - exit( -1 ); - - } - listsave = NULL; - - punchLeader( 0 ); - checksum = 0; - - /* Do pass two of the assembly */ - errors = 0; - save_error_count = 0; - page_lineno = LIST_LINES_PER_PAGE; - - if( xref ) - { - /* Get the amount of space that will be required for the concordance. */ - for( space = 0, ix = 0; ix < symbol_top; ix++ ) - { - symtab[ix].xref_index = space; /* Index into concordance table. */ - space += symtab[ix].xref_count + 1; - symtab[ix].xref_count = 0; /* Clear the count for pass 2. */ - - } - /* Allocate the necessary space. */ - xreftab = (WORD16 *) malloc( sizeof( WORD16 ) * space ); - - /* Clear the cross reference space. */ - for( ix = 0; ix < space; ix++ ) - { - xreftab[ix] = 0; - } - } - pass = 2; - onePass(); - - /* Undo effects of NOPUNCH for any following checksum */ - objectfile = objectsave; - punchChecksum(); - - /* Works great for trailer. */ - punchLeader( 8 ); - - /* undo effects of XLIST for any following output to listing file. */ - if( listfile == NULL ) - { - listfile = listsave; - } - - /* Display value of error counter. */ - if( errors == 0 ) - { - fprintf( listfile, "\n %s %s %s\n", s_no, s_detected, s_errors ); - } - else - { - fprintf( errorfile, "\n %d %s %s\n", errors, s_detected, - ( errors == 1 ? s_error : s_errors )); - fprintf( listfile, "\n %d %s %s\n", errors, s_detected, - ( errors == 1 ? s_error : s_errors )); - fprintf( stderr, " %d %s %s\n", errors, s_detected, - ( errors == 1 ? s_error : s_errors )); - } - /* Display value of link counter. */ - if( links == 0 ) - { - fprintf( listfile, " %s %s %s\n", s_no, s_links, s_generated ); - } - else - { - fprintf( errorfile, " %d %s %s\n", links, - ( links == 1 ? s_link : s_links ), - s_generated); - fprintf( listfile, " %d %s %s\n", links, - ( links == 1 ? s_link : s_links ), - s_generated); - fprintf( stderr, " %d %s %s\n", links, - ( links == 1 ? s_link : s_links ), - s_generated); - } - - if( symtab_print ) - { - printSymbolTable(); - } - - if( print_permanent_symbols ) - { - printPermanentSymbolTable(); - } - - if( xref ) - { - printCrossReference(); - } - - fclose( objectfile ); - fclose( listfile ); - fclose( errorfile ); - if( errors == 0 && errors_pass_1 == 0 ) - { - remove( errorpathname ); - } - - return( errors != 0 ); -} /* main() */ - -/******************************************************************************/ -/* */ -/* Function: getArgs */ -/* */ -/* Synopsis: Parse command line, set flags accordingly and setup input and */ -/* output files. */ -/* */ -/******************************************************************************/ -void getArgs( int argc, char *argv[] ) -{ - int len; - int ix, jx; - - /* Set the defaults */ - errorfile = NULL; - infile = NULL; - listfile = NULL; - listsave = NULL; - objectfile = NULL; - objectsave = NULL; - tabstops = 8; - - for( ix = 1; ix < argc; ix++ ) - { - if( argv[ix][0] == '-' ) - { - for( jx = 1; argv[ix][jx] != 0; jx++ ) - { - switch( argv[ix][jx] ) - { - case '$': - dollar_not_required = TRUE; - break; - - case 'd': - symtab_print = TRUE; - break; - - case 'a': - additional_enabled = TRUE; - break; - - case 'r': - rim_mode = TRUE; - break; - - case 'e': - literals_ok = FALSE; - break; - - case 'l': - literals_ok = TRUE; - break; - - case 'n': - perm_redef_error = TRUE; - break; - - case 'p': - print_permanent_symbols = TRUE; - break; - - /* Added -tN; RK 20071029 */ - /* Damn, this is ugly, we should use getopt() */ - case 't': - if (argv [ix][jx + 1]) { - tabstops = atoi (argv [ix] + (jx + 1)); - /* advance past numbers */ - for (jx++; argv [ix][jx]; jx++) ; - jx--; - } else { - ix++; - if (ix >= argc) { - fprintf( stderr, "%s: missing argument for -t, expected number of tabsopts\n", argv[0] ); - exit( -1 ); - } - for (jx = 0; argv [ix][jx]; jx++) ; - jx--; - tabstops = atoi (argv [ix]); - } - break; - - case 'x': - xref = TRUE; - break; - - case 'v': - fprintf( stderr, "%s\n", release ); - fflush( stderr ); - exit( -1 ); - break; - - default: - fprintf( stderr, "%s: unknown flag: %s\n", argv[0], argv[ix] ); - case 'h': - fprintf( stderr, " -$ -- allow file to not end with $\n" ); - fprintf( stderr, " -a -- enable additional function not in PAL8\n" ); - fprintf( stderr, " -d -- dump symbol table\n" ); - fprintf( stderr, " -e -- error if link generated\n" ); - fprintf( stderr, " -h -- show this help\n" ); - fprintf( stderr, " -l -- generate literal/link (default)\n" ); - fprintf( stderr, " -n -- no redefining with label permanent symbols\n" ); - fprintf( stderr, " -p -- output permanent symbols to file\n" ); - fprintf( stderr, " -r -- output rim format file\n" ); - fprintf( stderr, " -t N -- set tab stops to N\n" ); - fprintf( stderr, " -v -- display version\n" ); - fprintf( stderr, " -x -- output cross reference to file\n" ); - fflush( stderr ); - exit( -1 ); - } /* end switch */ - } /* end for */ - } - else - { - if( pathname != NULL ) - { - fprintf( stderr, "%s: too many input files\n", argv[0] ); - exit( -1 ); - } - pathname = &argv[ix][0]; - } - } /* end for */ - - if( pathname == NULL ) - { - fprintf( stderr, "%s: no input file specified\n", argv[0] ); - exit( -1 ); - } - - len = strlen( pathname ); - if( len > NAMELEN - 5 ) - { - fprintf( stderr, "%s: pathname \"%s\" too long\n", argv[0], pathname ); - exit( -1 ); - } - - /* Now open the input file. */ - if(( infile = fopen( pathname, "r" )) == NULL ) - { - fprintf( stderr, "%s: cannot open \"%s\": %s\n", argv[0], pathname, strerror(errno) ); - exit( -1 ); - } - - /* Now make the pathnames */ - /* Find last '.', if it exists. */ - jx = len - 1; - while( pathname[jx] != '.' && pathname[jx] != '/' - && pathname[jx] != '\\' && jx >= 0 ) - { - jx--; - } - - switch( pathname[jx] ) - { - case '.': - break; - - case '/': - case '\\': - jx = len; - break; - - default: - break; - } - - /* Add the pathname extensions. */ - strncpy( objectpathname, pathname, jx ); - objectpathname[jx] = '\0'; - strcat( objectpathname, rim_mode ? ".rim" : ".bin" ); - - strncpy( listpathname, pathname, jx ); - listpathname[jx] = '\0'; - strcat( listpathname, ".lst" ); - - strncpy( errorpathname, pathname, jx ); - errorpathname[jx] = '\0'; - strcat( errorpathname, ".err" ); - - strncpy( permpathname, pathname, jx ); - permpathname[jx] = '\0'; - strcat( permpathname, ".prm" ); - - /* Extract the filename from the path. */ - if( isalpha( pathname[0] ) && pathname[1] == ':' && pathname[2] != '\\' ) - { - pathname[1] = '\\'; /* MS-DOS style pathname */ - } - - jx = len - 1; - while( pathname[jx] != '/' && pathname[jx] != '\\' && jx >= 0 ) - { - jx--; - } - strcpy( filename, &pathname[jx + 1] ); - -} /* getArgs() */ - -/******************************************************************************/ -/* */ -/* Function: clearLiteralTable */ -/* */ -/* Synopsis: Clear the cp and max_page_used data storing literal */ -/* information. */ -/* */ -/******************************************************************************/ -void clearLiteralTable() -{ - int i; - - for (i = 0; i < DIM(cp); i++) - { - cp[i].loc = 0200; /* Points to end of page for [] operands. */ - cp[i].last_punched = 0200; /* Points to end of page for [] operands. */ - } - memset(max_page_used, 0, sizeof(max_page_used)); -} - -/******************************************************************************/ -/* */ -/* Function: onePass */ -/* */ -/* Synopsis: Do one assembly pass. */ -/* */ -/******************************************************************************/ -void onePass() -{ - char name[SYMLEN]; - WORD16 newclc; - BOOL scanning_line; - int start; - SYM_T *sym; - int term; - WORD16 val; - - clc = 0200; /* Default starting address is 200 octal. */ - field = 0; - fieldlc = clc & 07777; - reloc = 0; - clearLiteralTable(); - listed = TRUE; - lineno = 0; - list_pageno = 0; - list_lineno = 0; - last_xref_lexstart = 0; - last_xref_lineno = 0; - list_title_set = FALSE; - radix = 8; /* Initial radix is octal (base 8). */ - - if( !rim_mode ) - { - /* Put out initial origin if not in rim mode. */ - punchOrigin( clc ); - } - - while( TRUE ) - { - readLine(); - nextLexeme(); - - scanning_line = TRUE; - while( scanning_line ) - { - if( isend( line[lexstart] )) - { - scanning_line = FALSE; - } - else - { - switch( line[lexstart] ) - { - case '/': - scanning_line = FALSE; - break; - - case ';': - nextLexeme(); - break; - - case '$': - endOfBinary(); - return; - - case '*': - nextLexeme(); /* Skip '*', (set origin symbol) */ - newclc = ((getExpr())->val & 07777 ) | field; - /* Do not change Current Location Counter if an error occurred. */ - if( !error_in_line ) - { - if(( newclc & 07600 ) != ( clc & 07600 ) ) - { - /* Current page has changed. */ - punchLiteralPool( cp, 0 ); - } - clc = newclc - reloc; - fieldlc = clc & 07777; - - if( !rim_mode ) - { - /* Not rim mode, put out origin. */ - punchOrigin( clc ); - } - printLine( line, 0, fieldlc, LINE_VAL ); - } - break; - - default: - switch( line[lexterm] ) - { - case ',': - if( isalpha( line[lexstart] )) - { - /* Use lookup so symbol will not be counted as reference. */ - sym = lookup( lexemeToName( name, lexstart, lexterm )); - if( M_DEFINED( sym->type )) - { - if( (sym->val & 07777) != ( ( clc+reloc ) & 07777) && pass == 2 ) - { - errorSymbol( &duplicate_label, sym->name, lexstart ); - } - sym->type = sym->type | DUPLICATE; - } - /* Must call define on pass 2 to generate concordance. */ - defineLexeme( lexstart, lexterm, ( clc + reloc ), LABEL ); - } - else - { - errorLexeme( &label_syntax, lexstart ); - } - nextLexeme(); /* skip label */ - nextLexeme(); /* skip comma */ - break; - - case '=': - if( isalpha( line[lexstart] )) - { - start = lexstart; - term = lexterm; - delimiter = line[lexterm]; - nextLexBlank(); /* skip symbol */ - nextLexeme(); /* skip trailing =, allow blank */ - delimiter = line[lexterm]; - val = getExprs(); - defineLexeme( start, term, val, DEFINED ); - printLine( line, 0, val, LINE_VAL ); - } - else - { - errorLexeme( &symbol_syntax, lexstartprev ); - nextLexeme(); /* skip symbol */ - nextLexeme(); /* skip trailing = */ - getExprs(); /* skip expression */ - } - break; - - default: - if( isalpha( line[lexstart] )) - { - sym = evalSymbol(); - val = sym->val; - if( M_PSEUDO( sym->type )) - { - nextLexeme(); /* Skip symbol */ - scanning_line = pseudoOperators( (PSEUDO_T)val & 07777 ); - } - else - { - /* Identifier is not a pseudo-op, interpret as load value */ - punchOutObject( clc, getExprs() & 07777 ); - incrementClc(); - } - } - else - { - /* Identifier is a value, interpret as load value */ - punchOutObject( clc, getExprs() & 07777 ); - incrementClc(); - } - break; - } /* end switch */ - break; - } /* end switch */ - } /* end if */ - } /* end while( scanning_line ) */ - } /* end while( TRUE ) */ -} /* onePass() */ - - -/******************************************************************************/ -/* */ -/* Function: fixMRIInstruction */ -/* */ -/* Synopsis: Now that we have the final value figure out if page 0, current */ -/* page, or indirect needed and max final instruction */ -/* */ -/******************************************************************************/ -WORD16 fixMRIInstruction(WORD16 instruction, WORD16 value) -{ - /* Now have the address part of the MRI instruction. */ - if( value < 00200 ) - { - instruction |= value; /* Page zero MRI. */ - } - else if( (( fieldlc + reloc ) & 07600 ) <= value - && value <= ((( fieldlc + reloc ) & 07600) | 0177 )) - { - instruction |= ( PAGE_BIT | (value & ADDRESS_FIELD )); /* Current page MRI */ - } - else - { - if(( instruction & INDIRECT_BIT ) == INDIRECT_BIT ) - { - /* Already indirect, can't generate */ - errorSymbol( &illegal_indirect, NULL, lexstartprev ); - } - else - { - if( literals_ok ) - { - /* Now fix off page reference. */ - /* Search current page literal pool for needed instruction. */ - /* Set Indirect Current Page */ - instruction |= ( 00600 | insertLiteral( cp, value, GET_PAGE_INDEX(clc) )); - indirect_generated = TRUE; - if (pass == 2) - { - links++; - } - } - else - { - errorSymbol( &illegal_reference, NULL, lexstartprev ); - instruction |= ( value & 0177 ); - } - } - } - return instruction; -} -/******************************************************************************/ -/* */ -/* Function: getExprs */ -/* */ -/* Synopsis: Or together a list of blank separated expressions, from the */ -/* current lexeme onward. Leave the current lexeme as */ -/* the last one in the list. */ -/* */ -/******************************************************************************/ -WORD16 getExprs() -{ - SYM_T *symv; - SYM_T *symt; - WORD16 temp; - SYMTYP temp_type; - WORD16 value; - SYMTYP value_type; - BOOL MRI_held = FALSE; - WORD16 held_value = 0; - - symv = getExpr(); - value = symv->val; - value_type = symv->type; - - while( TRUE ) - { - if( isdone( line[lexstart] )) - { - if (MRI_held) - { - value = fixMRIInstruction(value, held_value); - } - return( value ); - } - switch( line[lexstart] ) - { - case ')': - case ']': - case '<': - if (MRI_held) - { - value = fixMRIInstruction(value, held_value); - } - return( value ); - - default: - break; - } - - /* Interpret space as logical or */ - symt = getExpr(); - temp = symt->val & 07777; - temp_type = symt->type; - - switch( value_type & (MRI | MRIFIX)) - { - case MRI: - case MRIFIX: - /* Previous symbol was a Memory Reference Instruction. */ - switch( temp_type & (MRI | MRIFIX) ) - { - case MRI: - case MRIFIX: - /* If we have held value don't or in more MRI's to instuction, they */ - /* are now instuction value */ - if (MRI_held) - { - held_value |= temp; - } - else - { - /* Current symbol is also a Memory Reference Instruction. */ - value |= temp; /* Just OR the MRI instructions. */ - } - break; - - default: - held_value |= temp; - MRI_held = TRUE; - break; - } - break; - - default: - if (value_type == UNDEFINED || temp_type == UNDEFINED) { - value = 0; - } else { - value |= temp; /* Normal 12 bit value. */ - } - break; - } - } /* end while */ -} /* getExprs() */ - - -/******************************************************************************/ -/* */ -/* Function: getExpr */ -/* */ -/* Synopsis: Get an expression, from the current lexeme onward, leave the */ -/* current lexeme as the one after the expression. Expressions */ -/* contain terminal symbols (identifiers) separated by operators. */ -/* */ -/******************************************************************************/ -SYM_T *getExpr() -{ - SYM_T *sym; - delimiter = line[lexterm]; - - - if( line[lexstart] == '-' ) - { - nextLexBlank(); - sym_getexpr = *(eval()); - sym_getexpr.val = ( - sym_getexpr.val ) & 07777; - } - else - { - if( line[lexstart] == '+' ) - { - nextLexBlank(); - } - sym_getexpr = *(eval()); - sym_getexpr.val = sym_getexpr.val & 07777; - } - - if( is_blank( delimiter )) - { - return( &sym_getexpr ); - } - - /* Here we assume the current lexeme is the operator separating the */ - /* previous operator from the next, if any. */ - while( TRUE ) - { - /* assert line[lexstart] == delimiter */ - if( is_blank( delimiter )) - { - return( &sym_getexpr ); - } - - switch( line[lexstart] ) - { - case '+': /* add */ - nextLexBlank(); /* skip over the operator */ - sym = eval(); - sym_getexpr.val += sym->val; - if (sym_getexpr.type == UNDEFINED || sym->type == UNDEFINED) { - sym_getexpr.val = 0; - sym_getexpr.type = UNDEFINED; - } - break; - - case '-': /* subtract */ - nextLexBlank(); /* skip over the operator */ - sym = eval(); - sym_getexpr.val -= sym->val; - if (sym_getexpr.type == UNDEFINED || sym->type == UNDEFINED) { - sym_getexpr.val = 0; - sym_getexpr.type = UNDEFINED; - } - break; - - case '^': /* multiply */ - nextLexBlank(); /* skip over the operator */ - sym = eval(); - sym_getexpr.val *= sym->val; - if (sym_getexpr.type == UNDEFINED || sym->type == UNDEFINED) { - sym_getexpr.val = 0; - sym_getexpr.type = UNDEFINED; - } - break; - - case '%': /* divide */ - nextLexBlank(); /* skip over the operator */ - sym = eval(); - sym_getexpr.val /= sym->val; - if (sym_getexpr.type == UNDEFINED || sym->type == UNDEFINED) { - sym_getexpr.val = 0; - sym_getexpr.type = UNDEFINED; - } - break; - - case '&': /* and */ - nextLexBlank(); /* skip over the operator */ - sym = eval(); - sym_getexpr.val &= sym->val; - if (sym_getexpr.type == UNDEFINED || sym->type == UNDEFINED) { - sym_getexpr.val = 0; - sym_getexpr.type = UNDEFINED; - } - break; - - case '!': /* or */ - nextLexBlank(); /* skip over the operator */ - sym = eval(); - sym_getexpr.val |= sym->val; - if (sym_getexpr.type == UNDEFINED || sym->type == UNDEFINED) { - sym_getexpr.val = 0; - sym_getexpr.type = UNDEFINED; - } - break; - - default: - if( isend( line[lexstart] )) - { - return( &sym_getexpr ); - } - - switch( line[lexstart] ) - { - case '/': - case ';': - case ')': - case ']': - case '<': - break; - - case '=': - errorMessage( &illegal_equals, lexstart ); - moveToEndOfLine(); - sym_getexpr.val = 0; - break; - - default: - errorMessage( &illegal_expression, lexstart ); - moveToEndOfLine(); - sym_getexpr.val = 0; - break; - } - return( &sym_getexpr ); - } - } /* end while */ -} /* getExpr() */ - - -/******************************************************************************/ -/* */ -/* Function: eval */ -/* */ -/* Synopsis: Get the value of the current lexeme, set delimiter and advance.*/ -/* */ -/******************************************************************************/ -SYM_T *eval() -{ - WORD16 digit; - int from; - WORD16 loc; - SYM_T *sym; - WORD16 val; - - val = 0; - - delimiter = line[lexterm]; - if( isalpha( line[lexstart] )) - { - sym = evalSymbol(); - if( M_UNDEFINED( sym->type ) && pass == 2 ) - { - errorSymbol( &undefined_symbol, sym->name, lexstart ); - nextLexeme(); - return( sym ); - } - else - { - nextLexeme(); - return( sym ); - } - } - else if( isdigit( line[lexstart] )) - { - from = lexstart; - val = 0; - while( from < lexterm ) - { - if( isdigit( line[from] )) - { - digit = (WORD16) line[from++] - (WORD16) '0'; - if( digit < radix ) - { - val = val * radix + digit; - } - else - { - errorLexeme( &number_not_radix, from - 1 ); - val = 0; - from = lexterm; - } - } - else - { - errorLexeme( ¬_a_number, lexstart ); - val = 0; - from = lexterm; - } - } - nextLexeme(); - sym_eval.val = val; - return( &sym_eval ); - } - else - { - switch( line[lexstart] ) - { - case '"': /* Character literal */ - if( cc + 1 <= maxcc ) - { - val = line[lexstart + 1] | 0200; - delimiter = line[lexstart + 2]; - cc = lexstart + 2; - } - else - { - errorMessage( &no_literal_value, lexstart ); - } - nextLexeme(); - break; - - case '.': /* Value of Current Location Counter */ - val = (clc & 07777) + reloc; - nextLexeme(); - break; - - case '[': /* Generate literal on page zero. */ - if( !literals_ok && LITERAL_ERROR) - { - errorMessage( &literal_gen_off, lexstart ); - } - nextLexBlank(); /* Skip bracket */ - val = getExprs() & 07777; - - if( line[lexstart] == ']' ) - { - nextLexBlank(); /* Skip end bracket */ - } - - sym_eval.val = (literals_ok || !LITERAL_ERROR) ? insertLiteral( cp , val, - GET_PAGE_INDEX(field)) : 0; - return( &sym_eval ); - - case '(': /* Generate literal on current page. */ - if( !literals_ok && LITERAL_ERROR) - { - errorMessage( &literal_gen_off, lexstart ); - } - - nextLexBlank(); /* Skip paren */ - val = getExprs() & 07777; - - if( line[lexstart] == ')' ) - { - nextLexBlank(); /* Skip end paren */ - } - - loc = (literals_ok || !LITERAL_ERROR) ? insertLiteral( cp, val, GET_PAGE_INDEX(clc) ) : 0; - sym_eval.val = loc + (( clc + reloc ) & 077600 ); - return( &sym_eval ); - - default: - switch( line[lexstart] ) - { - case '=': - errorMessage( &illegal_equals, lexstart ); - moveToEndOfLine(); - break; - - default: - errorMessage( &illegal_character, lexstart ); - break; - } - val = 0; /* On error, set value to zero. */ - nextLexBlank(); /* Go past illegal character. */ - } - } - sym_eval.val = val; - return( &sym_eval ); -} /* eval() */ - - -/******************************************************************************/ -/* */ -/* Function: inputDubl */ -/* */ -/* Synopsis: Get the value of the current lexeme as a double word. */ -/* */ -/******************************************************************************/ -void inputDubl() -{ - WORD32 dublvalue; - BOOL scanning_line; - - scanning_line = TRUE; - do - { - while( scanning_line ) - { - if( isend( line[lexstart] )) - { - scanning_line = FALSE; - } - else - { - switch( line[lexstart] ) - { - case '/': - scanning_line = FALSE; - break; - - case ';': - nextLexeme(); - break; - - case '+': - delimiter = line[lexterm]; - nextLexBlank(); - case '-': - default: - if( isdigit( line[lexstart] ) || line[lexstart] == '-' ) - { - dublvalue = getDublExprs(); - punchOutObject( clc, (WORD16)(( dublvalue >> 12 ) & 07777 )); - incrementClc(); - punchOutObject( clc, (WORD16)( dublvalue & 07777 )); - incrementClc(); - } - else - { - return; /* Non-numeric input, back to assembly. */ - } - break; - } /* end switch */ - } /* end if */ - - if( error_in_line ) - { - return; /* Error occurred, exit DUBL input mode. */ - } - } /* end while( scanning_line ) */ - readLine(); - nextLexeme(); - scanning_line = TRUE; - } - while( TRUE ); -} /* inputDubl() */ - - -/******************************************************************************/ -/* */ -/* Function: getDublExprs */ -/* */ -/* Synopsis: Get a DUBL expression. */ -/* */ -/******************************************************************************/ -WORD32 getDublExprs() -{ - WORD32 dublvalue; - - dublvalue = getDublExpr(); - - while( TRUE ) - { - if( isdone( line[lexstart] )) - { - return( dublvalue ); - } - else - { - errorMessage( &illegal_expression, lexstart - 1 ); - return( 0 ); - } - } /* end while */ -} /* getDublExprs() */ - - -/******************************************************************************/ -/* */ -/* Function: getDublExpr */ -/* */ -/* Synopsis: Get the value of the current lexeme as a double word. The */ -/* number is always considered to have a decimal radix. */ -/* */ -/******************************************************************************/ -WORD32 getDublExpr() -{ - WORD32 dublvalue; - - delimiter = line[lexterm]; - if( line[lexstart] == '-' ) - { - nextLexBlank(); - dublvalue = evalDubl( 0 ); - nextLexeme(); - /* Test for any value greater than 23 bits in length. */ - if( (unsigned long int)dublvalue > 040000000L ) - { - errorMessage( &dubl_overflow, lexstart ); - dublvalue = 0; - } - dublvalue = -dublvalue; - } - else - { - dublvalue = evalDubl( 0 ); - nextLexeme(); - /* Test for any value greater than 23 bits in length. */ - if( (unsigned long int)dublvalue > 037777777L ) - { - errorMessage( &dubl_overflow, lexstart ); - dublvalue = 0; - } - } - - if( is_blank( delimiter )) - { - return( dublvalue ); - } - - /* Here we assume the current lexeme is the operator separating the */ - /* previous operator from the next, if any. */ - while( TRUE ) - { - /* assert line[lexstart] == delimiter */ - if( is_blank( delimiter )) - { - errorMessage( &illegal_expression, lexstart ); - moveToEndOfLine(); - dublvalue = 0; - return( dublvalue ); - } - - switch( line[lexstart] ) - { - case '+': /* add */ - case '-': /* subtract */ - case '^': /* multiply */ - case '%': /* divide */ - case '&': /* and */ - case '!': /* or */ - errorMessage( &illegal_expression, lexstart ); - moveToEndOfLine(); - dublvalue = 0; - break; - - default: - if( isend( line[lexstart] )) - { - return( dublvalue ); - } - - switch( line[lexstart] ) - { - case '/': - case ';': - break; - - default: - errorMessage( &illegal_expression, lexstart ); - moveToEndOfLine(); - dublvalue = 0; - break; - } - return( dublvalue ); - } - } /* end while */ -} /* getDublExpr() */ - - -/******************************************************************************/ -/* */ -/* Function: evalDubl */ -/* */ -/* Synopsis: Get the value of the current lexeme as a double word. The */ -/* number is always considered to have a decimal radix. */ -/* */ -/******************************************************************************/ -WORD32 evalDubl( WORD32 initial_value ) -{ - WORD32 digit; - int from; - WORD32 dublvalue; - WORD32 olddublvalue; - - overflow = FALSE; - delimiter = line[lexterm]; - from = lexstart; - dublvalue = initial_value; - - while( from < lexterm ) - { - if( isdigit( line[from] )) - { - olddublvalue = dublvalue; - digit = (WORD32)( line[from++] - '0' ); - dublvalue = dublvalue * 10 + digit; - if( dublvalue < olddublvalue ) - { - overflow = TRUE; - } - } - else - { - errorLexeme( ¬_a_number, from ); - dublvalue = 0; - from = lexterm; - } - } - return( dublvalue ); -} /* evalDubl() */ - - -/******************************************************************************/ -/* */ -/* Function: inputFltg */ -/* */ -/* Synopsis: Get the value of the current lexeme as a Floating Point const. */ -/* */ -/******************************************************************************/ -void inputFltg() -{ - FLTG_T *fltg; - BOOL scanning_line; - - fltg_input = TRUE; /* Set lexeme scanner for floating point. */ - scanning_line = TRUE; - while( TRUE ) - { - while( scanning_line ) - { - if( isend( line[lexstart] )) - { - scanning_line = FALSE; - } - else - { - switch( line[lexstart] ) - { - case '/': - scanning_line = FALSE; - break; - - case ';': - nextLexeme(); - break; - - case '+': - delimiter = line[lexterm]; - nextLexBlank(); - case '-': - default: - if( isdigit( line[lexstart] ) || line[lexstart] == '-' ) - { - fltg = getFltgExprs(); - punchOutObject( clc, ( fltg->exponent & 07777 )); - incrementClc(); - punchOutObject( clc, (WORD16)(( fltg->mantissa >> 12 ) & 07777 )); - incrementClc(); - punchOutObject( clc, (WORD16)( fltg->mantissa & 07777 )); - incrementClc(); - } - else - { - fltg_input = FALSE; /* Reset lexeme scanner. */ - return; /* Non-numeric input, back to assembly. */ - } - break; - } /* end switch */ - } /* end if */ - - if( error_in_line ) - { - fltg_input = FALSE; /* Reset lexeme scanner. */ - return; /* Error occurred, exit FLTG input mode. */ - } - } /* end while( scanning_line ) */ - readLine(); - nextLexeme(); - scanning_line = TRUE; - } -} /* inputFltg() */ - - -/******************************************************************************/ -/* */ -/* Function: getFltgExprs */ -/* */ -/* Synopsis: Get a FLTG expression. */ -/* */ -/******************************************************************************/ -FLTG_T *getFltgExprs() -{ - FLTG_T *fltg; - - fltg = getFltgExpr(); - - while( TRUE ) - { - if( isdone( line[lexstart] )) - { - return( fltg ); - } - else - { - errorMessage( &illegal_expression, lexstart - 1 ); - return( 0 ); - } - } /* end while */ -} /* getFltgExprs() */ - - -/******************************************************************************/ -/* */ -/* Function: getFltgExpr */ -/* */ -/* Synopsis: Get the value of the current lexeme as a double word. The */ -/* number is always considered to have a decimal radix. */ -/* */ -/******************************************************************************/ -FLTG_T *getFltgExpr() -{ - FLTG_T *fltg; - - delimiter = line[lexterm]; - fltg = evalFltg(); - /* Test for any value greater than 23 bits in length. */ - if( (unsigned long int)fltg->mantissa> 077777777L ) - { - errorMessage( &fltg_overflow, lexstart ); - } - - if( is_blank( delimiter )) - { - return( fltg ); - } - - /* Here we assume the current lexeme is the operator separating the */ - /* previous operator from the next, if any. */ - while( TRUE ) - { - /* assert line[lexstart] == delimiter */ - if( is_blank( delimiter )) - { - errorMessage( &illegal_expression, lexstart ); - moveToEndOfLine(); - fltg = 0; - return( fltg ); - } - - switch( line[lexstart] ) - { - case '+': /* add */ - case '-': /* subtract */ - case '^': /* multiply */ - case '%': /* divide */ - case '&': /* and */ - case '!': /* or */ - errorMessage( &illegal_expression, lexstart ); - moveToEndOfLine(); - fltg = NULL; - break; - - default: - if( isend( line[lexstart] )) - { - return( fltg ); - } - - switch( line[lexstart] ) - { - case '/': - case ';': - break; - - default: - errorMessage( &illegal_expression, lexstart ); - moveToEndOfLine(); - fltg = NULL; - break; - } - return( fltg ); - } - } /* end while */ -} /* getFltgExpr() */ - - -/******************************************************************************/ -/* */ -/* Function: evalFltg */ -/* */ -/* Synopsis: Get the value of the current lexeme as a floating point value. */ -/* Floating point input is alwasy considered decimal. */ -/* The general format of a floating point number is: */ -/* +-ddd.dddE+-dd where each d is a decimal digit. */ -/* */ -/******************************************************************************/ -FLTG_T *evalFltg() -{ - int current_state; - int current_col; - WORD16 exponent; - FLTG_T *fltg; - WORD32 input_value; - BOOL negate; - BOOL negate_exponent; - int next_state; - int right_digits; - - /* This uses a lexical analyzer to parse the floating point format. */ - static BYTE state_table[][10] = - { - /* 0 1 2 3 4 5 6 Oolumn index */ - /* + - d . E sp p State Comment */ - { 2, 1, 3, 4, 10, 10, 10 }, /* 0 Initial state. */ - { 11, 11, 3, 4, 11, 11, 11 }, /* 1 - */ - { 11, 11, 3, 4, 11, 11, 11 }, /* 2 + */ - { 10, 10, 10, 4, 6, 10, 10 }, /* 3 # (+-ddd) */ - { 11, 11, 5, 11, 11, 10, 10 }, /* 4 . (+-ddd.) */ - { 11, 11, 11, 11, 6, 10, 11 }, /* 5 # (+-ddd.ddd) */ - { 8, 7, 9, 11, 11, 11, 11 }, /* 6 E (+-ddd.dddE) */ - { 11, 11, 9, 11, 11, 11, 11 }, /* 7 - (+-ddd.dddE- */ - { 11, 11, 9, 11, 11, 11, 11 }, /* 8 + (+-ddd.dddE+ */ - { 11, 11, 11, 11, 11, 10, 11 } /* 9 # (+-ddd.dddE+-dd */ - /* 10 Completion state */ - /* 11 Error state. */ - }; - - delimiter = line[lexterm]; - fltg = &fltg_ac; - fltg->exponent = 0; - fltg->mantissa = 0; - input_value = 0; - negate = FALSE; - negate_exponent = FALSE; - next_state = 0; - exponent = 0; - right_digits = 0; - current_state = 0; - - while( TRUE ) - { - /* Classify character. This is the column index. */ - switch( line[lexstart] ) - { - case '+': - current_col = 0; - break; - - case '-': - current_col = 1; - break; - - case '.': - current_col = 3; - break; - - case 'E': case 'e': - current_col = 4; - break; - - default: - if( isdigit( line[lexstart] )) - { - current_col = 2; - } - else if( isdone( line[lexstart] )) - { - current_col = 5; - } - else - { - current_col = 6; - } - break; - } - - next_state = state_table[current_state][current_col]; - - switch( next_state ) - { - case 1: /* - */ - negate = TRUE; - case 2: /* + */ - delimiter = line[lexterm]; /* Move past the + or - character. */ - nextLexBlank(); - break; - - case 3: /* Number (+-ddd) */ - input_value = evalDubl( 0 ); /* Integer part of the number. */ - nextLexeme(); /* Move past previous lexeme. */ - break; - - case 4: - delimiter = line[lexterm]; - nextLexBlank(); /* Move past the . character. */ - break; - - case 5: /* . (+-ddd.ddd) */ - /* Fractional part of the number. */ - input_value = evalDubl( input_value ); - right_digits = lexterm - lexstart;/* Digit count to right of decimal. */ - nextLexeme(); /* Move past previous lexeme. */ - break; - - case 6: /* E (+-ddd.dddE) */ - delimiter = line[lexterm]; /* Move past the E. */ - nextLexBlank(); - break; - - case 7: /* - (+-ddd.dddE-) */ - negate_exponent = TRUE; - case 8: /* + (+-ddd.dddE+) */ - delimiter = line[lexterm]; /* Move past the + or - character. */ - nextLexBlank(); - break; - - case 9: /* # (+-ddd.dddE+-dd) */ - exponent = (int)evalDubl( 0 ); /* Exponent of floating point number. */ - if( negate_exponent ) { exponent = - exponent; } - nextLexeme(); /* Move past previous lexeme. */ - break; - - case 10: /* Floating number parsed, convert */ - /* the number. */ - /* Get the exponent for the number as input. */ - exponent -= right_digits; - - /* Remove trailing zeros and adjust the exponent accordingly. */ - while(( input_value % 10 ) == 0 ) - { - input_value /= 10; - exponent++; - } - - /* Convert the number to floating point. The number is calculated with */ - /* a 27 bit mantissa to improve precision. The extra 3 bits are */ - /* discarded after the result has been calculated. */ - fltg->exponent = 26; - fltg->mantissa = input_value << 3; - normalizeFltg( fltg ); - - - while( exponent != 0 ) - { - if( exponent < 0 ) - { - /* Decimal point is to the left. */ - fltg->mantissa /= 10; - normalizeFltg( fltg ); - exponent++; - } - else if( exponent > 0 ) - { - /* Decimal point is to the right. */ - fltg->mantissa *= 10; - normalizeFltg( fltg ); - exponent--; - } - } - - /* Discard the extra precsion used for calculating the number. */ - fltg->mantissa >>= 3; - fltg->exponent -= 3; - if( negate ) - { - fltg->mantissa = (- fltg->mantissa ) & 077777777L; - } - return( fltg ); - - case 11: /* Error in format. */ - /* Not a properly constructued floating point number. */ - return( fltg ); - default: - break; - } - /* Set state for next pass through the loop. */ - current_state = next_state; - } -} /* evalFltg() */ - - - -/******************************************************************************/ -/* */ -/* Function: normalizeFltg */ -/* */ -/* Synopsis: Normalize a PDP-8 double precision floating point number. */ -/* */ -/******************************************************************************/ -void normalizeFltg( FLTG_T *fltg ) -{ - /* Normalize the floating point number. */ - if( fltg->mantissa != 0 ) - { - if(( fltg->mantissa & ~0x3FFFFFFL ) == 0 ) - { - while(( fltg->mantissa & ~0x1FFFFFFL ) == 0 ) - - { - fltg->mantissa <<= 1; - fltg->exponent--; - } - } - else - { - while(( fltg->mantissa & ~0x3FFFFFFL ) != 0 ) - { - fltg->mantissa >>= 1; - fltg->exponent++; - } - } - } - else - { - fltg->exponent = 0; - } - return; -} - - -/******************************************************************************/ -/* */ -/* Function: incrementClc */ -/* */ -/* Synopsis: Set the next assembly location. Test for collision with */ -/* the literal tables. */ -/* */ -/******************************************************************************/ -WORD16 incrementClc() -{ - testForLiteralCollision( clc ); - - /* Incrementing the location counter is not to change field setting. */ - clc = ( clc & 070000 ) + (( clc + 1 ) & 07777 ); - fieldlc = clc & 07777; - return( clc ); -} /* incrementClc() */ - - -/******************************************************************************/ -/* */ -/* Function: testForLiteralCollision */ -/* */ -/* Synopsis: Test the given location for collision with the literal tables. */ -/* */ -/******************************************************************************/ -BOOL testForLiteralCollision( WORD16 loc ) -{ - WORD16 pagelc; - BOOL result = FALSE; - WORD16 tmppage; - int tmpfield; - - tmpfield = GET_PAGE_INDEX(loc); - tmppage = loc & 07600; - pagelc = loc & 00177; - - if ( pagelc > max_page_used[tmpfield] ) - { - max_page_used[tmpfield] = pagelc; - } - if ( pagelc >= cp[tmpfield].loc ) - { - if ( tmppage == 0 ) - { - errorMessage( &pz_literal_overflow, -1 ); - } - else - { - errorMessage( &literal_overflow, -1 ); - } - result = TRUE; - } - - return( result ); -} /* testForLiteralCollision() */ - - -/******************************************************************************/ -/* */ -/* Function: readLine */ -/* */ -/* Synopsis: Get next line of input. Print previous line if needed. */ -/* */ -/******************************************************************************/ -void readLine() -{ - WORD16 ix; - WORD16 iy; - char inpline[LINELEN]; - - listLine(); /* List previous line if needed. */ - lineno++; /* Count lines read. */ - indirect_generated = FALSE; /* Mark no indirect address generated. */ - listed = FALSE; /* Mark as not listed. */ - cc = 0; /* Initialize column counter. */ - lexstartprev = 0; - - error_in_line = FALSE; - if(( fgets( inpline, LINELEN - 1, infile )) == NULL ) - { - inpline[0] = '$'; - inpline[1] = '\n'; - inpline[2] = '\0'; - if (!dollar_not_required) { - error_in_line = TRUE; - } - } - - /* Remove any tabs from the input line by inserting the required number */ - /* of spaces to simulate N character tab stops, where N defaults to 8 and */ - /* is set by the command line option -t. (RK 20071029) */ - /* Ignore \r if there is one. (DPI 20150501) */ - for( ix = 0, iy = 0; inpline[ix] != '\0' && iy < (LINELEN - 2); ix++ ) - { - switch( inpline[ix] ) - { - case '\t': - do - { - line[iy] = ' '; - iy++; - } - while(( iy % tabstops ) != 0 && iy < (LINELEN - 2)); - break; - - case '\r': /* dont copy the carriage return */ - break; - - default: - line[iy] = inpline[ix]; - iy++; - break; - } - } - if (iy >= (LINELEN - 2)) { - line [iy] = '\n'; - iy++; - } - line[iy] = '\0'; - - maxcc = iy; /* Save the current line length. */ - /* Save the first line for possible use as the listing title. */ - if( lineno == 1 ) - { - strcpy( list_title, line ); - } -} /* readLine() */ - - -/******************************************************************************/ -/* */ -/* Function: listLine */ -/* */ -/* Synopsis: Output a line to the listing file. */ -/* */ -/******************************************************************************/ -void listLine() -/* generate a line of listing if not already done! */ -{ - if( listfile != NULL && listed == FALSE ) - { - printLine( line, 0, 0, LINE ); - } -} /* listLine() */ - - -/******************************************************************************/ -/* */ -/* Function: printPageBreak */ -/* */ -/* Synopsis: Output a Top of Form and listing header if new page necessary. */ -/* */ -/******************************************************************************/ -void printPageBreak() -{ - if( page_lineno >= LIST_LINES_PER_PAGE ) - /* ( list_lineno % LIST_LINES_PER_PAGE ) == 0 ) */ - { - if( !list_title_set ) - { - /* strcpy( list_title, line ); */ - if( list_title[strlen(list_title) - 1] == '\n' ) - { - list_title[strlen(list_title) - 1] = '\0'; - } - if( strlen( list_title ) > TITLELEN ) - { - list_title[TITLELEN] = '\0'; - } - list_title_set = TRUE; - } - topOfForm( list_title, NULL ); - - } -} /* printPageBreak() */ - - -/******************************************************************************/ -/* */ -/* Function: printLine */ -/* */ -/* Synopsis: Output a line to the listing file with new page if necessary. */ -/* */ -/******************************************************************************/ -void printLine( char *line, WORD16 loc, WORD16 val, LINESTYLE_T linestyle ) -{ - char rlc; - - if( listfile == NULL ) - { - save_error_count = 0; - return; - } - - printPageBreak(); - - list_lineno++; - page_lineno++; - - if (reloc == 0) - { - rlc = ' '; - } - else - { - rlc = '*'; - } - - switch( linestyle ) - { - default: - case LINE: - fprintf(listfile, "%5d ", lineno ); - fputs( line, listfile ); - listed = TRUE; - break; - - case LINE_VAL: - fprintf(listfile, "%5d %4.4o ", lineno, val ); - fputs( line, listfile ); - listed = TRUE; - break; - - case LINE_LOC_VAL: - if( !listed ) - { - if( indirect_generated ) - { - fprintf( listfile, "%5d %5.5o%c %4.4o@ ", lineno, loc, rlc, val ); - } - else - { - fprintf( listfile, "%5d %5.5o%c %4.4o ", lineno, loc, rlc, val ); - } - fputs( line, listfile ); - listed = TRUE; - } - else - { - fprintf( listfile, " %5.5o%c %4.4o\n", loc, rlc, val ); - } - break; - - case LOC_VAL: - fprintf( listfile, " %5.5o%c %4.4o\n", loc, rlc, val ); - break; - } - printErrorMessages(); -} /* printLine() */ - - -/******************************************************************************/ -/* */ -/* Function: printErrorMessages */ -/* */ -/* Synopsis: Output any error messages from the current list of errors. */ -/* */ -/******************************************************************************/ -void printErrorMessages() -{ - WORD16 ix; - WORD16 iy; - - if( listfile != NULL ) - { - /* If any errors, display them now. */ - for( iy = 0; iy < save_error_count; iy++ ) - { - printPageBreak(); - fprintf( listfile, "%-18.18s", error_list[iy].mesg ); - if( error_list[iy].col >= 0 ) - { - for( ix = 0; ix < error_list[iy].col; ix++ ) - { - if( line[ix] == '\t' ) - { - putc( '\t', listfile ); - } - else - { - putc( ' ', listfile ); - } - } - fputs( "^", listfile ); - list_lineno++; - page_lineno++; - } - fputs( "\n", listfile ); - } - } - save_error_count = 0; -} /* printErrorMessages() */ - - -/******************************************************************************/ -/* */ -/* Function: endOfBinary */ -/* */ -/* Synopsis: Outputs both literal tables at the end of a binary segment. */ -/* */ -/******************************************************************************/ -void endOfBinary() -{ - /* Punch page 0 also. */ - punchLiteralPool( cp, 1 ); - if( error_in_line ) - { - listed = TRUE; - clc = ( clc & 070000 ) + (( clc - 1 ) & 07777 ); - errorMessage( &end_of_file, -1 ); - clc = ( clc & 070000 ) + (( clc + 1 ) & 07777 ); - } - else - { - listLine(); /* List line if not done yet. */ - } - return; -} /* endOfBinary() */ - - -/******************************************************************************/ -/* */ -/* Function: punchChecksum */ -/* */ -/* Synopsis: Output a checksum if the current mode requires it and an */ -/* object file exists. */ -/* */ -/******************************************************************************/ -void punchChecksum() -{ - /* If the assembler has output any BIN data output the checksum. */ - if( binary_data_output && !rim_mode ) - { - punchLocObject( 0, checksum ); - } - binary_data_output = FALSE; - checksum = 0; -} /* punchChecksum() */ - - -/******************************************************************************/ -/* */ -/* Function: punchLeader */ -/* */ -/* Synopsis: Generate 2 feet of leader on object file, as per DEC */ -/* documentation. Paper tape has 10 punches per inch. */ -/* */ -/******************************************************************************/ -void punchLeader( int count ) -{ - int ix; - - /* If value is zero, set to the default of 2 feet of leader. */ - count = ( count == 0 ) ? 240 : count; - - if( objectfile != NULL ) - { - for( ix = 0; ix < count; ix++ ) - { - fputc( 0200, objectfile ); - } - } -} /* punchLeader() */ - - -/******************************************************************************/ -/* */ -/* Function: punchOrigin */ -/* */ -/* Synopsis: Output an origin to the object file. */ -/* */ -/******************************************************************************/ -void punchOrigin( WORD16 loc ) -{ - punchObject((( loc >> 6 ) & 0077 ) | 0100 ); - punchObject( loc & 0077 ); -} /* punchOrigin() */ - - -/******************************************************************************/ -/* */ -/* Function: punchObject */ -/* */ -/* Synopsis: Put one character to object file and include it in checksum. */ -/* */ -/******************************************************************************/ -void punchObject( WORD16 val ) -{ - val &= 0377; - if( objectfile != NULL ) - { - fputc( val, objectfile ); - checksum += val; - } - binary_data_output = TRUE; -} /* punchObject() */ - - -/******************************************************************************/ -/* */ -/* Function: punchOutObject */ -/* */ -/* Synopsis: Output the current line and then then punch value to the */ -/* object file. */ -/* */ -/******************************************************************************/ -void punchOutObject( WORD16 loc, WORD16 val ) -{ - /* Adding reloc makes printout agree with PAL8 where is prints the */ - /* relocated address, not the address in the BIN file */ - printLine( line,( ( field | loc ) + reloc ), val, LINE_LOC_VAL ); - punchLocObject( loc, val ); -} /* punchOutObject() */ - -/******************************************************************************/ -/* */ -/* Function: punchLocObject */ -/* */ -/* Synopsis: Output the word (with origin if rim format) to the object file.*/ -/* */ -/******************************************************************************/ -void punchLocObject( WORD16 loc, WORD16 val ) -{ - if( rim_mode ) - { - punchOrigin( loc ); - } - punchObject(( val >> 6 ) & 0077 ); - punchObject( val & 0077 ); -} /* punchLocObject() */ - - -/******************************************************************************/ -/* */ -/* Function: punchLiteralPool */ -/* */ -/* Synopsis: Output the current page data. */ -/* */ -/******************************************************************************/ -void punchLiteralPool( LPOOL_T *p, BOOL punch_page0 ) -{ - WORD16 loc; - WORD16 tmplc; - int lpool_page = 0; /* Silence false uninitialized error from GCC */ - int i; - - for (i = MAX_PAGES-1; i >= 0; i--) { - lpool_page = (i << 7) & 07600; - - if ( p[i].loc != p[i].last_punched && (punch_page0 || lpool_page != 0) ) - { - if( !rim_mode ) - { - /* Put out origin if not in rim mode. */ - punchOrigin( p[i].loc | lpool_page ); - } - /* Put the literals in the object file. */ - for( loc = p[i].loc; loc < p[i].last_punched; loc++ ) - { - tmplc = loc + lpool_page; - printLine( line, (field | tmplc), p[i].pool[loc], LOC_VAL ); - punchLocObject( tmplc, p[i].pool[loc] ); - } - p[i].last_punched = p[i].loc; - } - } -} /* punchLiteralPool() */ - - -/******************************************************************************/ -/* */ -/* Function: insertLiteral */ -/* */ -/* Synopsis: Add a value to the given literal pool if not already in pool. */ -/* Return the location of the value in the pool. */ -/* */ -/******************************************************************************/ -WORD16 insertLiteral( LPOOL_T *pool, WORD16 value, int fieldpage_index ) -{ - WORD16 ix; - LPOOL_T *p; - - p = &pool[fieldpage_index]; - - /* Search the literal pool for any occurence of the needed value. */ - ix = PAGE_SIZE - 1; - while( ix >= p->loc && p->pool[ix] != value ) - { - ix--; - } - - /* Check if value found in literal pool. If not, then insert value. */ - if( ix < p->loc ) - { - (p->loc)--; - p->pool[p->loc] = value; - ix = p->loc; - if( max_page_used[fieldpage_index] >= p->loc ) { - if ( (fieldpage_index & 017) == 0 ) - { - errorMessage( &pz_literal_overflow, -1 ); - } - else - { - errorMessage( &literal_overflow, -1 ); - } - } - } - return( ix ); -} /* insertLiteral() */ - - -/******************************************************************************/ -/* */ -/* Function: printSymbolTable */ -/* */ -/* Synopsis: Output the symbol table. */ -/* */ -/******************************************************************************/ -void printSymbolTable() -{ - int col; - int cx; - char *fmt; - int ix; - char mark; - int page; - int row; - int symbol_base; - int symbol_lines; - - symbol_base = number_of_fixed_symbols; - - for( page=0, list_lineno=0, col=0, ix=symbol_base; ix < symbol_top; page++ ) - { - topOfForm( list_title, s_symtable ); - symbol_lines = LIST_LINES_PER_PAGE - page_lineno; - - for( row = 0; page_lineno < LIST_LINES_PER_PAGE && ix < symbol_top; row++) - { - list_lineno++; - page_lineno++; - fprintf( listfile, "%5d", list_lineno ); - - for( col = 0; col < SYMBOL_COLUMNS && ix < symbol_top; col++ ) - { - /* Get index of symbol for the current line and column */ - cx = symbol_lines * ( SYMBOL_COLUMNS * page + col ) + row; - cx += symbol_base; - - /* Make sure that there is a symbol to be printed. */ - if( number_of_fixed_symbols <= cx && cx < symbol_top ) - { - switch( symtab[cx].type & LABEL ) - { - case LABEL: - fmt = " %c%-6.6s %5.5o "; - break; - - default: - fmt = " %c%-6.6s %4.4o "; - break; - } - - switch( symtab[cx].type & ( DEFINED | REDEFINED )) - { - case UNDEFINED: - mark = '?'; - break; - - case REDEFINED: - mark = '#'; - break; - - default: - mark = ' '; - break; - } - fprintf( listfile, fmt, mark, symtab[cx].name, symtab[cx].val ); - ix++; - } - } - fprintf( listfile, "\n" ); - } - } -} /* printSymbolTable() */ - - -/******************************************************************************/ -/* */ -/* Function: printPermanentSymbolTable */ -/* */ -/* Synopsis: Output the permanent symbol table to a file suitable for */ -/* being input after the EXPUNGE pseudo-op. */ -/* */ -/******************************************************************************/ -void printPermanentSymbolTable() -{ - int ix; - FILE *permfile; - char *s_type; - - if(( permfile = fopen( permpathname, "w" )) == NULL ) - { - exit( 2 ); - } - - fprintf( permfile, "/ PERMANENT SYMBOL TABLE\n/\n" ); - fprintf( permfile, " EXPUNGE\n/\n" ); - /* Print the memory reference instructions first. */ - s_type = "FIXMRI"; - for( ix = 0; ix < symbol_top; ix++ ) - { - if( M_MRI( symtab[ix].type )) - { - fprintf( permfile, "%-7s %s=%4.4o\n", - s_type, symtab[ix].name, symtab[ix].val ); - } - } - - s_type = " "; - for( ix = 0; ix < symbol_top; ix++ ) - { - if( M_FIXED( symtab[ix].type )) - { - if( !M_MRI( symtab[ix].type ) && !M_PSEUDO( symtab[ix].type )) - { - fprintf( permfile, "%-7s %s=%4.4o\n", - s_type, symtab[ix].name, symtab[ix].val ); - } - } - } - fprintf( permfile, "/\n FIXTAB\n" ); - fclose( permfile ); -} /* printPermanentSymbolTable() */ - - -/******************************************************************************/ -/* */ -/* Function: printCrossReference */ -/* */ -/* Synopsis: Output a cross reference (concordance) for the file being */ -/* assembled. */ -/* */ -/******************************************************************************/ -void printCrossReference() -{ - int ix; - int symbol_base; - int xc; - int xc_index; - int xc_refcount; - int xc_cols; - - /* Force top of form for first page. */ - page_lineno = LIST_LINES_PER_PAGE; - - list_lineno = 0; - symbol_base = number_of_fixed_symbols; - - for( ix = symbol_base; ix < symbol_top; ix++ ) - { - list_lineno++; - page_lineno++; - if( page_lineno >= LIST_LINES_PER_PAGE ) - { - topOfForm( list_title, s_xref ); - } - - fprintf( listfile, "%5d", list_lineno ); - - /* Get reference count & index into concordance table for this symbol. */ - xc_refcount = symtab[ix].xref_count; - xc_index = symtab[ix].xref_index; - /* Determine how to label symbol on concordance. */ - switch( symtab[ix].type & ( DEFINED | REDEFINED )) - { - case UNDEFINED: - fprintf( listfile, " U "); - break; - - case REDEFINED: - fprintf( listfile, " M %5d ", xreftab[xc_index] ); - break; - - default: - fprintf( listfile, " A %5d ", xreftab[xc_index] ); - break; - } - fprintf( listfile, "%-6.6s ", symtab[ix].name ); - - /* Output the references, 8 numbers per line after symbol name. */ - for( xc_cols = 0, xc = 1; xc < xc_refcount + 1; xc++, xc_cols++ ) - { - if( xc_cols >= XREF_COLUMNS ) - { - xc_cols = 0; - page_lineno++; - if( page_lineno >= LIST_LINES_PER_PAGE ) - { - topOfForm( list_title, s_xref); - } - list_lineno++; - fprintf( listfile, "\n%5d%-19s", list_lineno, " " ); - } - fprintf( listfile, " %5d", xreftab[xc_index + xc] ); - } - fprintf( listfile, "\n" ); - } -} /* printCrossReference() */ - - -/******************************************************************************/ -/* */ -/* Function: topOfForm */ -/* */ -/* Synopsis: Prints title and sub-title on top of next page of listing. */ -/* */ -/******************************************************************************/ -void topOfForm( char *title, char *sub_title ) -{ - char temp[10]; - - list_pageno++; - strcpy( temp, s_page ); - sprintf( temp, "%s %d", s_page, list_pageno ); - - /* Output a top of form if not the first page of the listing. */ - if( list_pageno > 1 ) - { - fprintf( listfile, "\f" ); - } - fprintf( listfile, "\n\n\n %-63s %10s\n", title, temp ); - - /* Reset the current page line counter. */ - page_lineno = 3; - if( sub_title != NULL ) - { - fprintf( listfile, "%80s\n", sub_title ); - page_lineno++; - } - else - { - fprintf( listfile, "\n" ); - page_lineno++; - } - fprintf( listfile, "\n" ); - page_lineno++; -} /* topOfForm() */ - - -/******************************************************************************/ -/* */ -/* Function: lexemeToName */ -/* */ -/* Synopsis: Convert the current lexeme into a string. */ -/* */ -/******************************************************************************/ -char *lexemeToName( char *name, int from, int term ) -{ - int to; - - to = 0; - - while( from < term && to < ( SYMLEN - 1 )) - { - name[to++] = toupper( line[from++] ); - } - - while( to < SYMLEN ) - { - name[to++] = '\0'; - } - return( name ); -} /* lexemeToName() */ - -/******************************************************************************/ -/* */ -/* Function: defineLexeme */ -/* */ -/* Synopsis: Put lexeme into symbol table with a value. */ -/* */ -/******************************************************************************/ -SYM_T *defineLexeme( int start, /* start of lexeme being defined. */ - int term, /* end+1 of lexeme being defined. */ - WORD16 val, /* value of lexeme being defined. */ - SYMTYP type ) /* how symbol is being defined. */ -{ - char name[SYMLEN]; - - lexemeToName( name, start, term); - return( defineSymbol( name, val, type, start )); -} /* defineLexeme() */ - - -/******************************************************************************/ -/* */ -/* Function: defineSymbol */ -/* */ -/* Synopsis: Define a symbol in the symbol table, enter symbol name if not */ -/* not already in table. */ -/* */ -/******************************************************************************/ -SYM_T *defineSymbol( char *name, WORD16 val, SYMTYP type, WORD16 start ) -{ - SYM_T *sym; - int xref_count; - - if( strlen( name ) < 1 ) - { - return( &sym_undefined ); /* Protect against non-existent names. */ - } - sym = lookup( name ); - /* OS/8 PAL8 seems to allow permanent symbold to be redefined without error */ - if( ( M_FIXED( sym->type ) && pass == 1 && perm_redef_error ) || - (M_PERM_REDEFINED( sym->type ) && (sym->val != val)) ) - { - type |= PERM_REDEFINED; - } - - xref_count = 0; /* Set concordance for normal defintion. */ - - if( M_DEFINED( sym->type )) - { - if( pass == 2 && ( (sym->val & 07777) != (val & 07777) || M_PERM_REDEFINED(sym->type)) ) - { - /* Generate diagnostic if redefining a symbol. */ - if( M_PERM_REDEFINED( sym->type ) && (M_LABEL(sym->type) || M_LABEL(type)) ) - { - errorSymbol( &illegal_redefine, sym->name, start ); - } else - { - /* Generate diagnostic if redefining a symbol. */ - if( M_REDEFINED( sym->type ) && (M_LABEL(sym->type) || M_LABEL(type)) ) - { - errorSymbol( &redefined_symbol, sym->name, start ); - } - } - type = type | REDEFINED; - sym->xref_count++; /* Referenced suymbol, count it. */ - xref_count = sym->xref_count; - } - } - - if( pass == 2 && xref ) - { - /* Put the definition line number in the concordance table. */ - /* Defined symbols are not counted as references. */ - xreftab[sym->xref_index] = lineno; - /* Put the line number in the concordance table. */ - xreftab[sym->xref_index + xref_count] = lineno; - } - - /* Now set the value and the type. */ - sym->val = ( M_LABEL(type) ) ? val : val & 07777; - sym->type = ( pass == 1 ) ? ( type | CONDITION ) : type; - return( sym ); -} /* defineSymbol() */ - - -/******************************************************************************/ -/* */ -/* Function: lookup */ -/* */ -/* Synopsis: Find a symbol in table. If not in table, enter symbol in */ -/* table as undefined. Return address of symbol in table. */ -/* */ -/******************************************************************************/ -SYM_T *lookup( char *name ) -{ - int ix; /* Insertion index */ - int lx; /* Left index */ - int rx; /* Right index */ - - /* First search the permanent symbols. */ - lx = 0; - ix = binarySearch( name, lx, number_of_fixed_symbols ); - - /* If symbol not in permanent symbol table. */ - if( ix < 0 ) - { - /* Now try the user symbol table. */ - ix = binarySearch( name, number_of_fixed_symbols, symbol_top ); - - /* If symbol not in user symbol table. */ - if( ix < 0 ) - { - /* Must put symbol in table if index is negative. */ - ix = ~ix; - if( symbol_top + 1 >= SYMBOL_TABLE_SIZE ) - { - errorSymbol( &symbol_table_full, name, lexstart ); - exit( 1 ); - } - - for( rx = symbol_top; rx >= ix; rx-- ) - { - symtab[rx + 1] = symtab[rx]; - } - symbol_top++; - - /* Enter the symbol as UNDEFINED with a value of zero. */ - strcpy( symtab[ix].name, name ); - symtab[ix].type = UNDEFINED; - symtab[ix].val = 0; - symtab[ix].xref_count = 0; - if( xref && pass == 2 ) - { - xreftab[symtab[ix].xref_index] = 0; - } - } - } - - return( &symtab[ix] ); /* Return the location of the symbol. */ -} /* lookup() */ - - -/******************************************************************************/ -/* */ -/* Function: binarySearch */ -/* */ -/* Synopsis: Searches the symbol table within the limits given. If the */ -/* symbol is not in the table, it returns the insertion point. */ -/* */ -/******************************************************************************/ -int binarySearch( char *name, int start, int symbol_count ) -{ - int lx; /* Left index */ - int mx; /* Middle index */ - int rx; /* Right index */ - int compare; /* Results of comparison */ - - lx = start; - rx = symbol_count - 1; - while( lx <= rx ) - { - mx = ( lx + rx ) / 2; /* Find center of search area. */ - - compare = strcmp( name, symtab[mx].name ); - - if( compare < 0 ) - { - rx = mx - 1; - } - else if( compare > 0 ) - { - lx = mx + 1; - } - else - { - return( mx ); /* Found a match in symbol table. */ - } - } /* end while */ - return( ~lx ); /* Return insertion point. */ -} /* binarySearch() */ - - -/******************************************************************************/ -/* */ -/* Function: compareSymbols */ -/* */ -/* Synopsis: Used to presort the symbol table when starting assembler. */ -/* */ -/******************************************************************************/ -int compareSymbols( const void *a, const void *b ) -{ - return( strcmp( ((SYM_T *) a)->name, ((SYM_T *) b)->name )); -} /* compareSymbols() */ - - -/******************************************************************************/ -/* */ -/* Function: evalSymbol */ -/* */ -/* Synopsis: Get the pointer for the symbol table entry if exists. */ -/* If symbol doesn't exist, return a pointer to the undefined sym */ -/* */ -/******************************************************************************/ -SYM_T *evalSymbol() -{ - char name[SYMLEN]; - SYM_T *sym; - - sym = lookup( lexemeToName( name, lexstart, lexterm )); - - /* The symbol goes in the concordance iff it is in a different position in */ - /* the assembler source file. */ - if( lexstart != last_xref_lexstart || lineno != last_xref_lineno ) - { - sym->xref_count++; /* Count the number of references to symbol. */ - last_xref_lexstart = lexstart; - last_xref_lineno = lineno; - - /* Put the line number in the concordance table. */ - if( xref && pass == 2 ) - { - xreftab[sym->xref_index + sym->xref_count] = lineno; - } - } - - return( sym ); -} /* evalSymbol() */ - - -/******************************************************************************/ -/* */ -/* Function: moveToEndOfLine */ -/* */ -/* Synopsis: Move the parser input to the end of the current input line. */ -/* */ -/******************************************************************************/ -void moveToEndOfLine() -{ - while( !isend( line[cc] )) cc++; - lexstart = cc; - lexterm = cc; - lexstartprev = lexstart; -} /* moveToEndOfLine() */ - -/******************************************************************************/ -/* */ -/* Function: nextLexeme */ -/* */ -/* Synopsis: Get the next lexical element from input line. */ -/* */ -/******************************************************************************/ -void nextLexeme() -{ - /* Save start column of previous lexeme for diagnostic messages. */ - lexstartprev = lexstart; - lextermprev = lexterm; - - while( is_blank( line[cc] )) { cc++; } - lexstart = cc; - - if( isalnum( line[cc] )) - { - while( isalnum( line[cc] )) { cc++; } - } - else if( isend( line[cc] )) - { - /* End-of-Line, don't advance cc! */ - } - else - { - switch( line[cc] ) - { - case '"': /* Quoted letter */ - if( cc + 2 < maxcc ) - { - cc++; - cc++; - } - else - { - errorMessage( &no_literal_value, lexstart ); - cc++; - } - break; - - case '/': /* Comment, don't advance cc! */ - break; - - default: /* All other punctuation. */ - cc++; - break; - } - } - lexterm = cc; -} /* nextLexeme() */ - - -/******************************************************************************/ -/* */ -/* Function: nextLexBlank */ -/* */ -/* Synopsis: Used to prevent illegal blanks in expressions. */ -/* */ -/******************************************************************************/ -void nextLexBlank() -{ - nextLexeme(); - if( is_blank( delimiter )) - { - errorMessage( &illegal_blank, lexstart - 1 ); - } - delimiter = line[lexterm]; -} /* nextLexBlank() */ - - -/******************************************************************************/ -/* */ -/* Function: pseudoOperators */ -/* */ -/* Synopsis: Process pseudo-ops (directives). */ -/* */ -/******************************************************************************/ -BOOL pseudoOperators( PSEUDO_T val ) -{ - int count, count2; - int delim; - int index; - int ix; - int lexstartsave; - WORD16 newfield; - WORD16 oldclc; - int pack; - BOOL status; - SYM_T *sym; - FILE *temp; - int term; - WORD16 value; - char os8_name[8]; - int reloc_clc; - - status = TRUE; - switch( (PSEUDO_T) val ) - { - case ASCII: - /* added 18-Jan-2003 PNT -- derived from TEXT */ - delim = line[lexstart]; - index = lexstart + 1; - while( line[index] != delim && !isend( line[index] )) - { - punchOutObject( clc, (line[index] & 127) | 128 ); - incrementClc(); - index++; - } - if( isend( line[index] )) - { - cc = index; - lexterm = cc; - errorMessage( &text_string, cc ); - } - else - { - cc = index + 1; - lexterm = cc; - } - nextLexeme(); - break; - - case BANK: - errorSymbol( &no_pseudo_op, "BANK", lexstartprev ); - /* should select a different 32K out of 128K */ - break; - - case BINPUNCH: - /* If there has been data output and this is a mode switch, set up to */ - /* output data in BIN mode. */ - if( binary_data_output && rim_mode ) - { - clearLiteralTable(); - punchLeader( 8 ); /* Generate a short leader/trailer. */ - checksum = 0; - binary_data_output = FALSE; - } - rim_mode = FALSE; - break; - - case DECIMAL: - radix = 10; - break; - - case DUBL: - inputDubl(); - break; - - case EJECT: - page_lineno = LIST_LINES_PER_PAGE; /* This will force a page break. */ - status = FALSE; /* This will force reading of next line */ - break; - - case ENPUNCH: - if( pass == 2 ) - { - objectfile = objectsave; - } - break; - - case EXPUNGE: /* Erase symbol table */ - if( pass == 1 ) - { - symtab[0] = sym_undefined; - symbol_top = 0; - number_of_fixed_symbols = symbol_top; - fixed_symbols = &symtab[symbol_top - 1]; - - /* Enter the pseudo-ops into the symbol table. */ - for( ix = 0; ix < DIM( pseudo ); ix++ ) - { - defineSymbol( pseudo[ix].name, pseudo[ix].val, pseudo[ix].type, 0 ); - } - /* Enter the really permanent symbols into the table. */ - /* Also make them part of the permanent symbol table. */ - for( ix = 0; ix < DIM( really_permanent_symbols ); ix++ ) - { - defineSymbol( really_permanent_symbols[ix].name, - really_permanent_symbols[ix].val, - really_permanent_symbols[ix].type | DEFFIX , 0 ); - } - number_of_fixed_symbols = symbol_top; - fixed_symbols = &symtab[symbol_top - 1]; - - } - break; - - case FIELD: - /* Punch page 0 also */ - punchLiteralPool( cp, 1 ); - newfield = field >> 12; - lexstartsave = lexstartprev; - if( isdone( line[lexstart] )) - { - newfield += 1; /* Blank FIELD directive. */ - } - else - { - newfield = (getExpr())->val; /* FIELD with argument. */ - } - - if( rim_mode ) - { - errorMessage( &in_rim_mode, lexstartsave ); /* Can't change fields. */ - } - else if( newfield > 7 || newfield < 0 ) - { - errorMessage( &illegal_field_value, lexstartprev ); - } - else - { - value = (( newfield & 0007 ) << 3 ) | 00300; - punchObject( value ); - if( objectfile != NULL ) /* Only fix checksum if punching */ - { - checksum -= value; /* Field punches are not added to checksum. */ - } - field = newfield << 12; - } - - clc = 0200 | field; - fieldlc = clc & 07777; - - if( !rim_mode ) - { - punchOrigin( clc ); - } - - clearLiteralTable(); - - break; - - case FIXMRI: - if( line[lexterm] == '=' && isalpha( line[lexstart] )) - { - lexstartsave = lexstart; - term = lexterm; - nextLexeme(); /* Skip symbol. */ - nextLexeme(); /* Skip trailing = */ - defineLexeme( lexstartsave, term, getExprs(), MRI ); - } - else - { - errorLexeme( &symbol_syntax, lexstart ); - nextLexeme(); /* Skip symbol. */ - nextLexeme(); /* Skip trailing = */ - (void) getExprs(); /* Skip expression. */ - } - break; - - case FIXTAB: - if (pass == 1) /* Only fix on first pass, on second all are defined */ - { - /* Mark all current symbols as permanent symbols. */ - for( ix = 0; ix < symbol_top; ix++ ) - { - symtab[ix].type = symtab[ix].type | FIXED; - } - number_of_fixed_symbols = symbol_top; - fixed_symbols = &symtab[symbol_top - 1]; - - /* Re-sort the symbol table */ - qsort( symtab, symbol_top, sizeof(symtab[0]), compareSymbols ); - } - break; - - case FLTG: - inputFltg(); - /* errorSymbol( &no_pseudo_op, "FLTG", lexstartprev ); */ - break; - - case IFDEF: - if( isalpha( line[lexstart] )) - { - sym = evalSymbol(); - nextLexeme(); - if( M_DEFINED_CONDITIONALLY( sym->type )) - { - conditionTrue(); - } - else - { - conditionFalse(); - } - } - else - { - errorLexeme( &label_syntax, lexstart ); - } - break; - - case IFNDEF: - if( isalpha( line[lexstart] )) - { - sym = evalSymbol(); - nextLexeme(); - if( M_DEFINED_CONDITIONALLY( sym->type )) - { - conditionFalse(); - } - else - { - conditionTrue(); - } - } - else - { - errorLexeme( &label_syntax, lexstart ); - } - break; - - case IFNZERO: - if( getExprs() == 0 ) - { - conditionFalse(); - } - else - { - conditionTrue(); - } - break; - - case IFZERO: - if( getExprs() == 0 ) - { - conditionTrue(); - } - else - { - conditionFalse(); - } - break; - - case NOPUNCH: - if( pass == 2 ) - { - objectfile = NULL; - } - break; - - case OCTAL: - radix = 8; - break; - - case PAGE: - reloc_clc = clc + reloc; - punchLiteralPool( cp, 0 ); - oldclc = clc; - if( isdone( line[lexstart] )) - { - clc = (( reloc_clc + 0177 ) & 077600) - reloc; /* No argumnet. */ - fieldlc = clc & 07777; - } - else - { - value = (getExpr())->val; - clc = field + (( value & 037 ) << 7 ) - reloc; - fieldlc = clc & 07777; - } - testForLiteralCollision( clc + reloc ); - - if( !rim_mode && clc != oldclc ) - { - punchOrigin( clc ); - } - break; - - case PAUSE: - break; - - case RELOC: - if( isdone( line[lexstart] )) - { - reloc = 0; /* Blank RELOC directive. */ - } - else - { - value = (getExpr())->val; /* RELOC with argument. */ - reloc = (value & 07777) - ( clc & 07777); - } - break; - - case RIMPUNCH: - /* If the assembler has output any BIN data, output the literal tables */ - /* and the checksum for what has been assembled and setup for RIM mode. */ - if( binary_data_output && !rim_mode ) - { - endOfBinary(); - clearLiteralTable(); - punchChecksum(); - punchLeader( 8 ); /* Generate a short leader/trailer. */ - } - rim_mode = TRUE; - break; - - case SEGMNT: - punchLiteralPool( cp, 0 ); - if( isdone( line[lexstart] )) - { /* No argument. */ - clc = ( clc & 06000 ) + 02000; - fieldlc = clc & 07777; - } - else - { - getExpr(); - clc = ( val & 003 ) << 10; - fieldlc = clc & 07777; - } - if( !rim_mode ) - { - punchOrigin( clc ); - } - testForLiteralCollision( clc ); - break; - - case TEXT: - delim = line[lexstart]; - pack = 0; - count = 0; - index = lexstart + 1; - while( line[index] != delim && !isend( line[index] )) - { - pack = ( pack << 6 ) | ( line[index] & 077 ); - count++; - if( count > 1 ) - { - punchOutObject( clc, pack ); - incrementClc(); - count = 0; - pack = 0; - } - index++; - } - - if( count != 0 ) - { - punchOutObject( clc, pack << 6 ); - incrementClc(); - } - else - { - punchOutObject( clc, 0 ); - incrementClc(); - } - - if( isend( line[index] )) - { - cc = index; - lexterm = cc; - errorMessage( &text_string, cc ); - } - else - { - cc = index + 1; - lexterm = cc; - } - nextLexeme(); - break; - - case FILENAME: - memset(os8_name, 0, sizeof(os8_name)); - delimiter=line[lexstart]; - if (delimiter != '.') - { - for (index = lexstart, count = 0; index < lexterm && count < 6; index++) - { - os8_name[count++] = line[index]; - } - delimiter=line[lexterm]; - if (delimiter == '.') - { - nextLexeme(); /* Skip . */ - } - } - nextLexeme(); - if (delimiter == '.') - { - for (index = lexstart, count = 6; index < lexterm && count < 8; index++) - { - os8_name[count++] = line[index]; - } - } - - pack = 0; - count = 0; - for (count2 = 0; count2 < 8; count2++) - { - pack = ( pack << 6 ) | ( os8_name[count2] & 077 ); - count++; - if( count > 1 ) - { - punchOutObject( clc, pack ); - incrementClc(); - count = 0; - pack = 0; - } - } - nextLexeme(); - break; - - case DEVICE: - memset(os8_name, 0, sizeof(os8_name)); - for (index = lexstart, count = 0; index < lexterm && count < 4; index++) - { - os8_name[count++] = line[index]; - } - - pack = 0; - count = 0; - for (count2 = 0; count2 < 4; count2++) - { - pack = ( pack << 6 ) | ( os8_name[count2] & 077 ); - count++; - if( count > 1 ) - { - punchOutObject( clc, pack ); - incrementClc(); - count = 0; - pack = 0; - } - } - - nextLexeme(); - break; - - case TITLE: - delim = line[lexstart]; - ix = lexstart + 1; - /* Find string delimiter. */ - do - { - if( list_title[ix] == delim && list_title[ix + 1] == delim ) - { - ix++; - } - ix++; - } while( line[ix] != delim && !isend(line[ix]) ); - - if( line[ix] == delim ) - { - count = 0; - ix = lexstart + 1; - do - { - if( list_title[ix] == delim && list_title[ix + 1] == delim ) - { - ix++; - } - list_title[count] = line[ix]; - count++; - ix++; - list_title[count] = '\0'; - } while( line[ix] != delim && !isend(line[ix]) ); - - if( strlen( list_title ) > TITLELEN ) - { - list_title[TITLELEN] = '\0'; - } - - cc = ix + 1; - lexterm = cc; - page_lineno = LIST_LINES_PER_PAGE;/* Force top of page for new titles. */ - list_title_set = TRUE; - } - else - { - cc = ix; - lexterm = cc; - errorMessage( &text_string, cc ); - } - - nextLexeme(); - break; - - case XLIST: - if( isdone( line[lexstart] )) - { - temp = listfile; /* Blank XLIST directive. */ - listfile = listsave; - listsave = temp; - } - else - { - if( (getExpr())->val == 0 ) - { - if( listfile == NULL ) - { - listfile = listsave; - listsave = NULL; - } - } - else - { - if( listfile != NULL ) - { - listsave = listfile; - listfile = NULL; - } - } - } - break; - - case ZBLOCK: - value = (getExpr())->val; - if( value < 0 ) - { - errorMessage( &zblock_too_small, lexstartprev ); - } - else if( value + ( clc & 07777 ) - 1 > 07777 ) - { - errorMessage( &zblock_too_large, lexstartprev ); - } - else - { - for( ; value > 0; value-- ) - { - punchOutObject( clc, 0 ); - incrementClc(); - } - } - - break; - - default: - break; - } /* end switch for pseudo-ops */ - return( status ); -} /* pseudoOperators() */ - - -/******************************************************************************/ -/* */ -/* Function: conditionFalse */ -/* */ -/* Synopsis: Called when a false conditional has been evaluated. */ -/* Lex should be the opening <; ignore all text until */ -/* the closing >. */ -/* */ -/******************************************************************************/ -void conditionFalse() -{ - int level; - - if( line[lexstart] == '<' ) - { - /* Invariant: line[cc] is the next unexamined character. */ - level = 1; - while( level > 0 ) - { - if( isend( line[cc] )) - { - readLine(); - } - else - { - switch( line[cc] ) - { - case '>': - level--; - cc++; - break; - - case '<': - level++; - cc++; - break; - - case '$': - level = 0; - cc++; - break; - - default: - cc++; - break; - } /* end switch */ - } /* end if */ - } /* end while */ - nextLexeme(); - } - else - { - errorMessage( <_expected, lexstart ); - } -} /* conditionFalse() */ - -/******************************************************************************/ -/* */ -/* Function: conditionTrue */ -/* */ -/* Synopsis: Called when a true conditional has been evaluated. */ -/* Lex should be the opening <; skip it and setup for */ -/* normal assembly. */ -/* */ -/******************************************************************************/ -void conditionTrue() -{ - if( line[lexstart] == '<' ) - { - nextLexeme(); /* Skip the opening '<' */ - } - else - { - errorMessage( <_expected, lexstart ); - } -} /* conditionTrue() */ - - -/******************************************************************************/ -/* */ -/* Function: errorLexeme */ -/* */ -/* Synopsis: Display an error message using the current lexical element. */ -/* */ -/******************************************************************************/ -void errorLexeme( EMSG_T *mesg, int col ) -{ - char name[SYMLEN]; - - errorSymbol( mesg, lexemeToName( name, lexstart, lexterm ), col ); -} /* errorLexeme() */ - - -/******************************************************************************/ -/* */ -/* Function: errorSymbol */ -/* */ -/* Synopsis: Display an error message with a given string. */ -/* */ -/******************************************************************************/ -void errorSymbol( EMSG_T *mesg, char *name, int col ) -{ - char linecol[12]; - char *s; - - if( pass == 2 ) - { - s = ( name == NULL ) ? "" : name ; - errors++; - sprintf( linecol, "(%d:%d)", lineno, col + 1 ); - fprintf( errorfile, "%s%-9s : error: %s \"%s\" at Loc = %5.5o\n", - filename, linecol, mesg->file, s, clc ); - saveError( mesg->list, col ); - } - error_in_line = TRUE; -} /* errorSymbol() */ - - -/******************************************************************************/ -/* */ -/* Function: errorMessage */ -/* */ -/* Synopsis: Display an error message without a name argument. */ -/* */ -/******************************************************************************/ -void errorMessage( EMSG_T *mesg, int col ) -{ - char linecol[12]; - - if( pass == 2 ) - { - errors++; - sprintf( linecol, "(%d:%d)", lineno, col + 1 ); - fprintf( errorfile, "%s%-9s : error: %s at Loc = %5.5o\n", - filename, linecol, mesg->file, clc ); - saveError( mesg->list, col ); - } - error_in_line = TRUE; -} /* errorMessage() */ - -/******************************************************************************/ -/* */ -/* Function: saveError */ -/* */ -/* Synopsis: Save the current error in a list so it may displayed after the */ -/* the current line is printed. */ -/* */ -/******************************************************************************/ -void saveError( char *mesg, int col ) -{ - if( save_error_count < DIM( error_list )) - { - error_list[save_error_count].mesg = mesg; - error_list[save_error_count].col = col; - save_error_count++; - } - error_in_line = TRUE; - - if( listed ) - { - printErrorMessages(); - } -} /* saveError() */ -/* End-of-File */ DELETED pics/schofield-brtval.R Index: pics/schofield-brtval.R ================================================================== --- pics/schofield-brtval.R +++ /dev/null @@ -1,62 +0,0 @@ -# schofield-brtval.R: Generate SVG graph of the brightness curves -# produced by Ian Schofield's ILS patch. -# -# The schofield-brtval.svg file checked into Fossil is modified from the -# version output by this program: -# -# 1. The colors are modified to match the scheme on tangentsoft.com/pidp8i -# -# 2. The data line thickness was increased -# -# 3. The data lines were smoothed by Inkscape's "simplify" function -# -# Copyright © 2017 Warren Young -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. -# -# Except as contained in this notice, the names of the authors above shall -# not be used in advertising or otherwise to promote the sale, use or other -# dealings in this Software without prior written authorization from those -# authors. -# - -min = 0 -max = 32 -a = min -b = max - -rising = c(min); -falling = c(max); - -for (i in 1:400) { - a = a + (max - a) * 0.01 - b = b + (min - b) * 0.01 - - rising[i] = a - falling[i] = b - - if (a > 31 || b < 1) break -} - -data = data.frame(Rising = rising, Falling = falling) -dts = ts(data) -svg("schofield-brtval.svg", width=8, height=6) -plot.ts(dts, plot.type='single', ylab='Brightness', - yaxp=c(min, max, 8)) -dev.off() DELETED pics/schofield-brtval.svg Index: pics/schofield-brtval.svg ================================================================== --- pics/schofield-brtval.svg +++ /dev/null @@ -1,954 +0,0 @@ - - - Schofield ILS Brightness Curves - - - - image/svg+xml - - Schofield ILS Brightness Curves - - 2017-02-02 - - - Warren Young - - - English - Curves showing the brightness levels of an LED driven from 0 to full brightness and from full brightness back to 0 under Ian Schofield's indandescent lamp simulator for the PiDP-8/I. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DELETED pics/wy/back.jpg Index: pics/wy/back.jpg ================================================================== --- pics/wy/back.jpg +++ /dev/null cannot compute difference between binary files DELETED pics/wy/front.jpg Index: pics/wy/front.jpg ================================================================== --- pics/wy/front.jpg +++ /dev/null cannot compute difference between binary files DELETED pics/wy/power-switch.png Index: pics/wy/power-switch.png ================================================================== --- pics/wy/power-switch.png +++ /dev/null cannot compute difference between binary files DELETED pics/wy/serial-db9.jpg Index: pics/wy/serial-db9.jpg ================================================================== --- pics/wy/serial-db9.jpg +++ /dev/null cannot compute difference between binary files DELETED pics/wy/serial-kk.jpg Index: pics/wy/serial-kk.jpg ================================================================== --- pics/wy/serial-kk.jpg +++ /dev/null cannot compute difference between binary files DELETED pics/wy/system.jpg Index: pics/wy/system.jpg ================================================================== --- pics/wy/system.jpg +++ /dev/null cannot compute difference between binary files Index: src/PDP8/pdp8_cpu.c ================================================================== --- src/PDP8/pdp8_cpu.c +++ src/PDP8/pdp8_cpu.c @@ -52,10 +52,11 @@ ---------------------------------------------------------------------------- cpu central processor + 07-Sep-17 RMS Fixed sim_eval declaration in history routine (COVERITY) 09-Mar-17 RMS Fixed PCQ_ENTRY for interrupts (COVERITY) 13-Feb-17 RMS RESET clear L'AC, per schematics 28-Jan-17 RMS Renamed switch register variable to SR, per request 18-Sep-16 RMS Added alternate dispatch table for non-contiguous devices 17-Sep-13 RMS Fixed boot in wrong field problem (Dave Gesswein) @@ -219,11 +220,13 @@ pdp8_defs.h add device number and interrupt definitions pdp8_sys.c add sim_devices table entry */ -/* ---PiDP change------------------------------------------------------------------------------------------- */ +#include "pdp8_defs.h" + +/* ---PiDP add---------------------------------------------------------------------------------------------- */ #include "gpio-common.h" #include "pidp8i.h" /* ---PiDP end---------------------------------------------------------------------------------------------- */ #define PCQ_SIZE 64 /* must be 2**n */ @@ -410,11 +413,11 @@ // We're about to leave the loop, so repaint one last time // in case this is a Ctrl-E and we later get a "cont" // command. Set a flag that will let us auto-resume. extern int resumeFromInstructionLoopExit, swStop, swSingInst; resumeFromInstructionLoopExit = swStop = swSingInst = 1; - set_pidp8i_leds (PC, MA, MB, IR, LAC, MQ, IF, DF, SC, + set_pidp8i_leds (PC, MA, IR, LAC, MQ, IF, DF, SC, int_req, Pause); // Also copy SR hardware value to software register in case // the user tries poking at it from the sim> prompt. SR = get_switch_register(); @@ -436,11 +439,11 @@ // Have to keep display updated while stopped. This does // mean if the software starts with the STOP switch held // down, we'll put garbage onto the display for MA, MB, and // IR, but that's what the real hardware does, too. See // https://github.com/simh/simh/issues/386 - set_pidp8i_leds (PC, MA, MB, IR, LAC, MQ, IF, DF, SC, + set_pidp8i_leds (PC, MA, IR, LAC, MQ, IF, DF, SC, int_req, Pause); // Go no further in STOP mode. In particular, fetch no more // instructions, and do not touch PC! continue; @@ -1528,12 +1531,11 @@ // Save skips to inst counter and reset inst_count += skip_count; skip_count = 0; // We need to update the LED data again - set_pidp8i_leds (PC, MA, MB, IR, LAC, MQ, IF, DF, SC, int_req, Pause); - Pause = 0; + set_pidp8i_leds (PC, MA, IR, LAC, MQ, IF, DF, SC, int_req, Pause); // Has it been ~1s since we updated our max_skips value? time_t now; if (time(&now) > last_update) { // Yep; simulator IPS may have changed, so freshen it. @@ -1543,10 +1545,11 @@ // max_skips, dither, inst_count / 1e6); inst_count = 0; } dither = max_skips > 32 ? lrand48() % (max_skips >> 3) : 0; // 12.5% } + Pause = 0; // it's set outside the "if", so it must be *reset* outside /* ---PiDP end---------------------------------------------------------------------------------------------- */ } /* end while */ /* Simulation halted */ @@ -1762,11 +1765,10 @@ t_stat cpu_show_hist (FILE *st, UNIT *uptr, int32 val, CONST void *desc) { int32 l, k, di, lnt; const char *cptr = (const char *) desc; t_stat r; -t_value sim_eval; InstHistory *h; if (hst_lnt == 0) /* enabled? */ return SCPE_NOFNC; if (cptr) { @@ -1785,15 +1787,15 @@ l = (h->lac >> 12) & 1; /* link */ fprintf (st, "%05o %o %04o %04o ", h->pc & ADDRMASK, l, h->lac & 07777, h->mq); if (h->ir < 06000) fprintf (st, "%05o ", h->ea); else fprintf (st, " "); - sim_eval = h->ir; - if ((fprint_sym (st, h->pc & ADDRMASK, &sim_eval, &cpu_unit, SWMASK ('M'))) > 0) + sim_eval[0] = h->ir; + if ((fprint_sym (st, h->pc & ADDRMASK, sim_eval, &cpu_unit, SWMASK ('M'))) > 0) fprintf (st, "(undefined) %04o", h->ir); if (h->ir < 04000) fprintf (st, " [%04o]", h->opnd); fputc ('\n', st); /* end line */ } /* end else instruction */ } /* end for */ return SCPE_OK; } Index: src/PDP8/pidp8i.c.in ================================================================== --- src/PDP8/pidp8i.c.in +++ src/PDP8/pidp8i.c.in @@ -29,12 +29,16 @@ #include "pidp8i.h" #include "../gpio-common.h" +#include "pdp8_defs.h" + #include #include // for USB stick searching +#include +#include //// MODULE GLOBALS //////////////////////////////////////////////////// // handle_sing_step() sets this to nonzero and returns a value breaking @@ -213,21 +217,27 @@ size_t get_pidp8i_initial_max_skips (size_t updates_per_sec) { DEVICE *pthrot = find_dev ("INT-THROTTLE"); if (pthrot) { + extern int suppressILS; REG *ptyper = find_reg ("THROT_TYPE", NULL, pthrot); REG *pvalr = find_reg ("THROT_VAL", NULL, pthrot); if (ptyper && pvalr) { uint32 *ptype = ptyper->loc; uint32 *pval = pvalr->loc; size_t ips = 0; switch (*ptype) { case SIM_THROT_MCYC: ips = *pval * 1e6; break; case SIM_THROT_KCYC: ips = *pval * 1e3; break; + case SIM_THROT_SPC: { + suppressILS = 1; + break; + } } if (ips) { + suppressILS = 0; printf("PiDP-8/I initial throttle = %zu IPS\r\n", ips); return ips / updates_per_sec; } } } @@ -241,13 +251,13 @@ // Given all of the PDP-8's internal registers that affect the front // panel display, modify the GPIO thread's LED state values accordingly. // // Also update the LED brightness values based on those new states. -void set_pidp8i_leds (uint32 sPC, uint32 sMA, uint16 sMB, - uint16 sIR, int32 sLAC, int32 sMQ, int32 sIF, int32 sDF, - int32 sSC, int32 int_req, int Pause) +void set_pidp8i_leds (uint32 sPC, uint32 sMA, uint16 sIR, int32 sLAC, + int32 sMQ, int32 sIF, int32 sDF, int32 sSC, int32 int_req, + int Pause) { // Bump the instruction count. This should always be equal to the // Fetch LED's value, but integers are too cheap to get cute here. // // Note that we only update pdis_update directly once in this whole @@ -255,16 +265,22 @@ // working: we want to finish work on the same display even though // it's now called the paint-from display, so it's consistent. display* pd = pdis_update; ++pd->inst_count; - // Rows 0-4, easy cases: single-register LED strings + // Rows 0-4, easy cases: single-register LED strings. + // + // The only non-obvious one is that we use the PDP-8 simulator's IR + // register value for the MB lights due to a quirk in the way the + // register states are set at the time this function is called; MB + // is not passed to us because its value at the call time is bogus. set_pidp8i_row_leds (pd, 0, sPC); set_pidp8i_row_leds (pd, 1, sMA); - set_pidp8i_row_leds (pd, 2, sMB); + set_pidp8i_row_leds (pd, 2, sIR); set_pidp8i_row_leds (pd, 3, sLAC & 07777); set_pidp8i_row_leds (pd, 4, sMQ); + #if 0 // debugging static time_t last = 0, now; if (time(&now) != last) { uint16* pcurr = pd->curr; @@ -293,19 +309,26 @@ if ((inst_type <= 05000) && // it's a memory reference instruction (sIR & 00400)) { // and indirect addressing flag is set set_pidp8i_led (pd, 5, 1); } - // Row 5c: The Fetch LED is bumped once per CPU instruction, as is - // Execute while we're not in STOP state. They're set at different - // times, but they're twiddled so rapidly that they both just become - // a 50% blur in normal operation, so we don't make the CPU core set - // these "on-time." It just doesn't matter. + // Row 5c: The Fetch & Execute LEDs are pulsed once per instruction. + // On real hardware, the pulses don't happen at exactly the same + // time, but we can't simulate that because SIMH handles each CPU + // instruction "whole." When running real code, all we care about + // is that both LEDs are twiddled so rapidly that they both just + // become a 50% blur, mimicking the hardware closely enough. + // + // The exception is that when the CPU is stopped, both LEDs are off, + // because the pulses happen "outside" the STOP state: Fetch before + // and Execute after resuming from STOP. extern int swStop, swSingInst; int running = !swStop && !swSingInst; - if (running) set_pidp8i_led (pd, 5, 2); // Execute - set_pidp8i_led (pd, 5, 3); // Fetch + if (running) { + set_pidp8i_led (pd, 5, 2); // Execute + set_pidp8i_led (pd, 5, 3); // Fetch + } // Row 6a: Remaining LEDs in upper right block pd->curr[6] = 0; if (running) set_pidp8i_led (pd, 6, 7); // bump Run LED if (Pause) set_pidp8i_led (pd, 6, 8); // bump Pause LED @@ -328,17 +351,17 @@ } } //// mount_usb_stick_file ////////////////////////////////////////////// -// Search for a PDP-8 media image in one of the Pi's USB auto-mount -// directories and attempt to ATTACH it to the simulator. +// Search for a PDP-8 media image on a USB device mounted under /media +// and attempt to ATTACH it to the simulator. static void mount_usb_stick_file (int devNo, char *devCode) { char sFoundFile[CBUFSIZE] = { '\0' }; - char sUSBPath[CBUFSIZE]; // will be "/media/usb0" etc + char sDirName[CBUFSIZE]; // will be "/media/DIRNAME" etc char fileExtension[4]; // will be ".RX" etc int i, j; // Build expected file name extension from the first two characters of // the passed-in device code. @@ -357,59 +380,71 @@ // a given floppy image file attached to both RX01 drives, but you *can* // repeatedly re-ATTACH the same floppy image to the first RX01 drive. static char mountedFiles[8][CBUFSIZE]; mountedFiles[devNo][0] = '\0'; - for (i = 0; i < 8 && sFoundFile[0] == '\0'; ++i) { - // search all 8 USB mount points, numbered 0-7 - snprintf (sUSBPath, sizeof (sUSBPath), "/media/usb%d", i); - DIR *pDir = opendir (sUSBPath); - if (pDir) { - struct dirent* pDirent; - while ((pDirent = readdir (pDir)) != 0) { // search all files in directory - if (pDirent->d_name[0] == '.') continue; // dotfiles clutter debug output - - char* pext = strstr (pDirent->d_name, fileExtension); - if (pext && (pext == (pDirent->d_name + strlen (pDirent->d_name) - 3))) { - snprintf (sFoundFile, sizeof (sFoundFile), "%s/%s", - sUSBPath, pDirent->d_name); -#if 0 // debugging - printf("\r\nFound candidate file %s for dev %s, ext *%s...", - sFoundFile, devCode, fileExtension); -#endif - for (j = 0; j < 7; ++j) { - if (strncmp (mountedFiles[j], sFoundFile, CBUFSIZE) == 0) { -#if 0 // debugging - printf("\r\nAlready have %s mounted, slot %d; will not remount.", - sFoundFile, j); -#endif - sFoundFile[0] = '\0'; // don't leave outer loop; keep looking - break; - } - } - if (j == 7) { - // Media image file is not already mounted, so leave while - // loop with path set to mount it - break; - } - } -#if 0 // debugging - else { - printf("\r\nFile %s on %s doesn't match *%s...", - pDirent->d_name, sUSBPath, fileExtension); - } -#endif - } - - closedir (pDir); - } - else { - // Not a Pi or the USB auto-mounting software isn't installed - printf ("\r\nCannot open %s: %s\r\n", sUSBPath, strerror (errno)); - return; - } - } + // Search all directories under /media + DIR *pDir1 = opendir ("/media"); + if (pDir1) { + struct dirent* pDE1; + while ((pDE1 = readdir (pDir1)) != 0) { + if (pDE1->d_type != DT_DIR) continue; + + // Found a directory under /media. Search it for plausibly + // named files given devCode. + snprintf (sDirName, sizeof(sDirName), "/media/%s", pDE1->d_name); + DIR *pDir2 = opendir (sDirName); + if (pDir2) { + struct dirent* pDE2; + while ((pDE2 = readdir (pDir2)) != 0) { // search all files in directory + if (pDE2->d_name[0] == '.') continue; // dotfiles clutter debug output + + char* pext = strstr (pDE2->d_name, fileExtension); + if (pext && (pext == (pDE2->d_name + strlen (pDE2->d_name) - 3))) { + snprintf (sFoundFile, sizeof (sFoundFile), "%s/%s", + sDirName, pDE2->d_name); + #if 0 // debugging + printf("\r\nFound candidate file %s for dev %s, ext *%s...", + sFoundFile, devCode, fileExtension); + #endif + for (j = 0; j < 7; ++j) { + if (strncmp (mountedFiles[j], sFoundFile, CBUFSIZE) == 0) { + #if 0 // debugging + printf("\r\nAlready have %s mounted, slot %d; will not remount.", + sFoundFile, j); + #endif + sFoundFile[0] = '\0'; // don't leave outer loop; keep looking + break; + } + } + if (j == 7) { + // Media image file is not already mounted, so leave while + // loop with path set to mount it + break; + } + } + #if 0 // debugging + else { + printf("\r\nFile %s on %s doesn't match *%s...", + pDE2->d_name, sDirName, fileExtension); + } + #endif + } // end while (pDE2...) + + closedir (pDir2); + } // end if (pDir2) + else { + // USB auto-mounting either doesn't work here or uses + // something other than the /media/DIR/FILE.EXT scheme + // we expect. + printf ("\r\nCannot open %s: %s\r\n", sDirName, strerror (errno)); + return; + } + } // end while (pDE1...) + + closedir(pDir1); + } // end if (pDir1) if (sFoundFile[0]) { // no file found, exit if (access (sFoundFile, R_OK) == 0) { char sAttachCmd[CBUFSIZE] = { '\0' }; snprintf (sAttachCmd, sizeof(sAttachCmd), "%s %s", Index: src/PDP8/pidp8i.h ================================================================== --- src/PDP8/pidp8i.h +++ src/PDP8/pidp8i.h @@ -28,27 +28,28 @@ */ #if !defined(PIDP8I_H) #define PIDP8I_H -#include "pdp8_defs.h" +#include +#include typedef enum { pft_normal, pft_halt, pft_stop, } pidp8i_flow_t; extern char *build_pidp8i_scp_cmd (char* cbuf, size_t cbufsize); -extern int32 get_switch_register (void); +extern int32_t get_switch_register (void); extern size_t get_pidp8i_initial_max_skips (size_t updates_per_sec); -extern pidp8i_flow_t handle_flow_control_switches (uint16* pM, - uint32 *pPC, uint32 *pMA, int32 *pMB, int32 *pLAC, int32 *pIF, - int32 *pDF, int32* pint_req); +extern pidp8i_flow_t handle_flow_control_switches (uint16_t* pM, + uint32_t *pPC, uint32_t *pMA, int32_t *pMB, int32_t *pLAC, int32_t *pIF, + int32_t *pDF, int32_t* pint_req); -extern void set_pidp8i_leds (uint32 sPC, uint32 sMA, uint16 sMB, - uint16 sIR, int32 sLAC, int32 sMQ, int32 sIF, int32 sDF, - int32 sSC, int32 int_req, int Pause); +extern void set_pidp8i_leds (uint32_t sPC, uint32_t sMA, uint16_t sIR, + int32_t sLAC, int32_t sMQ, int32_t sIF, int32_t sDF, int32_t sSC, + int32_t int_req, int Pause); #endif // !defined(PIDP8I_H) ADDED src/asm/asr33-rim-loader.pal Index: src/asm/asr33-rim-loader.pal ================================================================== --- /dev/null +++ src/asm/asr33-rim-loader.pal @@ -0,0 +1,49 @@ +/ asr33-rim-loader.pal +/ +/ Load paper tapes in RIM format from a Teletype Model 33 ASR's paper +/ tape reader. By contrast with the loader in hs-rim-loader.pal, this +/ is the low-speed paper tape RIM loader. +/ +/ This is the RIM loader printed on the front panel of the PDP-8/I. The +/ RIM loader in hs-rim-loader.pal (which is translated to boot/1.script +/ at build time) is similar to this one, but it works on the DEC high- +/ speed paper tape reader peripheral, which is where the PiDP-8/I's +/ automatic paper tape mounting feature attaches any tape images it +/ finds via USB. +/ +/ Therefore, you cannot use this RIM loader if you want to use the +/ PiDP-8/I's automatic media attachment mechanism. It is included +/ here mainly for documentation at the moment, but also in case +/ someone works out a way to build the simulator so that it can +/ conditionally load tapes from an emulated ASR-33. +/ +/ Raw disassembly done from the octal values by Bernhard Baehr's PDP-8/E +/ Simulator. Comments and labels by Warren Young. Original copyright +/ by Digital Equipment Corporation: this program appeared in DEC manuals +/ and was printed on the front panel of every PDP-8/I. +/ +/ SIMH: echo Installing the RIM loader for the ASR 33 paper tape reader... +/ SIMH: set df disabled + + *7755 + HLT / nonstandard: auto-halt on SIMH startup + + / normal RIM loader entry point, 7756 +NEXT, KCC / clear PTR flag +RBYTE1, KSF / loop until PTR is ready + JMP RBYTE1 + KRB / read first byte in + CLL RTL / shift it left by 2, clearing link as well + RTL / and 2 more again + SPA / if top bit of AC is set... + JMP RBYTE1 / ...AC contains the addr's value + RTL / ...else rotate it another 2 positions +RBYTE2, KSF / wait for next character + JMP RBYTE2 + KRS / read second byte in + SNL / if link is set... + DCA I BYTE / ...it's the value's address +GOTVAL, DCA BYTE / ...else it's the value at that addr + JMP NEXT / go round again, getting next value from PTR +BYTE, 0 +$ ADDED src/asm/hs-rim-loader.pal Index: src/asm/hs-rim-loader.pal ================================================================== --- /dev/null +++ src/asm/hs-rim-loader.pal @@ -0,0 +1,47 @@ +/ hs-rim-loader.pal +/ +/ Load paper tapes in RIM format from the DEC high-speed PTR. +/ +/ This routine differs from that printed in DEC's manuals in that it +/ starts with a HLT instruction so that when you [re]start SIMH with +/ IF=1, running this program, the processor auto-halts, giving the user +/ the opportunity to attach a paper tape via DF or ATTACH, then start +/ the processor at 7756 as normal. +/ +/ The RIM loader code printed on the front panel of the PDP-8/I differs +/ because it is for the the paper tape reader built into a Teletype +/ Model 33 ASR. See asr33-rim-loader.pal for that other implementation, +/ including more information about it. +/ +/ Raw disassembly done from the octal values by Bernhard Baehr's PDP-8/E +/ Simulator. Comments and labels by Warren Young. Original copyright +/ by Digital Equipment Corporation: this program appeared in many DEC +/ manuals printed throughout the PDP-8 era variants of which were made +/ (and thus documented) from 1965 to 1979. +/ +/ SIMH: echo Installing the RIM loader for the DEC high-speed tape reader... +/ SIMH: set df disabled +/ SIMH: set cpu noidle + + *7755 + HLT / nonstandard: auto-halt on SIMH startup + + / normal RIM loader entry point, 7756 + RFC / clear PTR flag +RBYTE1, RSF / loop until PTR is ready + JMP RBYTE1 + RCC / read first byte in + CLL RTL / shift it left by 2, clearing link as well + RTL / and 2 more again + SPA / if top bit of AC is set... + JMP GOTVAL / ...AC contains the addr's value + RTL / ...else rotate it another 2 positions +RBYTE2, RSF / wait for next character + JMP RBYTE2 + RCC / read second byte in + SNL / if link is set... + DCA I BYTE / ...it's the value's address +GOTVAL, DCA BYTE / ...else it's the value at that addr + JMP RBYTE1 / go round again, getting next value from PTR +BYTE, 0 +$ ADDED src/cc8/GPL3.txt Index: src/cc8/GPL3.txt ================================================================== --- /dev/null +++ src/cc8/GPL3.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. ADDED src/cc8/Makefile.in Index: src/cc8/Makefile.in ================================================================== --- /dev/null +++ src/cc8/Makefile.in @@ -0,0 +1,46 @@ +######################################################################## +# Makefile.in - Processed by autosetup's configure script to generate +# an intermediate GNU make(1) file for building the PiDP-8/I software +# from within its src/cc8/ subdirectory. +# +# The resulting Makefile will redirect simple "make" calls to the top +# level as well as the major top-level targets (e.g. "make clean") but +# purposefully will not redirect anything like an installation or "run +# the system" type target. Its only purpose is to help out those who +# are working on CC8 from within this directory. If you need to work +# on the wider system, do it from the project's top level. +# +# If you are seeing this at the top of a file called Makefile and you +# intend to make edits, do that in Makefile.in. Saying "make" will then +# re-build Makefile from that modified Makefile.in before proceeding to +# do the "make" operation. +# +# Copyright © 2017 Warren Young +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +all clean ctags distclean tags reconfig: + cd @builddir@; make $@ ADDED src/cc8/README.md Index: src/cc8/README.md ================================================================== --- /dev/null +++ src/cc8/README.md @@ -0,0 +1,462 @@ +# A Minimal Implementation of C for the DEC PDP-8 Processor + +## Introduction + +The C language and its derivatives are now the industry standard for the +development of operating systems and utilities. The language has evolved +significantly since its initial specification in 1972. At this time, the +PDP-7 was used for the initial implementation and the compiler ported to +a number of other systems including the PDP-11. Also, the first +glimmerings of Unix appeared following a re-write of the assembly +language version in C and the rest is of course history. The PDP-8 was +introduced by DEC in 1965 at the same time as the PDP-7 with the +intention of being a small and cheap processor that could be used in a +variety of environments. From this simple machine, the modern desktop +device has evolved which I am using to type this document. Nonetheless, +far from fading into obscurity, there is a very active group of +enthusiasts who have looked to implementing the PDP-8 on modern hardware +and the thanks to Oscar Vermuelen and others, we can all have a PDP8/I +to play with. With this in mind, I thought it was time to have a modern +language compiler running on the PDP-8 which as far as I can tell, the +last native compiler developed for the PDP-8 was Pascal in 1979 by Heinz +Stegbauer. In more recent times, one cross-compiler has been developed +by Vince Slyngstad and updated by Paolo Maffei based on Ron Cain’s +Small-C using a VM approach. [This code][sms] is most certainly worth +examining, and I am delighted to acknowledge this work as I have used +some of the C library code in this project. + +[sms]: http://so-much-stuff.com/pdp8/C/C.php + +Finally, I would refer the reader to Fabrice Bellard’s OTCC. It is this +bit of remarkable software that suggested that there may be a chance to +implement a native PDP-8 compiler. + +Developing a native compiler for the PDP-8 is not an easy task as this +processor has a very limited address space and no hardware stack. And, +although the option exists to write the whole thing in assembly language +as has been the case for Pascal and Algol, this project has explored the +option of writing the compiler itself in C. To this end, 2 compilers +have been written. Firstly, a cross-compiler based again on Ron Cain’s +Small-C which is used to compile the native OS/8 compiler and library. +As yet, the native compiler has rather limited functionality and will +not compile itself. The cross-compiler will compile itself but produces +an enormous (28K) assembler file which cannot be run on the PDP-8. + + + +# The Cross-Compiler + +The code for this is in the `cross` subdirectory, and is built along +with the top-level PiDP-8/I software. When installed, it is in your +`PATH` as `cc8`. + +The CC8 cross-compiler is based upon Ron Cain’s famous Small-C compiler. +The reader is directed to the extensive documentation available on the +web. + +The key file is the PDP-8 code generator in `code8.c` which emits SABR — +Symbolic Assembler for Binary Relocatable programmes — assembly code. +SABR is normally used as the second pass of the OS/8 FORTRAN II system. + +When you use the cross-compiler on a POSIX type system such as the +Raspbian PiDP-8/I environment, the resulting `*.sb` files will have +LF-only line endings, but OS/8 expects CR+LF line endings. The `txt2ptp` +utility program included with the PiDP-8/I distribution will +automatically do that conversion for you when making a SIMH paper tape +image file, which you can then read into the OS/8 environment. + +The cross-compiler has some non-standard features to enable the +interface between the main programme and the C library. This constitutes +a compile time linkage system to allow for standard and vararg functions +to be called in the library. + +Several of the C programs in this distribution `#include ` which +inserts an assembly language initialization routine into the program at +that point using the `#asm` inline assembly feature. This file is +symlinked into each directory that has a `*.c` file needing it since CC8 +doesn't have an include path feature, and it must be in the current +directory in any case when using the OS/8 version of CC8. + +The `init.h` initialization routine defines some low-level subroutines, +initializes the environment for the programs, and calls into the LIBC +initialization code. This file was copied to the OS/8 boot disk as +`DSK:INIT.H` unless you gave `--disable-os8-cc8` when configuring the +PiDP-8/I software. + +The file `include/libc.h` is likewise copied to `DSK:LIBC.H`. It defines +the mappings between the familiar C library routine names and their +underlying implementation names. + +The linking loader determines the core layout for the built programs. +Most commonly, it uses this scheme: + +**Field 0:** FOTRAN library utility functions and OS/8 I/O system + +**Field 1:** The programme’s runtime stack/globals/literals + +**Field 2:** The programme's executable code + +**Field 3:** The LIBC library code + +Since this memory layout applies to the phases of the CC8 compiler as +well, this means that each phase uses approximately 16 kWords of core. + + + +## The Native Compiler + +This compiler is supplied in both source and binary forms as part of the +PiDP-8/I software distribution. + +We ship pre-built binaries to avoid a chicken-and-egg problem: the +binaries require a working OS/8 environment to be built, but when the +PiDP-8/I build system goes to build the bootable OS/8 media, it expects +to have the OS/8 CC8 binaries at hand so it can copy them to the RK05 +disk it is building. It's trivial to deal with that on our development +systems, since we normally have a working `os8v3d-*.rk05` disk set from +the previous build sitting around to bootstrap the process, so we break +the cycle at that point rather than do a two-stage RK05 bootstrap build +on end-user systems. + +These pre-built binaries are saved as `media/os8/subsys/cc8.tu56` by the +`tools/cc8-tu56-update` script. Basically, that script uses the +cross-compiler to produce SABR assembly files for each stage of the OS/8 +CC8 compiler, which it then copies into the OS/8 environment, then it +assembles, links, and saves the result as `CC*.SV`: + +2. `c8.c` → `c8.sb` → `CC.SV`: The compiler driver: accepts + the input file name from the user, and calls the first proper + compiler stage, `CC1`. Should we add a preprocessor feature, this + driver will call it before calling `CC1`. + +2. `n8.c` → `n8.sb` → `CC1.SV`: The parser/tokeniser section + of the compiler. + +3. `p8.c` → `p8.sb` → `CC2.SV`: The token to SABR code + converter section of the compiler. + +4. `libc.c` → `libc.sb` → `LIBC.RL`: The C library linked to + any program built with CC8, including the stages above, but also to + your own programs. + +If you are not changing the OS/8 CC8 source code, you needn't run the +`cc8-tu56-update` script or build the OS/8 version of CC8 by hand. + +The PiDP-8/I build system's OS/8 RK05 media build script copies those +files and the other files required for building C programs under OS/8 to +the appropriate OS/8 volumes: `CC*.SV` on `SYS:`, and everything else on +`DSK:`. + +Input programs should go on `DSK:`. Compiler outputs are also placed on +`DSK:`. + +To try it out: + +Boot OS/8 within the PiDP-8/I environment as you normally would. If +you're at the Linux command prompt within the PiDP-8/I source tree, you +can start it most easily with a `make run` command. + +With the OS/8 environment running, you can enter a C programme in lower +case via the editor, but before doing that, try building a copy of one +of the example programs: + + .R CC ⇠ compiler front end + >ps.c ⇠ takes name of C program; creates CC.SB + .COMP CC ⇠ compile SABR output of CC8 to CC.RL + +Link and run it with: + + .R LOADER + *CC,LIBC/G ⇠ CC.RL + pre-built LIBC.RL = runnable program; /G = "go" + +These steps are wrapped up into the `CC.BI` BATCH file: + + .EXE CC.BI ⇠ must specify .BI to avoid running CC.SV instead + >ps.c ⇠ builds, links, and runs it + +That demo is particularly interesting. It generates Pascal’s triangle +without using factorials, which are a bit out of range for 12 bits! + + + +## GOVERNMENT HEALTH WARNING + +**You are hereby warned**: The native OS/8 compiler does not contain any +error checking whatsoever. If the source files contain an error or you +mistype a build command, you may get: + +* A runtime crash in the compiler +* SABR assembly output that won't assemble +* Output that assembles but won't run correctly + +Rarely will any of these failure modes give any kind of sensible hint as +to the cause. OS/8 CC8 cannot afford the hundreds of kilobytes of error +checking and text reporting that you get in a modern compiler like GCC +or Clang. That would have required a roomful of core memory to achieve +on a real PDP-8. Since we're working within the constraints of the old +PDP-8 architecture, we only have about 3 kWords to construct the parse +result, for example. + +In addition, the native OS/8 compiler is severely limited in code space, +so it does not understand the full C language. It is less functional +than K&R C 1978; we do not have a good benchmark for what it compares to +in terms of other early C dialects, but we can sum it up in a single +word: "primitive." + +Nonetheless, our highly limited C dialect is Turing complete. It might +be better to think of it as a high-level assembly language that +resembles C rather than as "C" proper. + + + +### Features and Limitations of the Cross-Compiler + +The features of the cross-compiler are basically that of Small-C itself, +the primary difference being in the PDP-8 SABR code generator, which +doesn't affect its C language support. + +A good approximation is K&R C (1978) minus: + +* `struct` and `union` + +* function pointers + +* `float` and `long` + + + +### Features of the OS/8 CC8 Compiler + +The OS/8 version of CC8 is missing many features relative to the +cross-compiler, and much more compared to modern C. Before we list +those limitations, here is what is known to work: + +1. **Local and global variables** + +1. **Pointers,** within limitations given in the following section. + +1. **Functions:** Parameter lists must be declared in K&R form: + + int foo (a, b) + int a, b; + { + ... + } + +1. **Recursion:** See [`FIB.CC`][fib] for an example of this. + +[fib]: https://tangentsoft.com/pidp8i/doc/src/cc8/examples/fib.c + +1. **Simple arithmetic operators:** `+`, `-`, `*`, `/`, etc. + +1. **Bitwise operators:** `&`, ¦, `~` and `!` + +1. **Simple comparison operators:** False expressions evaluate as 0 and + true as -1 in twos complement form, meaning all 1's in binary form. + See the list of limitations below for the operators excluded by our + "simple" qualifier. + +1. **A few 2-character operators:** `++`, `--` (postfix only) and `==`. + +1. **Limited library:** See `libc.h` for allowed libc functions, of + which there are currently 31, including: + + 1. **A subset of stdio:** + + * `fopen` is implemented as + + void fopen(char *filename, char *mode) + + The filename must be upper case. Mode is either "w" or "r". + + * Only 1 input file and 1 output may be open at any one time + + * `fclose()` only closes the output file. + + * Call `fopen` to open a new input file. The current file does + not need to be closed. + + * `fprintf`, `fputc`, and `fputs` are as expected. + + * `fgets` is implemented. It will read and retain CR/LF. It + returns a null string on EOF. + + * `fscanf` is not implemented. Read a line with `fgets()` and + then call `sscanf` on it. + + * `feof` is not implemented; `fgetc` and `fgets` will return a + null on EOF. + + 1. **printf:** See `libc.c` for the allowed format specifiers: + `%d`, `%s` etc. Length and width.precision formatting is supported. + + There are many limitations in this library relative to Standard C or + even K&R C, which are documented below. + +1. **Limited structuring constructs:** `if`, `while`, `for`, etc. are + supported, but they may not work as expected when deeply nested or + in long `if/else if/...` chains. + + + +### Known Limitations of the OS/8 CC8 Compiler + +The OS/8 compiler has these known limitations relative to [those of the +cross-compiler](#cross-fl): + +1. The language is typeless in that everything is a 12 bit integer and + any variable/array can interpreted as `int`, `char` or pointer. All + variables and arrays must be declared as `int`. The return type may + be left off of a function's definition; it is implicitly `int` in + all cases, since `void` is not supported. + +2. There must be an `int main()` which must be the last function in the + single input C file. + +3. We do not yet support separate compilation of multiple C modules + that get linked together. You can produce relocatable libraries in + OS/8 `*.RL` format and link them with the OS/8 LOADER, but because + of the previous limitation, only one of these can be written in C. + +4. Unlike the CC8 cross-compiler, the OS/8 compiler ignores all C + preprocessor directives: `#define`, `#ifdef`, `#include`, etc. This + even includes inline assembly via `#asm`! + + One day, we may add a preprocessor called by the `CC.SV` driver + program, but not today. + + This means you cannot use `#include` directives to string multiple C + modules into a single program. + + If that then makes you wonder how the OS/8 compiler looks up the + stock library functions defined in `libc.h` — note that I've + resisted using the word "standard" here, for they are anything but + that in the Standard C sense — it is that the entry point mappings + declared in `libc.h` are hard-coded into the `CC2` compiler stage, + implemented in `p8.c`. + + Similarly, the program initialization code defined in `init.h` is + inserted into the program directly by the compiler rather than being + pulled in via the preprocessor. + + Both of these header files must be included when building with the + cross-compiler. The examples have these `#include` statements + stripped out as they are copied to the OS/8 RK05 disk during the + build process. This is done by `bin/cc8-to-os8`, a tool you may find + use for yourself if you use both compilers on a single source + program. + + If you have a program that is compiled using both the cross-compiler + and the OS/8 compiler, you may wish to use `#include` statements, + since the cross-compiler does process them. + +5. Variables are implicitly `static`, even when local. + +6. Arrays may only be single indexed. See `PS.CC` for an example. + +7. The compiler does not yet understand how to assign a variable's + initial value as part of its declaration. This: + + int i = 5; + + must instead be: + + int i; + i = 5; + +8. There is no `&&` nor ¦¦. Neither is there support for + complex relational operators like `>=` nor even `!=`. Abandon all + hope for complex assignment operators like `+=`. + + Most of this can be worked around through clever coding. For + example, this: + + if (i != 0 || j == 5) + + could be rewritten to avoid both missing operators as: + + if (!(i == 0) | (j == 5)) + + because a true result in each subexpression yields -1 per the + previous point, which when bitwise OR'd together means you get -1 if + either subexpression is true, which means the whole expression + evaluates to true if either subexpression is true. + + If the code you were going to write was instead: + + if (i != 0 || j != 5) + + then the rewrite is even simpler owing to the rules of [Boolean + algebra](https://en.wikipedia.org/wiki/Boolean_algebra): + + if (!(i == 0 & j == 5)) + + These rules mean that if we negate the entire expression, we get the + same truth table if we flip the operators around and swap the + logical test from OR to AND, which in this case converts the + expression to a form that is now legal in our limited C dialect. All + of this comes from the Laws section of the linked Wikipedia article; + if you learn nothing else about Boolean algebra, you would be well + served to memorize those rules. + +9. `atoi` is non-standard: `int atoi(char *, int *)`, returning + the length of the numeric string. + +10. `scanf` is not implemented; use `gets` then `sscanf` + +11. Dereferencing parenthesized expressions does not work: `*()` + +12. The stack, which includes all globals and literals, is only 4 kwords. + Stack overflow is not detected. Literals are inlcuded in this due + to a limitation in the way `COMMN` is implemented in SABR. + +13. There is no argument list checking, not even for standard library + functions. + +14. `do/while` loops are parsed, but the code is not properly generated. + Regular `while` loops work fine, however. + +15. `switch` doesn't work. + + + +### Known Bugs in the OS/8 CC8 Compiler + +1. Binary file I/O is not always reliable. You are strongly encouraged + to limit I/O to text files. + +2. Don’t forget to handle form feed. See `c8.c`. + +3. For some obscure reason, always open the input file first, then the + output file. I suspect a fault in `libc.c`, which you are welcome to + fix, keeping in mind that we're using every trick in the book to fit + as much functionality in as we currently do. It may not be possible + to make this as reliable as modern C programmers expect. + + +## Conclusion + +This is a somewhat limited manual which attempts to give an outline of a +very simple compiler for which I apologise as the source code is obscure +and badly commented. However, the native OS/8 compiler/tokeniser +(`n8.c`) is only 600 lines which is a nothing in the scale of things +these days. However, I hope this project gives some insight into +compiler design and code generation strategies to target a most +remarkable computer. I would also like to give credit to the builders of +OS/8 and in particular the FORTRAN II system which was never designed to +survive the onslaught of this kind of modern software. + +Don’t expect too much! This compiler will not build this week’s bleeding +edge kernel. But, it may be used to build any number of useful utility +programs for OS/8. + + +## License + +This document is under the [GNU GPLv3 License][gpl], copyright © May, +June, and November 2017 by [Ian Schofield][ian], with assorted updates +by [Warren Young][wy] in 2017. + +[gpl]: https://www.gnu.org/licenses/gpl.html +[ian]: mailto:Isysxp@gmail.com +[wy]: https://tangentsoft.com/ ADDED src/cc8/cross/README.md Index: src/cc8/cross/README.md ================================================================== --- /dev/null +++ src/cc8/cross/README.md @@ -0,0 +1,16 @@ +This directory contains the sources for the CC8 cross-compiler, which is +based on Ron Cain's Small-C system. + +It is built by the top-level build system as `bin/cc8` and is installed +to `$prefix/bin/cc8`. + +Call it as: + + cc8 myfile.c + +The compiler does not have any consequential command line options. + +The output file is `myfile.s` which is in SABR assembly code, intended +to be assembled within the PiDP-8/I OS/8 environment. See the `test` +subdirectory and [the top-level README][/doc/trunk/cc8/README.md] for +further details. ADDED src/cc8/cross/code8.c Index: src/cc8/cross/code8.c ================================================================== --- /dev/null +++ src/cc8/cross/code8.c @@ -0,0 +1,1126 @@ +/* + * This file is part of the CC8 cross-compiler. + * + * The CC8 cross-compiler is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * The CC8 cross-compiler is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the CC8 cross-compiler as ../GPL3.txt. If not, see + * . + */ + +#define unix + +#include +#include "defs.h" +#include "data.h" +#include + +/* Define ASNM and LDNM to the names of the assembler and linker + respectively */ + +/* + * Some predefinitions: + * + * INTSIZE is the size of an integer in the target machine + * BYTEOFF is the offset of an byte within an integer on the + * target machine. (ie: 8080,pdp11 = 0, 6809 = 1, + * 360 = 3) + * This compiler assumes that an integer is the SAME length as + * a pointer - in fact, the compiler uses INTSIZE for both. + */ +#define INTSIZE 1 +#define BYTEOFF 0 + +/* + * print all assembler info before any code is generated + * + */ +header () +{ + outstr ("/ Small C PDP8 Coder (1.0:27/1/99)"); + nl(); + FEvers(); + nl (); + ol ("OPDEF ANDI 0400"); + ol ("OPDEF TADI 1400"); + ol ("OPDEF ISZI 2400"); + ol ("OPDEF DCAI 3400"); + ol ("OPDEF JMSI 4400"); + ol ("OPDEF JMPI 5400"); + ol ("OPDEF MQL 7421"); + ol ("OPDEF MQA 7701"); + ol ("OPDEF MQO 7501"); + ol ("OPDEF SWP 7521"); + ol ("OPDEF CDF1 6211"); + ol ("OPDEF CDF0 6201"); + ol ("OPDEF RIF 6224"); + ol ("OPDEF CAF0 6203"); + ol ("OPDEF BSW 7002"); + ol ("OPDEF CAM 7621"); + ol ("/"); + return 0; +} + +nl () +{ + outbyte (EOL); +/* outbyte (10); */ + return 0; +} +initmac() +{ + defmac("cpm\t1"); + defmac("I8080\t1"); + defmac("RMAC\t1"); + defmac("smallc\t1"); + return 0; +} + +galign(t) +int t; +{ + return(t); +} + +/* + * return size of an integer + */ +intsize() { + return(INTSIZE); +} + +/* + * return offset of ls byte within word + * (ie: 8080 & pdp11 is 0, 6809 is 1, 360 is 3) + */ +byteoff() { + return(BYTEOFF); +} + +/* + * Output internal generated label prefix + */ +olprfix() { + ot("CC"); + return 0; +} + +/* + * Output a label definition terminator + */ +col () +{ + outbyte (','); + return 0; +} + +/* + * begin a comment line for the assembler + * + */ +comment () +{ + outbyte ('/'); + return 0; +} + +/* + * Emit user label prefix + */ +prefix () +{ + return 0; +} + +/* Stkbase output stack base->literals =stkp+2 ... ie 202(8) =130(10) + sizeof(globals) */ +stkbase() +{ + ot("GBL"); + return 0; +} + +/* + * print any assembler stuff needed after all code + * + */ +trailer () +{ +// ot("\tENTRY "); +// outbyte('M'); +// printlabel (litlab); +// nl(); + outbyte('M'); + printlabel (litlab); + col(); + ot("\t0"); + nl(); + ol("\tCDF1"); + ot("\tTAD L"); + printlabel (litlab); + nl(); + ol ("\tSNA CLA / Any literals to push?"); + ot ("\tJMP I M"); + printlabel (litlab); + nl(); + ot("\tTAD X"); + printlabel (litlab); + nl(); + ol ("\tDCA JLC"); + outbyte('D'); + printlabel (litlab); + col(); + ol("CDF0"); + ot("\tTADI JLC"); + nl(); + ol ("\tJMSI PSH"); + ol ("\tCLA"); + ol ("\tISZ JLC"); + ot("\tISZ L"); + printlabel (litlab); + nl(); + ot("\tJMP D"); + printlabel (litlab); + nl(); + ot ("\tJMP I M"); + printlabel (litlab); + nl(); + ol("CCEND,\t0"); + ol ("END"); + return 0; +} + + +/* + * function prologue + */ +prologue (sym) +char *sym; +{ + return 0; +} + +/* + * text (code) segment + */ +gtext () +{ +/* ol ("cseg"); */ + return 0; +} + +/* + * data segment + */ +gdata () +{ +/* ol ("dseg"); */ + return 0; +} + +/* + * Output the variable symbol at scptr as an extrn or a public + */ +ppubext(scptr) char *scptr; { + if (scptr[STORAGE] == STATIC) return 0; +// ot (scptr[STORAGE] == EXTERN ? "extrn\t" : "public\t"); +// prefix (); +// outstr (scptr); +// nl(); + return 0; +} + +/* + * Output the function symbol at scptr as an extrn or a public + */ +fpubext(scptr) char *scptr; { +/* if (scptr[STORAGE] == STATIC) return; +// ot (scptr[OFFSET] == FUNCTION ? "public\t" : "extrn\t"); +// prefix (); +// outstr (scptr); +// nl (); */ + return 0; +} + +/* + * Output a decimal number to the assembler file + */ +onum(num) int num; { + outdec(num); /* pdp11 needs a "." here */ + return 0; +} + + +/* + * fetch a static memory cell into the primary register +getmem (sym) +char *sym; +{ + int adr; + ol ("\tCLA"); + immd3 (); + adr=glint(sym)+128; + onum(glint(sym)+128); + nl(); + ol("\tDCA JLC"); + ol("\tTADI JLC"); +}*/ + +getmem (sym) +char *sym; +{ + int adr; + ol ("\tCLA"); + immd4 (); + adr=glint(sym)+128; + onum(glint(sym)+128); + nl(); + return 0; +} +/* + * fetch a static memory cell into the primary register (pre-increment*/ + +getincmem (sym) +char *sym; +{ + int adr; + ol ("\tCLA"); + adr=glint(sym)+128; + ot ("\tISZI ("); + onum(adr); + nl(); + immd4 (); + onum(adr); + nl(); + return 0; +} + + +/* + * fetch the address of the specified symbol into the primary register + * + */ +getloc (sym) +char *sym; +{ + ol("\tCLA"); + ol("\tTAD STKP"); + if (sym[STORAGE] == LSTATIC) { + immd3 (); + printlabel(-1-glint(sym)); + nl(); + } else { + if (stkp-glint(sym)==0) outstr("/"); + immd3 (); + outdec (stkp-glint(sym)); + nl (); + } + return 0; +} + +/* + * store the primary register into the specified static memory cell + * + +putmem (sym) +char *sym; +{ + ol("\tMQL"); + immd3 (); + onum(glint(sym)+128); + nl(); + ol("\tDCA JLC"); + ol("\tMQA"); + ol("\tDCAI JLC"); + ol("\tTADI JLC"); +} +*/ + +putmem (sym) +char *sym; +{ + ot("\tDCAI ("); + onum(glint(sym)+128); + nl(); + immd4 (); + onum(glint(sym)+128); + nl(); + return 0; +} + +/* + * store the specified object type in the primary register + * at the address on the top of the stack + * + */ +putstk (typeobj) +char typeobj; +{ + ol("\tJMSI PTSK"); + stkp = stkp + INTSIZE; + return 0; +} + +/* + * fetch the specified object type indirect through the primary + * register into the primary register + * + */ +indirect (typeobj) +char typeobj; +{ + ol("\tDCA JLC"); +/* ol("\tCDF1"); */ + ol("\tTADI JLC"); + return 0; +} + +/* + * fetch the specified object type indirect through the primary + * register into the primary register (pre-increment) + * + */ +incdirect (typeobj) +char typeobj; +{ + ol("\tDCA JLC"); + ol("\tISZI JLC"); + ol("\tTADI JLC"); + return 0; +} + + +/* + * swap the primary and secondary registers + * + */ +swap () +{ + ol ("\tSWP"); + return 0; +} +/* +* Clear primary reg +*/ +cpri() +{ + ol("\tCLA"); + return 0; +} +/* + * print partial instruction to get an immediate value into + * the primary register + * + */ +immed () +{ + ol ("\tCLA"); + ot ("\tTAD ("); + return 0; +} +immd2 () +{ + ol ("\tCLA"); + ot ("\tTAD "); + return 0; +} +immd3 () +{ + ot ("\tTAD ("); + return 0; +} + +immd4 () +{ + ot("\tTADI ("); + return 0; +} +/* + * push the primary register onto the stack + * + */ +gpush () +{ + ol ("\tJMSI PSH"); + stkp = stkp - INTSIZE; + return 0; +} + +/* + * pop the top of the stack into the secondary register + * + */ +gpop () +{ + ol ("\tJMSI POP"); + stkp = stkp + INTSIZE; + return 0; +} + +/* + * swap the primary register and the top of the stack + * + */ +swapstk () +{ + ol ("\tMQL"); + gpop(); + ol ("\tSWP"); + gpush(); + ol ("\tSWP"); + return 0; +} + +/* + * call the specified subroutine name + * varag is allowed for libc functions using a v prefix. In this case, the arg count+1 is pushed onto the stack as well. + * For the actual routine, the declaration should be a single arg eg printf(int args) in this case, the value of args is the count and &args-args point to the first arg in the caller's list. + */ +gcall (sname,nargs) +char *sname; +int *nargs; +{ + char tm[10]; + + if (strstr(sname,"vlibc")) { + immed(); + sname++; + outdec(*nargs); + outstr("\t/ PUSH ARG COUNT"); + nl(); + ol("\tJMSI PSH"); + stkp = stkp - INTSIZE; + (*nargs)++; + } + if (strstr(sname,"libc")) + { + strcpy(tm,sname); + immed(); + outstr(tm+4); + nl(); + ol("\tMQL"); + ol("\tCALL 1,LIBC"); + ol("\tARG STKP"); + ol("\tCDF1"); /* Make sure DF is correct */ + return 0; + } + ol("\tCPAGE 2"); + ol("\tJMSI PCAL"); + ot ("\t"); + outstr (sname); + nl (); + return 0; +} + +stri() +{ + ol("\tDCAI 10"); + return 0; +} +iinit() +{ + ol("\tCIA;CMA"); + ol("\tDCA 10"); + return 0; +} + +/* + * return from subroutine + * + */ +gret (sym) +char *sym; +{ + ol ("\tJMPI POPR"); + return 0; +} + +/* + * perform subroutine call to value on top of stack + * + */ +callstk () +{ + immed (); + outstr ("$+5"); + nl (); + swapstk (); + ol ("pchl"); + stkp = stkp + INTSIZE; + return 0; +} + +/* + * jump to specified internal label number + * + */ +jump (label) +int label; +{ + ot ("\tJMP\t"); + printlabel (label); + nl (); + return 0; +} + +/* + * test the primary register and jump if false to label + * + */ +testjump (label, ft) +int label, + ft; +{ + if (ft) + ol ("\tSZA"); + else + ol ("\tSNA"); + jump (label); + return 0; +} + +casejump() +{ + ol("\tTAD TMP"); + ol("\tSNA CLA"); + return 0; +} +/* + * print pseudo-op to define a byte + * + */ +defbyte () +{ + ot ("\t"); + return 0; +} + +/* + * print pseudo-op to define storage + * + */ +defstorage () +{ + ot ("COMMN\t"); + return 0; +} + +/* + * print pseudo-op to define a word + * + */ +defword () +{ + ot ("\t"); + return 0; +} + +/* + * modify the stack pointer to the new value indicated + * + */ +modstk (newstkp) +int newstkp; +{ + int k; + + k = galign(stkp-newstkp); + if (k == 0) + return (newstkp); + if (k>0 && k<5) { + while (k--) ol ("\tISZ STKP"); + return (newstkp); + } + ol ("\tMQL"); + immd3 (); + outdec (k); + nl (); + ol ("\tTAD STKP"); + ol ("\tDCA STKP"); + swap (); + return (newstkp); +} + +/* + * multiply the primary register by INTSIZE + */ +gaslint () +{ + return 0; +} + +/* + * divide the primary register by INTSIZE + */ +gasrint() +{ + return 0; +} + +/* + * Case jump instruction + */ +gjcase() { + ol ("\tCIA"); + ol ("\tDCA TMP"); + return 0; +} + +/* + * add the primary and secondary registers + * if lval2 is int pointer and lval is not, scale lval + */ +gadd (lval,lval2) int *lval,*lval2; +{ +/* if (lval==0) ol("\tCIA");*/ + ol("\tDCA JLC"); + ol("\tJMSI POP"); + ol("\tMQA"); + ol("\tTAD JLC"); + stkp = stkp + INTSIZE; + return 0; +} + +/* + * subtract the primary register from the secondary + * + */ +gsub () +{ + ol("\tCIA"); + ol("\tDCA JLC"); + ol("\tJMSI POP"); + ol("\tMQA"); + ol("\tTAD JLC"); + stkp = stkp + INTSIZE; + return 0; +} + +/* + * multiply the primary and secondary registers + * (result in primary) + * + */ +gmult () +{ + ol("\tDCA JLC"); + ol("\tJMSI POP"); + ol("\tMQA"); + ol("\tCALL 1,MPY"); + ol("\tARG JLC"); + ol("\tCDF1"); + stkp = stkp + INTSIZE; + return 0; +} + +/* + * divide the secondary register by the primary + * (quotient in primary, remainder in secondary) + * + */ +gdiv () +{ + ol("\tDCA JLC"); + ol("\tJMSI POP"); + ol("\tMQA"); + ol("\tCALL 1,DIV"); + ol("\tARG JLC"); + ol("\tCDF1"); + stkp = stkp + INTSIZE; + return 0; +} + +/* + * compute the remainder (mod) of the secondary register + * divided by the primary register + * (remainder in primary, quotient in secondary) + * + */ +gmod () +{ + ol("\tDCA JLC"); + ol("\tJMSI POP"); + ol("\tMQA"); + ol("\tCALL 1,DIV"); + ol("\tARG JLC"); + ol("\tCALL 1,IREM"); + ol("\tARG 0"); + ol("\tCDF1"); + stkp = stkp + INTSIZE; + return 0; +} + +/* + * inclusive 'or' the primary and secondary registers + * + */ +gor () +{ + ol("\tJMSI POP"); + ol("\tMQA"); + stkp = stkp + INTSIZE; + return 0; +} + +/* + * exclusive 'or' the primary and secondary registers + * + */ +gxor () +{ + gpop(); + gcall ("?xor"); + return 0; +} + +/* + * 'and' the primary and secondary registers + * + */ +gand () +{ + ol("\tDCA JLC"); + ol("\tJMSI POP"); + ol("\tMQA"); + ol("\tAND JLC"); + stkp = stkp + INTSIZE; + return 0; +} + +/* + * arithmetic shift right the secondary register the number of + * times in the primary register + * (results in primary register) + * + */ +gasr () +{ + int lbl; + + lbl=getlabel(); + ol("\tCIA"); + ol("\tJMSI POP"); + gnlabel(lbl); + ol("\tSWP"); + ol("\tCLL RAR"); + ol("\tSWP"); + ol("\tIAC"); + ol("\tSZA"); + jump(lbl); + ol("\tSWP"); + stkp = stkp + INTSIZE; + return 0; +} + +/* + * arithmetic shift left the secondary register the number of + * times in the primary register + * (results in primary register) + * + */ +gasl () +{ + int lbl; + + lbl=getlabel(); + ol("\tCIA"); + ol("\tJMSI POP"); + gnlabel(lbl); + ol("\tSWP"); + ol("\tCLL RAL"); + ol("\tSWP"); + ol("\tIAC"); + ol("\tSZA"); + jump(lbl); + ol("\tSWP"); + stkp = stkp + INTSIZE; + return 0; +} + +/* + * two's complement of primary register + * + */ +gneg () +{ + ol("\tCIA"); + return 0; +} + +/* + * logical complement of primary register + * + */ +glneg () +{ + ol("\tSNA CLA"); + ol("\tCMA"); + return 0; +} + +/* + * one's complement of primary register + * + */ +gcom () +{ + ol("\tCMA"); + return 0; +} + +/* + * Convert primary value into logical value (0 if 0, 1 otherwise) + * + */ +gbool () +{ + ol("\tSZA CLA"); + ol("\tIAC"); + return 0; +} + +/* + * increment the primary register by 1 if char, INTSIZE if + * int + */ +ginc (lval) int lval[]; +{ + ol ("\tIAC"); +/* if (lval[2] == CINT) +// ol ("inx\th"); */ + return 0; +} +/* + * Shortened INC +*/ + +gisz (lval) +int *lval; +{ + int adr; + char *sym=lval[0]; + + if (lval[1]) { + ol ("\tISZI JLC"); + return 0; + } + + ot ("\tISZI ("); + adr=stkp-glint(sym); +// if (lval[STORAGE] == PUBLIC) + adr=glint(sym)+128; + onum(adr); + nl(); + return 0; +} +/* + * decrement the primary register by one if char, INTSIZE if + * int + */ +gdec (lval) int lval[]; +{ + ol ("\tTAD (-1"); +/* if (lval[2] == CINT) +// ol("dcx\th"); */ + return 0; +} + +/* + * following are the conditional operators. + * they compare the secondary register against the primary register + * and put a literl 1 in the primary if the condition is true, + * otherwise they clear the primary register + * + */ + +/* + * equal + * + */ +geq () +{ + ol("\tCIA"); + ol("\tTADI STKP"); + gpop(); + ol("\tSNA CLA"); + ol("\tCMA"); + return 0; +} + +/* + * not equal + * + */ +gne () +{ + gpop(); + ol("\tCIA"); + ol("\tDCA JLC"); + ol("\tMQA"); + ol("\tTAD JLC"); + return 0; +} + +/* + * less than (signed) + * + */ +glt () +{ + gpop(); + ol("\tCIA"); + ol("\tDCA JLC"); + ol("\tMQA"); + ol("\tTAD JLC"); + ol("\tAND (2048"); + return 0; +} + +/* + * less than or equal (signed) + * + */ +gle () +{ + gpop(); + ol("\tCIA"); + ol("\tDCA JLC"); + ol("\tMQA"); + ol("\tTAD JLC"); + ol("\tSNA"); + ol("\tCLA CMA"); + ol("\tAND (2048"); + return 0; +} + +/* + * greater than (signed) + * + */ +ggt () +{ + gpop(); + ol("\tSWP"); + ol("\tCIA"); + ol("\tDCA JLC"); + ol("\tMQA"); + ol("\tTAD JLC"); + ol("\tAND (2048"); + return 0; +} + +/* + * greater than or equal (signed) + * + */ +gge () +{ + gpop(); + ol("\tSWP"); + ol("\tCIA"); + ol("\tDCA JLC"); + ol("\tMQA"); + ol("\tTAD JLC"); + ol("\tSNA"); + ol("\tCLA CMA"); + ol("\tAND (2048"); + return 0; +} + +/* + * less than (unsigned) + * + */ +gult () +{ + gpop(); + ol("\tCLL CIA"); + ol("\tDCA JLC"); + ol("\tMQA"); + ol("\tTAD JLC"); + ol("\tSNL CLA"); + ol("\tIAC"); + return 0; +} + +/* + * less than or equal (unsigned) + * + */ +gule () +{ + gpop(); + ol("\tCLL CIA"); + ol("\tDCA JLC"); + ol("\tMQA"); + ol("\tTAD JLC"); + ol("\tSNL CLA"); + ol("\tIAC"); + return 0; +} + +/* + * greater than (unsigned) + * + */ +gugt () +{ + gpop(); + ol("\tCLL CIA"); + ol("\tDCA JLC"); + ol("\tMQA"); + ol("\tTAD JLC"); + ol("\tSNA SZL CLA"); + ol("\tIAC"); + return 0; +} + +/* + * greater than or equal (unsigned) + * + */ +guge () +{ + gpop(); + ol("\tSWP"); + ol("\tCLL CIA"); + ol("\tDCA JLC"); + ol("\tMQA"); + ol("\tTAD JLC"); + ol("\tSNL CLA"); + ol("\tIAC"); + return 0; +} + +/* Squirrel away argument count in a register that modstk + doesn't touch. +*/ + +gnargs(d) +int d; { +/* ot ("mvi\ta,"); +// onum(d); +// nl (); */ + return 0; +} + +assemble(s) +char *s; { +#ifdef ASNM + char buf[100]; + strcpy(buf, ASNM); + strcat(buf, " "); + strcat(buf, s); + buf[strlen(buf)-1] = 's'; + return(system(buf)); +#else + return(0); +#endif +} ADDED src/cc8/cross/ctype.h Index: src/cc8/cross/ctype.h ================================================================== --- /dev/null +++ src/cc8/cross/ctype.h @@ -0,0 +1,1 @@ +/* Nothing needed in this file */ ADDED src/cc8/cross/data.c Index: src/cc8/cross/data.c ================================================================== --- /dev/null +++ src/cc8/cross/data.c @@ -0,0 +1,55 @@ +/* File data.c: 2.2 (84/11/27,16:26:13) */ +/*% cc -O -c % + * + */ + +#include +#include "defs.h" + +/* storage words */ + +char symtab[SYMTBSZ]; +char *glbptr, *rglbptr, *locptr; +int ws[WSTABSZ]; +int *wsptr; +int swstcase[SWSTSZ]; +int swstlab[SWSTSZ]; +int swstp; +char litq[LITABSZ]; +int litptr; +char macq[MACQSIZE]; +int macptr; +char line[LINESIZE]; +char mline[LINESIZE]; +int lptr, mptr, gsize; + +/* miscellaneous storage */ + +int nxtlab, + litlab, + stkp, + argstk, + ncmp, + errcnt, + glbflag, + ctext, + cmode, + lastst, + inbreak; + +FILE *input, *input2, *output; +FILE *inclstk[INCLSIZ]; +int inclsp; +char fname[NAMESIZE]; +FILE *bfile; + +char quote[2]; +unsigned char *cptr; +int *iptr; +int fexitlab; +int iflevel, skiplevel; +int errfile; +int sflag; +int cflag; +int errs; +int aflag; ADDED src/cc8/cross/data.h Index: src/cc8/cross/data.h ================================================================== --- /dev/null +++ src/cc8/cross/data.h @@ -0,0 +1,48 @@ +/* File data.h: 2.2 (84/11/27,16:26:11) */ + +/* storage words */ + +extern char symtab[]; +extern char *glbptr, *rglbptr, *locptr; +extern int ws[]; +extern int *wsptr; +extern int swstcase[]; +extern int swstlab[]; +extern int swstp; +extern char litq[]; +extern int litptr; +extern char macq[]; +extern int macptr; +extern char line[]; +extern char mline[]; +extern int lptr, mptr, gsize; + +/* miscellaneous storage */ + +extern int nxtlab, + litlab, + stkp, + argstk, + ncmp, + errcnt, + glbflag, + ctext, + cmode, + lastst, + inbreak; + +extern FILE *input, *input2, *output, *bfile; +extern FILE *inclstk[]; +extern int inclsp; +extern char fname[]; + +extern char quote[]; +extern char *cptr; +extern int *iptr; +extern int fexitlab; +extern int iflevel, skiplevel; +extern int errfile; +extern int sflag; +extern int cflag; +extern int errs; +extern int aflag; ADDED src/cc8/cross/defs.h Index: src/cc8/cross/defs.h ================================================================== --- /dev/null +++ src/cc8/cross/defs.h @@ -0,0 +1,124 @@ +/* File defs.h: 2.1 (83/03/21,02:07:20) */ + + +#define FOREVER for(;;) +#define FALSE 0 +#define TRUE 1 +#define NO 0 +#define YES 1 + +/* miscellaneous */ + +#define EOS 0 +#define EOL 10 +#define BKSP 8 +#define CR 13 +#define FFEED 12 +#define TAB 9 + +/* symbol table parameters */ + +#define SYMSIZ 14 +#define SYMTBSZ 2800 +#define NUMGLBS 150 +#define STARTGLB symtab +#define ENDGLB (STARTGLB+NUMGLBS*SYMSIZ) +#define STARTLOC (ENDGLB+SYMSIZ) +#define ENDLOC (symtab+SYMTBSZ-SYMSIZ) + +/* symbol table entry format */ + +#define NAME 0 +#define IDENT 9 +#define TYPE 10 +#define STORAGE 11 +#define OFFSET 12 + +/* system-wide name size (for symbols) */ + +#define NAMESIZE 20 +#define NAMEMAX 20 + +/* possible entries for "ident" */ + +#define VARIABLE 1 +#define ARRAY 2 +#define POINTER 3 +#define FUNCTION 4 + +/* possible entries for "type" */ + +#define CCHAR 1 +#define CINT 2 + +/* possible entries for storage */ + +#define PUBLIC 1 +#define AUTO 2 +#define EXTERN 3 + +#define STATIC 4 +#define LSTATIC 5 +#define DEFAUTO 6 +/* "do"/"for"/"while"/"switch" statement stack */ + +#define WSTABSZ 100 +#define WSSIZ 7 +#define WSMAX ws+WSTABSZ-WSSIZ + +/* entry offsets in "do"/"for"/"while"/"switch" stack */ + +#define WSSYM 0 +#define WSSP 1 +#define WSTYP 2 +#define WSCASEP 3 +#define WSTEST 3 +#define WSINCR 4 +#define WSDEF 4 +#define WSBODY 5 +#define WSTAB 5 +#define WSEXIT 6 + +/* possible entries for "wstyp" */ + +#define WSWHILE 0 +#define WSFOR 1 +#define WSDO 2 +#define WSSWITCH 3 + +/* "switch" label stack */ + +#define SWSTSZ 100 + +/* literal pool */ + +#define LITABSZ 2000 +#define LITMAX LITABSZ-1 + +/* input line */ + +#define LINESIZE 200 +#define LINEMAX (LINESIZE-1) +#define MPMAX LINEMAX + +/* macro (define) pool */ + +#define MACQSIZE 1000 +#define MACMAX (MACQSIZE-1) + +/* "include" stack */ + +#define INCLSIZ 3 + +/* statement types (tokens) */ + +#define STIF 1 +#define STWHILE 2 +#define STRETURN 3 +#define STBREAK 4 +#define STCONT 5 +#define STASM 6 +#define STEXP 7 +#define STDO 8 +#define STFOR 9 +#define STSWITCH 10 ADDED src/cc8/cross/error.c Index: src/cc8/cross/error.c ================================================================== --- /dev/null +++ src/cc8/cross/error.c @@ -0,0 +1,48 @@ +/* File error.c: 2.1 (83/03/20,16:02:00) */ +/*% cc -O -c % + * + */ + +#include +#include "defs.h" +#include "data.h" + +error (ptr) +char ptr[]; +{ + FILE *tempfile; + + tempfile = output; + output = stdout; + doerror(ptr); + output = tempfile; + doerror(ptr); + errcnt++; + return 0; +} + +doerror(ptr) +char *ptr; +{ + int k; + comment (); + outstr (line); + nl (); + comment (); + k = 0; + while (k < lptr) { + if (line[k] == 9) + tab (); + else + outbyte (' '); + k++; + } + outbyte ('^'); + nl (); + comment (); + outstr ("****** "); + outstr (ptr); + outstr (" ******"); + nl (); + return 0; +} ADDED src/cc8/cross/expr.c Index: src/cc8/cross/expr.c ================================================================== --- /dev/null +++ src/cc8/cross/expr.c @@ -0,0 +1,616 @@ +/* File expr.c: 2.2 (83/06/21,11:24:26) */ +/*% cc -O -c % + * + */ + +#include +#include "defs.h" +#include "data.h" + +/* + * lval[0] - symbol table address, else 0 for constant + * lval[1] - type indirect object to fetch, else 0 for static object + * lval[2] - type pointer or array, else 0 + */ + +expression (comma) +int comma; +{ + int lval[3]; + + do { + if (heir1 (lval)) + rvalue (lval); + if (!comma) + return 0; + } while (match (",")); + return 0; +} + +heir1 (lval) +int lval[]; +{ + int k, lval2[3]; + char fc; + + k = heir1a (lval); + if (match ("=")) { + if (k == 0) { + needlval (); + return (0); + } + if (lval[1]) + gpush (); + if (heir1 (lval2)) + rvalue (lval2); + store (lval); + return (0); + } else + { + fc = ch(); + if (match ("-=") || + match ("+=") || + match ("*=") || + match ("/=") || + match ("%=") || + match (">>=") || + match ("<<=") || + match ("&=") || + match ("^=") || + match ("|=")) { + if (k == 0) { + needlval (); + return (0); + } + if (lval[1]) + gpush (); + rvalue (lval); + gpush (); + if (heir1 (lval2)) + rvalue (lval2); + switch (fc) { + case '-': { + if (dbltest(lval,lval2)) + gaslint(); + gsub(); + result (lval, lval2); + break; + } + case '+': { + if (dbltest(lval,lval2)) + gaslint(); + gadd (lval,lval2); + result(lval,lval2); + break; + } + case '*': gmult (); break; + case '/': gdiv (); break; + case '%': gmod (); break; + case '>': gasr (); break; + case '<': gasl (); break; + case '&': gand (); break; + case '^': gxor (); break; + case '|': gor (); break; + } + store (lval); + return (0); + } else + return (k); + } +} + +heir1a (lval) +int lval[]; +{ + int k, lval2[3], lab1, lab2; + + k = heir1b (lval); + blanks (); + if (ch () != '?') + return (k); + if (k) + rvalue (lval); + FOREVER + if (match ("?")) { + testjump (lab1 = getlabel (), FALSE); + if (heir1b (lval2)) + rvalue (lval2); + jump (lab2 = getlabel ()); + printlabel (lab1); + col (); + nl (); + blanks (); + if (!match (":")) { + error ("missing colon"); + return (0); + } + if (heir1b (lval2)) + rvalue (lval2); + printlabel (lab2); + col (); + nl (); + } else + return (0); +} + +heir1b (lval) +int lval[]; +{ + int k, lval2[3], lab; + + k = heir1c (lval); + blanks (); + if (!sstreq ("||")) + return (k); + if (k) + rvalue (lval); + FOREVER + if (match ("||")) { + testjump (lab = getlabel (), TRUE); + if (heir1c (lval2)) + rvalue (lval2); + printlabel (lab); + col (); + nl (); + gbool(); + } else + return (0); +} + +heir1c (lval) +int lval[]; +{ + int k, lval2[3], lab; + + k = heir2 (lval); + blanks (); + if (!sstreq ("&&")) + return (k); + if (k) + rvalue (lval); + FOREVER + if (match ("&&")) { + testjump (lab = getlabel (), FALSE); + if (heir2 (lval2)) + rvalue (lval2); + printlabel (lab); + col (); + nl (); + gbool(); + } else + return (0); +} + +heir2 (lval) +int lval[]; +{ + int k, lval2[3]; + + k = heir3 (lval); + blanks (); + if ((ch() != '|') | (nch() == '|') | (nch() == '=')) + return (k); + if (k) + rvalue (lval); + FOREVER { + if ((ch() == '|') & (nch() != '|') & (nch() != '=')) { + inbyte (); + gpush (); + if (heir3 (lval2)) + rvalue (lval2); + gor (); + blanks(); + } else + return (0); + } +} + +heir3 (lval) +int lval[]; +{ + int k, lval2[3]; + + k = heir4 (lval); + blanks (); + if ((ch () != '^') | (nch() == '=')) + return (k); + if (k) + rvalue (lval); + FOREVER { + if ((ch() == '^') & (nch() != '=')){ + inbyte (); + gpush (); + if (heir4 (lval2)) + rvalue (lval2); + gxor (); + blanks(); + } else + return (0); + } +} + +heir4 (lval) +int lval[]; +{ + int k, lval2[3]; + + k = heir5 (lval); + blanks (); + if ((ch() != '&') | (nch() == '|') | (nch() == '=')) + return (k); + if (k) + rvalue (lval); + FOREVER { + if ((ch() == '&') & (nch() != '&') & (nch() != '=')) { + inbyte (); + gpush (); + if (heir5 (lval2)) + rvalue (lval2); + gand (); + blanks(); + } else + return (0); + } +} + +heir5 (lval) +int lval[]; +{ + int k, lval2[3]; + + k = heir6 (lval); + blanks (); + if (!sstreq ("==") & + !sstreq ("!=")) + return (k); + if (k) + rvalue (lval); + FOREVER { + if (match ("==")) { + gpush (); + if (heir6 (lval2)) + rvalue (lval2); + geq (); + } else if (match ("!=")) { + gpush (); + if (heir6 (lval2)) + rvalue (lval2); + gne (); + } else + return (0); + } +} + +heir6 (lval) +int lval[]; +{ + int k, lval2[3]; + + k = heir7 (lval); + blanks (); + if (!sstreq ("<") && + !sstreq ("<=") && + !sstreq (">=") && + !sstreq (">")) + return (k); + if (sstreq ("<<") || sstreq (">>")) + return (k); + if (k) + rvalue (lval); + FOREVER { + if (match ("<=")) { + gpush (); + if (heir7 (lval2)) + rvalue (lval2); + if (lval[2] || lval2[2]) { + gule (); + continue; + } + gle (); + } else if (match (">=")) { + gpush (); + if (heir7 (lval2)) + rvalue (lval2); + if (lval[2] || lval2[2]) { + guge (); + continue; + } + gge (); + } else if ((sstreq ("<")) && + !sstreq ("<<")) { + inbyte (); + gpush (); + if (heir7 (lval2)) + rvalue (lval2); + if (lval[2] || lval2[2]) { + gult (); + continue; + } + glt (); + } else if ((sstreq (">")) && + !sstreq (">>")) { + inbyte (); + gpush (); + if (heir7 (lval2)) + rvalue (lval2); + if (lval[2] || lval2[2]) { + gugt (); + continue; + } + ggt (); + } else + return (0); + blanks (); + } +} + +heir7 (lval) +int lval[]; +{ + int k, lval2[3]; + + k = heir8 (lval); + blanks (); + if (!sstreq (">>") && + !sstreq ("<<") || sstreq(">>=") || sstreq("<<=")) + return (k); + if (k) + rvalue (lval); + FOREVER { + if (sstreq(">>") && ! sstreq(">>=")) { + inbyte(); inbyte(); + gpush (); + if (heir8 (lval2)) + rvalue (lval2); + gasr (); + } else if (sstreq("<<") && ! sstreq("<<=")) { + inbyte(); inbyte(); + gpush (); + if (heir8 (lval2)) + rvalue (lval2); + gasl (); + } else + return (0); + blanks(); + } +} + +heir8 (lval) +int lval[]; +{ + int k, lval2[3]; + + k = heir9 (lval); + blanks (); + if ((ch () != '+') & (ch () != '-') | nch() == '=') + return (k); + if (k) + rvalue (lval); + FOREVER { + if (match ("+")) { + gpush (); + if (heir9 (lval2)) + rvalue (lval2); + /* if left is pointer and right is int, scale right */ + if (dbltest (lval, lval2)) + gaslint (); + /* will scale left if right int pointer and left int */ + gadd (lval,lval2); + result (lval, lval2); + } else if (match ("-")) { + gpush (); + if (heir9 (lval2)) + rvalue (lval2); + /* if dbl, can only be: pointer - int, or + pointer - pointer, thus, + in first case, int is scaled up, + in second, result is scaled down. */ + if (dbltest (lval, lval2)) + gaslint (); + gsub (); + /* if both pointers, scale result */ + if ((lval[2] == CINT) && (lval2[2] == CINT)) { + gasrint(); /* divide by intsize */ + } + result (lval, lval2); + } else + return (0); + } +} + +heir9 (lval) +int lval[]; +{ + int k, lval2[3]; + + k = heir10 (lval); + blanks (); + if (((ch () != '*') && (ch () != '/') && + (ch () != '%')) || (nch() == '=')) + return (k); + if (k) + rvalue (lval); + FOREVER { + if (match ("*")) { + gpush (); + if (heir10 (lval2)) + rvalue (lval2); + gmult (); + } else if (match ("/")) { + gpush (); + if (heir10 (lval2)) + rvalue (lval2); + gdiv (); + } else if (match ("%")) { + gpush (); + if (heir10 (lval2)) + rvalue (lval2); + gmod (); + } else + return (0); + } +} + +heir10 (lval) +int lval[]; +{ + int k; + unsigned char *ptr; + + if (match ("++")) { + if ((k = heir10 (lval)) == 0) { + needlval (); + return (0); + } +// if (lval[1]) +// gpush (); + rivalue (lval); +// ginc (lval); +// store (lval); + return (0); + } else if (match ("--")) { + if ((k = heir10 (lval)) == 0) { + needlval (); + return (0); + } + if (lval[1]) + gpush (); + rvalue (lval); + gdec (lval); + store (lval); + return (0); + } else if (match ("-")) { + k = heir10 (lval); + if (k) + rvalue (lval); + gneg (); + return (0); + } else if (match ("~")) { + k = heir10 (lval); + if (k) + rvalue (lval); + gcom (); + return (0); + } else if (match ("!")) { + k = heir10 (lval); + if (k) + rvalue (lval); + glneg (); + return (0); + } else if (ch()=='*' && nch() != '=') { + inbyte(); + k = heir10 (lval); + if (k) + rvalue (lval); + if (ptr = lval[0]) + lval[1] = ptr[TYPE]; + else + lval[1] = CINT; + lval[2] = 0; /* flag as not pointer or array */ + return (1); + } else if (ch()=='&' && nch()!='&' && nch()!='=') { + inbyte(); + k = heir10 (lval); + if (k == 0) { + error ("illegal address"); + return (0); + } + ptr = lval[0]; + lval[2] = ptr[TYPE]; + if (lval[1]) + return (0); + /* global and non-array */ + immed (); + k=128+ptr[OFFSET]+ptr[OFFSET+1]*256; + onum(k); + ot("\t/Offset from stackbase at 128 (200(8))"); + nl (); + lval[1] = ptr[TYPE]; + return (0); + } else { + k = heir11 (lval); + if (match ("++")) { + if (k == 0) { + needlval (); + return (0); + } +// if (lval[1]) +// gpush (); + rvalue (lval); + gisz (lval); +// ginc (lval); +// store (lval); +// gdec (lval); + return (0); + } else if (match ("--")) { + if (k == 0) { + needlval (); + return (0); + } + if (lval[1]) + gpush (); + rvalue (lval); + gdec (lval); + store (lval); + ginc (lval); + return (0); + } else + return (k); + } +} + +heir11 (lval) +int *lval; +{ + int k; + char *ptr; + + k = primary (lval); + ptr = lval[0]; + blanks (); + if ((ch () == '[') | (ch () == '(')) + FOREVER { + if (match ("[")) { + if (ptr == 0) { + error ("can't subscript"); + junk (); + needbrack ("]"); + return (0); + } else if (ptr[IDENT] == POINTER) + rvalue (lval); + else if (ptr[IDENT] != ARRAY) { + error ("can't subscript"); + k = 0; + } + gpush (); + expression (YES); + needbrack ("]"); + if (ptr[TYPE] == CINT) + gaslint (); + gadd (NULL,NULL); + lval[0] = 0; + lval[1] = ptr[TYPE]; + k = 1; + } else if (match ("(")) { + if (ptr == 0) + callfunction (0); + else if (ptr[IDENT] != FUNCTION) { + rvalue (lval); + callfunction (0); + } else + callfunction (ptr); + k = lval[0] = 0; + } else + return (k); + } + if (ptr == 0) + return (k); + if (ptr[IDENT] == FUNCTION) { + immed (); + prefix (); + outstr (ptr); + nl (); + return (0); + } + return (k); +} ADDED src/cc8/cross/function.c Index: src/cc8/cross/function.c ================================================================== --- /dev/null +++ src/cc8/cross/function.c @@ -0,0 +1,165 @@ +/* File function.c: 2.1 (83/03/20,16:02:04) */ +/*% cc -O -c % + * + */ + +#include +#include "defs.h" +#include "data.h" + +/* + * begin a function + * + * called from "parse", this routine tries to make a function out + * of what follows + * modified version. p.l. woods + * + */ +int argtop; +newfunc () +{ + char n[NAMESIZE], *ptr, rtn[NAMESIZE]; + fexitlab = getlabel(); + + if (!symname (n) ) { + error ("illegal function or declaration"); + kill_line (); + return 0; + } + if (ptr = findglb (n)) { + if (ptr[IDENT] != FUNCTION) + multidef (n); + else if (ptr[OFFSET] == FUNCTION) + multidef (n); + else + ptr[OFFSET] = FUNCTION; + } else + addglb (n, FUNCTION, CINT, 0, PUBLIC); // Do not allocate any storage to global functions + if (!match ("(")) + error ("missing open paren"); + prefix (); + if (astreq(n,"main",4)) { + if (inbreak) { + ol("\tEND"); + output=bfile; + } + outstr("xmain"); + } + else outstr (n); + strcpy(rtn,n); + col (); +// outstr("\t0"); + nl (); + if (inbreak) { + ol("\tCLA CLL"); + ol("\tCALL 2,PGINIT"); + ol("\tARG STKP"); + ol("\tARG GBL"); + } + prologue (rtn); + locptr = STARTLOC; + argstk = 0; + while (!match (")")) { + if (symname (n)) { + if (findloc (n)) + multidef (n); + else { + addloc (n, 0, 0, argstk, AUTO); + argstk = argstk + intsize(); + } + } else { + error ("illegal argument name"); + junk (); + } + blanks (); + if (!streq (line + lptr, ")")) { + if (!match (",")) + error ("expected comma"); + } + if (endst ()) + break; + } + stkp = 0; + argtop = argstk; + while (argstk) { + if (amatch ("register", 8)) { + if (amatch("char", 4)) + getarg(CCHAR); + else if (amatch ("int", 3)) + getarg(CINT); + else + getarg(CINT); + ns(); + } else if (amatch ("char", 4)) { + getarg (CCHAR); + ns (); + } else if (amatch ("int", 3)) { + getarg (CINT); + ns (); + } else { + error ("wrong number args"); + break; + } + } + statement(YES); + printlabel(fexitlab); + col(); + nl(); + if (astreq(n,"main",4)) /* On exit from main pop literal table as well */ + modstk(0); + else + modstk (0); + gret (rtn); + stkp = 0; + locptr = STARTLOC; + return 0; +} + +/* + * declare argument types + * + * called from "newfunc", this routine add an entry in the local + * symbol table for each named argument + * completely rewritten version. p.l. woods + * + */ +getarg (t) +int t; +{ + int j, legalname, address; + char n[NAMESIZE], c, *argptr; + + FOREVER { + if (argstk == 0) + return 0; + if (match ("*")) + j = POINTER; + else + j = VARIABLE; + if (!(legalname = symname (n))) + illname (); + if (match ("[")) { + while (inbyte () != ']') + if (endst ()) + break; + j = POINTER; + } + if (legalname) { + if (argptr = findloc (n)) { + argptr[IDENT] = j; + argptr[TYPE] = t; + address = argtop - glint(argptr); + if (t == CCHAR && j == VARIABLE) + address = address + byteoff(); + argptr[OFFSET] = (address) & 0xff; + argptr[OFFSET + 1] = (address >> 8) & 0xff; + } else + error ("expecting argument name"); + } + argstk = argstk - intsize(); + if (endst ()) + return 0; + if (!match (",")) + error ("expected comma"); + } +} ADDED src/cc8/cross/gen.c Index: src/cc8/cross/gen.c ================================================================== --- /dev/null +++ src/cc8/cross/gen.c @@ -0,0 +1,171 @@ +/* File gen.c: 2.1 (83/03/20,16:02:06) */ +/*% cc -O -c % + * + */ + +#include +#include "defs.h" +#include "data.h" + +/* ToUpper routine */ + + ucase(ch) + int ch; + { + if ('a'>ch || ch>'z') return(ch); + return(ch-32); + } +/* + * return next available internal label number + * + */ +getlabel () +{ + return (nxtlab++); +} + +/* + * print specified number as label + */ +printlabel (label) +int label; +{ + olprfix (); + outdec (label); + return 0; +} + +/* + * glabel - generate label + */ +glabel (lab) +char *lab; +{ + prefix (); + outstr (lab); + col (); + nl (); + return 0; +} + +/* + * gnlabel - generate numeric label + */ +gnlabel (nlab) +int nlab; +{ + printlabel (nlab); + col (); + nl (); + return 0; +} + +outbyte (c) +char c; +{ + if (c == 0) + return (0); + fputc (c, output); + return (c); +} + +outstr (ptr) +char ptr[]; +{ + int k; + + k = 0; + while (outbyte (ucase(ptr[k++]))); + return 0; +} + + +tab () +{ + outbyte (9); + return 0; +} + +ol (ptr) +char ptr[]; +{ + ot (ptr); + nl (); + return 0; +} + +ot (ptr) +char ptr[]; +{ + outstr (ptr); + return 0; +} + +outdec (number) +int number; +{ + int k, zs; + char c; + + if (number == -32768) { + outstr ("-32768"); + return 0; + } + zs = 0; + k = 10000; + if (number < 0) { + number = (-number); + outbyte ('-'); + } + while (k >= 1) { + c = number / k + '0'; + if ((c != '0' | (k == 1) | zs)) { + zs = 1; + outbyte (c); + } + number = number % k; + k = k / 10; + } + return 0; +} + +store (lval) +int *lval; +{ + if (lval[1] == 0) + putmem (lval[0]); + else + putstk (lval[1]); + return 0; +} + +rvalue (lval) +int *lval; +{ + if ((lval[0] != 0) & (lval[1] == 0)) + getmem (lval[0]); + else + indirect (lval[1]); + return 0; +} + +rivalue (lval) +int *lval; +{ + if ((lval[0] != 0) & (lval[1] == 0)) + getincmem (lval[0]); + else + incdirect (lval[1]); + return 0; +} + +test (label, ft) +int label, + ft; +{ + needbrack ("("); + expression (YES); + needbrack (")"); + testjump (label, ft); + return 0; +} ADDED src/cc8/cross/io.c Index: src/cc8/cross/io.c ================================================================== --- /dev/null +++ src/cc8/cross/io.c @@ -0,0 +1,191 @@ +/* File io.c: 2.1 (83/03/20,16:02:07) */ +/*% cc -O -c % + * + */ + +#include +#include "defs.h" +#include "data.h" + +/* + * open input file + */ +openin (p) char *p; +{ + strcpy(fname, p); + fixname (fname); + if (!checkname (fname)) + return (NO); + if ((input = fopen (fname, "r")) == NULL) { + pl ("Open failure\n"); + return (NO); + } + kill_line (); + return (YES); +} + +/* + * open output file + */ +openout () +{ + outfname (fname); + if ((output = fopen (fname, "w")) == NULL) { + pl ("Open failure"); + return (NO); + } + kill_line (); + return (YES); +} + +/* + * Change input filename to output filename. We already checked that + * the input file name matches *.c, so we replace the 'c' with an 's' + * and then, space permitting, append a 'b'. + * + */ +outfname (s) +char *s; +{ + char *os = s; + while (*s) + s++; + *--s = 's'; + + if ((s - os) < (NAMESIZE - 2)) { + *++s = 'b'; + *++s = '\0'; + } + + return 0; + +} + +/* + * remove NL from filenames + * + */ +fixname (s) +char *s; +{ + while (*s && *s++ != EOL); + if (!*s) return 0; + *(--s) = 0; + return 0; +} + +/* + * check that filename is "*.c" + */ +checkname (s) +char *s; +{ + while (*s) + s++; + if (*--s != 'c') + return (NO); + if (*--s != '.') + return (NO); + return (YES); +} + +kill_line () +{ + lptr = 0; + line[lptr] = 0; + return 0; +} + +inln () +{ + int k; + FILE *unit; + + FOREVER { + if (feof (input)) + return 0; + if ((unit = input2) == NULL) + unit = input; + kill_line (); + while ((k = fgetc (unit)) != EOF) { + if ((k == EOL) | (lptr >= LINEMAX)) + break; + if (k != 13) line[lptr++] = k; + } + line[lptr] = 0; + if (output && cmode) { + outstr("/\t"); + ol(line); + } + if (k <= 0) + if (input2 != NULL) { + input2 = inclstk[--inclsp]; + fclose (unit); + } + if (lptr) { + if ((ctext) & (cmode)) { + comment (); + outstr (line); + nl (); + } + lptr = 0; + return 0; + } + } +} + +inbyte () +{ + while (ch () == 0) { + if (feof (input)) + return (0); + preprocess (); + } + return (gch ()); +} + +inchar () +{ + if (ch () == 0) + inln (); + if (feof (input)) + return (0); + return (gch ()); +} + +gch () +{ + if (ch () == 0) + return (0); + else + return (line[lptr++] & 127); +} + +nch () +{ + if (ch () == 0) + return (0); + else + return (line[lptr + 1] & 127); +} + +ch () +{ + return (line[lptr] & 127); +} + +/* + * print a carriage return and a string only to console + * + */ +pl (str) +char *str; +{ + int k; + + k = 0; + putchar (EOL); + while (str[k]) + putchar (str[k++]); + return 0; +} ADDED src/cc8/cross/lex.c Index: src/cc8/cross/lex.c ================================================================== --- /dev/null +++ src/cc8/cross/lex.c @@ -0,0 +1,175 @@ +/* File lex.c: 2.1 (83/03/20,16:02:09) */ +/*% cc -O -c % + * + */ + +#include +#include "defs.h" +#include "data.h" + +/* + * semicolon enforcer + * + * called whenever syntax requires a semicolon + * + */ +ns () +{ + if (!match (";")) + error ("missing semicolon"); + return 0; +} + +junk () +{ + if (an (inbyte ())) + while (an (ch ())) + gch (); + else + while (an (ch ())) { + if (ch () == 0) + break; + gch (); + } + blanks (); + return 0; +} + +endst () +{ + blanks (); + return ((streq (line + lptr, ";") | (ch () == 0))); +} + +needbrack (str) +char *str; +{ + if (!match (str)) { + error ("missing bracket"); + comment (); + outstr (str); + nl (); + } + return 0; +} + +/* + * test if given character is alpha + * + */ +alpha (c) +char c; +{ + c = c & 127; + return (((c >= 'a') & (c <= 'z')) | + ((c >= 'A') & (c <= 'Z')) | + (c == '_')); +} + +/* + * test if given character is numeric + * + */ +numeric (c) +char c; +{ + c = c & 127; + return ((c >= '0') & (c <= '9')); +} + +/* + * test if given character is alphanumeric + * + */ +an (c) +char c; +{ + return ((alpha (c)) | (numeric (c))); +} + +sstreq (str1) char *str1; { + return (streq(line + lptr, str1)); +} + +streq (str1, str2) +char str1[], str2[]; +{ + int k; + + k = 0; + while (str2[k]) { + if ((str1[k] != str2[k])) + return (0); + k++; + } + return (k); +} + +astreq (str1, str2, len) +char str1[], str2[]; +int len; +{ + int k; + + k = 0; + while (k < len) { + if ((str1[k] != str2[k])) + break; + if (str1[k] == 0) + break; + if (str2[k] == 0) + break; + k++; + } + if (an (str1[k])) + return (0); + if (an (str2[k])) + return (0); + return (k); +} + +match (lit) +char *lit; +{ + int k; + + blanks (); + if (k = streq (line + lptr, lit)) { + lptr = lptr + k; + return (1); + } + return (0); +} + +amatch (lit, len) +char *lit; +int len; +{ + int k; + + blanks (); + if (k = astreq (line + lptr, lit, len)) { + lptr = lptr + k; + while (an (ch ())) + inbyte (); + return (1); + } + return (0); +} + +blanks () +{ + FOREVER { + while (ch () == 0) { + preprocess (); + if (feof (input)) + break; + } + if (ch () == ' ') + gch (); + else if (ch () == 9) + gch (); + else + return 0; + } +} ADDED src/cc8/cross/main.c Index: src/cc8/cross/main.c ================================================================== --- /dev/null +++ src/cc8/cross/main.c @@ -0,0 +1,301 @@ +/* File main.c: 2.7 (84/11/28,10:14:56) */ +/*% cc -O -c % + * + */ + +#include +#include "defs.h" +#include "data.h" + +main (argc, argv) +int argc; +char** argv; /* OS/8 CC8 can't cope, but bootstrapping CC8 doesn't work, either */ +{ + char *p,*bp; + int smacptr; + macptr = 0; + ctext = 0; + argc--; argv++; + errs = 0; + aflag = 1; + while (p = *argv++) + if (*p == '-') while (*++p) + switch(*p) { + case 't': case 'T': + ctext = 1; + break; + case 's': case 'S': + sflag = 1; + break; + case 'c': case 'C': + cflag = 1; + break; + case 'a': case 'A': + aflag = 0; + break; + case 'd': case 'D': + bp = ++p; + if (!*p) usage(); + while (*p && *p != '=') p++; + if (*p == '=') *p = '\t'; + while (*p) p++; + p--; + defmac(bp); + break; + default: + usage(); + } + else break; + + smacptr = macptr; + if (!p) + usage(); + while (p) { + errfile = 0; + if (typof(p) == 'c' || typof(p) =='C') { + glbptr = STARTGLB; + locptr = STARTLOC; + wsptr = ws; + inclsp = + iflevel = + skiplevel = + swstp = + litptr = + stkp = + errcnt = + ncmp = + lastst = + quote[1] = + gsize = + inbreak = + 0; + macptr = smacptr; + input2 = NULL; + quote[0] = '"'; + cmode = 1; + glbflag = 1; + nxtlab = 0; + litlab = getlabel (); + defmac("end\tmemory"); + rglbptr = glbptr; + defmac("short\tint"); + initmac(); + /* + * compiler body + */ + if (!openin (p)) + return 0; + if (!openout ()) + return 0; + header (); + gtext (); + parse (); + fclose (input); + gdata (); + dumplits (); + dumpglbs (); + errorsummary (); + trailer (); + fclose (output); + pl (""); + errs = errs || errfile; +#ifndef NOASLD + } + if (!errfile && !sflag) + errs = errs || assemble(p); +#else + } else { + fputs("Don't understand file ", stderr); + fputs(p, stderr); + errs = 1; + } +#endif + p = *argv++; + } + exit(errs != 0); + return 0; +} + +FEvers() +{ + outstr("/\tFront End (1.0:27/1/99)"); + return 0; +} + +usage() +{ + fputs("usage: sccXXXX [-tcsa] [-dSYM[=VALUE]] files\n", stderr); + exit(1); +} + +/* + * process all input text + * + * at this level, only static declarations, defines, includes, + * and function definitions are legal. + * + */ +parse () +{ + while (!feof (input)) { + if (amatch ("extern", 6)) + dodcls(EXTERN); + else if (amatch ("static",6)) + dodcls(STATIC); + else if (dodcls(PUBLIC)) ; + else if (match ("#asm")) + doasm (); + else if (match ("#include")) + doinclude (); + else if (match ("#define")) + dodefine(); + else if (match ("#undef")) + doundef(); + else + newfunc (); + blanks (); + } + return 0; +} + +/* + * parse top level declarations + */ + +dodcls(stclass) +int stclass; { + blanks(); + if (amatch("char", 4)) + declglb(CCHAR, stclass); + else if (amatch("int", 3)) + declglb(CINT, stclass); + else if (stclass == PUBLIC) + return(0); + else + declglb(CINT, stclass); + ns (); + return(1); +} + + +/* + * dump the literal pool + */ +dumplits () +{ + int j, k; + +/* A loc containing the size */ + ol("\tLAP"); + ot ("\tCPAGE "); + onum (2+litptr); + nl(); + outbyte('L'); + printlabel (litlab); + col(); + ot("\t"); + onum (-litptr); + nl(); + if (litptr == 0) + return 0; +/* Generate a loc containing the address of the literals */ + outbyte('X'); + printlabel (litlab); + col(); + ot("\t"); + printlabel (litlab); + nl(); + printlabel (litlab); + col (); + k = 0; + while (k < litptr) { + defbyte (); + j = 8; + while (j--) { + onum (litq[k++] & 127); + if ((j == 0) | (k >= litptr)) { + nl (); + break; + } + outbyte (';'); + } + } + ol("\tEAP"); + return 0; +} + +/* + * dump all static variables + */ +dumpglbs () +{ + int j; + + if (!glbflag) { + ot("GBLS,\t0"); + nl(); + return 0; + } + cptr = rglbptr; + while (cptr < glbptr) { + if (cptr[IDENT] != FUNCTION) { + ppubext(cptr); + if (cptr[STORAGE] != EXTERN) { + //prefix (); + //outstr (cptr); + //col (); + //defstorage (); + j = glint(cptr); + if ((cptr[TYPE] == CINT) || + (cptr[IDENT] == POINTER)) + j = j * intsize(); + //onum (j); + //nl (); + } + } else { + fpubext(cptr); + } + cptr = cptr + SYMSIZ; + } + ot("GBLS,\t"); + onum(gsize+128); // Beginning of stack after globals + nl(); + return 0; +} + +/* + * report errors + */ +errorsummary () +{ + if (ncmp) + error ("missing closing bracket"); + nl (); + comment (); + outdec (errcnt); + if (errcnt) errfile = YES; + outstr (" error(s) in compilation"); + nl (); + comment(); + ot("literal pool:"); + outdec(litptr); + nl(); + comment(); + ot("global pool:"); + outdec(glbptr-rglbptr); + nl(); + comment(); + ot("Macro pool:"); + outdec(macptr); + nl(); + pl (errcnt ? "Error(s)" : "No errors"); + return 0; +} + +typof(s) +char *s; { + s += strlen(s) - 2; + if (*s == '.') + return(*(s+1)); + return(' '); +} ADDED src/cc8/cross/preproc.c Index: src/cc8/cross/preproc.c ================================================================== --- /dev/null +++ src/cc8/cross/preproc.c @@ -0,0 +1,349 @@ +/* File preproc.c: 2.3 (84/11/27,11:47:40) */ +/*% cc -O -c % + * + */ + +#include +#include "defs.h" +#include "data.h" + +/* + * open an include file + */ +doinclude () +{ + char *p; + FILE *inp2; + + blanks (); + if (inp2 = fixiname ()) + if (inclsp < INCLSIZ) { + inclstk[inclsp++] = input2; + input2 = inp2; + } else { + fclose (inp2); + error ("too many nested includes"); + } + else { + error ("Could not open include file"); + } + kill_line (); + return 0; +} + +/* + * fixiname - remove "brackets" around include file name + */ +fixiname () +{ + char c1, c2, *p, *ibp; + char buf[20]; + + ibp = &buf[0]; + + if ((c1 = gch ()) != '"' && c1 != '<') + return (NULL); + for (p = line + lptr; *p ;) + *ibp++ = *p++; + c2 = *(--p); + if (c1 == '"' ? (c2 != '"') : (c2 != '>')) { + error ("incorrect delimiter"); + return (NULL); + } + *(--ibp) = 0; + + return c1 == '<' ? fopen(buf, "r") : NULL; +} + +/* + * "asm" pseudo-statement + * + * enters mode where assembly language statements are passed + * intact through parser + * + */ +doasm () +{ + cmode = 0; + FOREVER { + inln (); + if (match ("#endasm")) { + ol("/\t#ENDASM"); + break; + } + if (feof (input)) + break; + outstr (line); + nl (); + } + kill_line (); + cmode = 1; + return 0; +} + +dodefine () +{ + addmac(); + return 0; +} + +doundef () +{ + int mp; + char sname[NAMESIZE]; + + if (!symname(sname)) { + illname(); + kill_line (); + return 0; + } + + if (mp = findmac(sname)) + delmac(mp); + kill_line (); + return 0; +} + +preprocess () +{ + if (ifline()) return 0; + while (cpp()); + return 0; +} + +doifdef (ifdef) +int ifdef; +{ + char sname[NAMESIZE]; + int k; + + blanks(); + ++iflevel; + if (skiplevel) return 0; + k = symname(sname) && findmac(sname); + if (k != ifdef) skiplevel = iflevel; + return 0; +} + +ifline() +{ + FOREVER { + inln(); + if (feof(input)) return(1); + if (match("#ifdef")) { + doifdef(YES); + continue; + } else if (match("#ifndef")) { + doifdef(NO); + continue; + } else if (match("#else")) { + if (iflevel) { + if (skiplevel == iflevel) skiplevel = 0; + else if (skiplevel == 0) skiplevel = iflevel; + } else noiferr(); + continue; + } else if (match("#endif")) { + if (iflevel) { + if (skiplevel == iflevel) skiplevel = 0; + --iflevel; + } else noiferr(); + continue; + } + if (!skiplevel) return(0); + } +} + +noiferr() +{ + error("no matching #if..."); + return 0; +} + + +cpp () +{ + int k; + char c, sname[NAMESIZE]; + int tog; + int cpped; /* non-zero if something expanded */ + + cpped = 0; + /* don't expand lines with preprocessor commands in them */ + if (!cmode || line[0] == '#') return(0); + + mptr = lptr = 0; + while (ch ()) { + if ((ch () == '/') & (nch () == '/')) { + inln(); + } + if ((ch () == ' ') | (ch () == 9)) { + keepch (' '); + while ((ch () == ' ') | (ch () == 9)) + gch (); + } else if (ch () == '"') { + keepch (ch ()); + gch (); + while (ch () != '"') { + if (ch () == 0) { + error ("missing quote"); + break; + } + if (ch() == '\\') keepch(gch()); + keepch (gch ()); + } + gch (); + keepch ('"'); + } else if (ch () == 39) { + keepch (39); + gch (); + while (ch () != 39) { + if (ch () == 0) { + error ("missing apostrophe"); + break; + } + if (ch() == '\\') keepch(gch()); + keepch (gch ()); + } + gch (); + keepch (39); + } else if ((ch () == '/') & (nch () == '*')) { + inchar (); + inchar (); + while ((((c = ch ()) == '*') & (nch () == '/')) == 0) + if (c == '$') { + inchar (); + tog = TRUE; + if (ch () == '-') { + tog = FALSE; + inchar (); + } + if (alpha (c = ch ())) { + inchar (); + toggle (c, tog); + } + } else { + if (ch () == 0) + inln (); + else + inchar (); + if (feof (input)) + break; + } + inchar (); + inchar (); + } else if (an (ch ())) { + k = 0; + while (an (ch ())) { + if (k < NAMEMAX) + sname[k++] = ch (); + gch (); + } + sname[k] = 0; + if (k = findmac (sname)) { + cpped = 1; + while (c = macq[k++]) + keepch (c); + } else { + k = 0; + while (c = sname[k++]) + keepch (c); + } + } else + keepch (gch ()); + } + keepch (0); + if (mptr >= MPMAX) + error ("line too long"); + lptr = mptr = 0; + while (line[lptr++] = mline[mptr++]); + lptr = 0; + return(cpped); +} + +keepch (c) +char c; +{ + mline[mptr] = c; + if (mptr < MPMAX) + mptr++; + return (c); +} + +defmac(s) +char *s; +{ + kill_line(); + strcpy(line, s); + addmac(); + return 0; +} + +addmac () +{ + char sname[NAMESIZE]; + int k; + int mp; + + if (!symname (sname)) { + illname (); + kill_line (); + return 0; + } + if (mp = findmac(sname)) { + error("Duplicate define"); + delmac(mp); + } + k = 0; + while (putmac (sname[k++])); + while (ch () == ' ' | ch () == 9) + gch (); + while (putmac (gch ())); + if (macptr >= MACMAX) + error ("macro table full"); + return 0; +} + +delmac(mp) int mp; { + --mp; --mp; /* step over previous null */ + while (mp >= 0 && macq[mp]) macq[mp--] = '%'; + return 0; +} + + +putmac (c) +char c; +{ + macq[macptr] = c; + if (macptr < MACMAX) + macptr++; + return (c); +} + +findmac (sname) +char *sname; +{ + int k; + + k = 0; + while (k < macptr) { + if (astreq (sname, macq + k, NAMEMAX)) { + while (macq[k++]); + return (k); + } + while (macq[k++]); + while (macq[k++]); + } + return (0); +} + +toggle (name, onoff) +char name; +int onoff; +{ + switch (name) { + case 'C': + ctext = onoff; + break; + } + return 0; +} ADDED src/cc8/cross/primary.c Index: src/cc8/cross/primary.c ================================================================== --- /dev/null +++ src/cc8/cross/primary.c @@ -0,0 +1,320 @@ +/* File primary.c: 2.4 (84/11/27,16:26:07) */ +/*% cc -O -c % + * + */ + +#include +#include "defs.h" +#include "data.h" + +primary (lval) +int *lval; +{ + unsigned char *ptr, sname[NAMESIZE]; + int num[1]; + int k; + + lval[2] = 0; /* clear pointer/array type */ + if (match ("(")) { + k = heir1 (lval); + needbrack (")"); + return (k); + } + if (amatch("sizeof", 6)) { + needbrack("("); + immed(); + if (amatch("int", 3)) onum(intsize()); + else if (amatch("char", 4)) onum(1); + else if (symname(sname)) { + if ((ptr = findloc(sname)) || + (ptr = findglb(sname))) { + if (ptr[STORAGE] == LSTATIC) + error("sizeof local static"); + k = glint(ptr); + if ((ptr[TYPE] == CINT) || + (ptr[IDENT] == POINTER)) + k *= intsize(); + onum(k); + } else { + error("sizeof undeclared variable"); + onum(0); + } + } else { + error("sizeof only on type or variable"); + } + needbrack(")"); + nl(); + return(lval[0] = lval[1] = 0); + } + if (symname (sname)) { + if (ptr = findloc (sname)) { + getloc (ptr); + lval[0] = ptr; + lval[1] = ptr[TYPE]; + if (ptr[IDENT] == POINTER) { + lval[1] = CINT; + lval[2] = ptr[TYPE]; + } + if (ptr[IDENT] == ARRAY) { + lval[2] = ptr[TYPE]; + lval[2] = 0; + return (0); + } + else + return (1); + } + if (ptr = findglb (sname)) + if (ptr[IDENT] != FUNCTION) { + lval[0] = ptr; + lval[1] = 0; + if (ptr[IDENT] != ARRAY) { + if (ptr[IDENT] == POINTER) + lval[2] = ptr[TYPE]; + return (1); + } + immed (); + onum(128+ptr[OFFSET]+ptr[OFFSET+1]*256); + ot("\t/Offset from stackbase at 128 (200(8))"); + nl (); + lval[1] = lval[2] = ptr[TYPE]; + lval[2] = 0; + return (0); + } + blanks (); + if (ch() != '(') + error("undeclared variable"); + ptr = addglb (sname, FUNCTION, CINT, 0, PUBLIC); + lval[0] = ptr; + lval[1] = 0; + return (0); + } + if (constant (num)) + return (lval[0] = lval[1] = 0); + else { + error ("invalid expression"); + immed (); + onum (0); + nl (); + junk (); + return (0); + } +} + +/* + * true if val1 -> int pointer or int array and val2 not pointer or array + */ +dbltest (val1, val2) +int val1[], val2[]; +{ + if (val1 == NULL) + return (FALSE); + if (val1[2] != CINT) + return (FALSE); + if (val2[2]) + return (FALSE); + return (TRUE); +} + +/* + * determine type of binary operation + */ +result (lval, lval2) +int lval[], + lval2[]; +{ + if (lval[2] && lval2[2]) + lval[2] = 0; + else if (lval2[2]) { + lval[0] = lval2[0]; + lval[1] = lval2[1]; + lval[2] = lval2[2]; + } + return 0; +} + +constant (val) +int val[]; +{ + if (number (val)) + { + if (val[0]==0) { + ol("\t/ (0)"); + cpri(); + return (1); + } + immed (); + } + else if (pstr (val)) + immed (); + else if (qstr (val)) { + immd2 (); + stkbase(); + nl(); +/* outbyte ('+'); */ + immd3 (); + } else + return (0); + onum (val[0]); + nl (); + return (1); +} + +number (val) +int val[]; +{ + int k, minus, base; + char c; + + k = minus = 1; + while (k) { + k = 0; + if (match ("+")) + k = 1; + if (match ("-")) { + minus = (-minus); + k = 1; + } + } + if (!numeric (c = ch ())) + return (0); + if (match ("0x") || match ("0X")) + while (numeric (c = ch ()) || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F')) { + inbyte (); + k = k * 16 + + (numeric (c) ? (c - '0') : ((c & 07) + 9)); + } + else { + base = (c == '0') ? 8 : 10; + while (numeric (ch ())) { + c = inbyte (); + k = k * base + (c - '0'); + } + } + if (minus < 0) + k = (-k); + val[0] = k; + return (1); +} + +pstr (val) +int val[]; +{ + int k; + char c; + + k = 0; + if (!match ("'")) + return (0); + while ((c = gch ()) != 39) { + c = (c == '\\') ? spechar(): c; + k = (k & 255) * 256 + (c & 255); + } + val[0] = k; + return (1); +} + +qstr (val) +int val[]; +{ + char c; + + if (!match (quote)) + return (0); + val[0] = litptr; + while (ch () != '"') { + if (ch () == 0) + break; + if (litptr >= LITMAX) { + error ("string space exhausted"); + while (!match (quote)) + if (gch () == 0) + break; + return (1); + } + c = gch(); + litq[litptr++] = (c == '\\') ? spechar(): c; + } + gch (); + litq[litptr++] = 0; + return (1); +} + +/* + * decode special characters (preceeded by back slashes) + */ +spechar() { + char c; + c = ch(); + + if (c == 'n') c = EOL; + else if (c == 't') c = TAB; + else if (c == 'r') c = CR; + else if (c == 'f') c = FFEED; + else if (c == 'b') c = BKSP; + else if (c == '0') c = EOS; + else if (c == EOS) return 0; + + gch(); + return (c); +} + +/* + * perform a function call + * + * called from "heir11", this routine will either call the named + * function, or if the supplied ptr is zero, will call the contents + * of HL + * NB Added section to load Acc with nargs for vararg calls + * NB have addded pseudo functions here as well + */ +callfunction (ptr) +char *ptr; +{ + int nargs; + + if (strcmp(ptr,"stri")==0) { + expression(NO); + stri(); + needbrack(")"); + return 0; + } + if (strcmp(ptr,"iinit")==0) { + expression(NO); + iinit(); + needbrack(")"); + return 0; + } + + nargs = 0; + blanks (); + if (ptr == 0) + gpush (); + while (!streq (line + lptr, ")")) { + if (endst ()) + break; + expression (NO); + if (ptr == 0) + swapstk (); + gpush (); + nargs = nargs + intsize(); + if (!match (",")) + break; + } + needbrack (")"); + if (aflag) + gnargs(nargs / intsize()); + if (ptr) + gcall (ptr,&nargs); + else + callstk (); + stkp = modstk (stkp + nargs); + return 0; +} + +needlval () +{ + error ("must be lvalue"); + return 0; +} ADDED src/cc8/cross/stmt.c Index: src/cc8/cross/stmt.c ================================================================== --- /dev/null +++ src/cc8/cross/stmt.c @@ -0,0 +1,438 @@ +/* File stmt.c: 2.1 (83/03/20,16:02:17) */ +/*% cc -O -c % + * + */ + +#include +#include "defs.h" +#include "data.h" + +/* + * statement parser + * + * called whenever syntax requires a statement. this routine + * performs that statement and returns a number telling which one + * + * 'func' is true if we require a "function_statement", which + * must be compound, and must contain "statement_list" (even if + * "declaration_list" is omitted) + */ + +statement (func) +int func; +{ + if ((ch () == 0) & feof (input)) + return (0); + lastst = 0; + if (func) + if (match ("{")) { + compound (YES); + return (lastst); + } else + error ("function requires compound statement"); + if (match ("{")) + compound (NO); + else + stst (); + return (lastst); +} + +/* + * declaration + */ +stdecl () +{ + if (amatch("register", 8)) + doldcls(DEFAUTO); + else if (amatch("auto", 4)) + doldcls(DEFAUTO); + else if (amatch("static", 6)) + doldcls(LSTATIC); + else if (doldcls(AUTO)) ; + else + return (NO); + return (YES); +} + +doldcls(stclass) +int stclass; +{ + blanks(); + if (amatch("char", 4)) + declloc(CCHAR, stclass); + else if (amatch("int", 3)) + declloc(CINT, stclass); + else if (stclass == LSTATIC || stclass == DEFAUTO) + declloc(CINT, stclass); + else + return(0); + ns(); + return(1); +} + + +/* + * non-declaration statement + */ +stst () +{ + if (amatch ("if", 2)) { + doif (); + lastst = STIF; + } else if (amatch ("while", 5)) { + dowhile (); + lastst = STWHILE; + } else if (amatch ("switch", 6)) { + doswitch (); + lastst = STSWITCH; + } else if (amatch ("do", 2)) { + dodo (); + ns (); + lastst = STDO; + } else if (amatch ("for", 3)) { + dofor (); + lastst = STFOR; + } else if (amatch ("return", 6)) { + doreturn (); + ns (); + lastst = STRETURN; + } else if (amatch ("break", 5)) { + dobreak (); + ns (); + lastst = STBREAK; + } else if (amatch ("continue", 8)) { + docont (); + ns (); + lastst = STCONT; + } else if (match (";")) + ; + else if (amatch ("case", 4)) { + docase (); + lastst = statement (NO); + } else if (amatch ("default", 7)) { + dodefault (); + lastst = statement (NO); + } else if (match ("#asm")) { + doasm (); + lastst = STASM; + } else if (match ("{")) + compound (NO); + else { + expression (YES); +/* if (match (":")) { + dolabel (); + lastst = statement (NO); + } else { +*/ ns (); + lastst = STEXP; +/* } +*/ } + return 0; +} + +/* + * compound statement + * + * allow any number of statements to fall between "{" and "}" + * + * 'func' is true if we are in a "function_statement", which + * must contain "statement_list" + */ +compound (func) +int func; +{ + int decls; + + decls = YES; + ncmp++; + while (!match ("}")) { + if (feof (input)) + return 0; + if (decls) { + if (!stdecl ()) + decls = NO; + } else + stst (); + } + ncmp--; + return 0; +} + +/* + * "if" statement + */ +doif () +{ + int fstkp, flab1, flab2; + char *flev; + + flev = locptr; + fstkp = stkp; + flab1 = getlabel (); + test (flab1, FALSE); + statement (NO); + stkp = modstk (fstkp); + locptr = flev; + if (!amatch ("else", 4)) { + gnlabel (flab1); + return 0; + } + jump (flab2 = getlabel ()); + gnlabel (flab1); + statement (NO); + stkp = modstk (fstkp); + locptr = flev; + gnlabel (flab2); + return 0; +} + +/* + * "while" statement + */ +dowhile () +{ + int ws[7]; + + ws[WSSYM] = locptr; + ws[WSSP] = stkp; + ws[WSTYP] = WSWHILE; + ws[WSTEST] = getlabel (); + ws[WSEXIT] = getlabel (); + addwhile (ws); + gnlabel (ws[WSTEST]); + test (ws[WSEXIT], FALSE); + statement (NO); + jump (ws[WSTEST]); + gnlabel (ws[WSEXIT]); + locptr = ws[WSSYM]; + stkp = modstk (ws[WSSP]); + delwhile (); + return 0; +} + +/* + * "do" statement + */ +dodo () +{ + int ws[7]; + + ws[WSSYM] = locptr; + ws[WSSP] = stkp; + ws[WSTYP] = WSDO; + ws[WSBODY] = getlabel (); + ws[WSTEST] = getlabel (); + ws[WSEXIT] = getlabel (); + addwhile (ws); + gnlabel (ws[WSBODY]); + statement (NO); + if (!match ("while")) { + error ("missing while"); + return 0; + } + gnlabel (ws[WSTEST]); + test (ws[WSBODY], TRUE); + gnlabel (ws[WSEXIT]); + locptr = ws[WSSYM]; + stkp = modstk (ws[WSSP]); + delwhile (); + return 0; +} + +/* + * "for" statement + */ +dofor () +{ + int ws[7], + *pws; + + ws[WSSYM] = locptr; + ws[WSSP] = stkp; + ws[WSTYP] = WSFOR; + ws[WSTEST] = getlabel (); + ws[WSINCR] = getlabel (); + ws[WSBODY] = getlabel (); + ws[WSEXIT] = getlabel (); + addwhile (ws); + pws = readwhile (); + needbrack ("("); + if (!match (";")) { + expression (YES); + ns (); + } + gnlabel (pws[WSTEST]); + if (!match (";")) { + expression (YES); + testjump (pws[WSBODY], TRUE); + jump (pws[WSEXIT]); + ns (); + } else + pws[WSTEST] = pws[WSBODY]; + gnlabel (pws[WSINCR]); + if (!match (")")) { + expression (YES); + needbrack (")"); + jump (pws[WSTEST]); + } else + pws[WSINCR] = pws[WSTEST]; + gnlabel (pws[WSBODY]); + statement (NO); + jump (pws[WSINCR]); + gnlabel (pws[WSEXIT]); + locptr = pws[WSSYM]; + stkp = modstk (pws[WSSP]); + delwhile (); + return 0; +} + +/* + * "switch" statement + */ +doswitch () +{ + int ws[7]; + int *ptr; + + ws[WSSYM] = locptr; + ws[WSSP] = stkp; + ws[WSTYP] = WSSWITCH; + ws[WSCASEP] = swstp; + ws[WSTAB] = getlabel (); + ws[WSDEF] = ws[WSEXIT] = getlabel (); + addwhile (ws); +// immed (); +// printlabel (ws[WSTAB]); +// nl (); +// gpush (); + needbrack ("("); + expression (YES); + needbrack (")"); +// stkp = stkp + intsize(); /* '?case' will adjust the stack */ + gjcase (); + jump (ws[WSTAB]); + statement (NO); + ptr = readswitch (); +// if (ptr[WSDEF]!=ptr[WSEXIT]) jump (ptr[WSDEF]); + jump (ptr[WSEXIT]); + dumpsw (ptr); + gnlabel (ptr[WSEXIT]); + locptr = ptr[WSSYM]; +// stkp = modstk (ptr[WSSP]); + swstp = ptr[WSCASEP]; + delwhile (); + return 0; +} + +/* + * "case" label + */ +docase () +{ + int val; + + val = 0; + if (readswitch ()) { + if (!number (&val)) + if (!pstr (&val)) + error ("bad case label"); + addcase (val); + if (!match (":")) + error ("missing colon"); + } else + error ("no active switch"); + return 0; +} + +/* + * "default" label + */ +dodefault () +{ + int *ptr, + lab; + + if (ptr = readswitch ()) { + ptr[WSDEF] = lab = getlabel (); + gnlabel (lab); + if (!match (":")) + error ("missing colon"); + } else + error ("no active switch"); + return 0; +} + +/* + * "return" statement + */ +doreturn () +{ + if (endst () == 0) + expression (YES); + jump(fexitlab); + return 0; +} + +/* + * "break" statement + */ +dobreak () +{ + int *ptr; + + if ((ptr = readwhile ()) == 0) + return 0; + modstk (ptr[WSSP]); + jump (ptr[WSEXIT]); + return 0; +} + +/* + * "continue" statement + */ +docont () +{ + int *ptr; + + if ((ptr = findwhile ()) == 0) + return 0; +/* modstk (ptr[WSSP]); */ + if (ptr[WSTYP] == WSFOR) + jump (ptr[WSINCR]); + else + jump (ptr[WSTEST]); + return 0; +} + +/* + * dump switch table + */ +dumpsw (ws) +int ws[]; +{ + int i,j; + + gdata (); + gnlabel (ws[WSTAB]); + if (ws[WSCASEP] != swstp) { + j = ws[WSCASEP]; + while (j < swstp) { + i = 4; + while (i--) { + immd3(); + onum (swstcase[j]); + nl(); + casejump(); + jump (swstlab[j++]); + if ((i == 0) | (j >= swstp)) { + nl (); + break; + } + nl(); + } + } + jump(ws[WSDEF]); + } + gtext (); + return 0; +} ADDED src/cc8/cross/sym.c Index: src/cc8/cross/sym.c ================================================================== --- /dev/null +++ src/cc8/cross/sym.c @@ -0,0 +1,251 @@ +/* File sym.c: 2.1 (83/03/20,16:02:19) */ +/*% cc -O -c % + * + */ + +#include +#include "defs.h" +#include "data.h" + +/* + * declare a static variable + */ +declglb (typ, stor) +int typ, + stor; +{ + int k, j; + char sname[NAMESIZE]; + + FOREVER { + FOREVER { + if (endst ()) + return 0; + k = 1; + if (match ("*")) + j = POINTER; + else + j = VARIABLE; + if (!symname (sname)) + illname (); + if (findglb (sname)) + multidef (sname); + if (match ("[")) { + k = needsub (); + if (k || stor == EXTERN) + j = ARRAY; + else + j = POINTER; + } + addglb (sname, j, typ, k, stor); + break; + } + if (!match (",")) + return 0; + } +} + +/* + * declare local variables + * + * works just like "declglb", but modifies machine stack and adds + * symbol table entry with appropriate stack offset to find it again + */ +declloc (typ, stclass) +int typ, stclass; +{ + int k, j; + char sname[NAMESIZE]; + + FOREVER { + FOREVER { + if (endst ()) + return 0; + if (match ("*")) + j = POINTER; + else + j = VARIABLE; + if (!symname (sname)) + illname (); + if (findloc (sname)) + multidef (sname); + if (match ("[")) { + k = needsub (); + if (k) { + j = ARRAY; + if (typ == CINT) + k = k * intsize(); + } else { + j = POINTER; + k = intsize(); + } + } else + if ((typ == CCHAR) & (j != POINTER)) + k = 1; + else + k = intsize(); + if (stclass != LSTATIC) { + k = galign(k); + stkp = modstk (stkp - k); + addloc (sname, j, typ, stkp+k-1, AUTO); /* Reversed stack for PDP8 */ + } else + addloc( sname, j, typ, k, LSTATIC); + break; + } + if (!match (",")) + return 0; + } +} + +/* + * get required array size + */ +needsub () +{ + int num[1]; + + if (match ("]")) + return (0); + if (!number (num)) { + error ("must be constant"); + num[0] = 1; + } + if (num[0] < 0) { + error ("negative size illegal"); + num[0] = (-num[0]); + } + needbrack ("]"); + return (num[0]); +} + +findglb (sname) +char *sname; +{ + char *ptr; + + ptr = STARTGLB; + while (ptr != glbptr) { + if (astreq (sname, ptr, NAMEMAX)) + return (ptr); + ptr = ptr + SYMSIZ; + } + return (0); +} + +findloc (sname) +char *sname; +{ + char *ptr; + + ptr = locptr; + while (ptr != STARTLOC) { + ptr = ptr - SYMSIZ; + if (astreq (sname, ptr, NAMEMAX)) + return (ptr); + } + return (0); +} + +addglb (sname, id, typ, value, stor) +char *sname, id, typ; +int value, + stor; +{ + char *ptr; + + if (cptr = findglb (sname)) + return (cptr); + if (glbptr >= ENDGLB) { + error ("global symbol table overflow"); + return (0); + } + cptr = ptr = glbptr; + while (an (*ptr++ = *sname++)); + cptr[IDENT] = id; + cptr[TYPE] = typ; + cptr[STORAGE] = stor; + cptr[OFFSET] = gsize & 0xff; + cptr[OFFSET+1] = (gsize >> 8) & 0xff; + gsize = gsize + value; + glbptr = glbptr + SYMSIZ; + return (cptr); +} + +addloc (sname, id, typ, value, stclass) +char *sname, id, typ; +int value, stclass; +{ + char *ptr; + int k; + + if (cptr = findloc (sname)) + return (cptr); + if (locptr >= ENDLOC) { + error ("local symbol table overflow"); + return (0); + } + cptr = ptr = locptr; + while (an (*ptr++ = *sname++)); + cptr[IDENT] = id; + cptr[TYPE] = typ; + cptr[STORAGE] = stclass; + if (stclass == LSTATIC) { + gdata(); + printlabel(k = getlabel()); + col(); + defstorage(); + onum(value); + nl(); + gtext(); + value = k; + } else + value = galign(value); + cptr[OFFSET] = value & 0xff; + cptr[OFFSET+1] = (value >> 8) & 0xff; + locptr = locptr + SYMSIZ; + return (cptr); +} + +/* + * test if next input string is legal symbol name + * + */ +symname (sname) +char *sname; +{ + int k; + char c; + + blanks (); + if (!alpha (ch ())) + return (0); + k = 0; + while (an (ch ())) + sname[k++] = gch (); + sname[k] = 0; + return (1); +} + +illname () +{ + error ("illegal symbol name"); + return 0; +} + +multidef (sname) +char *sname; +{ + error ("already defined"); + comment (); + outstr (sname); + nl (); + return 0; +} + +glint(syment) char *syment; { + short l,u,r; + l = syment[OFFSET]; + u = syment[OFFSET+1]; + r = (l & 0xff) + ((u << 8) & ~0x00ff); + return (r); +} ADDED src/cc8/cross/while.c Index: src/cc8/cross/while.c ================================================================== --- /dev/null +++ src/cc8/cross/while.c @@ -0,0 +1,79 @@ +/* File while.c: 2.1 (83/03/20,16:02:22) */ +/*% cc -O -c % + * + */ + +#include +#include "defs.h" +#include "data.h" + +addwhile (ptr) +int ptr[]; +{ + int k; + + if (wsptr == WSMAX) { + error ("too many active whiles"); + return 0; + } + k = 0; + while (k < WSSIZ) + *wsptr++ = ptr[k++]; + return 0; +} + +delwhile () +{ + if (readwhile ()) + wsptr = wsptr - WSSIZ; + return 0; +} + +int readwhile () +{ + if (wsptr == ws) { + error ("no active do/for/while/switch"); + return (0); + } else + return (wsptr-WSSIZ); +} + +int *findwhile () +{ + int *ptr; + + for (ptr = wsptr; ptr != ws;) { + ptr = ptr - WSSIZ; + if (ptr[WSTYP] != WSSWITCH) + return (ptr); + } + error ("no active do/for/while"); + return (0); +} + +int *readswitch () +{ + int *ptr; + + if (ptr = (int *)readwhile ()) + if (ptr[WSTYP] == WSSWITCH) + return (ptr); + return (0); +} + +addcase (val) +int val; +{ + int lab; + + if (swstp == SWSTSZ) + error ("too many case labels"); + else { + swstcase[swstp] = val; + swstlab[swstp++] = lab = getlabel (); + printlabel (lab); + col (); + nl (); + } + return 0; +} ADDED src/cc8/examples/README.md Index: src/cc8/examples/README.md ================================================================== --- /dev/null +++ src/cc8/examples/README.md @@ -0,0 +1,104 @@ +# Using the Examples + +This directory contains several example programs. We will use the +`calc.c` example throughout this section. + +The program may be compiled using the cc8 cross-compiler to SABR sources +like so: + + $ cc8 calc.c + +You can then use the `txt2ptp` program to turn the resulting `calc.s` +file into a paper tape to be loaded into OS/8: + + $ txt2ptp < calc.s > calc.pt + $ pidp8i ⇠ ...start PDP-8 sim somehow, then hit Ctrl-E + sim> att -r dt0 calc.pt + sim> cont + .R PIP + *CALC.SBEnter key starts the transfer in the final command. The +transfer stops when `PIP` sees the Ctrl-Z EOF marker added to +the end of the paper tape by `txt2ptp`. The first Escape +finalizes the transfer and the second exits PIP, returning you to the +OS/8 command prompt. + +See the [assembly examples' `README.md` file][aerm] or the [U/W FOCAL +manual supplement][uwfs] for more ideas on how to get text files like +this SABR file into OS/8. + +However you manage it, you can then assemble, load, and run the programs +on the OS/8 side with: + + .COMP CALC.SB + .R LOADER + *CALC,LIBC/G ⇠ press Esc to execute command and exit LOADER + +The `/G` flag causes the loader to run the linked program immediately, +but once you're done modifying the program, you probably want to save it +as a core image so it can be run directly instead of being linked and +loaded again each time. You can give `/M` instead, which finalizes the +link and then exits, printing a map file before it does so. You can then +save the result where the OS/8 `R` command can find it with: + + .SAVE SYS:CALC + +That produces `SYS:CALC.SV`, which an `R CALC` command will load and +run. + +If you wish to compile from C source code on the OS/8 side rather than +cross-compile, I recommend using the `CC0` front-end rather than the +method shown in the [top level `README.md` file][tlrm] involving the +`CC1` stage: + + .R CC0 + + >calc.cc + + .COMP CC.SB + + .R LOADER + *CC,LIBC/M + +Notice that the front-end processor produces `CC.SB`, not `CALC.SB` as +you might be expecting. This is where the `CC` comes from in the `COMP` +and `LOADER` commands. + +Note that `CC0` tolerates lowercase input. + + +[aerm]: /doc/trunk/examples/README.md +[tlrm]: /doc/trunk/src/cc8/README.md +[uwfs]: /doc/trunk/doc/uwfocal-manual-supp.md + + +# The Examples + +In order of complexity, they are: + +## calc.c + +This is a simple 4-function calculator. + + +## ps.c + +This prints several levels of [Pascal's triangle][pt]. + +[pt]: https://en.wikipedia.org/wiki/Pascal%27s_triangle + + +## fib.c + +This program calculates [Fibonacci humbers][fn], which implicitly +demonstrates the C compiler's ability to handle [recursion][rec]. + +[fn]: https://en.wikipedia.org/wiki/Fibonacci_number +[rec]: https://en.wikipedia.org/wiki/Recursion_(computer_science) + + +## basic.c + +A very simple BASIC interpreter. This program tests a broad swath of the +compiler's functionality. ADDED src/cc8/examples/basic.c Index: src/cc8/examples/basic.c ================================================================== --- /dev/null +++ src/cc8/examples/basic.c @@ -0,0 +1,245 @@ +#include +#include + + +#define SMAX 10 +#define CMAX 256 +#define BMAX 64 +#define LMAX 32 +#define DMAX 32 +#define CBMX 1024 +#define LXMX 999 + + +int E [SMAX]; /* subroutine line number stack */ +int L [CMAX]; /* FOR loop beginning line number */ +int M [CMAX]; /* FOR loop maximum index value */ +int P [CMAX]; /* program variable value */ +char Lb[CBMX]; /* Line buffer of CBMX chars */ +int l,i,j; +int *C; /* subroutine stack pointer */ +char B [BMAX]; /* command input buffer */ +char F [2]; /* temporary search string */ +char *m [LXMX]; /* pointers to lines of program. This is a real waste of space! */ +char *p,*q,*x,*y,*z,*s,*d; + + + +G( ) { /* get program line from buffer */ + atoi(B,&l); + y=m[l]; + if(y){ + if(strstr(B," ")) + strcpy(y,B); + else + y=m[l]=0; + return; + } + y=Lb; + while(*y) + y=y+DMAX; + strcpy(y,B); + m [l]=y; +} /* end G */ + +/* recursive descent parser for arithmetic/logical expressions */ +S( ) { + int o; + + o=J( ); + switch(*p++){ + case '=': return o==S( ); + break; + case '#': return o!=S( ); + default: p--; return o; + } +} /* end S */ + +J( ) { + int o; + + o=K( ); + switch(*p++){ + case '<': return o': return o>J( ); + default: p--; return o; + } +} /* end J */ + +K( ) { + int o; + + o=V( ); + switch(*p++){ + case '$': return o<=K( ); + break; + case '!': return o>=K( ); + default: p--; return o; + } +} /* end K */ + +V( ) { + int o; + + o=W( ); + switch(*p++){ + case '+': return o+V( ); + break; + case '-': return o-V( ); + default: p--; return o; + } +} /* end V */ + +W( ) { + int o; + + o=Y( ); + switch(*p++){ + case '*': return o*W( ); + break; + case '/': return o/W( ); + default: p--; return o; + } +} /* end W */ + +Y( ) { + int o; + + if(*p=='-'){ + p++; + return -Y(); + } + q=p; + if(*p>='0'&&*p<='9'){ + while(*p>='0'&&*p<='9') + p++; + atoi(q,&o); + return o; + } + if(*p=='('){ + p++; o=S( ); p++; + return o; + } + return P [*p++]; +} /* end Y */ + +bufclear() +{ + memset(m,0,LXMX); + memset(Lb,0,CBMX); +} + +main( ) { + int tmp; /* temp var to fix bug 07Sep2005 Somos */ + + bufclear(); + while(puts("Ok\r\n"),gets(B)) + switch(*B){ + case 'R': /* "RUN" command */ + C=E; + l=1; + for(i=0; i" ) ) ) * p ++ = '#' , * p = ' ' ; + while ( ( p = strstr ( s , "<=" ) ) ) * p ++ = '$' , * p = ' ' ; + while ( ( p = strstr ( s , ">=" ) ) ) * p ++ = '!' , * p = ' ' ; + } + d=B; + j=0; + while(*s){ + if(*s=='"') j++; + if(*s!=' '||(j&1)) *d++=*s; + s++; + } + *d=j=0; + d--; /* backup to last char in line */ + if(B [1]!='='){ + switch(*B){ + case 'E': /* "END" */ + l=-1; + break; + case 'R': /* "REM" */ + if(B [2]!='M') l=*--C; /* "RETURN" */ + break; + case 'I': + if(B [1]=='N'){ /* "INPUT" */ + tmp=*d; /* save for bug fix next line 07Sep2005 Somos */ + gets(p=B); P [tmp]=S( ); + } else { /* "IF" */ + *(tmp=strstr(B,"TH"))=0; /* "THEN" */ + p=B+2; + if(S( )){ + p=tmp+4; l=S( )-1; + } + } + break; + case 'P': /* "PRINT" */ + tmp=','; + p=B+5; + while(tmp==','){ + if(*p=='"'){ + while(*++p!='"') + putc(*p); + p++; + } else { + printf("%d",S( )); + } + tmp=*p++; + putc(' '); + } + puts("\r\n"); + break; + case 'G': /* "GOTO" */ + p=B+4; + if(B [2]=='S'){ /* "GOSUB" */ + *C++=l; p++; + } + l=S( )-1; + break; + case 'F': /* "FOR" */ + *(tmp=strstr(B,"TO"))=0; /* "TO" */ + p=B+5; + P [i=B [3]]=S( ); + p=tmp+2; + M [i]=S( ); + L [i]=l; + break; + case 'N': /* "NEXT" */ + tmp=*d; + if(P [tmp] +#include + +main() +{ + int x, y, ans; + int choice; + int bfr[10]; + + /* CC8 doesn't let you initialize variables at declaration time. */ + ans = 'Y'; + + /* This would be clearer as a do/while loop, but CC8 doesn't support + * that yet. */ + while (1) { + /* Force answer from tail end of loop to uppercase since CC8 + * doesn't know the || operator yet. CC8's libc doesn't have + * toupper(), and I can't seem to get its cupper() alternative + * to work. Don't rewrite with the -= operator: that doesn't + * work yet, either. */ + if (ans > 'Z') ans = ans - 32; + + /* You might be tempted to write "if (ans != 'Y') break;" and + * then do away with one indent level for the main body of code + * that follows, but CC8 doesn't know the != operator yet. */ + if (ans == 'Y') { + printf("\r\nENTER ANY TWO NUMBERS.\r\n"); + + printf("ENTER THE FIRST NUMBER: "); + gets(bfr); + sscanf(bfr, "%d", &x); + + printf("ENTER THE SECOND NUMBER: "); + gets(bfr); + sscanf(bfr, "%d", &y); + + printf("SELECT THE OPERATION:\r\n"); + + printf("1: ADDITION\r\n"); + printf("2: SUBTRACTION\r\n"); + printf("3: MULTIPLICATION\r\n"); + printf("4: DIVISION\r\n"); + + printf("CHOICE: "); + gets(bfr); + sscanf(bfr, "%d", &choice); + + if (choice == 1) printf("Result: %d\r\n", x + y); + if (choice == 2) printf("Result: %d\r\n", x - y); + if (choice == 3) printf("Result: %d\r\n", x * y); + if (choice == 4) printf("Result: %d\r\n", x / y); + + printf("DO YOU WISH TO CONTINUE (Y/N): "); + ans = getc(); + } + else { + break; + } + } +} ADDED src/cc8/examples/fib.c Index: src/cc8/examples/fib.c ================================================================== --- /dev/null +++ src/cc8/examples/fib.c @@ -0,0 +1,18 @@ +#include +#include + +fib(n) +{ + if (n < 2) + return n; + else + return fib(n-1)+fib(n-2); +} + + +main() +{ + int i; + for (i=0;i<10;i++) + printf("Fib#:%d = %d\r\n", i, fib(i)); +} ADDED src/cc8/examples/init.h Index: src/cc8/examples/init.h ================================================================== --- /dev/null +++ src/cc8/examples/init.h @@ -0,0 +1,1 @@ +../include/init.h ADDED src/cc8/examples/libc.h Index: src/cc8/examples/libc.h ================================================================== --- /dev/null +++ src/cc8/examples/libc.h @@ -0,0 +1,1 @@ +../include/libc.h ADDED src/cc8/examples/ps.c Index: src/cc8/examples/ps.c ================================================================== --- /dev/null +++ src/cc8/examples/ps.c @@ -0,0 +1,19 @@ +#include +#include + +main() +{ + int ar[20],i,j,n; + + n=14; + for (i=1;i1;j--) + ar[j]=ar[j-1]+ar[j]; + for (j=0;j<2*(n-i-1);j++) + putc(' '); + for (j=1;j. + */ + +#asm +/ +/ PDP8/E Run time routines for the Small-C compiler. SABR syntax. +/ +ABSYM POP 160 +ABSYM PSH 161 +ABSYM JLC 162 +ABSYM STKP 163 +ABSYM PTSK 164 +ABSYM POPR 165 +ABSYM PCAL 166 +ABSYM TMP 167 +ABSYM GBL 170 +ABSYM ZTMP 171 +/ + DECIM +/ +STK, COMMN 3840 +/ +/ +/ + ENTRY MAIN +MAIN, BLOCK 2 + TAD GBLS + DCA STKP + TAD GBLS + DCA GBL + ISZ GBL / LOCAL LITERALS = STKP+1 + TAD PVL + DCA PSH + TAD OVL + DCA POP + TAD MVL + DCA PTSK + TAD PVR + DCA POPR + TAD PVC + DCA PCAL + RIF + TAD (3201 + DCA PCL1 + TAD PCL1 + DCA DCC0 + JMS MCC0 + CLA CMA + MQL + CALL 1,LIBC + ARG STKP + CALL 0,OPEN + JMSI PCAL + XMAIN + CALL 0,EXIT +/ +PUSH, 0 + CDF1 + ISZ STKP + DCAI STKP + TADI STKP + JMPI PUSH +PPOP, 0 + CDF1 + DCA TMP + TADI STKP + MQL + CMA + TAD STKP + DCA STKP + TAD TMP + JMPI PPOP +PUTSTK, 0 + JMSI POP + SWP + DCA JLC + SWP + DCAI JLC + TADI JLC + JMPI PUTSTK +POPRET, JMSI POP + SWP + DCA ZTMP + SWP + JMPI ZTMP +PCALL, 0 + CLA CLL +PCL1, 0 + TADI PCALL + DCA ZTMP + TAD PCALL + IAC + JMSI PSH / PUSH RETURN + CLA + JMPI ZTMP +PVL, PUSH +OVL, PPOP +MVL, PUTSTK +SVL, STK +PVR, POPRET +PVC, PCALL +/ +#endasm ADDED src/cc8/include/libc.h Index: src/cc8/include/libc.h ================================================================== --- /dev/null +++ src/cc8/include/libc.h @@ -0,0 +1,58 @@ +/* + * This file is part of the CC8 cross-compiler. + * + * The CC8 cross-compiler is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * The CC8 cross-compiler is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the CC8 cross-compiler as ../GPL3.txt. If not, see + * . + */ + +/* + * Libc header + * + * Please note: no function declarations are made, so make sure the + * arg lists are correct in the calling code. + * +*/ + +#define itoa libc0 +#define puts libc1 +#define nl libc2 +#define getc libc3 +#define gets libc4 +#define atoi libc5 +#define sscanf vlibc6 +#define xinit libc7 +#define memcpy libc8 +#define kbhit libc9 +#define putc libc10 +#define strcpy libc11 +#define strcat libc12 +#define strstr libc13 +#define exit libc14 +#define isnum libc15 +#define isdigit libc15 +#define isalpha libc16 +#define sprintf vlibc17 +#define memset libc18 +#define fgetc libc19 +#define fopen libc20 +#define fputc libc21 +#define fclose libc22 +#define printf vlibc23 +#define isalnum libc24 +#define isspace libc25 +#define fprintf vlibc26 +#define fputs libc27 +#define strcmp libc28 +#define cupper libc29 +#define fgets libc30 ADDED src/cc8/os8/Makefile.in Index: src/cc8/os8/Makefile.in ================================================================== --- /dev/null +++ src/cc8/os8/Makefile.in @@ -0,0 +1,46 @@ +######################################################################## +# Makefile.in - Processed by autosetup's configure script to generate +# an intermediate GNU make(1) file for building the PiDP-8/I software +# from within its src/cc8/os8 subdirectory. +# +# The resulting Makefile will redirect simple "make" calls to the top +# level as well as the major top-level targets (e.g. "make clean") but +# purposefully will not redirect anything like an installation or "run +# the system" type target. Its only purpose is to help out those who +# are working on CC8 from within this directory. If you need to work +# on the wider system, do it from the project's top level. +# +# If you are seeing this at the top of a file called Makefile and you +# intend to make edits, do that in Makefile.in. Saying "make" will then +# re-build Makefile from that modified Makefile.in before proceeding to +# do the "make" operation. +# +# Copyright © 2017 Warren Young +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +all clean ctags distclean tags reconfig: + cd @builddir@; make $@ ADDED src/cc8/os8/bldcc8.bi Index: src/cc8/os8/bldcc8.bi ================================================================== --- /dev/null +++ src/cc8/os8/bldcc8.bi @@ -0,0 +1,15 @@ +$JOB BUILD C COMPILER AND LIBRARY +.COMP C8.SB +.COMP P8.SB +.COMP N8.SB +.COMP LIBC.SB +.R LOADER +*P8,LIBC/I/O$ +.SAVE SYS CC2 +.R LOADER +*N8,LIBC/I/O$ +.SAVE SYS CC1 +.R LOADER +*C8,LIBC/I/O$ +.SAVE SYS CC +$END ADDED src/cc8/os8/c8.c Index: src/cc8/os8/c8.c ================================================================== --- /dev/null +++ src/cc8/os8/c8.c @@ -0,0 +1,46 @@ +/* + * This file is part of the CC8 OS/8 C compiler. + * + * The CC8 OS/8 C compiler is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * The CC8 OS/8 C compiler is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the CC8 OS/8 C compiler as ../GPL3.txt. If not, see + * . + */ + +#include +#include + +/* C compiler driver: asks for input files, copies to CC.C & runs CC1 */ + +main() +{ + int bfr; + int fnm[10]; + + putc('>'); + gets(fnm); + cupper(fnm); + fopen(fnm,"r"); + fopen("CC.C","w"); + while (bfr=fgetc()) + if (bfr!=12) /* Ignore form feed */ + fputc(bfr); + fclose(); +#asm + CALL 1,CHAIN + ARG FNM + HLT +FNM, TEXT "CC1@@@" +#endasm + + +} ADDED src/cc8/os8/cc.bi Index: src/cc8/os8/cc.bi ================================================================== --- /dev/null +++ src/cc8/os8/cc.bi @@ -0,0 +1,6 @@ +$JOB COMPILE AND RUN C PROGRAMME +.R CC +.COMP CC.SB +.R LOADER +*CC,LIBC/I/O/G +$END ADDED src/cc8/os8/header.sb Index: src/cc8/os8/header.sb ================================================================== --- /dev/null +++ src/cc8/os8/header.sb @@ -0,0 +1,135 @@ +// +// This file is part of the CC8 OS/8 C compiler. +// +// The CC8 OS/8 C compiler is free software: you can redistribute it +// and/or modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// The CC8 OS/8 C compiler is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the CC8 OS/8 C compiler as ../GPL3.txt. If not, see +// . +// + +/ SABR DEFINITIONS +/ FRONT END (2.0:1/4/2017) + +OPDEF ANDI 0400 +OPDEF TADI 1400 +OPDEF ISZI 2400 +OPDEF DCAI 3400 +OPDEF JMSI 4400 +OPDEF JMPI 5400 +OPDEF MQL 7421 +OPDEF MQA 7701 +OPDEF MQO 7501 +OPDEF SWP 7521 +OPDEF CDF1 6211 +OPDEF CDF0 6201 +OPDEF RIF 6224 +OPDEF CAF0 6203 +OPDEF BSW 7002 +OPDEF CAM 7621 +/ +/ PDP8/E Run time routines for Small c compiler +/ +ABSYM POP 160 +ABSYM PSH 161 +ABSYM JLC 162 +ABSYM STKP 163 +ABSYM PTSK 164 +ABSYM POPR 165 +ABSYM PCAL 166 +ABSYM TMP 167 +ABSYM GBL 170 +ABSYM ZTMP 171 +/ + DECIM +/ +STK, COMMN 3840 +/ +/ +/ + ENTRY MAIN +MAIN, BLOCK 2 + TAD GBLS + DCA STKP + TAD GBLS + DCA GBL + ISZ GBL / LOCAL LITERALS = STKP+1 + TAD PVL + DCA PSH + TAD OVL + DCA POP + TAD MVL + DCA PTSK + TAD PVR + DCA POPR + TAD PVC + DCA PCAL + RIF + TAD (3201 + DCA PCL1 + TAD PCL1 + DCA DCC0 + JMS MCC0 + CLA CMA + MQL + CALL 1,LIBC + ARG STKP + CALL 0,OPEN + JMSI PCAL + XMAIN + CALL 0,EXIT +/ +PUSH, 0 + CDF1 + ISZ STKP + DCAI STKP + TADI STKP + JMPI PUSH +PPOP, 0 + CDF1 + DCA TMP + TADI STKP + MQL + CMA + TAD STKP + DCA STKP + TAD TMP + JMPI PPOP +PUTSTK, 0 + JMSI POP + SWP + DCA JLC + SWP + DCAI JLC + TADI JLC + JMPI PUTSTK +POPRET, JMSI POP + SWP + DCA ZTMP + SWP + JMPI ZTMP +PCALL, 0 + CLA CLL +PCL1, 0 + TADI PCALL + DCA ZTMP + TAD PCALL + IAC + JMSI PSH / PUSH RETURN + CLA + JMPI ZTMP +PVL, PUSH +OVL, PPOP +MVL, PUTSTK +SVL, STK +PVR, POPRET +PVC, PCALL +/ ADDED src/cc8/os8/init.h Index: src/cc8/os8/init.h ================================================================== --- /dev/null +++ src/cc8/os8/init.h @@ -0,0 +1,1 @@ +../include/init.h ADDED src/cc8/os8/libc.c Index: src/cc8/os8/libc.c ================================================================== --- /dev/null +++ src/cc8/os8/libc.c @@ -0,0 +1,985 @@ +/* + * This file is part of the CC8 OS/8 C compiler. + * + * The CC8 OS/8 C compiler is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * The CC8 OS/8 C compiler is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the CC8 OS/8 C compiler as ../GPL3.txt. If not, see + * . + */ + +/* + * PDP8/E LIBC routines for Small-C compiler. + * + * This is a complex collection of mixed C and SABR assembly routines. + * Some functions have been substantially shortened to save space + * relative to the original versions. Eventually, most of the C will + * need to be rewritten in SABR and hand optimised; e.g. atoi(). + */ + +#asm +ABSYM POP 147 +ABSYM PSH 150 +ABSYM JLC 151 +ABSYM STKP 152 +ABSYM PTSK 153 +ABSYM POPR 154 +ABSYM PCAL 155 +ABSYM TMP 156 +ABSYM GBL 157 +ABSYM ZTMP 146 +ABSYM ZPTR 145 +ABSYM ZCTR 144 +ABSYM FPTR 160 +/ + DECIM +/ +/ +/ +/ + DUMMY ARGST + DUMMY ARGNM +ARGST, BLOCK 2 +ARGNM, BLOCK 2 +/ + ENTRY LIBC +LIBC, BLOCK 2 + CLA CLL + TAD I LIBC + DCA ARGST + INC LIBC# + TAD I LIBC + DCA ARGST# + INC LIBC# + TAD I ARGST + DCA STKP + IAC + TAD LCALL / INIT ? LCALL==-1 + SZA CLA + JMP LB1 + TAD STKP + DCA GBL / SET LOCAL GBL(LITPTR) + ISZ GBL + TAD PVL + DCA PSH + TAD OVL + DCA POP + TAD MVL + DCA PTSK + TAD PVR + DCA POPR + TAD PVC + DCA PCAL + RIF + TAD (3201 + DCA PCL1 + TAD PCL1 + DCA DCC0 + JMS MCC0 + TAD STKP + DCA I ARGST / UPDATE MASTER STKP + DCA ZPTR / INIT PRINTF FLAG + DCA FPTR / INIT FPRINTF FLAG +LB1, MQA / CALL INDEX IN MQ + SPA + JMP LRET + TAD CPNT + DCA LCALL + TAD I LCALL + DCA LCALL + JMSI PCAL +LCALL, -1 + RETRN LIBC +LRET, CLA MQL + DCA LCALL / INIT OK + RETRN LIBC +/ +PUSH, 0 + CDF1 + ISZ STKP + DCAI STKP + TADI STKP + JMPI PUSH +PPOP, 0 + CDF1 + DCA TMP + TADI STKP + MQL + CMA + TAD STKP + DCA STKP + TAD TMP + JMPI PPOP +PUTSTK, 0 + JMSI POP + SWP + DCA JLC + SWP + DCAI JLC + TADI JLC + JMPI PUTSTK +POPRET, JMSI POP + SWP + DCA ZTMP + TAD STKP + DCA I ARGST / UPDATE MASTER STKP + SWP + CDF1 + JMPI ZTMP +PCALL, 0 + CLA CLL +PCL1, 0 + TADI PCALL + DCA ZTMP + TAD PCALL + IAC + JMSI PSH / PUSH RETURN + CLA + TAD STKP + DCA I ARGST / UPDATE MASTER STKP + CDF1 + JMPI ZTMP +PVL, PUSH +OVL, PPOP +MVL, PUTSTK +PVR, POPRET +PVC, PCALL +/ +CPNT, CLIST + CPAGE 24 +/ +/ THIS IS THE DISPATCH LIST FOR THIS LIBRARY +/ MAKE SURE LIBC.H MATCHES +/ +CLIST, ITOA + PUTS + DISPXY + GETC + GETS + ATOI + SSCANF + XINIT + MEMCPY + KBHIT + PUTC + STRCPY + STRCAT + STRSTR + EXIT + ISNUM + ISALPHA + SPRINTF + MEMSET + FGETC + FOPEN + FPUTC + FCLOSE + PRINTF + ISALNUM + ISSPACE + FPRINTF + FPUTS + STRCMP + CUPPER + FGETS +#endasm + +#define stdout 0 +#define NULL 0 +#define isdigit isnum + + + +fgetc() +{ +#asm + + CLA CLL + CALL 2,CHRIO + ARG (-4 + ARG FRSL + TAD FRSL + TAD (-26 /^Z + SNA CLA + DCA FRSL + TAD FRSL + CDF1 + JMPI POPR +FRSL,BLOCK 2 + +// CHRIO - CHARACTER I/O. +/ +/ CALL CHRIO(IDEVN,ICHAR) +/ +/ IDEV = FORT II DEVICE NUMBER. +/ +/ ICHAR = 7 OR 8 BIT CHARACTER. +/ +/ IF IDEV IS POSITIVE, THE CHAR IS OUTPUTTED. +/ +/ IF IDEV IS NEGATIVE, THE NEXT CHAR IS +/ READ FROM THE DEVICE, AND PUT IN ICHAR. +/ +// + ENTRY CHRIO +CHRIO, BLOCK 2 + JMS GETP + SPA /WHAT IS DEVICE SIGN? + JMP RCHAR /NEG DEV. MEANS READ. + JMS SETDEV /POS DEV. MEANS WRITE. + 0000 + JMS GETP + DCA ICHAR + JMS CHSUB + JMP XIT + +IDEV, 0 +ICHAR, 0 +ADDR, 0 + +RCHAR, CIA /READ A CHAR. + JMS SETDEV + 1024 /SET BIT FOR READ. (8 UNITS NOW!) + JMS GETP + CLA + TAD CDFB + DCA CDFCH + JMS CHSUB +CDFCH, HLT + AND (127 / 7 BIT FOR NOW + DCAI ADDR +XIT, CLA + RETRN CHRIO + +SETDEV, 0 + TAD (-1 + AND (7 + CLL RAR;RTR;RTR + TAD I SETDEV + INC SETDEV + DCA IDEV + JMP I SETDEV + +CHSUB, 0 + TAD ICHAR + AND (255 + TAD IDEV + CALL 0,GENIO + JMP I CHSUB + +GETP, 0 + TAD CHRIO + DCA CDFA +CDFA, HLT + TADI CHRIO# + DCA CDFB + INC CHRIO# + TADI CHRIO# + DCA ADDR + INC CHRIO# +CDFB, HLT + TADI ADDR + JMP I GETP +#endasm +} + +fputc(ch) +int ch; +{ + ch; +#asm + DCA FRSL + CALL 2,CHRIO + ARG (4 + ARG FRSL + CDF1 + TAD FRSL +#endasm +} + sixbit(p) +char *p; +{ + *p++; +#asm + AND (63 + BSW + MQL +#endasm + *p; +#asm + AND (63 + MQO +#endasm +} + +fputs(p) +int *p; +{ + while (*p++) +#asm + DCA FRSL + CALL 2,CHRIO + ARG (4 + ARG FRSL + CDF1 +#endasm +} + + +fopen (fnm,flg) +char *fnm; +int flg; +{ + char *p; + p=fnm; + p=strstr(fnm,"."); + if (p==0) + return(-1); + if (*flg=='w') { +#asm + CLA + TAD FC1# + DCA FBSE# + JMP FC3 +FC1, CALL 0,OOPEN +FC2, CALL 0,IOPEN +#endasm + } + if (*flg=='r') { +#asm + CLA + TAD FC2# + DCA FBSE# +FC3, CDF1 +#endasm + *p++=0; + sixbit(p); +#asm + PAGE + / OFFSET IOPEN+81 = FILEEX + DCA ZTMP + TAD FC2# / CODE + AND (63 + TAD (128 + DCA FDCT + CDF0 + TADI FDCT + DCA FEX1 + TAD FDCT + TAD (64 + DCA FDCT + TADI FDCT + TAD (81 / OFFSET OF EXTENSION + DCA FDCT +FEX1, HLT + TAD ZTMP + DCAI FDCT + CDF1 +#endasm + fnm; +#asm + DCA ZTMP / PACK 6 8BIT CHARS INTO FILENAME + TAD (-3 + DCA FDCT + TAD FDCA + DCA FP4 +FP1, CAM + TADI ZTMP + SNA + JMP FP2 + AND (63 + BSW + MQL + ISZ ZTMP +FP2, TADI ZTMP / WILL USE STACK FIELD + AND (63 + SZA + ISZ ZTMP + MQO +FP4, DCA FFNM + ISZ FP4 + ISZ FDCT + JMP FP1 + TAD (46 + DCAI ZTMP / PUT . BACK INTO FNM + CLA CLL CMA + TAD STKP + DCA STKP +FBSE, CALL 2,IOPEN + ARG FDEV + ARG FFNM + JMPI POPR +FDCA, DCA FFNM +FDCT, 0 +FFNM, TEXT /TM@@@@/ +FDEV, TEXT /DSK@@@/ +#endasm + } +} + +fclose() +{ +#asm + CALL 0,OCLOS +#endasm +} + + +puts(p) +char *p; + { + while (*p++) +#asm + TLS +XC1, TSF + JMP XC1 +#endasm + } + +dispxy(x,y) +int x,y; +{ + x; +#asm + 3115 / DIX +#endasm + y; +#asm + 3116 / DIY + 3117 / DIL +#endasm +} + +getc() +{ +#asm + CLA +GT1, KSF + JMP GT1 + KRB + TLS + AND (127 /* 7 BIT! */ +#endasm +} + +gets(p) +char *p; +{ +int q,tm; + tm=1; + q=p; + while (tm) { +#asm +XC2, CLA CLL + KSF + JMP XC2 + KRB + TAD (-255 + CLA + KRB + SNL / DO NOT ECHO BS + TLS +XC3, AND (127 + TAD (-13 / CR IS END OF STRING -> 0 + SZA + TAD (13 + DCAI STKP +#endasm + if (tm!=127) + *p++=tm; + else + if (p-q) { + puts("\b \b"); + p--; + } + } + putc(10); /* newline */ + return 1; +} + + +atoi(p,rsl) +char *p; +int *rsl; +{ +#asm + DCA ZTMP + DCA ZCTR + TAD (3584 / NOP + DCA XINV + CDF1 / Change DF back to 1 in case SABR changes it! +#endasm + while (*p==' ') + p++; + if (*p=='-') { +#asm + CLA + TAD (3617 + DCA XINV / CIA + CDF1 +#endasm + p++; + } + while (*p++) { +#asm + TAD (-48 / '0' ... SEE CODE + DCA JLC + TAD JLC + SPA CLA + JMP XRET + TAD (-10 + TAD JLC + SMA CLA + JMP XRET / EXIT IF NOT NUMBER + TAD ZTMP + CLL RTL / *4 + TAD ZTMP / *5 + CLL RAL / *10 + TAD JLC + DCA ZTMP + ISZ ZCTR / CHAR COUNTER +#endasm + } +#asm +XRET, TAD ZCTR + MQL + CMA + TAD STKP / ->RSL + DCA TMP + TADI TMP + DCA TMP + TAD ZTMP +XINV, NOP + DCAI TMP / WRITE RSL + MQA / RETURN LENGTH +#endasm +} + + +xinit() +{ + puts("PDP-8 C Compiler V1.0:\r\n"); +} + + +memcpy(dst,src,cnt) +int dst,src,cnt; +{ +#asm + CLA + TAD STKP + TAD (-4 + DCA 12 + CMA + TADI 12 + DCA 11 + CMA + TADI 12 + DCA 10 + TADI 12 + CIA + DCA ZTMP +CP1, TADI 10 + DCAI 11 + ISZ ZTMP + JMP CP1 +#endasm + +} + +kbhit() +{ +#asm + CLA CMA + KSF + CLA +#endasm +} + +putc(p) +char p; +{ + p; +#asm + TLS +MP1, TSF + JMP MP1 +#endasm +} + +strcmp( dm , sm ) +char *dm,*sm; +{ + int rsl; + + rsl=0; + while (*dm) + rsl+=(*sm++-*dm++); + return rsl; +} + +strcpy( dm , sm ) +char *dm,*sm; +{ + while (*dm++=*sm++); +} + +strcat( dm , sm ) +char *dm,*sm; +{ + int qm; + qm=dm; + while(*dm) dm++; + strcpy(dm,sm); + return qm; +} + +strstr ( s , o ) +char *s , *o ; +{ +char *x , *y , *z ; + for ( x = s ; * x ; x ++ ) { + for ( y = x , z = o ; * z && * y == * z ; y ++ ) z ++ ; + if ( z > o && ! * z ) return x ; + } return 0 ; +} + +exit(retval) +int retval; +{ +#asm + CALL 0,EXIT + HLT +#endasm +} + +isalnum(vl) +int vl; +{ + return (isnum(vl) + isalpha(vl)); +} + +isnum(vl) +int vl; +{ + vl; +#asm + TAD (-48 + SPA + JMP XNO + TAD (-10 + SMA CLA +XNO, CLA SKP + IAC +#endasm +} + +isspace(vl) +int vl; +{ + vl; +#asm + SNA + JMP YNO + TAD (-33 + SMA CLA +YNO, CLA SKP + IAC +#endasm +} + + +isalpha(vl) +int vl; +{ + vl; /* Include '?' and '@' as alpha vars */ +#asm + TAD (-65 + SPA + JMP ANO + TAD (-26 + SPA + JMP BNO + TAD (-6 + SPA + JMP ANO + TAD (-26 +BNO, SMA CLA +ANO, CLA SKP + IAC +#endasm +} + +cupper(p) /* In place convert to uppercase */ +int p; +{ + p; +#asm + DCA ZTMP +CPP1, CLA + TADI ZTMP + SNA + JMP CPP2 + TAD (-97 + SPA + JMP CPP3 + TAD (-26 + SMA + JMP CPP3 + TAD (91 + DCAI ZTMP +CPP3, ISZ ZTMP + JMP CPP1 +CPP2, +#endasm +} + +/* Arbitrary fgets(). Read until LF, CR/LF are retained*/ +/* EOF returns null, else strlen(*p) */ + +fgets(p) +char *p; +{ +char *q; + q=p; + while(*p=fgetc()) { + if (*p++==10) + break; + } + *p=0; + return (p-q); +} + +memset(dst, dt, sz) +char *dst; +int dt,sz; +{ + int i; + for (i=0;ilen) width = width - len; else width = 0; + if(!left) while(width--) {*obfr++=pad; ++cc;} + while(len--) {*obfr++=*sptr++; ++cc; } + if(left) while(width--) {*obfr++=pad; ++cc;} + } + *obfr=0; + zptr; +#asm + SNA / IF ZPTR, EITHER USE PUTS OR FPUTS + JMP PF1 + JMSI PSH + CLA + TAD FPTR + SNA CLA + JMP PF2 + JMSI PCAL + FPUTS + JMP PF3 +PF2, JMSI PCAL + PUTS +PF3, JMSI POP +PF1, CLA + DCA ZPTR + DCA FPTR +#endasm + + return(cc); + } + +/* +** itoa(n,s) - Convert n to characters in s +*/ +itoa(n, s) char *s; int n; { + int sign; + char *ptr; + ptr = s; + if ((sign = n) < 0) n = -n; + do { + *ptr++ = n % 10 + '0'; + } while ((n = n / 10) > 0); + if (sign < 0) *ptr++ = '-'; + *ptr = '\0'; + reverse(s); + } + +/* +** itoab(n,s,b) - Convert "unsigned" n to characters in s using base b. +** NOTE: This is a non-standard function. +*/ +itoab(n, s, b) int n; char *s; int b; { + char *ptr; + int lowbit; + ptr = s; + b >>= 1; + do { + lowbit = n & 1; + n = (n >> 1) & 4095; + *ptr = ((n % b) << 1) + lowbit; + if(*ptr < 10) *ptr += '0'; else *ptr += 55; + ++ptr; + } while(n /= b); + *ptr = 0; + reverse (s); + } + + +strlen(p) +char *p; +{ + int n; + + n=0; + while (*p++) + n++; + return n; +} + +#define EOF 0 + +sscanf(nxtarg) int nxtarg; { + char *ctl; + int u; + int *narg, wast, ac, width, ch, cnv, base, ovfl, sign, *ibfr; + ac = 0; + nxtarg = &nxtarg-nxtarg; + ibfr = *nxtarg++; + ctl = *nxtarg++; + while(*ctl) { + if(*ctl++ != '%') continue; + narg = *nxtarg++; + ctl += atoi(ctl, &width); + if (!width) + width=-1; + if(!(cnv = *ctl++)) break; + switch(cnv) { + case 'c': + *narg = *ibfr++; + break; + case 's': + while(width--) + if((*narg++ = *ibfr++) == 0) break; + *narg = 0; + break; + default: + switch(cnv) { + case 'b': base = 2; break; + case 'd': base = 10; break; + case 'o': base = 8; break; + case 'x': base = 16; break; + default: return (ac); + } + *narg = u = 0; + sign = 1; + while(width-- && (ch=*ibfr++)>32) { + if(ch == '-') {sign = -1; continue;} + if(ch < '0') break; + if(ch >= 'a') ch -= 87; + else if(ch >= 'A') ch -= 55; + else ch -= '0'; + u = u * base + ch; + } + *narg = sign * u; + } + ++ac; + } + return (ac); + } + ADDED src/cc8/os8/libc.h Index: src/cc8/os8/libc.h ================================================================== --- /dev/null +++ src/cc8/os8/libc.h @@ -0,0 +1,1 @@ +../include/libc.h ADDED src/cc8/os8/n8.c Index: src/cc8/os8/n8.c ================================================================== --- /dev/null +++ src/cc8/os8/n8.c @@ -0,0 +1,610 @@ +/* + * This file is part of the CC8 OS/8 C compiler. + * + * The CC8 OS/8 C compiler is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * The CC8 OS/8 C compiler is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the CC8 OS/8 C compiler as ../GPL3.txt. If not, see + * . + */ + + +#include +#include + + +#define SMAX 10 +#define CMAX 256 +#define BMAX 64 +#define LMAX 32 +#define DMAX 32 +#define CBMX 1024 +#define LXMX 999 + +int asm[CBMX]; +int ltbf[512]; +int xlt[CMAX]; +int gm[CMAX]; /* Global symbol table */ +int tkbf[LMAX]; +int *p,*q,*s,*ltpt; +int gsym,lsym,gadr,ladr,stkp,lctr,*fptr,gsz,ctr,tm,ectr,cop; +int glim,*n,ccm; +int tmp; +int tkn[BMAX]; +int bfr[BMAX]; +int tmbf[LMAX]; +int smbf[LMAX]; +int Lb[BMAX]; +int lm[CMAX]; /* Auto symbol table */ +int fstk[BMAX]; /* Push down stack for For etc. */ +int inproc,addr,cbrk; +int izf,ixf,idf,ssz,icd; + + +skpsp() +{ + while (isspace(*p)) + p++; +} + +getsym() +{ + q=tkbf; + skpsp(); + while (isalnum(*p)) + *q++=*p++; + *q=0; + skpsp(); + return *tkbf; +} + + +/* recursive descent parser for arithmetic/logical expressions */ + +S( ) { + + cop=0; + J( ); + switch(*p++){ + case '=': + S(); + stri(1); + stkp--; + break; + case ']': + case ')': + return 1; + case ',': + break; + default: + p--; + } + return 0; +} /* end S */ + +J( ) { + + K( ); + switch(*p++){ + case '&': J( ); stri(20); break; + case '|': J( ); stri(-20); break; + default: p--; return; + } + stkp--; +} /* end J */ + +K( ) { + + V( ); + switch(*p++){ + case '<': K( ); stri(11); break; + case '>': K( ); stri(-11); break; + case '$': K( ); stri(24); break; + default: p--; return; + } + stkp--; +} /* end K */ + +V( ) { + + W( ); + switch(*p++){ + case '+': V(); stri(2); break; + case '-': V(); stri(3); break; + default: p--; return; + } + stkp--; +} /* end V */ + +W( ) { + + Y( ); + skpsp(); + cop=*p; + switch(*p++) { + case '*': W( ); stri(13); break; + case '/': W( ); stri(14); break; + case '%': W( ); stri(14);stri(-14); break; + case '=': if (*p=='=') { + *p='$';return; + } + default: p--; return; + } + stkp--; +} /* end W */ + + +Y( ) { + int o,ctx; + int txbf[10]; + + skpsp(); + + if (!*p) + return; + + if (cop) { + stri(19); + stkp++; + } + + if (*p=='"') { + stri(10); + stri(ltpt-ltbf); + while (*++p-'"') { + if (*p=='\\') + switch (*++p) { + case 'r': + *p=13; + break; + case 'n': + *p=10; + } + *ltpt++=*p; + } + *ltpt++=0; + p++; + return; + } + n=q=p; + if (*p=='-') + p++; + if(isdigit(*p)) { + while(isdigit(*p)) + p++; + stri(4); + atoi(q,&tmp); + stri(tmp); + return; + } + if (*p==39) { + stri(4); + stri(*++p); + p+=2; + return; + } + ixf=izf=idf=icd=0; + if (!getsym()) { + switch (*p++) { + case '&': + getsym(); + stri(21); + stri(fndlcl(tkbf)); + return; + case '*': + getsym(); + ixf++; + break; + case '!': + Y(); + stri(26); + return; + case '~': + Y(); + stri(-26); + return; + case '(': + S(); + return; + case ')': + icd=1; + return; + } + } + if(*p=='('){ + strcpy(txbf,tkbf); + ctx=o=0;p++; + while (*p && !o) { + o=S( ); + if (icd) + break; + stkp++; + stri(19); + ctx++; /* arg count */ + } + stri(9); + stri(ctx); + stkp-=ctx; + if ((o=strstr(gm,txbf))){ + stri(o-gm); + } + else { + stri(gsz); + strpad(txbf); + strcat(gm,smbf); + gsz+=9; + } + return; + } + /* Digraphs */ + + q=p+1; + if (tmp=*q==*p) + switch (*p) { + case '+': + izf=-tmp; + p+=2; + break; + case '-': + idf=-tmp; + p+=2; + break; + } + + o=fndlcl(tkbf); + tmp=-17; + if (ssz>1) { + if (*p=='[') { + stri(21); + stri(o); + stri(19); + stkp++; + p++;S(); + stri(2); + if (*p=='=') + stri(19); + else { + stri(22); + stkp--; + } + return; + } + tmp=21; + } + switch (*p) { + case '=': + if (*q=='=') + break; + tmp=8; + if (ixf) + tmp=-8; + ixf=0; + stkp++; + break; + } + stri(tmp); + stri(o); + if (*n=='-') + stri(27); + if (izf) + stri(15); + if (idf) + stri(25); + if (ixf) + stri(22); + return; +} /* end Y */ + +procst(trm) +char trm; +{ + ccm=ctr=1; + p=q=Lb; + while(1) { + tm=fgetc(); + ctr-=tm=='('; + ctr+=tm==')'; + ccm-=tm==','; + if (!ctr || tm==trm) + break; + *q++=tm; + } + *q=0; + if (inproc) + while (*p) + S(); +} + +strpad(sym) +char *sym; +{ + char *a,*b; + + strcpy(a=smbf," "); /* 9 spaces */ + while (*sym) + *a++=*sym++; +} + +addsym(sym,sz) +char *sym; +int sz; +{ + strpad(sym); + smbf[8]=sz; + if (inproc+(sz<0)) { + smbf[7]=stkp+1; + stkp+=sz; + strcat(lm,smbf); + stri(6); + stri(sz); + return; + } + smbf[7]=gadr; + gadr+=sz; + strcat(gm,smbf); + gsz+=9; +} + +fndlcl(sym) +char *sym; +{ + strpad(sym); + smbf[7]=0; + if (s=strstr(lm,smbf)) { + ssz=s[8]; + s=s+7; + return *s-stkp; + } + if (s=strstr(gm,smbf)) { + ssz=s[8]; + s=s+7; + return *s; + } + return 0; +} + +gettk() +{ + char xtm; + + q=tkbf; + while (isspace(xtm=fgetc())); + while (isalnum(xtm)) { + *q++=xtm; + xtm=fgetc(); + } + *q=0; + return xtm; +} + +popfr() +{ + while (*fptr==inproc) { + cbrk=*--fptr; + stri(23); + stri(*--fptr); + stri(5); + stri(*fptr+2); + fptr--; + } +} + +dostt() +{ + p=tmbf; + while (tm!=';') { + *p++=tm; + tm=fgetc(); + } + *p=0; + strcpy(Lb,tkbf); + strcat(Lb,tmbf); + p=Lb; + S(); + tm=1; +} + +fnbrk() +{ + while (tm!='(') + tm=fgetc(); +} + +next() +{ + char *lp; + int fflg; + + lp=0; + if (*tkbf) { + strcat(tkbf," "); + lp=strstr(tkn,tkbf); + } + fflg=lctr; + if (lp) { + switch(lp-tkn) { + case 0: + while (tm!=';' && tm!='{') { + tm=gettk(); + strcpy(bfr,tkbf); + while (isspace(tm)) + tm=fgetc(); + switch (tm) { + case '[': + tm=gettk(); + atoi(tkbf,&fflg); + addsym(bfr,fflg); + tm=fgetc(); + break; + case '(': + stri(7); + stri(gsz); + if (strstr("main",tkbf)) + strcpy(tkbf,"XMAIN"); + addsym(tkbf,1); + procst(')'); + stkp=-(ccm+1); + while (*p) { + getsym(); + addsym(tkbf,-1); + p++; + stkp+=2; + } + stkp=0; + tm=gettk(); + cbrk=100; + break; + case ',': + case ';': + addsym(tkbf,1); + break; + } /* end whie */ + } /* end case 0: */ + break; + case 4: + fflg=fflg+100; + case 12: + fnbrk(); + stri(5); + *++fptr=fflg; + stri(fflg); + procst(0); + stri(12); + stri(tm=*fptr+2); + *++fptr=cbrk; + if (fflg<100) + cbrk=tm; + *++fptr=inproc; + lctr+=3; + tm=0; + stri(99); + break; + case 7: + tm=0; + break; + case 18: + stri(23); + stri(cbrk); + break; + case 24: + procst(';'); + stri(23); + stri(ectr); + tm=1; + break; + case 31: + fnbrk(); + procst(';'); + stri(5); + stri(lctr++); + *++fptr=lctr; + procst(';'); + stri(12); + stri(lctr+2); + stri(23); + stri(lctr+1); + stri(5); + stri(lctr++); + procst(')'); + *++fptr=cbrk; + *++fptr=inproc; + stri(23); + stri(lctr-2); + stri(5); + stri(lctr++); + cbrk=lctr++; + tm=0; + break; + default: + dostt(); + } /* End switch */ + } else + switch (tm) { + case '{': + tm=1; + inproc++; + break; + case '}': + break; + case -1: + case 0: + stri(0); +#asm + CALL 1,CHAIN + ARG FNM + HLT +FNM, TEXT "CC2@@@" +#endasm + case '/': + while (fgetc()!='/'); /* Skip comment */ + tm=1; + break; + default: + dostt(); + } + return tm; +} + + + +main() +{ + char trm; + + memset(ltbf,0,&ssz-ltbf); + fopen("CC.C","r"); + strcpy(tkn,"int if else while break return for "); + lctr = 10; + ectr = 900; + ltpt = ltbf; + fptr = fstk; + *fptr = -1; + gadr = 128; /* Start of globals */ + iinit(asm); + tm=gettk(); + while (1) { + trm=next(); + tm=gettk(); + switch (trm) { + case '{': + inproc++; + break; + case '}': + inproc--; + if (!inproc) { + stri(5); + stri(ectr++); + stri(16); + stri(-stkp); + stkp = *lm = 0; + break; + } + case ';': + case 1: + stri(99); + if (!strcmp("else",tkbf)) { + stri(-23); + stri(100+lctr+2); + popfr(); + *++fptr=100+lctr++; + *++fptr=cbrk; + *++fptr=inproc; + } + else + popfr(); + case 0: + break; + default: + procst(';'); + } + } +} + ADDED src/cc8/os8/p8.c Index: src/cc8/os8/p8.c ================================================================== --- /dev/null +++ src/cc8/os8/p8.c @@ -0,0 +1,208 @@ +/* + * This file is part of the CC8 OS/8 C compiler. + * + * The CC8 OS/8 C compiler is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * The CC8 OS/8 C compiler is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the CC8 OS/8 C compiler as ../GPL3.txt. If not, see + * . + */ + + +#include +#include + +#define SMAX 10 +#define CMAX 256 +#define BMAX 64 +#define LMAX 32 +#define DMAX 32 +#define CBMX 1024 +#define LXMX 999 + +int asm[CBMX]; +int ltbf[512]; +int xlt[CMAX]; +int gm[CMAX]; /* Global symbol table */ +int tkbf[LMAX]; +int *p,*q,*s,*ltpt; +int gsym,lsym,gadr,ladr,stkp,*as,lctr,*fptr,gsz,ctr,tm,ectr; +int glim; +int ltsz,pflg,t; +int tmstr[32]; + +main() +{ + + as=asm; + fopen("HEADER.SB","r"); + fopen("CC.SB","w"); + strcpy(xlt,"ITOA PUTS DISPXY GETC GETS ATOI SSCANF XINIT MEMCPY KBHIT PUTC STRCPY STRCAT STRSTR EXIT ISNUM "); + strcat(xlt,"ISALPH SPRINTF MEMSET FGETC FOPEN FPUTC FCLOSE PRINTF ISALNUM ISSPACE FPRINTF FPUTS STRCMP CUPPER FGETS "); + while (t=fgetc()) + if (t!=12) /* Ignore form feed */ + fputc(t); + cupper(gm); + while (*as) { + pflg=0; + *tmstr=0; + switch (*as++) { + case 99: + fprintf("/\r"); + break; + case 1: + fprintf("\tJMSI PTSK\r"); + break; + case 3: + strcpy(tmstr,"\tCIA\r"); + case 2: + fprintf("%s\tTADI STKP\r\tJMSI POP\r",tmstr); + break; + case 4: + fprintf("\tCLA\r\tTAD (%d\r",*as++); + break; + case 5: + if (*as<0) + *as=100-*as; + fprintf("CC%d,\r",*as++); + break; + case 6: + if (*as>1) + fprintf("\tTAD STKP\r\tTAD (%d\r\tDCA STKP\r",*as); + else + if (*as>0) + fputs("\tISZ STKP\r"); + as++; + break; + case 7: + p=gm+*as++; + while (*p-' ') + fputc(*p++); + fputs(",\r"); + break; + case -8: + strcpy(tmstr,"\tDCA JLC\r\tTADI JLC\r"); + case 8: + if (*as>0) + fprintf("\tCLA\r\tTAD (%d\r%s\tJMSI PSH\r",*as++,tmstr); + else + fprintf("\tCLA\r\tTAD STKP\r\tTAD (%d\r%s\tJMSI PSH\r",*as++,tmstr); + break; + case 9: + tm=*as++; + p=gm+*as++; + strcpy(tkbf," "); + memcpy(tkbf,p,7); + if (p=strstr(xlt,tkbf)) { + t=(p-xlt)>>3; + if ((t==6) + (t==17) + (t==23)) + fprintf("\tCLA\r\tTAD (%d\r\tJMSI PSH\r",tm++); + fprintf("\tCLA\r\tTAD (%d\r\tMQL\r\tCALL 1,LIBC\r\tARG STKP\r\tCDF1\r",t); + } + else + fprintf("\tJMSI PCAL\r\t%s\r",tkbf); + if (tm) + fprintf("\tMQL\r\tTAD (%d\r\tTAD STKP\r\tDCA STKP\r\tSWP\r",-tm); + break; + case 10: + fprintf("\tCLA\r\tTAD GBL\r\tTAD (%d\r",*as++); + break; + case -11: + fprintf("\tCIA\r\tTADI STKP\r\tJMSI POP\r\tSMA SZA CLA\r\tCMA\r"); + break; + case 11: + fprintf("\tCIA\r\tTADI STKP\r\tJMSI POP\r\tSPA CLA\r\tCMA\r"); + break; + case 12: + fprintf("\tSNA\r\tJMP CC%d\r",*as++); + break; + case 13: + fprintf("\tJMSI POP\r\tDCA JLC\r\tSWP\r\tCALL 1,MPY\r\tARG JLC\r\tCDF1\r"); + break; + case -14: + fprintf("\tCALL 1,IREM\r\tARG 0\r\tCDF1\r"); + break; + case 14: + fprintf("\tJMSI POP\r\tDCA JLC\r\tSWP\r\tCALL 1,DIV\r\tARG JLC\r\tCDF1\r"); + break; + case 15: + fprintf("\tISZI JLC\r\tNOP\r"); + break; + case 16: + fprintf("\tMQL\r\tTAD STKP\r\tTAD (%d\r\tDCA STKP\r\tSWP\r\tJMPI POPR\r/\r",*as++); + break; + case 17: + pflg++; + case -17: + if (*as>0) + fprintf("\tCLA\r\tTAD (%d\r\tDCA JLC\r\tTADI JLC\r",*as++); + else + fprintf("\tCLA\r\tTAD STKP\r\tTAD (%d\r\tDCA JLC\r\tTADI JLC\r",*as++); + if (pflg==0) + break; + case 19: + fprintf("\tJMSI PSH\r"); + break; + case 20: + fprintf("\tANDI STKP\r\tJMSI POP\r"); + break; + case -20: + fprintf("\tJMSI POP\r\tMQO\r"); + break; + case 21: + if (*as>0) + fprintf("\tCLA\r\tTAD (%d\r",*as++); + else + fprintf("\tCLA\r\tTAD STKP\r\tTAD (%d\r",*as++); + break; + case 22: + fprintf("\tDCA JLC\r\tTADI JLC\r"); + break; + case 23: + if (*as<100) + fprintf("\tJMP CC%d\r",*as); + as++; + break; + case -23: + fprintf("\tJMP CC%d\r",*as++); + break; + case 24: + fprintf("\tCIA\r\tTADI STKP\r\tJMSI POP\r\tSNA CLA\r\tCMA\r"); + break; + case 25: + fprintf("\tMQL\r\tCMA\r\tTADI JLC\r\tDCAI JLC\r\tSWP\r"); + break; + case 26: + fprintf("\tSNA CLA\r"); + case -26: + fprintf("\tCMA\r"); + break; + case 27: + fputs("\tCIA\r"); + } + } + ltsz=ltpt-ltbf; + fprintf("\tLAP\r\tCPAGE %d\rLCC0,\t%d\rXCC0,\tCC0\rCC0,\t\r",ltsz+2,-ltsz); + p=ltbf; + while (ltsz) { + fprintf("%d",*p++); + if (ltsz>1) + fputs("; "); + if ((ltsz&7)==0) + fputc(13); + ltsz--; + } + fprintf("\r\tEAP\rGBLS,\t%d\r",gadr); + fprintf("\rMCC0,\t0\r\tCDF1\r\tTAD LCC0\r\tSNA CLA\r\tJMP I MCC0\r\tTAD XCC0\r\tDCA JLC\rDCC0,\tCDF0\r\tTADI JLC\r"); + fprintf("\tJMSI PSH\r\tCLA\r\tISZ JLC\r\tISZ LCC0\r\tJMP DCC0\r\tJMP I MCC0\rCCEND,\t0\r\t\END\r"); + + fclose(); +} ADDED src/d8tape/LICENSE.md Index: src/d8tape/LICENSE.md ================================================================== --- /dev/null +++ src/d8tape/LICENSE.md @@ -0,0 +1,12 @@ +I received the following license grant via private email, which +supercedes the LICENSE file text distributed with d8tape in 2002: + +--------- + + On Fri, Nov 10, 2017 at 6:38 PM, Rob Krten wrote: + + As far as d8tape goes: + + I, Robert Krten, hereby provide a royalty free, perpetual, worldwide + license to use the source code and information provided in my PDP-8 + disassembler, called "d8tape" for any purpose whatsoever. ADDED src/d8tape/d8tape.h Index: src/d8tape/d8tape.h ================================================================== --- /dev/null +++ src/d8tape/d8tape.h @@ -0,0 +1,48 @@ + +/* + * d8tape.h + * + * (C) Copyright 2007 by Robert Krten, all rights reserved. + * Please see the LICENSE file for more information. + * + * This module contains the manifest constants and other header + * information. + * + * 2007 10 25 R. Krten created + * 2007 10 28 R. Krten added TAG_INDIRECTFC +*/ + +// constants +#define CORE_SIZE 4096 // size of core memory + +#define TAG_DATA 0x0001 // memory region is tagged as data, +#define TAG_SUB 0x0002 // subroutine target, or, +#define TAG_LABEL 0x0004 // label +#define TAG_RETURN 0x0008 // return from subroutine +#define TAG_TYPE_MASK 0x00ff // mask of above types +#define TAG_WRITABLE 0x0100 // set if anyone writes to this data location (else constant) +#define TAG_KONSTANT 0x0200 // can be changed from Caaaa -> Kvvvv +#define TAG_INDIRECTFC 0x0400 // target of an indirect flow control (JMS I / JMP I) (only meaningful if not writable) + +#include + +// segment info + +typedef struct +{ + uint16_t saddr; // starting address + uint16_t nwords; // number of contiguous words +} segment_t; + + +// prototypes + +// flow.c +extern void flow (void); + +// dasm.c +extern int ea (int addr, int opcode, int *indirect, int *curpage); +extern void disassemble (void); +extern int is_data (int v); +extern int fetch_iot (int code, char *dis, char *com); + ADDED src/d8tape/dasm.c Index: src/d8tape/dasm.c ================================================================== --- /dev/null +++ src/d8tape/dasm.c @@ -0,0 +1,687 @@ + +/* + * dasm.c + * + * (C) Copyright 2003 by Robert Krten, all rights reserved. + * Please see the LICENSE file for more information. + * + * This module contains the PDP-8 disassembler. + * Note that the 8/I and 8/L are featured at this point; other models + * should be added (particularly the 8/E's EAE instructions, as well + * as new IOT decodings, etc) + * + * 2003 12 16 R. Krten created + * 2007 10 29 R. Krten added better output formatting + * 2007 11 02 R. Krten added xrefs + * 2009 02 08 D. North fixups for missing 7002 BSW and removal of + * 7600 CLA case (conflicts with 7200 on reassembly) +*/ + +#include +#include +#include +#include +#include +#include + +#include "d8tape.h" +#include "iot.h" // decode table for IOTs + +extern char *progname; // main.c +extern char *version; // version.c + +extern int optv; // main.c +extern char *tapename; // main.c, name of tape image + +extern short int core []; // main.c, in-core image (-1 means location never used) +extern uint16_t tags []; // main.c, analysis tags +extern segment_t *segments; // main.c, used to accumulate runs of data (from origin for nwords) +extern int nsegments; // main.c, indicates how many segments we have + +static void header (void); +static void disassemble_range (int start, int end); +static void pad (char *buf, int off, int pos); +static void xrefs (int addr); + +/* + * dasm8 + * + * This takes the current address and the instruction value + * and prints the decoded instruction. A static variable + * is used to kludge up 2-word instructions (e.g., MUY ). + * + * The IOTs are coded in a table; in some cases, conflicts exist, you'll + * need to select the appropriate #defines to make them go away :-) + * As shipped, the #defines match my preferences. + * + * Formatting rules: + * - the following types of output exist: + * - labels + * - banners + * - code + * - data + * + * For each type, the following format is used (all tabs, as shown by the single backtick + * character, are assumed to be at 4 character tabstops. If you don't like this, pipe + * the output through "expand -4"). + * + * Labels: + * 1111111111222222222233333333334444444444555555555566666666667777777777 + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + * t t t t t t t t t t t t t t t t t t t t + * TAAAA, + * + * where "T" is the label type, one of "D" for data, "L" for executable label, + * and "S" for subroutine entry, and "AAAA" is the four digit octal address. + * (See "Data" below for additional details of the "D" type). + * + * Banners: + * 1111111111222222222233333333334444444444555555555566666666667777777777 + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + * t t t t t t t t t t t t t t t t t t t t + * //////////////////////////////////////////////////////////////////////////////// + * / + * /```CONTENT + * / + * //////////////////////////////////////////////////////////////////////////////// + * + * Where "CONTENT" is the content of the banner, e.g., "SUBROUTINE S1234". + * + * Code: + * 1111111111222222222233333333334444444444555555555566666666667777777777 + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + * t t t t t t t t t t t t t t t t t t t t + * op1`````````````````````/ COMMENTS..............................@@=AAAA,OOOO + * opc1````````````````````/ COMMENTS..............................@@=AAAA,OOOO + * op1 TAAAA```````````````/ COMMENTS..............................@@=AAAA,OOOO + * op1 I TAAAA`````````````/ COMMENTS..............................@@=AAAA,OOOO + * op1 op2 op3 op4 op5 op6`/ COMMENTS..............................@@=AAAA,OOOO + * 12345678911234567892123 1234567891123456789212345678931234567 (DISLEN and COMLEN, resp.) + * 01234567891123456789212345678 234567891123456789212345678931234567890 (COMSTART and DATASTART, resp.) + * + * Where "op1", "opc1", "op2" through "op6" are 3 or 4 character mnemonic + * opcodes. "T" is the label type (as above), and "AAAA" is the address. + * Tabs are used to fill whitespace to the "/" comment delimiter, and from the + * end of the comment to the @@. The area at the "@@" indicates the address + * and the contents. + * + * This is where the COMLEN and DISLEN buffer sizes are derived from, and the + * COMSTART position (28, the start of the "/") + * + * Data: + * 1111111111222222222233333333334444444444555555555566666666667777777777 + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + * t t t t t t t t t t t t t t t t t t t t + * CAAAA,``VVVV````````````````/ op1 op2 op3 op4 op5 op6 COMMENTS.................. + * DAAAA,``VVVV````````````````/ op1 op2 op3 op4 op5 op6 COMMENTS.................. + * KVVVV,``VVVV````````````````/ op1 op2 op3 op4 op5 op6 COMMENTS.................. + * + * Where "C" is used for constants whose values are not unique, "D" is used + * for writable data, and "K" is used for constants that can be added in the + * symbol table. The distinction between "C" and "K" is that if two different + * memory locations both define the same constant value, we need to use "C" + * because it's tagged based on the address, whereas "K" is tagged based on + * the value. + * + * Other types to consider are comment blocks imported from a control file. +*/ + +static const char *codes [6] = {"AND", "TAD", "ISZ", "DCA", "JMS", "JMP"}; // IOT and OPR are decoded elsewhere + +static unsigned short int two_word = 0; // set to hold two-word instruction (e.g., EAE ops), else zero (instruction 0, AND, is not a two-word instruction) + +#define COMLEN 37 // length of comment, see "Code", above (both the number of bytes and the max number of characters; tabs will only make the number of bytes less) +#define DISLEN 23 // length of disassembly area, see "Code" above (ditto) +#define COMSTART 28 // 0-based column number where the comments start +#define DATASTART 40 // 0-based column number where the data (@@=AAAA,OOOO) starts +static char disbuf [DISLEN + 1]; +static char combuf [COMLEN + 1]; + +void +dasm8 (int addr, unsigned short int buf) +{ + int ind, cur; + int eff_addr; + int primary; // is primary disassembly 'c'ode or 'd'ata? + + if (optv > 1) { + printf ("dasm8 (addr 0%04o word 0%04o tag 0x%04x)\n", addr, buf, tags [addr]); + } + + eff_addr = ea (addr, buf, &ind, &cur); + + if (two_word) { + printf ("\t%04o /\t\t\t\t\t / (operand)\n", buf); + two_word = 0; + return; + } + + // prepare buffer areas for disassembly and comments. + memset (disbuf, 0, sizeof (disbuf)); + memset (combuf, 0, sizeof (combuf)); + + primary = 'c'; // default to code disassembly + + if (tags [addr] & TAG_LABEL) { + printf ("L%04o,\n", addr); + } + + if (tags [addr] & TAG_SUB) { + printf ("\n"); + printf ("////////////////////////////////////////////////////////////////////////////////\n"); + printf ("/\n"); + printf ("/\tSUBROUTINE: S%04o\n", addr); + printf ("/\n"); + xrefs (addr); + printf ("////////////////////////////////////////////////////////////////////////////////\n"); + printf ("S%04o,\n", addr); + printf ("\t0\t\t\t\t\t\t/ return area\n"); + // done; can't be SUB and anything else (except label, perhaps) + return; + } + + if (tags [addr] & TAG_DATA) { + // if it's data, set primary as data + primary = 'd'; + if ((addr & 07770) == 00010) { // addresses 0010 -> 0017 are autoindex + printf ("AI%o,\t%04o\t\t\t\t/ AUTO-INDEX REGISTER", addr & 7, core [addr]); + } else { + if (tags [addr] & TAG_INDIRECTFC) { + printf ("C%04o,\n", addr); + } + printf ("%c%04o,\t", tags [addr] & TAG_KONSTANT ? 'K' : tags [addr] & TAG_WRITABLE ? 'D' : 'C', tags [addr] & TAG_KONSTANT ? core [addr] : addr); + printf ("%04o\t\t\t\t/", core [addr]); + } + } + + switch (buf & 07000) { + case 00000: // AND + case 01000: // TAD + case 02000: // ISZ + case 03000: // DCA + case 04000: // JMS + case 05000: // JMP + sprintf (disbuf, "%s ", codes [buf >> 9]); + + if (ind) { + strcat (disbuf, "I "); + } else { + strcat (disbuf, " "); + } + + if (tags [eff_addr] & TAG_SUB) { + strcat (disbuf, "S"); + } else if (tags [eff_addr] & TAG_LABEL) { + strcat (disbuf, "L"); + } else { + if ((eff_addr & 07770) == 00010) { // addresses 0010 -> 0017 + strcat (disbuf, "AI"); + strcat (combuf, "AUTO INDEX REGISTER"); + } else { + strcat (disbuf, (tags [eff_addr] & TAG_KONSTANT) ? "K" : tags [eff_addr] & TAG_WRITABLE ? "D" : "C"); + } + } + + if (tags [addr] & TAG_RETURN) { + strcat (combuf, "return "); + } else { + // comment indirect flow control change to reflect ultimate target + switch (buf & 07400) { + case 04400: + sprintf (combuf + strlen (combuf), "long call to S%04o ", core [eff_addr]); + break; + case 05400: + sprintf (combuf + strlen (combuf), "long jump to L%04o ", core [eff_addr]); + break; + } + } + + if ((eff_addr & 07770) == 00010) { // address 0010 -> 0017 + sprintf (disbuf + strlen (disbuf), "%o ", eff_addr & 7); + } else { + sprintf (disbuf + strlen (disbuf), "%04o ", (tags [eff_addr] & TAG_KONSTANT) ? core [eff_addr] : eff_addr); + } + + break; + + case 06000: // IOT + fetch_iot (buf, disbuf, combuf); + break; + + case 07000: // OPR + // perform "short form" OPRs here first... + switch (buf) { + case 07000: sprintf (disbuf, "NOP"); break; + case 07002: sprintf (disbuf, "BSW"); break; + case 07041: sprintf (disbuf, "CIA"); break; + case 07120: sprintf (disbuf, "STL"); break; + case 07204: sprintf (disbuf, "GLK"); break; + case 07240: sprintf (disbuf, "STA"); strcat (combuf, "AC = 7777 (-0001)"); break; + case 07300: sprintf (disbuf, "CLA CLL"); strcat (combuf, "AC = 0000"); break; + case 07301: sprintf (disbuf, "CLA CLL IAC"); strcat (combuf, "AC = 0001"); break; + case 07302: sprintf (disbuf, "CLA IAC BSW"); strcat (combuf, "AC = 0100 (64)"); break; + case 07305: sprintf (disbuf, "CLA CLL IAC RAL"); strcat (combuf, "AC = 0002"); break; + case 07325: sprintf (disbuf, "CLA CLL CML IAC RAL"); strcat (combuf, "AC = 0003"); break; + case 07326: sprintf (disbuf, "CLA CLL CML RTL"); strcat (combuf, "AC = 0002"); break; + case 07307: sprintf (disbuf, "CLA CLL IAC RTL"); strcat (combuf, "AC = 0004"); break; + case 07327: sprintf (disbuf, "CLA CLL CML IAC RTL"); strcat (combuf, "AC = 0006"); break; + case 07330: sprintf (disbuf, "CLA CLL CML RAR"); strcat (combuf, "AC = 4000 (-4000 = -2048 dec)"); break; + case 07332: sprintf (disbuf, "CLA CLL CML RTR"); strcat (combuf, "AC = 2000 (1024)"); break; + case 07333: sprintf (disbuf, "CLA CLL CML IAC RTL"); strcat (combuf, "AC = 6000 (-2000 = -1024 dec)"); break; + case 07340: sprintf (disbuf, "CLA CLL CMA"); strcat (combuf, "AC = 7777 (-0001)"); break; + case 07344: sprintf (disbuf, "CLA CLL CMA RAL"); strcat (combuf, "AC = 7776 (-0002)"); break; + case 07346: sprintf (disbuf, "CLA CLL CMA RTL"); strcat (combuf, "AC = 7775 (-0003)"); break; + case 07350: sprintf (disbuf, "CLA CLL CMA RAR"); strcat (combuf, "AC = 3777 (2047)"); break; + case 07352: sprintf (disbuf, "CLA CLL CMA RTR"); strcat (combuf, "AC = 5777 (-2001 = -1025 dec)"); break; + case 07401: sprintf (disbuf, "NOP"); break; + case 07410: sprintf (disbuf, "SKP"); break; + case 07600: sprintf (disbuf, "7600"); strcat (combuf, "AKA \"CLA\""); break; + case 07610: sprintf (disbuf, "SKP CLA"); break; + case 07604: sprintf (disbuf, "LAS"); break; + case 07621: sprintf (disbuf, "CAM"); break; + default: + + // determine group (0401 is 0000/0001 for group 1, 0400 for group 2, 0401 for EAE) + switch (buf & 00401) { + case 00000: // group 1 + case 00001: // group 1 + // sequence 1 + if (buf & 00200) { + strcat (disbuf, "CLA "); + } + if (buf & 00100) { + strcat (disbuf, "CLL "); + } + // sequence 2 + if (buf & 00040) { + strcat (disbuf, "CMA "); + } + if (buf & 00020) { + strcat (disbuf, "CML "); + } + // sequence 3 + if (buf & 00001) { + strcat (disbuf, "IAC "); + } + // sequence 4 + if (buf & 00010) { + if (buf & 00002) { + strcat (disbuf, "RTR "); + } else { + strcat (disbuf, "RAR "); + } + } + if (buf & 00004) { + if (buf & 00002) { + strcat (disbuf, "RTL "); + } else { + strcat (disbuf, "RAL "); + } + } + break; + + case 00400: // group 2 + // sequence 1 + if (buf & 00100) { + if (buf & 00010) { + strcat (disbuf, "SPA "); + } else { + strcat (disbuf, "SMA "); + } + } + if (buf & 00040) { + if (buf & 00010) { + strcat (disbuf, "SNA "); + } else { + strcat (disbuf, "SZA "); + } + } + if (buf & 00020) { + if (buf & 00010) { + strcat (disbuf, "SZL "); + } else { + strcat (disbuf, "SNL "); + } + } + // sequence 2 + if (buf & 00200) { + strcat (disbuf, "CLA "); + } + // sequence 3 + if (buf & 00004) { + strcat (disbuf, "OSR "); + } + if (buf & 00002) { + strcat (disbuf, "HLT "); + } + break; + case 00401: // EAE + // sequence 1 + if (buf & 00200) { + strcat (disbuf, "CLA "); + } + // sequence 2 + if (buf & 00100) { + strcat (disbuf, "MQA "); + } + if (buf & 00040) { + strcat (disbuf, "SCA "); + } + if (buf & 00020) { + strcat (disbuf, "MQL "); + } + // sequence 3 + switch (buf & 00016) { + case 0: // no further ops, done + break; + case 002: + strcat (disbuf, "SCL "); + two_word = buf; + break; + case 004: + strcat (disbuf, "MUY "); + two_word = buf; + break; + case 006: + strcat (disbuf, "DVI "); + two_word = buf; + break; + case 010: + strcat (disbuf, "NMI"); + break; + case 012: + strcat (disbuf, "SHL "); + two_word = buf; + break; + case 014: + strcat (disbuf, "ASR "); + two_word = buf; + break; + case 016: + strcat (disbuf, "LSR "); + two_word = buf; + break; + } + break; + } + break; + } + break; + } + if (two_word) { + strcat (disbuf, "+"); + } + // trim any trailing spaces + while (*disbuf && disbuf [strlen (disbuf) - 1] == ' ') { + disbuf [strlen (disbuf) - 1] = 0; + } + + if (primary == 'c') { // if primary is code, then spill data too + pad (disbuf, 0, COMSTART - 4); // add tabs to get disassembly to comment start (one tab less because we print it next) + printf ("\t%s", disbuf); // print disassembly so far + printf ("/ "); // print comment start + pad (combuf, 2, DATASTART); // pad comment buffer to get to data area + printf ("%s@@%04o=%04o\n", combuf, addr, buf); // print comment, address and opcode + } else { // else we've already spilled both, just terminate the line + pad (disbuf, 2, DATASTART); + printf (" %s", disbuf); + printf ("\n"); + two_word = 0; // we don't care that it's a two-word when we're printing it as data + } +} + +/* + * ea + * + * Calculate the effective address given the + * address and opcode. Opcodes that don't have + * an effective address (e.g., IOTs), return -1. + * + * The indirect pointer is optional, and, if specified, + * will cause the location to be returned with a zero + * or one indicating indirection. The indirect pointer + * is not modified in case of a non-EA opcode. + * + * Similarly for the curpage pointer. +*/ + +int +ea (int addr, int opcode, int *indirect, int *curpage) +{ + int eff_addr; + int c; + int i; + + if (opcode >= 06000) { // IOTs and OPRs don't have an EA + return (-1); + } + + i = opcode & 00400; + c = opcode & 00200; + eff_addr = c ? (addr & 07600) + (opcode & 00177) : opcode & 00177; + if (indirect) { + *indirect = i; + } + if (curpage) { + *curpage = c; + } + return (eff_addr); +} + +/* + * disassemble + * + * This drives disassembly once the flow analysis has been done. + * + * We disassemle in segment order. +*/ + +void +disassemble (void) +{ + int snum; + + header (); + for (snum = 0; snum < nsegments; snum++) { + printf ("\n*%04o\n", segments [snum].saddr); + disassemble_range (segments [snum].saddr, segments [snum].saddr + segments [snum].nwords); + } +} + +static void +header (void) +{ + struct tm *tm; + time_t now; + int nused, ndata, ncode; + int i; + + time (&now); + tm = localtime (&now); + + nused = ndata = ncode = 0; + for (i = 0; i < CORE_SIZE; i++) { + if (core [i] >= 0) { + nused++; + if (tags [i] & TAG_DATA) { + ndata++; + } else { + ncode++; + } + } + } + + printf ("TITLE \"AUTOMATIC DISASSEMBLY OF %s BY D8TAPE\"\n", tapename); + printf ("////////////////////////////////////////////////////////////////////////////////\n"); + printf ("/\n"); + printf ("/\tAutomatic Disassembly of %s\n", tapename); + printf ("/\tGenerated %04d %02d %02d %02d:%02d:%02d\n", tm -> tm_year + 1900, tm -> tm_mon + 1, tm -> tm_mday, tm -> tm_hour, tm -> tm_min, tm -> tm_sec); + printf ("/\tGenerated by d8tape version %s\n", version); + printf ("/\tVisit http://www.pdp12.org/pdp8/software/index.html for updates\n"); + printf ("/\n"); + printf ("/\tSymbol format:\n"); + printf ("/\t\tAIx -- Auto-index variables (address range 001x)\n"); + printf ("/\t\tCaaaa -- Constants (non-unique)\n"); + printf ("/\t\tDaaaa -- Data (read/write variables)\n"); + printf ("/\t\tKvvvv -- Program-wide unique constants\n"); + printf ("/\t\tLaaaa -- Labels for control flow targets\n"); + printf ("/\t\tSaaaa -- Subroutines\n"); + printf ("/\n"); + printf ("/\tWhere:\n"); + printf ("/\t\taaaa is the definition address\n"); + printf ("/\t\tvvvv is the value of the constant\n"); + printf ("/\t\tx is the last digit of the address 001x for auto-index variables\n"); + printf ("/\n"); + printf ("/\t%04o locations used, %04o code and %04o data\n", nused, ncode, ndata); + printf ("////////////////////////////////////////////////////////////////////////////////\n"); +} + +static void +disassemble_range (int start, int end) +{ + int addr; + + for (addr = start; addr < end; addr++) { + dasm8 (addr, core [addr]); + } +} + +/* + * fetch_iot + * + * This function looks up in the iot table (iot.h) to find + * the opcode passed in "code" and updates the disassembled + * output "dis" and the comment "com". + * + * More work needs to be done here for conflicting IOTs. + * + * Current, I assume that there are no conflicts (actually, I + * return the first match, regardless of conflicts). A command + * line / control file option needs to be created to allow + * the selection of devices. Something like "-i vc8i", for example + * to allow the VC8/I IOTs to be enabled. +*/ + +int +fetch_iot (int code, char *dis, char *com) +{ + int i; + + for (i = 0; i < sizeof (iots) / sizeof (iots [0]); i++) { + if (code == iots [i].code) { + if (dis) { + strcpy (dis, iots [i].mnemonic); + } + if (com) { + strncpy (com, iots [i].comment, COMLEN - 1); + } + return (1); + } + } + if (dis) { + sprintf (dis, "%04o", code); + } + if (com) { + sprintf (com, "unknown IOT"); + } + return (0); +} + +/* + * pad + * + * Figures out where the current print position is based on expanding + * the current tabs in "buf" and adds more tabs to get to "pos". +*/ + +static void +pad (char *buf, int loc, int pos) +{ + for (; *buf; buf++) { + if (*buf == '\t') { + if ((loc & 3) == 0) { + loc += 4; + } else { + loc += 4 - (loc & ~3); + } + } else { + loc++; + } + } + + loc = pos / 4 - loc / 4; + while (loc--) { + *buf++ = '\t'; + } + *buf = 0; +} + +int +is_data (int v) +{ + return ((v & TAG_TYPE_MASK) == TAG_DATA); +} + +static void +xrefs (int addr) +{ + int i; + int eff; + int count; + + count = 0; + for (i = 0; i < CORE_SIZE; i++) { + if (core [i] < 0) { + continue; + } + + if (tags [i] & TAG_DATA) { +//printf ("+XREF ADDR %04o CHECK %04o IS DATA\n", addr, i); + continue; + } + + + if ((core [i] & 07400) == 04000) { // direct JMS + eff = ea (i, core [i], NULL, NULL); +//printf ("+XREF ADDR %04o CHECK %04o %04o JMS EA %04o\n", addr, i, core [i], eff); + if (eff == addr) { + if (!count) { + printf ("/\tCalled from:\n/\t"); + } + printf ("%04o ", i); + count++; + if ((count % 15) == 0) { + printf ("\n/\t"); + } + } + } else if ((core [i] & 07400) == 04400) { // indirect JMS + eff = ea (i, core [i], NULL, NULL); +//printf ("+XREF ADDR %04o CHECK %04o %04o JMS I EA %04o\n", addr, i, core [i], eff); + if (tags [eff] & TAG_WRITABLE) { + continue; + } +//printf ("+XREF ADDR %04o CHECK %04o %04o JMS I is not writable\n", addr, i, core [i]); + if (core [eff] < 0) { + continue; + } +//printf ("+XREF ADDR %04o CHECK %04o %04o JMS I has valid indirect value\n", addr, i, core [i]); + if (core [eff] == addr) { + if (!count) { + printf ("/\tCalled from:\n/\t"); + } + printf ("%04o ", i); + count++; + if ((count % 15) == 0) { + printf ("\n/\t"); + } + } + } + } + if (count) { + printf ("\n"); + printf ("/\tTotal %04o (%d) calls\n", count, count); + } else { + printf ("/\tNever called\n"); + } +} + ADDED src/d8tape/flow.c Index: src/d8tape/flow.c ================================================================== --- /dev/null +++ src/d8tape/flow.c @@ -0,0 +1,466 @@ + +/* + * flow.c + * + * (C) Copyright 2007 by Robert Krten, all rights reserved. + * Please see the LICENSE file for more information. + * + * This module contains the PDP-8 flow analyzer. + * + * 2007 10 25 R. Krten created + * 2007 10 28 R. Krten added TAG_INDIRECTFC +*/ + +#include +#include +#include +#include +#include + +#include "d8tape.h" + +extern char *progname; // main.c +extern char *version; // version.c + +extern int optv; // main.c +extern short int core []; // main.c, in-core image (-1 means location never used) +extern uint16_t tags []; // main.c, analysis tags + +char konstmap [4096]; // 12 bits gives us 0..4095, so 4096 different constants (0 == not used, 1 == unique, 2 == used but not unique) + +static void pass1 (void); +static void pass2 (void); +static void pass3 (void); +static void pass4 (void); +static void verify_subroutines (void); +static int valid_opr (int opr); + +/* + * Main flow analysis + * + * See individual functions for more details + * + * Basic idea is that there is a shadow array called "tags", which indicates + * information about the given memory location (e.g., tags [0017] gives + * information about core [0017]). + * + * The various passes affect the tags[] array with whatever they can detect. +*/ + +void +flow (void) +{ + pass1 (); + pass2 (); + pass3 (); +// pass4 (); // this pass is fundamentally broken (well, maybe not the pass, but the interpretation of the results in dasm.c) + verify_subroutines (); +} + +/* + * pass1 + * + * On pass 1, we can say with certainty only the following statements: + * - if it's an invalid IOT or OPR, then it's data. + * - if the instruction must have a valid EA, and it's invalid, + * then it's data. + * - if the JMS (direct only) target is not zero or the same as the + * address, then it's data +*/ + +static void +pass1 (void) +{ + int addr, eff; + + for (addr = 0; addr < CORE_SIZE; addr++) { + // skip unused + if (core [addr] == -1) { + continue; + } + // handle IOTs + if ((core [addr] & 07000) == 06000) { + // if we can't decode it, it's not valid + if (!fetch_iot (core [addr], NULL, NULL)) { + tags [addr] |= TAG_DATA; + } + continue; + } + // check OPRs; if they're invalid, tag as DATA, else skip + if ((core [addr] & 07000) == 07000) { + if (!valid_opr (core [addr])) { + tags [addr] |= TAG_DATA; + if (optv > 1) { + printf ("+FLOW1 %04o %04o has invalid OPR -> DATA\n", addr, core [addr]); + } + } + // done OPRs, skip + continue; // @@@ NOTE: this does not work with EAE OPRs that take the next location as their parameter... + } + + // ea() is always valid for opcodes < 06000 + eff = ea (addr, core [addr], NULL, NULL); + + // if the instruction should have a valid EA and doesn't... + if (core [eff] == -1) { + // then it's not an instruction + tags [addr] |= TAG_DATA; + if (optv > 1) { + printf ("+FLOW1 %04o %04o has EA %04o (invalid or not in core) and OP < 6000 -> DATA\n", addr, core [addr], eff); + } + continue; + } + + // if it's a plain JMS + if ((core [addr] & 07400) == 04000) { + // and the target isn't zero or it's not the same as the address + if (core [eff] && core [eff] != eff) { + tags [addr] |= TAG_DATA; + if (optv > 1) { + printf ("+FLOW1 %04o %04o JMS target not 0000 or ADDR (EA %04o is %04o)\n", addr, core [addr], eff, core [eff]); + } + continue; + } + // else, if it's ok, then the target is writable, after all (JMS drops the return address there) + tags [eff] |= TAG_WRITABLE; + if (optv > 1) { + printf ("+FLOW1 %04o %04o JMS target is 0000 or ADDR, marking EA %04o as WRITABLE\n", addr, core [addr], eff); + } + } + } +} + +/* + * pass2 + * + * In this pass, we operate on the direct targets (no indirection) + * and mark the targets as data, variable, label, or subroutine + * target. +*/ + +static void +pass2 (void) +{ + int addr; + int eff; + + for (addr = 0; addr < CORE_SIZE; addr++) { + // skip unused or data locations + if (core [addr] == -1 || (tags [addr] & TAG_DATA)) { + continue; + } + + eff = ea (addr, core [addr], NULL, NULL); + + switch (core [addr] & 07400) { // check opcode + case 00000: // AND + case 00400: // AND I + case 01000: // TAD + case 01400: // TAD I + case 02000: // ISZ + case 02400: // ISZ I + case 03000: // DCA + case 03400: // DCA I + tags [eff] |= TAG_DATA; // the referenced EA is data + if (optv > 1) { + printf ("+FLOW2 %04o %04o is AND/TAD/ISZ/DCA's EA %04o tagged as DATA\n", addr, core [addr], eff); + } + + // mark as writable if someone writes to it directly (ISZ and DCA only) + if ((core [addr] & 07400) == 02000 || (core [addr] & 07400) == 03000) { + tags [eff] |= TAG_WRITABLE; + if (optv > 1) { + printf ("+FLOW2 %04o %04o is ISZ/DCA so EA %04o is WRITABLE\n", addr, core [addr], eff); + } + } + break; + + case 04000: // JMS + if (core [eff] == 0 || core [eff] == eff) { // first word of JMS target must be zero or the address itself + tags [eff] |= TAG_SUB; // otherwise, it's a valid subroutine target + if (optv > 1) { + printf ("+FLOW2 %04o %04o is JMS with good target %04o content (%04o) so tagged as SUB\n", addr, core [addr], eff, core [eff]); + } + } else { + tags [addr] |= TAG_DATA; // then invalidate this "instruction", it's bogus + if (optv > 1) { + printf ("+FLOW2 %04o %04o is JMS with bad target %04o content (%04o) so tagged as DATA\n", addr, core [addr], eff, core [eff]); + } + } + break; + + case 05000: // JMP + tags [eff] |= TAG_LABEL; + if (optv > 1) { + printf ("+FLOW2 %04o %04o is JMP so EA %04o is LABEL\n", addr, core [addr], eff); + } + break; + + break; + + // JMS I, JMP I, IOTs, and OPRs are not handled in this pass + } + } +} + +/* + * pass3 + * + * In this pass, we verify and mark the indirects +*/ + +static void +pass3 (void) +{ + int addr; + int eff; + + for (addr = 0; addr < CORE_SIZE; addr++) { + // skip unused, data, or non-indirect opcodes + if (core [addr] == -1 || (tags [addr] & TAG_DATA) || core [addr] >= 06000 || !(core [addr] & 00400)) { + if (optv > 2 && core [addr] != -1) { + printf ("+FLOW3 %04o %04o tags 0x%04x skipped\n", addr, core [addr], tags [addr]); + } + continue; + } + + eff = ea (addr, core [addr], NULL, NULL); + + switch (core [addr] & 07000) { // check opcode (indirectness assured above) + case 00000: // AND I + case 01000: // TAD I + case 02000: // ISZ I + case 03000: // DCA I + if (core [eff] != -1 && !(tags [eff] & TAG_WRITABLE)) { // if it's valid and constant + tags [core [eff]] |= TAG_DATA; // then the target is data + if (optv > 1) { + printf ("+FLOW3 %04o %04o is AND/TAD/ISZ/DCA I through constant EA %04o so target %04o is DATA\n", addr, core [addr], eff, core [eff]); + } + + // mark as writable if someone writes to it (ISZ and DCA only) + if ((core [addr] & 07000) == 02000 || (core [addr] & 07000) == 03000) { + tags [core [eff]] |= TAG_WRITABLE; + if (optv > 1) { + printf ("+FLOW3 %04o %04o is ISZ/DCA I thorugh constant EA %04o so target %04o is WRITABLE\n", addr, core [addr], eff, core [eff]); + } + } + } + break; + + case 04000: // JMS I + if (core [eff] != -1 && !(tags [eff] & TAG_WRITABLE)) { // if it's valid and constant + if (core [core [eff]] == 0 || core [core [eff]] == core [eff]) { // valid first word of JMS I target + tags [core [eff]] |= TAG_SUB; // ultimate target is a valid subroutine target + tags [eff] |= TAG_DATA; // and the pointer is a valid data type + tags [eff] |= TAG_INDIRECTFC; // and the pointer is used in an indirect flow control target + if (optv > 1) { + printf ("+FLOW3 %04o %04o is JMS I through constant EA %04o so target %04o (content %04o) is ok, so tagged as SUB\n", addr, core [addr], eff, core [eff], core [core [eff]]); + } + } else { + tags [addr] |= TAG_DATA; // then this isn't a valid instruction + if (optv > 1) { + printf ("+FLOW3 %04o %04o is JMS I through constant EA %04o with target %04o (invalid content %04o), so tagged as DATA\n", addr, core [addr], eff, core [eff], core [core [eff]]); + } + } + } + break; + + case 05000: // JMP I + if (core [eff] != -1) { // if it's valid + if (!(tags [eff] & TAG_WRITABLE)) { // and constant + tags [eff] |= TAG_DATA; // pointer is a valid data type + tags [eff] |= TAG_INDIRECTFC; // and the pointer is used in an indirect flow control target + tags [core [eff]] |= TAG_LABEL; // ultimate target is a valid JMP target + if (optv > 1) { + printf ("+FLOW3 %04o %04o is JMP I through constant EA %04o with valid target %04o, so EA tagged as DATA | INDIRECTFC and target data %04o tagged as LABEL\n", addr, core [addr], eff, core [eff], core [core [eff]]); + } + } + } else { + tags [addr] |= TAG_DATA; // else, it's not really a valid constant expression + if (optv > 1) { + printf ("+FLOW3 %04o %04o is JMP I through constant EA %04o with invalid target %04o, so tagged as DATA\n", addr, core [addr], eff, core [eff]); + } + } + break; + } + } +} + +/* + * pass4 + * + * In this pass, we update the constant map (konstmap). + * + * This effectively converts Cxxxx to Kxxxx, and results in more human- + * friendly code. So, instead of: + * + * C1234, 0777 + * + * you'd see: + * + * K0777, 0777 + * + * The only trick to this is that since symbols must be unique in 6 + * characters (for PAL compatibility), we need to ensure that the + * items that convert from Cxxxx to Kxxxx are unique. That's why + * konstmap[] has the values 0, 1, and 2: + * + * 0 == constant is not used anywhere in the code + * 1 == constant is defined exactly once (can be converted) + * 2 == constant has been defined more than once (cannot be converted) + * + * Any constant that's used in an indirect flow control manner, however, + * is not a candidate, because technically it's not used as a K-style + * constant. +*/ + +static void +pass4 (void) +{ + int i; + + memset (konstmap, 0, sizeof (konstmap)); + + // populate konstant[] map + for (i = 0; i < CORE_SIZE; i++) { + if (core [i] == -1) { + continue; + } + + if (tags [i] & TAG_WRITABLE) { + if (optv > 1) { + printf ("+FLOW4 %04o %04o TAG %02X is writable, therefore, not a constant\n", i, core [i], tags [i]); + } + continue; + } + + if (optv > 1) { + printf ("+FLOW4 %04o %04o TAG %02X\n", i, core [i], tags [i]); + } + if ((tags [i] & TAG_DATA) && !(tags [i] & TAG_INDIRECTFC)) { + switch (konstmap [core [i]]) { + case 0: + if (optv > 1) { + printf ("+FLOW4 %04o %04o FRESH KONSTANT\n", i, core [i]); + } + konstmap [core [i]]++; // this is our first one, bump the counter + break; + case 1: + if (optv > 1) { + printf ("+FLOW4 %04o %04o NO LONGER UNIQUE KONSTANT\n", i, core [i]); + } + konstmap [core [i]]++; // this is our second one, go to "2" + break; + case 2: + if (optv > 1) { + printf ("+FLOW4 %04o %04o PROMISCUOUS CONSTANT\n", i, core [i]); + } + // do nothing, we're at "2" indicating "non-unique" + break; + } + } + } + + // analyze konstant[] map + for (i = 0; i < CORE_SIZE; i++) { + if (core [i] == -1) { + continue; + } + if (tags [i] & TAG_WRITABLE) { + if (optv > 1) { + printf ("+FLOW4 %04o %04o TAG %02X is writable, therefore, not a constant\n", i, core [i], tags [i]); + } + continue; + } + + if (optv > 1) { + printf ("+FLOW4 %04o %04o TESTING (%02X)\n", i, core [i], tags [i]); + } + if (tags [i] & TAG_DATA) { + if (optv > 1) { + printf ("+FLOW4 %04o %04o TESTING...DATA\n", i, core [i]); + } + if (konstmap [core [i]] == 1) { // if it's unique + tags [i] |= TAG_KONSTANT; // then go ahead and tag it + if (optv > 1) { + printf ("+FLOW4 %04o %04o TAGGED AS KONSTANT\n", i, core [i]); + } + } + } + } +} + +/* + * verify_subroutines + * + * This is used to verify that a target really is a subroutine. + * Verification consists of ensuring that somewhere within the + * same page is a JMP I through the return address. If not, + * then the subroutine is bogus, because you can't return from + * it, so we knock down the "TAG_SUB" flag. + * + * BUG: This misses the following case: + * + * *0 + * 0 /return area + * *4000 + * jmp i 0 / return through zero page + * + * because we only search for returns within the page that the + * subroutine definition is in. I don't think this is a major + * problem, just something to be aware of. Plus, the TAG_RETURN + * is *really* only used as a comment field indicator anyway. +*/ + +static void +verify_subroutines (void) +{ + int addr; + int page; + int found; + + for (addr = 0; addr < CORE_SIZE; addr++) { + if (!(tags [addr] & TAG_SUB)) { + continue; + } + + // try and find returns within page + found = 0; + for (page = addr; page <= (addr | 00177); page++) { + if ((core [page] & 07400) == 05400) { + if (ea (page, core [page], NULL, NULL) == addr) { // JMP I found! + tags [page] |= TAG_RETURN; // mark the returns + found++; + } + } + } + if (!found) { + tags [addr] &= ~TAG_SUB; // not a subroutine, no return + } + } +} + +static int +valid_opr (int opr) +{ + // a valid OPR must be 07xxx + if ((opr & 07000) != 07000) { + return (0); + } + + if ((opr & 07400) == 07000) { // group 1 + if ((opr & 00014) == 00014) { // with both L and R rotate bits on + return (0); + } + } else if ((opr & 07401) == 07400) { // group 2 + // all ok + } else if ((opr & 07401) == 07401) { // EAE + // if bits 6, 8, 9, or 10 are set... + if (opr & 00056) { + return (0); // @@@ we're disabling EAE for now + } + // otherwise it's an "MQ microinstruction", which is ok + } + return (1); +} + ADDED src/d8tape/iot.h Index: src/d8tape/iot.h ================================================================== --- /dev/null +++ src/d8tape/iot.h @@ -0,0 +1,418 @@ + +/* + * iot.h + * + * (C) Copyright 2003 by Robert Krten, all rights reserved. + * Please see the LICENSE file for more information. + * + * This module contains the IOT decode table. + * + * 2003 12 16 R. Krten created +*/ + +/* + * Conflicting decodes for IOTs + * + * 6050 - 6077 VC8I or KV8I; one only + * 6440 - 6457 PT08 uses some of the data areas from the Data Communications System 680/I -- definition of PT08 is optional + * 6530 - 6547 AF01A or AF04A; one only + * 6551 used by AA01A, AA05, or not used. Define zero or one only. + * 6571 used by AF04A, AC01A, or not used. Define zero or one only. + * 6600 - 6667 used by DP01AA, overrides DF32/RF08 selection. If defining DP01AA, don't bother with DF32/RF08 + * 6600 - 6627 used by DF32 and RF08, define one only. + * 6640 - 6647 used by RF08 or not used, define RF08 or not. + * 6700 - 6727 used by TC58, TR02, and TA8A, define one only +*/ + +// Select one of the following: +#define VC8I +//#define KV8I + +// Select PT08 if required; it will override some of the IOTs from the Data Communications System 680/I +//#define PT08 + +// Select one of the following: +#define AF01A +//#define AF04A + +// Select one of the following: +#define AA01A +//#define AA05 + +// If selecting DP01AA, don't bother with DF32/RF08 +//#define DP01AA +#ifndef DP01AA +// Select one of the following: +#define DF32 +// #define RF08 +#endif // DP01AA + +// Select one of the following: +#define TA8A +//#define TC58 +//#define TR02 + +typedef struct +{ + int code; // code number, e.g. 06001 + char *option; // hardware option, e.g., vc8i + char *mnemonic; // decoded value, e.g., "ION" + char *comment; // code description of IOT, e.g., "clear x coordinate buffer" +} one_iot_t; + +#define OP_ALL "" +one_iot_t iots [] = +{ + // 6000 Interrupts +{ 06001, OP_ALL, "ION", "Enable Interrupts"}, +{ 06002, OP_ALL, "IOF", "Disable Interrupts"}, + // 6010 High Speed Perforated Tape Reader and Control +{ 06011, OP_ALL, "RSF", "Skip if reader flag is a 1."}, +{ 06012, OP_ALL, "RFB", "Read the content of the reader buffer and clear the reader flag. This instructions does not clear the AC. RB v AC4-11 -> AC4-11"}, +{ 06014, OP_ALL, "RFC", "Clear reader flag and reader buffer, fetch one character from tape and load it into the reader buffer, and set the reader flag when done."}, + // 6020 High Speed Perforated Tape Punch and Control +{ 06021, OP_ALL, "PSF", "Skip if punch flag is a 1"}, +{ 06022, OP_ALL, "PCF", "Clear punch flag and punch buffer."}, +{ 06024, OP_ALL, "PPC", "Load the punch buffer from bits 4 through 11 of the AC and punch the character. This instructions does not clear the punch flag or punch buffer. AC4-11 v PB -> PB"}, +{ 06026, OP_ALL, "PLS", "Clear the punch flag, clear the bunch buffer, load the punch buffer from the content of bits 4 through 11 of the accumulator, punch the character, and set the punch flag to 1 when done."}, + // 6030 Teletype Keyboard / Reader +{ 06031, OP_ALL, "KSF", "Skip if keyboard flag is a 1."}, +{ 06032, OP_ALL, "KCC", "Clear AC and clear keyboard flag."}, +{ 06034, OP_ALL, "KRS", "Read keyboard buffer static. This is a static command in that neither the AC nor the keyboard flag is cleared. TTI v AC4-11 -> AC4-11"}, +{ 06036, OP_ALL, "KRB", "Clear AC, clear keyboard flag, and read the content of the keyboard buffer into the content of AC4-11."}, + // 6040 Teletype Teleprinter / Punch +{ 06041, OP_ALL, "TSF", "Skip if teleprinter flag is a 1."}, +{ 06042, OP_ALL, "TCF", "Clear teleprinter flag."}, +{ 06044, OP_ALL, "TPC", "Load the TTO from the content of AC4-11 and print and/or punch the character."}, +{ 06046, OP_ALL, "TLS", "Load the TTO from the content of AC4-11, clear the teleprinter flag, and print and/or punch the character."}, +#ifdef VC8I + // 6050 Oscilloscope Display Type VC8/I [VC8/L] +{ 06051, OP_ALL, "DCX", "Clear X coordinate buffer"}, +{ 06053, OP_ALL, "DXL", "Clear and load X coordinate buffer. AC2-11 -> YB"}, +{ 06054, OP_ALL, "DIX", "Intensify the point defined by the content of the X and Y coordinate buffers."}, +{ 06057, OP_ALL, "DXS", "Executes the combined functions of DXL followed by DIX"}, + // 6060 (continued) +{ 06061, OP_ALL, "DCY", "Clear Y coordinate buffer"}, +{ 06063, OP_ALL, "DYL", "Clear and load Y coordinate buffer. AC2-11 -> YB"}, +{ 06064, OP_ALL, "DIY", "Intensify the point defined by the content of the X and Y coordinate buffers."}, +{ 06067, OP_ALL, "DYS", "Executes the combined functions of DYL followed by DIY"}, + // 6070 (continued) +{ 06071, OP_ALL, "DSF", "(Light Pen Type 370) Skip if display flag is a 1."}, +{ 06072, OP_ALL, "DCF", "(Light Pen Type 370) Clear the display flag."}, +{ 06074, OP_ALL, "DSB", "Zero brightness"}, +{ 06075, OP_ALL, "DSB", "Set minimum brightness"}, +{ 06076, OP_ALL, "DSB", "Set medium brightness"}, +{ 06077, OP_ALL, "DSB", "Set maximum brightness"}, +#endif +#ifdef KV8I + // 6050 Storage Tube Display Control, Type KV8/I [KV8/L] +{ 06051, OP_ALL, "SNC", "Senses the condition of the cursor interrupt flag. The flag produces an interrupt request when set by operation of the interrupt pushbutton on the joystick. The flag is initially cleared when the computer is started. As with all flag-sent instructions, SNC can be used under interrupt conditions to detect the source of the interrupt, or it can be used under interrupt on (ION) when the interrupt request has been caused by the operation of thecursor interrupt button. In a program running with the interrupt off, SNC can be used to ignore the cursor successive approximation subroutine in the program if a request for service has not been made from the joystick controller."}, +{ 06052, OP_ALL, "CCF", "This instruction is used to clear the cursor flag after a request for service has been acknowledged by the program."}, + // 6060 (continued) +{ 06062, OP_ALL, "SAC", "The analog comparator is set to compare the analog content of any one of six analog sources with the content of the digital-to-analog converter. The analog sources are chosen according to a 3-bit binary code. This code establishes the parameter for choosing the wanted register according to the content of AC2, AC3, and AC6."}, +{ 06063, OP_ALL, "LDF", "This instruction is used to establish the mode in which a wanted graphic is to be produced according to a 2-bit binary code. This code determines whether the wanted vector will be linear absolute relative, whether the point plot mode will be used, or whether the cursor will be displayed. This code establishes the paramteres for these formats according to the content of AC2 and AC3. The LDF instruction must precede the LDX and LDY instructions."}, +{ 06064, OP_ALL, "LDX", "The X-axis sample and hold register is loaded with the binary equivalent of the X-axis coordinate according to the contents of AC2-11. This data appears at the output of the digital-to-analog converter as the analog equivalent of the X-axis value of the binary word stored in the AC. The LDX instruction clears an existing ready flag and sets the ready flag after 100 +/- 20 us."}, +{ 06065, OP_ALL, "LDY", "The Y-axis sample and hold register is loaded with the binary equivalent of the Y-axis coordinate according to the contents of AC2-11. This data appears at the output of the digital-to-analog converter as the analog equivalent of the binary word in the AC. The LDY instruction clears an existing ready flag and sets the ready flag after 100 +/- 20 us."}, +{ 06066, OP_ALL, "EXC", "Used to execute the wanted vector according to the contents of AC2-4 and AC6-11. The parameter word establishes long or short formats, circular vectors, display erasure, reset of the integrators, and intensification of the vector. The EXC instruction clears an existing ready flag and sets the ready flag as follows: a) after 20 +/- 5 us for a point or vector continue; b) after 250 us for short vectors; c) after 4.05 ms for long vectors; d) after 500 ms for an erase."}, + // 6070 (continued) +{ 06071, OP_ALL, "SRF", "Used to determine when the controller is ready to perform the next execute instruction. The ready flag produces an interrupt condition when set. The flag can be set by pressing the erase pushbutton on the VT01 unit. Normally, however, the state of this flag is determined by the controller. This flag is initially cleared when the computer is started and prior to an LDX, LDY, or EXC instruction."}, +{ 06072, OP_ALL, "CRF", "This instruction clears the ready flag after a skip instruction has been acknowledged."}, +{ 06073, OP_ALL, "SDA", "Used in the successive approximation subroutine to determine the digital equivalent of the selected analog holding register. This instruction is used with the SAC (6062) instruction."}, +{ 06074, OP_ALL, "LDA", "This instruction is used to load the content of AC2-11. This instruction is used with DSA (6073) in the successive approximation subroutine to determine the digital value of the content of the selected analog holding register. Does not change flag states."}, +#endif + // 6100 Memory Parity Type MP8/I [MP8/L] + // 6100 Automatic Restart Type KP8/I [KP8/L] +{ 06101, OP_ALL, "SMP", "(MP8/I) Skip if memory parity error flag = 0."}, +{ 06102, OP_ALL, "SPL", "(KP8/I) Skip if power low"}, +{ 06104, OP_ALL, "CMP", "(MP8/I) Clear memory parity error flag."}, + // 6110 Multiple Asynchronous Serial Line Interface Unit, Type DC02D +{ 06111, OP_ALL, "MKSF", "Skip the next instruction if the keyboard flag is set."}, +{ 06112, OP_ALL, "MKCC", "Clear the keyboard and reader flags; clear the AC."}, +{ 06113, OP_ALL, "MTPF", "(DC02A) Transfer status of teleprinter flags to AC 0-3."}, +{ 06114, OP_ALL, "MKRS", "Transfer the shift register contents to AC 4-11."}, +{ 06115, OP_ALL, "MINT", "(DC02A) Interrupt on if AC 11 is set (interrupt request, if any flags)."}, +{ 06116, OP_ALL, "MKRB", "Clear the keyboard and reader flags, clear the AC, trasnfer the shift register contents to AC 4-11 (MKCC and MKRS combined)."}, +{ 06117, OP_ALL, "MTON", "Transfer AC0-3 to selection register (SELF) (select stations when bit is set)."}, + // 6120 Multiple Asynchronous Line Unit, Type DC02A +{ 06121, OP_ALL, "MTSF", "Skip the next instruction if the teleprinter flag is set."}, +{ 06122, OP_ALL, "MTCF", "Clear the teleprinter flag."}, +{ 06123, OP_ALL, "MTKF", "Transfer status of keyboard flags to AC 0-3."}, +{ 06124, OP_ALL, "MTPC", "Load AC4-11 into the shift register (begin print/punch)."}, +{ 06125, OP_ALL, "MINS", "Skip if the interrupt request is active (if interrupt is on and any flag is raised)."}, +{ 06126, OP_ALL, "MTLS", "Clear the teleprinter flag and load AC4-11 into the shift register (MTCF and MTPC combined)"}, +{ 06127, OP_ALL, "MTRS", "Trasnfer the status of the selection register to AC 0-3."}, + // 6130 Real Time Clock, Type KW8/I [KW8/L] +{ 06132, OP_ALL, "CCFF", "The flag, flag buffer, clock enable, and interrupt enable flip-flops are cleared. This disables the real-time clock."}, +{ 06133, OP_ALL, "CSCF", "When the flag flip-flop has been set by a clock pulse, the flag buffer flip-flop is set to a 1. Upon execution of this instruction, an IO BUS IN SKIP is generated if the flag is set. The content of the PC is incremented by 1, so that the next sequential instruction is skipped. The flag flip-flop is then cleared. If the flag flip-flop has not been set, no skip is generated nor is the flag flip-flop cleared."}, +{ 06134, OP_ALL, "CRCA", "The output buffer is gated to the I/O BUS during IOP4, and a CLK AC CLR signal generated. This register contains the last count in the count register. The transfer from the count register is synchronized with this instruction so that a transfer that would occur during this instruction is not made."}, +{ 06136, OP_ALL, "CCEC", "All clock control flip-flops are first cleared, then the clock enable flip-flop is set. For the variable frequency clock, the frequency source is enabled synchronously with program operation. With all clocks, the data input to the flag is enabled after IOP2 time. This represents an 800-ns mask, after the clock is enabled."}, +{ 06137, OP_ALL, "CECI", "All clock control flip-flops are cleared, then the clock enable, and interrupt enable flip-flops are set. The clock enable flip-flop is described with the CCEC instruction. The interrupt enable flip-flop allows an IO BUS IN INT signal when the flag is set."}, + // 6140 + // 6150 + // 6160 + // 6170 + // 6200 through 6277 Memory Extension Control Type MC8/I [MC8/L] +{ 06201, OP_ALL, "CDF0", "Change to data field 0. The data field register is loaded with the selected field number (0). All subsequent memory requests for operands are automatically switched to that data field until the data field number is changed by a new CDF command."}, +{ 06202, OP_ALL, "CIF0", "Prepare to change to instruction field N (0). The instruction buffer register is loaded with the selected field number (0). The next JMP or JMS instruction causes the new field to be entered."}, +{ 06204, OP_ALL, "CINT", "(KT8/I) Clear user interrupt. Resets the user interrupt (UINT) flip-flop to the 0 state."}, + // 6210 +{ 06211, OP_ALL, "CDF0", "Change to data field 1. The data field register is loaded with the selected field number (1). All subsequent memory requests for operands are automatically switched to that data field until the data field number is changed by a new CDF command."}, +{ 06212, OP_ALL, "CIF0", "Prepare to change to instruction field N (1). The instruction buffer register is loaded with the selected field number (1). The next JMP or JMS instruction causes the new field to be entered."}, +{ 06214, OP_ALL, "RDF", "Read data field into AC6-8. Bit 0-5 and 9-11 of the AC are not affected."}, + // 6220 +{ 06221, OP_ALL, "CDF0", "Change to data field 2. The data field register is loaded with the selected field number (2). All subsequent memory requests for operands are automatically switched to that data field until the data field number is changed by a new CDF command."}, +{ 06222, OP_ALL, "CIF0", "Prepare to change to instruction field N (2). The instruction buffer register is loaded with the selected field number (2). The next JMP or JMS instruction causes the new field to be entered."}, +{ 06224, OP_ALL, "RIF", "Same as RDF except reads the instruction field"}, + // 6230 +{ 06231, OP_ALL, "CDF0", "Change to data field 3. The data field register is loaded with the selected field number (3). All subsequent memory requests for operands are automatically switched to that data field until the data field number is changed by a new CDF command."}, +{ 06232, OP_ALL, "CIF0", "Prepare to change to instruction field N (3). The instruction buffer register is loaded with the selected field number (3). The next JMP or JMS instruction causes the new field to be entered."}, +{ 06234, OP_ALL, "RIB", "Read interrupt buffer. The instruction field and data field stored during an interrupt are read into AC6-8 and AC9-11, respectively."}, + // 6240 +{ 06241, OP_ALL, "CDF0", "Change to data field 4. The data field register is loaded with the selected field number (4). All subsequent memory requests for operands are automatically switched to that data field until the data field number is changed by a new CDF command."}, +{ 06242, OP_ALL, "CIF0", "Prepare to change to instruction field N (4). The instruction buffer register is loaded with the selected field number (4). The next JMP or JMS instruction causes the new field to be entered."}, +{ 06244, OP_ALL, "RMF", "Restore memory field. Used to exit from a program interrupt."}, + // 6250 +{ 06251, OP_ALL, "CDF0", "Change to data field 5. The data field register is loaded with the selected field number (5). All subsequent memory requests for operands are automatically switched to that data field until the data field number is changed by a new CDF command."}, +{ 06252, OP_ALL, "CIF0", "Prepare to change to instruction field N (5). The instruction buffer register is loaded with the selected field number (5). The next JMP or JMS instruction causes the new field to be entered."}, +{ 06254, OP_ALL, "SINT", "(KT8/I) Skip on user interrupt. When the user interrupt (UINT) flip-flop is in the 1 state, sets the user skip flip-flop (USF) to the 1 state and causes the program to skip the next instruction."}, + // 6260 +{ 06261, OP_ALL, "CDF0", "Change to data field 6. The data field register is loaded with the selected field number (6). All subsequent memory requests for operands are automatically switched to that data field until the data field number is changed by a new CDF command."}, +{ 06262, OP_ALL, "CIF0", "Prepare to change to instruction field N (6). The instruction buffer register is loaded with the selected field number (6). The next JMP or JMS instruction causes the new field to be entered."}, +{ 06264, OP_ALL, "CUF", "(KT8/I) Clears the user flag. Clears the user buffer (UB) flip-flop."}, + // 6270 +{ 06271, OP_ALL, "CDF0", "Change to data field 7. The data field register is loaded with the selected field number (7). All subsequent memory requests for operands are automatically switched to that data field until the data field number is changed by a new CDF command."}, +{ 06272, OP_ALL, "CIF0", "Prepare to change to instruction field N (7). The instruction buffer register is loaded with the selected field number (7). The next JMP or JMS instruction causes the new field to be entered."}, +{ 06274, OP_ALL, "SUF", "(KT8/I) Sets the user flag. Sets user buffer (UB) and inhibits processor interrupts until the next JMP or JMS instruction. Generation of IB -> IF during the next JMP or JMS instruction transfers the state of UB to the user field (UF) flip-flop."}, + // 6400 Data Communications System 680/I +{ 06401, OP_ALL, "TTINCR", "This instruction causes the contents of the line register to be incremented by 1. This command, when microprogrammed with a TTO command is executed."}, +{ 06402, OP_ALL, "TTI", "Causes a JMS to be executed (N+3) if the R register does not equal 0 and either the line hold bit of the selected line (specified by bits 2-8 of the LSW) is in the 1 state, or as a result of jamming the line state into and shifting the CAW; bit 11 of the CAW is a 1."}, +{ 06404, OP_ALL, "TTO", "Clears the link and shifts the link and accumulator one bit position to the right. Bit 11 of the accumulator is shifted into the line unit specified by the line register. The previuos contents (1 bit) of the selected line unit is lost."}, + // 6410 +{ 06411, OP_ALL, "TTCL", "The command sets the contents of the line register to 0."}, +{ 06412, OP_ALL, "TTSL", "The contents of AC5-11 are ORed into the line register."}, +{ 06413, OP_ALL, "TTLL", "The contents of AC5-11 are trasnferred into the line register. This is a microprogram of TTCL and TTSL."}, +{ 06414, OP_ALL, "TTRL", "The contents of the line register are ORed into AC5-11. The AC must be 0 for a true transfer."}, + // 6420 +{ 06421, OP_ALL, "T1skip", "(Data Communications System 680/I) Clock Control Instruction: Causes the program to skip the next instruction if clock flag 1 is in the 1 state. To clear the flag, either T1on or T1off can be used."}, +{ 06422, OP_ALL, "T1off", "(Data Communications System 680/I) Clock Control Instruction: Inhibits clock 1 from setting its flag. This instruction also sets the flag to the 0 state."}, +{ 06424, OP_ALL, "T1on", "(Data Communications System 680/I) Clock Control Instruction: Enables clock 1 to set its flag at the predetermined lcock rate. The flag in the 1 state causes a program interrupt when the interrupt is enabled. This instruction also sets the flag to the 0 state."}, + // 6430 +{ 06431, OP_ALL, "T2skip", "(Data Communications System 680/I) same as T1skip except for clock 2"}, +{ 06432, OP_ALL, "T2off", "(Data Communications System 680/I) same as T1off except for clock 2"}, +{ 06434, OP_ALL, "T2on", "(Data Communications System 680/I) same as T1on except for clock 2"}, +#ifdef PT08 + // 6440 Asynchronous Serial Line Interface, Type PT08 +{ 06441, OP_ALL, "TSFXXX", "Skip if teleprinter/punch 3 flag is a 1."}, +{ 06442, OP_ALL, "TCFXXX", "Clear teleprinter/punch 3 flag."}, +{ 06444, OP_ALL, "TPCXXX", "Load teleprinter 3 buffer (TTOX) with AC4-11 and print/punch the character."}, +{ 06446, OP_ALL, "TLSXXX", "Load TTOX with AC4-11, 3 flag, print/punch the character and clear teleprinter/punch."}, +{ "6450"}, +{ 06451, OP_ALL, "KSFXXX", "Skip if keyboard/reader 3 flag is a 1."}, +{ 06452, OP_ALL, "KCCXXX", "Clear AC and keyboard/reader 3 flag."}, +{ 06454, OP_ALL, "KRSXXX", "Read keyboard/reader 3 buffer (TTI3) static. TTI3 is loaded into AC4-11 by an OR transfer."}, +{ 06456, OP_ALL, "KRBXXX", "Clear the AC, read TTI3 into AC4-11, and clear keyboard 3 flag."}, +{#else "PT08"}, +{ 06441, OP_ALL, "T3skip", "(Data Communications System 680/I) same as T1skip except for clock 3"}, +{ 06442, OP_ALL, "T3off", "(Data Communications System 680/I) same as T1off except for clock 3"}, +{ 06444, OP_ALL, "T3on", "(Data Communications System 680/I) same as T1on except for clock 3"}, +{ 06451, OP_ALL, "T4skip", "(Data Communications System 680/I) same as T1skip except for clock 4"}, +{ 06452, OP_ALL, "T4off", "(Data Communications System 680/I) same as T1off except for clock 4"}, +{ 06454, OP_ALL, "T4on", "(Data Communications System 680/I) same as T1on except for clock 4"}, +#endif +{ 06461, OP_ALL, "TTRINC", "(Data Communications System 680/I) This command causes the contents of the R register to be incremented by 1. Because it is loaded with a 2's complement number, the result is a subtract. This instruction can be microprogrammed with TTRR."}, +{ 06464, OP_ALL, "TTRR", "(Data Communications System 680/I) This command reads the contents of the R register into AC7-11. The contents of the AC must be 0s before issuing this instruction. This instruction, when microprogrammed with TTINCR, causes the incremented results to be read into the AC."}, + // 6470 +{ 06471, OP_ALL, "TTCR", "(Data Communications System 680/I) This command causes the R register to be set to 0"}, +{ 06472, OP_ALL, "TTLR", "(Data Communications System 680/I) This command causes the contents of AC7-11 to be trasnferred into the R register."}, + // 6500 Incremental Plotter and Control Type VP8/I +{ 06501, OP_ALL, "PLSF", "Skip if plotter flag is a 1."}, +{ 06502, OP_ALL, "PLCF", "Clear plotter flag."}, +{ 06504, OP_ALL, "PLPU", "Plotter pen up. Raise pen off of paper."}, + // 6510 +{ 06511, OP_ALL, "PLPR", "Plotter pen right."}, +{ 06512, OP_ALL, "PLDU", "Plotter drum (paper) upward"}, +{ 06514, OP_ALL, "PLDD", "Plotter drum (paper) downward."}, + // 6520 +{ 06521, OP_ALL, "PLPL", "Plotter pen left."}, +{ 06522, OP_ALL, "PLUD", "Plotter drum (paper) upward. Same as 6512"}, +{ 06524, OP_ALL, "PLPD", "Plotter pen down. Lower pen on to paper."}, +#ifdef AF01A + // 6530 General Purpose Converter and Multiplexer Control Type AF01A (this option is mutually exclusive with AF04A) +{ 06531, OP_ALL, "ADSE", "Skip if A/D converter flag is a 1."}, +{ 06532, OP_ALL, "ADCV", "Clear A/D converter flag and convert input voltage to a digital number, flag will set to 1 at end of conversion. Number of bits in converted number determined by switch setting, 11 bits maximum."}, +{ 06534, OP_ALL, "ADRB", "Read A/D converter buffer into AC, left justified, and clear flag."}, + // 6540 +{ 06541, OP_ALL, "ADCC", "Clear multiplexer channel address register."}, +{ 06542, OP_ALL, "ADSC", "Set up multiplexer channel as per AC6-11. Maximum of 64 single ended or 32 differntial input channels."}, +{ 06544, OP_ALL, "ADIC", "Index multiplexer channel address (present address + 1). Upon reaching address limit, increment will cause channel 00 to be selected."}, +#endif // AF01A +#ifdef AF04A + // 6530 Guarded Scanning Digital Voltmeter Type AF04A (this option is mutually exclusive with AF01A) +{ 06531, OP_ALL, "VSDR", "Skip if data ready flag is a 1."}, +{ 06532, OP_ALL, "VRD", "Selected byte of voltmeter is transferred to the accumulator and the data ready flag is cleared."}, +{ 06534, OP_ALL, "VBA", "BYTE ADVANCE command requests next twelve bits, data ready flag is set."}, + // 6540 +{ 06541, OP_ALL, "VCNV", "The contents of the accumulator are transferred to the AF04A channel address register. Analog signal on selected channel is automatically digitized."}, +{ 06542, OP_ALL, "VSEL", "The contents of the accumulator are transferred to the AF04A control register."}, +{ 06544, OP_ALL, "VINX", "The last channel address is incremented by one and the analog signal on the selected channel is automatically digitized."}, +#endif // AF04A + // 6550 +#ifdef AA01A +{ 06551, OP_ALL, "DAL1", "(AA01A) The character in the accumulator is loaded into the channel 1 buffer. The DAC then converts the buffered value to the analog equivalent. NOTE: Similar instructions for DAL2 and DAL3 load respective DACs."}, +#else // AA01A +#ifdef AA05 +{ 06551, OP_ALL, "CLDA", "The address register in the AA05/AA07 is cleared."}, +#else // AA05 +#endif // AA05 +#endif // AA01A +{ 06552, OP_ALL, "LDAD", "(AA05/AA07) The address register in the AA05/AA07 is loaded with the contents of AC0-5."}, + // 6560 +{ 06562, OP_ALL, "LDAR", "(AA05/AA07) The buffer (input buffer, if the channel is double-buffered) of the DAC is loaded from AC0-9."}, +{ 06564, OP_ALL, "UPDT", "(AA05/AA07) The contents of the input buffers of all double-buffered channels are trasnferred to their respective output buffers. The input buffer is not affected by this instruction."}, + // 6570 +#ifdef AF04A +{ 06571, OP_ALL, "VSCC", "SAMPLE CURRENT CHANNEL when required to digitize analog signal on current channel repeatedly (AF04A)"}, +#else // AF04A +#ifdef AC01A +{ 06571, OP_ALL, "HRAN", "The contents of AC3-5 are trasnferred to the channel address register (CHAR). The 3-bit code is decoded to address any of the 8 channels."}, +#else // AC01A +#endif // AC01A +#endif // AF04A +#ifdef AC01A +{ 06572, OP_ALL, "HSIM", "Simultaneously places all 8 channels into the hold mode."}, +#else // AC01A +#endif // AC01A +{ 06574, OP_ALL, "SAMP", "(AA01A) Places all 8 channels into the sample (or track) mode."}, +#ifdef DP01AA + // 6600 Synchronous Modem Interface, Type DP01AA +{ 06601, OP_ALL, "TAC", "Causes the contents of the AC (6, 7, 8, or 9 bits right-justified) to be transferred into the TB."}, +{ 06602, OP_ALL, "CTF", "Resets the trasnmit flag. If trasnmit active flag is set, CTF also causes the program to skip the next instruction."}, +{ 06604, OP_ALL, "CIM", "Resets the transmit logic idle mode (IM) flip-flop."}, +{ 06611, OP_ALL, "STF", "Causes the program to skip the next instruction if the transmit flag is in the 0 state. When the transmit flag is in the 1 state, the trasnmit buffer register (TB) is ready to accept another character."}, +{ 06612, OP_ALL, "RRB", "Transfers the contents of the receiver buffer (RB) (6, 7, 8, or 9 bits, right-justified) to the computer AC. RRB also resets the receive flag."}, +{ 06614, OP_ALL, "SIM", "Sets the transmit idle mode (IM) flip-flop."}, +{ 06621, OP_ALL, "SEF", "Causes the program to skip the next instruction if the receive end flag is 0. The receive end flag slip-flop is set when the receive logic has stopped receiving serial data from the communications equipment due to termination of th SERIAL CLOCK RECEIVE pulse train."}, +{ 06622, OP_ALL, "CEF", "Clears the receive end flag"}, +{ 06624, OP_ALL, "SRE", "Sets the ring enable (RE) flip-flop to a 1, which permits the ring flag to request a program interrupt."}, +{ 06631, OP_ALL, "SRI", "Causes the program to skip the next instruction if the ring flag is 0. The ring flag is set when a ring input is received."}, +{ 06632, OP_ALL, "CRF", "Clears the ring flag."}, +{ 06634, OP_ALL, "STR", "Sets the terminal read (TR) flip-flop to the 1 state. This causes the terminal ready lead to the modem to be set on the ON state. The state changes to OFF for CTR"}, +{ 06641, OP_ALL, "SSR", "Causes the program to skip the next instruction if the data-set-ready lead from the modem is in the ON state."}, +{ 06642, OP_ALL, "CTR", "Clears the terminal ready (TR) flip-flop (see STR)"}, +{ 06644, OP_ALL, "CRE", "Clears the ring enable (RE) flip-flop."}, +{ 06651, OP_ALL, "SRF", "Causes the program to skip the next instruction if the receive flag is 0. The flag is set when a received character is ready for trasnfer to the AC and the flag is cleared when an RRB instruction is issued."}, +{ 06652, OP_ALL, "CRA", "Clears the receive active (RA) flip-flop, taking the receive logic out of the active state. This inhibits any more receive flags until a new sync character is received."}, +{ 06654, OP_ALL, "XOB", "Causes an exclusive OR of the AC with the buffer register (BR)."}, +{ 06661, OP_ALL, "COB", "Clears the XOR buffer."}, +{ 06662, OP_ALL, "ROB", "Transfers the buffer register (BR) content to the AC."}, +{ 06664, OP_ALL, "IOB", "Transfers 1s from the AC to the buffer register (BR)."}, +#else // DP01AA +#ifdef DF32 + // 6600 Random Access Disc File (type DF32) +{ 06601, OP_ALL, "DCMA", "Clears memory address register, parity erorr and completion flags. This instruction clears the disk memory request flag and interrupt flags."}, +{ 06603, OP_ALL, "DMAR", "The contents of the AC are loaded into the disk memory address register and the AC is cleared. Begin to read information from the disk into the specified core location. Clears parity error and completion flags. Clears interrupt flags."}, +{ 06605, OP_ALL, "DMAW", "The contents of the AC are loaded into the disk memory address register and the AC is cleared. Begin to write information into the disk from the specified core location. Clears parity error and completion flags."}, + // 6610 +{ 06611, OP_ALL, "DCEA", "Clears the disk extended address and memory address extension register."}, +{ 06612, OP_ALL, "DSAC", "Skips next instruction if address confirmed flag is a 1. AC is cleared."}, +{ 06615, OP_ALL, "DEAL", "The disk extended-address extension registers are cleared and loaded with the track data held in the AC."}, +{ 06616, OP_ALL, "DEAC", "Clear the AC then loads the contents of the disk extended-address register into the AC to allow program evaluation. Skip next instruction if address confirmed flag is a 1."}, + // 6620 +{ 06621, OP_ALL, "DFSE", "Skip next instruction if the completion flag is a 1. Indicates data transfer is complete."}, +{ 06626, OP_ALL, "DMAC", "Clear the AC then loads contents of disk memory address register into the AC to allow program evaluation."}, +#endif // DF32 +#ifdef RF08 + // 6600 Disk File and Control, Type RF08/Expander Disk File, Type RS08 + // 6610 +{ 06611, OP_ALL, "DCIM", "Clear the disk interrupt enable and core memory address extension register."}, +{ 06615, OP_ALL, "DIML", "Clear the interrupt enable and memory address extension register, then load the interrupt enable and memory address extension registers with data held in the AC. Then clear the AC. NOTE: Transfers cannot occur across memory fields. Attempts to do so will cause the transfer to "wrap around" within the specified memory field."}, +{ 06616, OP_ALL, "DIMA", "Clear the AC. Then load the contents of the status register (STR) into the AC to allow program evaluation."}, + // 6620 +{ 06621, OP_ALL, "DFSE", "Skip next instruction if there is a parity error, data request late, write lock status, or nonexistent disk flag set."}, +{ 06623, OP_ALL, "DISK", "If either the error or data completion flag (or both) is set, the next instruction is skipped."}, +#endif // RF08 + // 6630 Card Reader and Control Type CR8/I [CR8/L] (see also 6670) +{ 06631, OP_ALL, "RCSF", "Generates an IOP pulse (IOP 1) to test the data-ready flag output. If the data ready flag is 1, the next sequential program instruction is skipped."}, +{ 06632, OP_ALL, "RCRA", "Generates an IOP pulse (IOP 2) to read the alphanumeric data at the control-logic buffer register and clear the data ready flag."}, +{ 06634, OP_ALL, "RCRB", "Generates an IOP pulse (IOP 4) to read the BCD data at the control logic buffer register and clear the data ready flag."}, +#ifdef RF08 + // 6640 +{ 06641, OP_ALL, "DCXA", "Clear the high order 8-bits of the disk address register (DAR)."}, +{ 06643, OP_ALL, "DXAL", "Clear the high order 8 bits of the DAR. Then load the DAR from data stored in the AC. Then clear the AC."}, +{ 06645, OP_ALL, "DXAC", "Clear the AC; then load the contents of the high order 8-bit DAR into the AC."}, +{ 06646, OP_ALL, "DMMT", "For maintenance purposes only with the appropriate maintenance cable connections and the disk disconnected from the RS08 logic, the (given) standard signals may be generated by IOT 6646 and associated AC bits. The AC is cleared and the maintenance register is initiated by issuing an IOT 6601 command."}, +#else // RF08 + // 6640 +#endif // RF08 + // 6650 Automatic Line Printer and Control Type 645 +{ 06651, OP_ALL, "LSE", "Skip if line printer error flag is a 1."}, +{ 06652, OP_ALL, "LCB", "Clear both sections of the printing buffer."}, +{ 06654, OP_ALL, "LLB", "Load printing buffer from the content of AC6-11 and clear the AC"}, + // 6660 Automatic Line Printer and Control Type 645 (continued) +{ 06661, OP_ALL, "LSD", "Skip if the printer done flag is a 1."}, +{ 06662, OP_ALL, "LCF", "Clear line printer done and error flags."}, +{ 06664, OP_ALL, "LPR", "Clear the format register, load the format register from the content of AC9-11, print the line contained in the section of the printer buffer loaded last, clear the AC, and advance the paper in accordance with the selected channel of the format tape if the content of AC8=1. If the content of AC8=0, the line is printed and paper advance is inhibited."}, +#endif // DP01AA + // 6670 Card Reader and Control Type CR8/I [CR8/L] (see also 6630) +{ 06671, OP_ALL, "RCSD", "Generates an IOP pulse (IOP 1) to test the card-done flag output. If the card done flag is 1, the next sequential program instruction is skipped."}, +{ 06672, OP_ALL, "RCSE", "Generates an IOP pulse (IOP 2) to advance the card, clear the card done flag, and produce a skip flag is reader is ready. If skip flag is generated, the next sequential program instruction is skipped."}, +{ 06674, OP_ALL, "RCRD", "Generates an IOP pulse (IOP 4) to clear the card done flag."}, +#ifdef TA8A + // 6700 -> 6707 TU60 DECassette Controller TA8A +{ 06700, OP_ALL, "KCLR", "Clear All; clear the status A and B register"}, +{ 06701, OP_ALL, "KSDR", "Skip the next instruction if the data flag is set during read or write operations"}, +{ 06702, OP_ALL, "KSEN", "Skip the next instruction if any of the following are true: a) tape is at EOT/BOT, b) the TU60 is not ready or the selected drive is empty"}, +{ 06703, OP_ALL, "KSBF", "Skip the next instruction if the ready flag is set"}, +{ 06704, OP_ALL, "KLSA", "Load status A from AC4-AC11, clear the AC, and load the complement of status A back into the AC"}, +{ 06705, OP_ALL, "KSAF", "Skip on any flag or error condition"}, +{ 06706, OP_ALL, "KGOA", "Assert the contents of the status A register and transfer data into the AC during a read operation or out of the AC to the Read/Write buffer during a write operation. This instruction has three functions: a) enables the command in the status A register to be executed by the TU60, b) for read operations, the first KGOA instruction causes the tape to start moving, and when the data flag sets, a second KGOA transfers the first byte from the read/write buffer to the AC. The data flag sets after each 8-bit byte is read from the TU60. c) for write operations, the status A register is set up for a write, and the AC contains the first byte to be written on tape. When the KGOA instruction is executed, the tape starts to move and the first byte is transferred to the TU60."}, +{ 06707, OP_ALL, "KRSB", "Transfer the contents of the status B register into AC4-AC11."}, +#endif +#ifdef TC58 + // 6700 Automatic Magnetic Tape Control Type TC58 +{ 06701, OP_ALL, "MTSF", "Skip on error flag or magnetic tape flag. The status of the error flag (EF) and the magnetic tape flag (MTF) are sampled. If either or both are set to 1, the content of the PC is incremented by one to skip the next sequential instruction."}, +{ 06702, OP_ALL, "6702", "(no mnemonic assigned) Clear the accumulator."}, +{ 06704, OP_ALL, "6704", "(no mnemonic assigned) Inclusively OR the contents of the status register into AC0-11"}, +{ 06706, OP_ALL, "MTRS", "Read the contents of the status register into AC0-11."}, + // 6710 +{ 06711, OP_ALL, "MTCR", "Skip on tape control ready (TCR). If the tape control is ready to receive a command, the PC is incremented by one to skip the next sequential instruction."}, +{ 06712, OP_ALL, "MTAF", "Clear the status and command registers, and the EF and MTF if tape control ready. If tape control not ready, clears MTF and EF flags only."}, +{ 06714, OP_ALL, "MTCM", "Inclusively OR the contents of AC0-5, AC9-11 into the command register; JAM transfer bits 6, 7, 8 (command function)"}, +{ 06716, OP_ALL, "MTLC", "Load the contents of AC0-1j1 into the command register."}, + // 6720 +{ 06721, OP_ALL, "MTTR", "Skip on tape transport ready (TTR). The next sequential instruction is skipped if the tape transport is ready."}, +{ 06722, OP_ALL, "MTGO", "Set "go" bit to execute command in the command register if command is legal."}, +{ 06724, OP_ALL, "MTRC", "Inclusively OR the contents of the contents of the command register into AC0-11."}, +#endif // TC58 +#ifdef TR02 + // 6700 Incremental Magnetic Tape Controller, Type TR02 +{ 06701, OP_ALL, "IRS", "When data is ready to be strobed into the AC from the read buffer (RB), the PC is incremented by one to skip the next sequential instruction. The read done flag is cleared only if the skip occurs."}, +{ 06702, OP_ALL, "ISR", "The content of the status register (STR) is read into AC0-8. The AC should be cleared before it is read by this instruction."}, +{ 06703, OP_ALL, "IWS", "If the write done flag is set, the next instruction is skipped and the write done flag is cleared."}, +{ 06704, OP_ALL, "IMC", "The move command decoded from AC0-2 is generated. This instruction also clears the read done, write done, and gap detect flags. The indicated flag is set when the command has been executed."}, +{ 06705, OP_ALL, "IGS", "If the gap detect flag is set, the next instruction is skipped and the gap detect flag is cleared."}, +{ 06706, OP_ALL, "IWR", "The contents of the AC are loaded into the tape input data buffer (WB) and a write step command is generated. The write done flag is set when writing is completed."}, +{ 06707, OP_ALL, "IRD", "The AC is cleared and the content of the read buffer (RB) is loaded into the AC. Data bits are transferred into AC6-11 (7-track) or AC4-11 (9-track). Parity error is transferred into AC0 which is 0 if there is no parity error."}, + // 6710 +{ 06711, OP_ALL, "IRSA", "When data is ready to be strobed into the AC from the read buffer (RB), the PC is incremented by one to skip the next sequential instruction. The read done flag is cleared only if the skip occurs."}, +{ 06712, OP_ALL, "ISRA", "The content of the status register (STR) is read into AC0-8. The AC should be cleared before it is read by this instruction."}, +{ 06713, OP_ALL, "IWSA", "If the write done flag is set, the next instruction is skipped and the write done flag is cleared."}, +{ 06714, OP_ALL, "IMCA", "The move command decoded from AC0-2 is generated. This instruction also clears the read done, write done, and gap detect flags. The indicated flag is set when the command has been executed."}, +{ 06715, OP_ALL, "IGSA", "If the gap detect flag is set, the next instruction is skipped and the gap detect flag is cleared."}, +{ 06716, OP_ALL, "IWRA", "The contents of the AC are loaded into the tape input data buffer (WB) and a write step command is generated. The write done flag is set when writing is completed."}, +{ 06717, OP_ALL, "IRDA", "The AC is cleared and the content of the read buffer (RB) is loaded into the AC. Data bits are transferred into AC6-11 (7-track) or AC4-11 (9-track). Parity error is transferred into AC0 which is 0 if there is no parity error."}, + // 6720 +#endif // TC58 + // 6730 + // 6740 + // 6750 + // 6760 DECtape Transport Type TU55 and DECtape Control Type TC01 +{ 06761, OP_ALL, "DTRA", "The content of status register A is read into AC0-9 by an OR transfer. The bit assignments are: AC0-2 = Transport unit select numnber; AC3-4 = Motion; AC5 = Mode; AC6-8 = Function; AC9 = Enable/disable DECtape control flag."}, +{ 06762, OP_ALL, "DCTA", "Clear status register A. All flags undisturbed."}, +{ 06764, OP_ALL, "DTXA", "Status register A is loaded by an exclusive OR transfer from the content of the AC, and AC10 and AC11 are sampled. If AC10 = 0, the error flags are cleared. If AC11 = 0, the DECtape control flag is cleared."}, + // 6770 +{ 06771, OP_ALL, "DTSF", "Skip if error flag is a 1 or if DECtape control flag is a 1."}, +{ 06772, OP_ALL, "DTRB", "The content of status register B is read into the AC by an OR transfer. The bit assignments are: AC0 = Error flag; AC1 = Mark track error; AC2 = End of tape ; AC3 = Select error ; AC4 = Parity error; AC5 = Timing error; AC6-8 = Memory field; AC9-10 = Unused; AC11 = DECtape flag."}, +{ 06774, OP_ALL, "DTLB", "The memory field portion of status register B is loaded from the content of AC6-8."}, +}; + ADDED src/d8tape/main.c Index: src/d8tape/main.c ================================================================== --- /dev/null +++ src/d8tape/main.c @@ -0,0 +1,848 @@ + +/* + * main.c + * + * (C) Copyright 2000 by Robert Krten, all rights reserved. + * Please see the LICENSE file for more information. + * + * This module represents the main module for the RIM/BIN + * dumper/disassembler/converter/reverse engineering tool. + * + * This program will dump a RIM/BIN-formatted image to stdout, or + * convert a tape from one format to another, or clean up a tape, + * or disassemble a tape with reverse-engineering aids. + * + * 2001 01 07 R. Krten created + * 2003 12 16 R. Krten added disassembler + * 2003 12 17 R. Krten made it RIM/BIN aware. + * 2007 10 25 R. Krten added reverse-engineering features + * 2007 11 04 R. Krten fixed header skip logic (see dumptape()) +*/ + +#ifdef __USAGE + +%C [options] papertapefile [papertapefile...] + +where [options] are optional parameters chosen from: + -b generate a BIN-formatted output (adds ".bin") + -d suppress disassembly (useful for conversion-only mode) + -r generate a RIM-formatted output (adds ".rim") + -T generate test pattern 0000=0000..7756=7756 + -v verbose operation + +Dumps the RIM- or BIN-formatted input file(s) specified on +the command line. If the "-r" and/or "-b" options are +present, also creates a RIM and/or BIN version of the output +by adding ".rim" and/or ".bin" (respectively) to the input +filename (can be used to "clean up" BIN and RIM images by +deleting excess data before and after the leader/trailer). + +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "d8tape.h" + +static void seg_add (uint16_t addr, int len); +static void seg_more (int len); + +static void optproc (int, char **); +static int dumptape (unsigned char *t, int n); +static int dumprim (unsigned char *t, int n); +static int dumpbin (unsigned char *t, int n); +static int checkrim (unsigned char *t, int n); +static int checkbin (unsigned char *t, int n); +static void writebin (void); +static void writerim (void); +static int blank (short int *core, int size); + +const char *progname = "d8tape"; +const char *blankname= " "; +extern char *version; // version.c + +int optb; +int optd; +int optr; +int optT; +int optv; +unsigned char *tape; // paper tape image +short int core [CORE_SIZE]; // in-core image (-1 means location never used) +uint16_t tags [CORE_SIZE]; // analysis tags +segment_t *segments; // used to accumulate runs of data (from origin for nwords) +int nsegments; // indicates how many segments we have +char *tapename; + +/* + * main + * + * Main simply calls the option processor, from which everything happens. +*/ + +int +main (int argc, char **argv) +{ + optproc (argc, argv); + exit (EXIT_SUCCESS); +} + +/* + * usageError + * + * This is the usage message +*/ + +static void +usageError () +{ + fprintf (stderr, "\nUsage: %s [options] papertapefile [papertapefile...]\n\n", progname); + fprintf (stderr, "where [options] are optional parameters chosen from:\n"); + fprintf (stderr, " -b generate a BIN-formatted output (adds \".bin\")\n"); + fprintf (stderr, " -d suppress disassembly (useful for conversion-only mode)\n"); + fprintf (stderr, " -r generate a RIM-formatted output (adds \".rim\")\n"); + fprintf (stderr, " -v verbose operation\n"); + fprintf (stderr, "\n"); + fprintf (stderr, "Dumps the RIM- or BIN-formatted input file(s) specified on\n"); + fprintf (stderr, "the command line. If the \"-r\" and/or \"-b\" options are\n"); + fprintf (stderr, "present, also creates a RIM and/or BIN version of the output\n"); + fprintf (stderr, "by adding \".rim\" and/or \".bin\" (respectively) to the input\n"); + fprintf (stderr, "filename (can be used to \"clean up\" BIN and RIM images by\n"); + fprintf (stderr, "deleting excess data before and after the leader/trailer).\n"); + fprintf (stderr, "\n"); + fprintf (stderr, "Disassembly conforms to PAL III input requirements\n"); + fprintf (stderr, "\n"); + exit (EXIT_FAILURE); +} + +/* + * optproc + * + * This is the option processor. It detects the command line options, and + * then processes the individual files. +*/ + +static void +optproc (int argc, char **argv) +{ + int opt; + int got_any; + int fd; + int i; + struct stat statbuf; + int sts; + + if (!argc) { + usageError (); + } + + // clear out option values to defaults + optb = optr = optT = 0; + + // handle command line options + got_any = 0; + while ((opt = getopt (argc, argv, "bdrTv")) != -1) { + switch (opt) { + case 'b': + optb++; + break; + case 'd': + optd++; + break; + case 'r': + optr++; + break; + case 'T': + optT++; + break; + case 'v': + optv++; + if (optv > 1) { + fprintf (stderr, "Verbosity is %d\n", optv); + } + break; + default: + usageError (); + break; + } + } + + // handle command line arguments + for (; optind < argc; optind++) { + got_any++; + tapename = argv [optind]; // snap tapename to global + + // open the tape + fd = open (tapename, O_RDONLY); + if (fd == -1) { + fprintf (stderr, "%s: couldn't open %s for O_RDONLY, errno %d\n", progname, tapename, errno); + perror (NULL); + exit (EXIT_FAILURE); + } + fstat (fd, &statbuf); // get the size, so we can read it into memory + + tape = calloc (1, statbuf.st_size); + if (tape == NULL) { + fprintf (stderr, "%s: can't allocate %zu bytes during processing of %s, errno %d (%s)\n", progname, statbuf.st_size, tapename, errno, strerror (errno)); + exit (EXIT_FAILURE); + } + + // initialize data areas + memset (core, 0xff, sizeof (core)); // set to -1 -- since we are only using 12 bits of each 16 bit word, -1 isn't a valid PDP-8 core value + memset (tags, 0, sizeof (tags)); // reset tags + nsegments = 0; + segments = NULL; + + read (fd, tape, statbuf.st_size); + close (fd); + + // dump the tape (this also reads the tape into "core" and disassembles) + sts = dumptape (tape, statbuf.st_size); + + free (tape); + + if (!sts) { + continue; // skip tape + } + + // convert to RIM/BIN if required (-b and/or -r) + if (optb || optr) { + // see if there is any data there at all.. + if (blank (core, CORE_SIZE)) { + fprintf (stderr, "%s: tape image from %s is empty, not creating a BIN version\n", progname, tapename); + return; + } + if (optb) { + writebin (); + } + if (optr) { + writerim (); + } + } + } + + // if no arguments given, dump usage message + if (optT) { + memset (core, 0xff, sizeof (core)); // set to -1 (assumption; all 0xff's in an int is -1; pretty safe) + for (i = 0; i < 07600; i++) { + core [i] = ((i & 07700) >> 6) + ((i & 00077) << 6); + } + tapename = "test"; + if (optb) { + writebin (); + } + if (optr) { + writerim (); + } + } else { + if (!got_any) { + usageError (); + } + } +} + +/* + * dumptape + * + * This function does some basic processing (detecting and skipping the + * header, killing off data past the trailer) and then determines via + * checkrim() and ckeckbin() the format of the tape. Depending on the + * type of tape, dumprim() or dumpbin() is called to read the tape into + * the "core" array and disassemble it. + * + * 20071104: Changed the 'skip header' logic to look for a character + * less than 0x80, not just not equal to, because a tape I received + * had the following: + * 0000000 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 + * * + * 0000100 300 100 000 000 000 050 001 000 002 000 003 000 000 000 000 054 + * + * Notice the "300" (0xc0) at location 0100. + * + * Returns 0 on error. +*/ + +static int +dumptape (unsigned char *t, int n) +{ + int i; + + // basic preprocessing; find header + for (i = 0; i < n; i++) { + if (t [i] == 0x80) { // got a header + break; + } + } + if (i == n) { + fprintf (stderr, "%s: couldn't find a 0x80 leader on tape %s; tape ignored\n", progname, tapename); + return (0); + } + + // skip header + for (; i < n; i++) { + if (t [i] < 0x80) { // RK 20071104 was "!= 0x80"; + break; + } + } + if (i == n) { + fprintf (stderr, "%s: no data content found after 0x80 leader on tape %s; tape ignored\n", progname, tapename); + return (0); + } + + // at this point, we're positioned on the first-non-leader byte of the tape + if (n - i < 4) { + fprintf (stderr, "%s: tape %s is too short; tape ignored\n", progname, tapename); + return (0); + } + + // skip leader (t now points to start of tape; n indicates remaining # bytes) + if (optv) { + fprintf (stderr, "%s: tape %s skipped %d (0x%0X, 0%0o) bytes of header, original size %d new size %d\n", progname, tapename, i, i, i, n, n - i); + } + t += i; + n -= i; + + // find first 0x80 -- trailer + for (i = 0; i < n; i++) { + if (t [i] == 0x80) { + break; + } + } + if (i == n) { + fprintf (stderr, "%s: warning -- tape %s does not have a trailer\n", progname, tapename); + // find first data >= 0x80, then + for (i = 0; i < n; i++) { + if (t [i] >= 0x80) { + // at least stop on the first invalid character, then + break; + } + } + } + + // reset end-of-tape to last character + if (optv > 2) { + printf ("%s: tape %s skipped %d bytes of trailer, new size is %d bytes\n", progname, tapename, n - i, i); + } + n = i; + + // determine type of tape and dump it + if (checkrim (t, n)) { + if (!dumprim (t, n)) { + return (0); + } + } else if (checkbin (t, n)) { + if (!dumpbin (t, n)) { + return (0); + } + } else { + fprintf (stderr, "%s: tape %s is neither RIM nor BIN (first four bytes are 0%03o 0%03o 0%03o 0%03o)\n", progname, tapename, t [0], t [1], t [2], t [3]); + return (0); + } + + if (!optd) { + flow (); // perform flow analysis + disassemble (); // disassemble + printf ("\n$\n"); + } + return (1); +} + +/* + * checkrim + * checkbin + * + * These two functions try to determine what format the tape is in. + * A zero return indicates the tape is not in the given format; a one + * indicates it is. The heuristics used here are fairly simple. +*/ + +static int +checkrim (unsigned char *t, int n) +{ + int i; + + if (n % 4) { + if (optv > 2) { + printf ("%s: tape %s size (%d bytes) is not divisible by four; not a RIM tape\n", progname, tapename, n); + } + return (0); + } + + // see if it's a RIM-formatted tape; we're looking for 01xxxxxx 00xxxxxx 00xxxxxx 00xxxxxx + for (i = 0; i < n; i += 4) { + if ((t [i] & 0xC0) != 0x40 || (t [i + 1] & 0xC0) || (t [i + 2] & 0xC0) || (t [i + 3] & 0xC0)) { + if (optv > 2) { + printf ("%s: tape %s does not have the RIM signature at offset 0%04o; expected 01xxxxxx 00xxxxxx 00xxxxxx 00xxxxxx, got 0%04o 0%04o 0%04o 0%04o\n", progname, tapename, i, t [i], t [i + 1], t [i + 2], t [i + 3]); + } + return (0); + } + } + return (1); +} + +static int +checkbin (unsigned char *t, int n) +{ + if (n % 2) { + if (optv > 2) { + printf ("%s: tape %s size (%d bytes) is not divisible by two; not a BIN tape\n", progname, tapename, n); + } + return (0); + } + + // see if it's a BIN-formatted tape; 01xxxxxx 00xxxxxx (i.e., we at least expect an origin) + if ((t [0] & 0xC0) != 0x40 || (t [1] & 0xC0)) { + if (optv > 2) { + printf ("%s: tape %s does not have the BIN origin signature; expected header of 01xxxxxx 00xxxxxx, got 0%04o 0%04o\n", progname, tapename, t [0], t [1]); + } + return (0); + } + + return (1); +} + +/* + * From the PDP-8/I & PDP-8/L Small Computer Handbook (099-00272-A1983 / J-09-5) + * Appendix D, "Perforated-Tape Loader Sequences", page 383 + * + * READIN MODE LOADER + * The readin mode (RIM) loader is a minimum length, basic perforated-tape + * reader program for the ASR33, it is initially stored in memory by manual use + * of the operator console keys and switches. The loader is permanently stored in + * 18 locations of page 37. + * + * A perforated tape to be read by the RIM loader must be in RIM format: + * + * Tape Channel + * 8 7 6 5 4 3 2 1 OCTAL Format + * --------------- ----- ------------------------ + * 1 0 0 0 0 0 0 0 200 Leader-trailer code + * 0 1 -A1- -A2- 1AA Absolute address to + * 0 0 -A3- -A4- 0AA contain next 4 digits + * 0 0 -X1- -X2- 0XX Content of previous + * 0 0 -X3- -X4- 0XX 4-digit address + * 0 1 -A1- -A2- 1AA Address + * 0 0 -A3- -A4- 0AA + * 0 0 -X1- -X2- 0XX Content + * 0 0 -X3- -X4- 0XX + * (etc) (etc) + * 1 0 0 0 0 0 0 0 200 Leader-trailer code + * + * The RIM loader can only be used in conjunction with the ASR33 reader (not + * the high-speed perforated-tape reader). Because a tape in RIM format is, in + * effect, twice as long as it need be, it is suggested that the RIM loader be used + * only to read the binary loader when using the ASR33. (Note that PDP-8 diag- + * nostic program tapes are in RIM format.) + * + * The complete PDP-8/I RIM loader (SA=7756) is as follows: + * + * Absolute Octal + * Address Content Tag Instruction I Z Comments + * 7756, 6032 BEG, KCC /CLEAR AC AND FLAG + * 7757, 6031 KSF /SKIP IF FLAG = 1 + * 7760, 5357 JMP .-1 /LOOKING FOR CHARACTER + * 7761, 6036 KRB /READ BUFFER + * 7762, 7106 CLL RTL + * 7763, 7006 RTL /CHANNEL 8 IN AC0 + * 7764, 7510 SPA /CHECKING FOR LEADER + * 7765, 5357 JMP BEG+1 /FOUND LEADER + * 7766, 7006 RTL /OK, CHANNEL 7 IN LINK + * 7767, 6031 KSF + * 7770, 5367 JMP .-1 + * 7771, 6034 KRS /READ, DO NOT CLEAR + * 7772, 7420 SNL /CHECKING FOR ADDRESS + * 7773, 3776 DCA I TEMP /STORE CONTENT + * 7774, 3376 DCA TEMP /STORE ADDRESS + * 7775, 5356 JMP BEG /NEXT WORD + * 7776, 0 TEMP, 0 /TEMP STORAGE + * 7777, 5XXX JMP X /JMP START OF BIN LOADER +*/ + +/* + * dumprim + * + * This is a finite-state-machine that runs through the tape reading the address + * and data records, stuffs them into core[], and disassembles the opcodes unless + * "-d" is specified. + * + * Note that disassembly is done after the complete tape has been read in, this + * allows us to do some flow analysis. +*/ + +#define RIM_Initial 1 // waiting for address +#define RIM_Addr 2 // got top part of address, process bottom part +#define RIM_Data1 3 // got address, process top part of data +#define RIM_Data2 4 // got top part of data, process bottom part + +static int +dumprim (unsigned char *t, int n) +{ + int state; + int i; + uint16_t addr, data; + uint16_t cur_addr; + + state = RIM_Initial; + + cur_addr = 0xffff; // impossible address for PDP-8 + + for (i = 0; i < n; i++) { + if (optv > 2) { + printf ("[%03o] ", t [i]); fflush (stdout); + if ((i % 13) == 12) { + printf ("\n"); + } + } + switch (state) { + case RIM_Initial: + if (t [i] & 0100) { // indicates 1st part of address + addr = (t [i] & 0077) << 6; // store top part + state = RIM_Addr; + } + break; + case RIM_Addr: + addr |= (t [i] & 0077); + state = RIM_Data1; + break; + case RIM_Data1: + data = (t [i] & 0077) << 6; // store top part + state = RIM_Data2; + break; + case RIM_Data2: // final decode complete, store data + data |= (t [i] & 0077); + core [addr] = data; // stash data into core image + + // segment management -- if it's the next byte, add, else create new + if (addr == cur_addr) { + cur_addr++; + seg_more (1); + } else { + seg_add (addr, 1); + cur_addr = addr + 1; + } + state = RIM_Initial; + break; + } + } + if (optv > 2) { + printf ("\n"); + } + return (1); +} + +/* + * BIN format, from the same doc as above, page 384: + * + * BINARY LOADER + * The binary loader (BIN) is used to read machine language tapes (in binary + * format) produced by the program assembly language (PAL). A tape in binary + * format is about one-half the length of the comparable RIM format tape. It can, + * therefore, be read about twice as fast as a RIM tape and is, for this reason, + * the more desirable format to use with the 10 cps ASR33 reader or the Type + * PR8/I High-Speed Perforated-Tape Reader. + * + * The format of a binary tape is as follows: + * + * LEADER: about 2 feet of leader-trailer codes. + * + * BODY: characters representing the absolute, machine language program + * in easy-to-read binary (or octal) form. The section of tape may contain + * characters representing instructions (channel 8 and 7 not punched) or + * origin resettings (channel 8 not punched, channel 7 punched) and is + * concluded by 2 characters (channel 8 and 7 not punched) that represent + * a check sum for the entire section. + * + * TRAILER: same as leader. + * + * I.e., + * + * Tape Channel + * 8 7 6 5 4 3 2 1 OCTAL Format + * --------------- ----- ------------------------ + * 1 0 0 0 0 0 0 0 200 Leader + * 0 1 A A A A A A 1AA Address (top) + * 0 1 B B B B B B 1BB Address (bottom) + * 0 0 C C C C C C 0CC Data (top) + * 0 0 D D D D D D 0DD Data (bottom) + * 0 0 C C C C C C 0CC Data (top) + * 0 0 D D D D D D 0DD Data (bottom) + * . . . ... next data (2 bytes) + * 0 1 A A A A A A 1AA New address (top) + * 0 1 B B B B B B 1BB New address (bottom) + * . . . ... next data (2 bytes) + * 0 0 X X X X X X 0XX Checksum (top) + * 0 0 Y Y Y Y Y Y 0YY Checksum (bottom) + * +*/ + +/* + * dumpbin + * + * This is a finite-state-machine that runs through the tape looking for the + * origin and subsequent data fields, stuffs them into the core[] array, and + * optionally disassembles the opcodes. + * + * Every time we hit an origin change, we create a new segment and accumulate + * bytes into it. +*/ + +#define BIN_Initial 1 // initial state; we require an origin to get out of it +#define BIN_Origin 2 // we got the top part of the origin, now need to get the bottom part +#define BIN_DataHW 3 // we have an address, so we are looking for another origin or the top part of the data +#define BIN_DataLW 4 // we have the top part of the data, now fetching the low part + +static int +dumpbin (unsigned char *t, int n) +{ + int tape_checksum; // checksum stored on tape + int calc_checksum; // calculated checksum + int i; + int state; + unsigned short int addr, data; + + if (n < 4) { + fprintf (stderr, "%s: tape %s is too short; tape skipped\n", progname, tapename); + return (0); + } + + tape_checksum = ((t [n - 2] & 0x3f) << 6) + (t [n - 1] & 0x3f); + if (optv > 1) { + printf ("%s: tape %s expected checksum 0%04o\n", progname, tapename, tape_checksum); + } + n -= 2; // tape is now shorter by the two bytes + + // now calculate checksum + calc_checksum = 0; + for (i = 0; i < n; i++) { + calc_checksum += t [i]; + } + calc_checksum &= 07777; // mask to 12 bits + if (optv > 1) { + printf ("%s: tape %s calculated checksum 0%04o\n", progname, tapename, calc_checksum); + } + + if (tape_checksum != calc_checksum) { + fprintf (stderr, "%s: tape %s calculated checksum [0%04o] != stored checksum [0%04o]; tape skipped\n", progname, tapename, calc_checksum, tape_checksum); + return (0); + } + + // now we can dump the binary data via the state machine + state = BIN_Initial; + for (i = 0; i < n; i++) { + if (optv > 2) { + printf ("[%03o] ", t [i]); fflush (stdout); + if ((i % 13) == 12) { + printf ("\n"); + } + } + switch (state) { + case BIN_Initial: + if (t [i] & 0100) { // indicates origin setting code + addr = (t [i] & 0077) << 6; // store top part + state = BIN_Origin; + } + break; + case BIN_Origin: + addr += (t [i] & 0077); // store bottom part + state = BIN_DataHW; + seg_add (addr, 0); + break; + case BIN_DataHW: + if (t [i] & 0100) { // another origin; skip loading data and load address instead + addr = (t [i] & 0077) << 6; + state = BIN_Origin; + } else { + data = (t [i] & 0077) << 6; // store top part of data + state = BIN_DataLW; + } + break; + case BIN_DataLW: + data += (t [i] & 0077); + core [addr] = data; + seg_more (1); + addr++; // the magic of BIN-format is the autoincrement of the address + state = BIN_DataHW; + } + } + if (optv > 2) { + printf ("\n"); + } + return (1); +} + +/* + * writebin + * writerim + * + * These two functions write the BIN and RIM format tapes to a file. + * The filename is constructed by appending ".bin" or ".rim" to the + * input filename. + * + * The header and trailer written are short, LEADER_LENGTH bytes. + * + * The writebin() uses a finit-state-machine to generate the origin. +*/ + +#define LEADER_LENGTH 16 // 16 chars of leader/trailer should be plenty + +#define WBIN_Initial 1 // looking for first/next in-use core[] element +#define WBIN_Writing 2 // origin written, dumping consecutive words + +static void +writebin (void) +{ + char fname [PATH_MAX]; + char leader [LEADER_LENGTH]; + FILE *fp; + int i; + int cksum; + int state; + + // create filename and open it + sprintf (fname, "%s.bin", tapename); + if ((fp = fopen (fname, "w")) == NULL) { + fprintf (stderr, "%s: unable to open BIN output file %s for w, errno %d (%s); creation of output file skipped\n", progname, fname, errno, strerror (errno)); + return; + } + + // write leader + memset (leader, 0x80, sizeof (leader)); + fwrite (leader, 1, sizeof (leader), fp); + + // now scan through "core" and write the data out... + cksum = 0; + state = WBIN_Initial; + for (i = 0; i < CORE_SIZE; i++) { + switch (state) { + case WBIN_Initial: // transit out of WBIN_Initial on a "used" core position + if (core [i] != -1) { + state = WBIN_Writing; + fprintf (fp, "%c%c", 0x40 | ((i & 07700) >> 6), i & 00077); // write origin directive + fprintf (fp, "%c%c", (core [i] & 07700) >> 6, core [i] & 00077); // write data + cksum += (0x40 | ((i & 07700) >> 6)) + (i & 00077) + ((core [i] & 07700) >> 6) + (core [i] & 00077); + } + break; + case WBIN_Writing: + if (core [i] == -1) { + state = WBIN_Initial; // waiting again for a used core position + } else { + fprintf (fp, "%c%c", (core [i] & 07700) >> 6, core [i] & 00077); + cksum += ((core [i] & 07700) >> 6) + (core [i] & 00077); + } + break; + } + } + + // now write the checksum + fprintf (fp, "%c%c", (cksum & 07700) >> 6, cksum & 00077); + + // write trailer + fwrite (leader, 1, sizeof (leader), fp); + + fclose (fp); +} + +static void +writerim (void) +{ + char fname [PATH_MAX]; + char leader [LEADER_LENGTH]; + FILE *fp; + int i; + + // create the filename and open it + sprintf (fname, "%s.rim", tapename); + if ((fp = fopen (fname, "w")) == NULL) { + fprintf (stderr, "%s: unable to open RIM output file %s for w, errno %d (%s); creation of output file skipped\n", progname, fname, errno, strerror (errno)); + return; + } + + // write leader + memset (leader, 0x80, sizeof (leader)); + fwrite (leader, 1, sizeof (leader), fp); + + for (i = 0; i < CORE_SIZE; i++) { + if (core [i] != -1) { + fprintf (fp, "%c%c%c%c", 0x40 + ((i & 07700) >> 6), i & 00077, (core [i] & 07700) >> 6, core [i] & 00077); + } + } + + // write trailer + fwrite (leader, 1, sizeof (leader), fp); + + fclose (fp); +} + +/* + * blank + * + * A utility routine to see if core[] is blank (returns 1). + * Used to avoid writing an empty tape. +*/ + +static int +blank (short int *c, int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (c [i] != -1) { + return (0); + } + } + return (1); +} + +/* + * seg_add (addr, len) + * seg_more (more) + * + * These functions manipulate the segment data stored + * in "segments[]" and "nsegments". + * + * seg_add creates a new segment with the given address + * and length. + * + * seg_more lengthens the current segment by "more" + * words. +*/ + +static void +seg_add (uint16_t addr, int len) +{ + if (optv > 3) { + printf ("seg_add (0%04o, %d (0%04o))\n", addr, len, len); + } + + segments = realloc (segments, (nsegments + 1) * sizeof (segments [0])); + if (segments == NULL) { + fprintf (stderr, "%s: couldn't realloc segments array to be %d elements (%ld bytes) long, errno %d (%s)\n", progname, nsegments + 1, (nsegments + 1) * sizeof (segments [0]), errno, strerror (errno)); + exit (EXIT_FAILURE); + } + segments [nsegments].saddr = addr; + segments [nsegments].nwords = len; + nsegments++; +} + +static void +seg_more (int len) +{ + if (optv > 3) { + printf ("seg_more (+%d (0%04o))\n", len, len); + } + + if (nsegments) { + segments [nsegments - 1].nwords += len; + } else { + fprintf (stderr, "%s: seg_more called with no segments in existence\n", progname); + exit (EXIT_FAILURE); + } +} + ADDED src/d8tape/version.c Index: src/d8tape/version.c ================================================================== --- /dev/null +++ src/d8tape/version.c @@ -0,0 +1,1 @@ +const char *version="0.352"; Index: src/gpio-common.c.in ================================================================== --- src/gpio-common.c.in +++ src/gpio-common.c.in @@ -143,10 +143,18 @@ // modified by -- the PDP8/pidp8i module, but we can't define these // there because we refer to them below, and not all programs that link // to us link to that module as well. For such programs, it's fine if // these two flags stay 0. int swStop = 0, swSingInst = 0; + +// Flag to override ILS mode, forcing fallback to NLS mode. Set when +// the PDP-8 instruction decoding loop detects that we're using the +// ratio form of SET THROTTLE, which prevents the use of ILS due to the +// way instructions are executed in that mode. Defined here rather than +// in gpio-ils.c because we don't want to make code that sets this +// conditional based on whether ILS is in fact actually enabled. +int suppressILS = 0; // Flag set when sim_instr() exits due to some SIMH event like Ctrl-E, // which lets us resume from our imposed "pause" display state. int resumeFromInstructionLoopExit = 0; @@ -154,11 +162,11 @@ //// MEMORY MAPPED GPIO FUNCTIONS ////////////////////////////////////// //// map_peripheral //////////////////////////////////////////////////// // Exposes the physical address defined in the passed structure -int map_peripheral(struct bcm2835_peripheral *p, int exclusive) +static int map_peripheral(struct bcm2835_peripheral *p, int exclusive) { // Name of GPIO memory-mapped device static const char* gpio_mem_dev = "/dev/gpiomem"; if (access(gpio_mem_dev, F_OK) < 0) { @@ -222,11 +230,11 @@ //// bcm_host_get_peripheral_address /////////////////////////////////// // Find Pi's GPIO base address -unsigned bcm_host_get_peripheral_address(void) +static unsigned bcm_host_get_peripheral_address(void) { unsigned address = ~0; FILE *fp = fopen("/proc/device-tree/soc/ranges", "rb"); if (fp) { unsigned char buf[4]; @@ -563,53 +571,17 @@ INP_GPIO (ledrows[row]); } } -//// gpio_thread /////////////////////////////////////////////////////// -// The GPIO thread entry point: initializes GPIO and then calls -// the gpio_core () implementation linked to this program. - -static void *gpio_thread (void *terminate) -{ - // Set thread to real time priority - struct sched_param sp; - sp.sched_priority = 4; // not high, just above the minimum of 1 - int rt = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp) == 0; - - // Tell the user about our configuration, succinctly - const char* pt = pi_type(); - printf( - "PiDP-8/I @VERSION@ [%s] [%cls] [%spcb] [%sgpio]" -#ifdef DEBUG - " [debug]" -#endif - "%s", - pt, - ILS_MODE ? 'i' : 'n', - pt[0] == 'p' ? -#ifdef PCB_SERIAL_MOD_OV - "sermod" : -#elif PCB_SERIAL_MOD_JLW - "altser" : -#else - "std" : -#endif - "no", - (pidp8i_gpio_present ? "" : "no"), - (rt ? " [rt]" : "") - ); - - // It's okay for our caller to resume executing, if it's locked - // waiting for us to finish the initialization bits that can only - // happen in this thread. - pthread_mutex_unlock (&gpio_start_mutex); - - // If we didn't map the GPIO peripheral and get here, it was - // optional, and we've done all we can without it. - if (!pidp8i_gpio_present) return (void*)-1; - +//// init_pidp8i_gpio ////////////////////////////////////////////////// +// Initialize the GPIO pins to the initial states required by +// gpio_thread(). It's a separate exported function so that scanswitch +// can also use it. + +void init_pidp8i_gpio (void) +{ // initialise GPIO (all pins used as inputs, with pull-ups enabled on cols) int i; for (i = 0; i < NLEDROWS; i++) { // Define ledrows as input INP_GPIO(ledrows[i]); GPIO_CLR = 1 << ledrows[i]; // so go to Low when switched to output @@ -657,10 +629,58 @@ usleep(1); GPIO_PULL = 0; // reset GPPUD register usleep(1); GPIO_PULLCLK0 = 0; // remove clock usleep(1); // probably unnecessary +} + + +//// gpio_thread /////////////////////////////////////////////////////// +// The GPIO thread entry point: initializes GPIO and then calls +// the gpio_core () implementation linked to this program. + +static void *gpio_thread (void *terminate) +{ + // Set thread to real time priority + struct sched_param sp; + sp.sched_priority = 4; // not high, just above the minimum of 1 + int rt = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp) == 0; + + // Tell the user about our configuration, succinctly + const char* pt = pi_type(); + printf( + "PiDP-8/I @VERSION@ [%s] [%cls] [%spcb] [%sgpio]" +#ifdef DEBUG + " [debug]" +#endif + "%s", + pt, + ILS_MODE ? 'i' : 'n', + pt[0] == 'p' ? +#ifdef PCB_SERIAL_MOD_OV + "sermod" : +#elif PCB_SERIAL_MOD_JLW + "altser" : +#else + "std" : +#endif + "no", + (pidp8i_gpio_present ? "" : "no"), + (rt ? " [rt]" : "") + ); + + // It's okay for our caller to resume executing, if it's locked + // waiting for us to finish the initialization bits that can only + // happen in this thread. + pthread_mutex_unlock (&gpio_start_mutex); + + // If we didn't map the GPIO peripheral and get here, it was + // optional, and we've done all we can without it. + if (!pidp8i_gpio_present) return (void*)-1; + + // Set GPIO pins to initial states + init_pidp8i_gpio (); // Hand off control to the gpio_core () variant linked to this // program: either the new incandescent lamp simulator or the old // stock version. extern void gpio_core (struct bcm2835_peripheral*, int* terminate); @@ -671,19 +691,16 @@ gss_initted = 0; return 0; } -//// start/stop_pidp8i_gpio_thread ///////////////////////////////////// -// Start and stop gpio_thread(). We export these functions rather than -// export gpio_thread() directly so this module's users don't have to -// know anything about pthreads and such. - -int start_pidp8i_gpio_thread (const char* must_map) -{ - char errs[100]; - +//// map_gpio_for_pidp8i /////////////////////////////////////////////// +// Wrapper around map_peripheral() taking care of some higher level +// details. This is the interface we expose to outsiders. + +int map_gpio_for_pidp8i (int must_map) +{ // Find GPIO address (it varies by Pi model) unsigned int gpio_base_addr = bcm_host_get_peripheral_address(); gpio.addr_p = gpio_base_addr + 0x200000; // Attempt to map the GPIO peripheral for our exclusive use. Some @@ -696,10 +713,27 @@ // at configure time, so disable the hysteresis on the GPIO inputs. map_peripheral (&gpio2, 0); // assume success since prior mapping worked gpio2.addr_p = gpio_base_addr + 0x100000; gpio2.addr[0x0B] = (gpio2.addr[0x0B] & 0xF7) | (0x5A << 24); #endif + + return 0; +} + + +//// start/stop_pidp8i_gpio_thread ///////////////////////////////////// +// Start and stop gpio_thread(). We export these functions rather than +// export gpio_thread() directly so this module's users don't have to +// know anything about pthreads and such. + +int start_pidp8i_gpio_thread (const char* must_map) +{ + char errs[100]; + + if (map_gpio_for_pidp8i (must_map != 0) != 0) { + return EFAULT; + } // Until gpio_core () reads the switches for the first time, we need // to mark them as all-open, lest we have a race condition with the // simulator where it interprets the all-0 initial switchstatus[] // value as "all switches closed," which includes the shutdown seq! Index: src/gpio-common.h ================================================================== --- src/gpio-common.h +++ src/gpio-common.h @@ -121,10 +121,15 @@ // Compatibility interface for programs like src/test.c that depend on // being able to modify the LED values directly. #define ledstatus (pdis_update->curr) extern int pidp8i_simple_gpio_mode; +// Simplified interface for single-threaded programs that don't use our +// GPIO thread, but do want to share some our implementation. +extern void init_pidp8i_gpio (void); +extern int map_gpio_for_pidp8i (int must_map); + extern uint16_t switchstatus[]; extern uint8_t cols[]; extern uint8_t ledrows[]; extern uint8_t rows[]; extern uint8_t pidp8i_gpio_present; @@ -132,12 +137,14 @@ extern int start_pidp8i_gpio_thread (const char* must_map); extern void stop_pidp8i_gpio_thread (); extern void turn_off_pidp8i_leds (); extern void update_led_states (const us_time_t delay); -extern unsigned bcm_host_get_peripheral_address(void); -extern int map_peripheral(struct bcm2835_peripheral *p, int exclusive); +// Alternative to start_pidp8i_gpio_thread() for programs that run +// single-threaded and do their own GPIO scanning, like scanswitch. +extern void init_pidp8i_gpio (void); + extern void unmap_peripheral(struct bcm2835_peripheral *p); extern void read_switches (ns_time_t delay); extern void swap_displays (); Index: src/gpio-ils.c ================================================================== --- src/gpio-ils.c +++ src/gpio-ils.c @@ -140,15 +140,18 @@ } } } // Light up LEDs - extern int swStop, swSingInst; - if (swStop || swSingInst) { - // The CPU is in STOP mode, so show the current LED states - // full-brightness using the same mechanism NLS uses. + extern int swStop, swSingInst, suppressILS; + if (swStop || swSingInst || suppressILS) { + // The CPU is in STOP mode or someone has suppressed the ILS, + // so show the current LED states full-brightness using the + // same mechanism NLS uses. Force a display swap if the next + // loop iteration won't do it in case this isn't STOP mode. update_led_states (intervl * 60); + if (step != (MAX_BRIGHTNESS - 1)) swap_displays(); } else { // Normal case: PWM display using the on-count values for (size_t row = 0; row < NLEDROWS; ++row) { // Output 0 (CLR) for LEDs in this row which should be on ADDED src/palbart/LICENSE.md Index: src/palbart/LICENSE.md ================================================================== --- /dev/null +++ src/palbart/LICENSE.md @@ -0,0 +1,16 @@ +# palbart License + +The following was extracted from the top of [`palbart.c`][1] in this +directory: + +--------- + +This is free software. There is no fee for using it. You may make any +changes that you wish and also give it away. If you can make commercial +product out of it, fine, but do not put any limits on the purchaser's +right to do the same. If you improve it or fix any bugs, it would be +nice if you told me and offered me a copy of the new version. + +--------- + +[1]: https://tangentsoft.com/pidp8i/doc/trunk/palbart/palbart.c ADDED src/palbart/palbart.1 Index: src/palbart/palbart.1 ================================================================== --- /dev/null +++ src/palbart/palbart.1 @@ -0,0 +1,200 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH PALBART 1 "January 16, 2000" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +palbart \- BART enhanced PDP8 crossassembler +.SH SYNOPSIS +.B palbart +.RI [options] inputfile +.br +.SH DESCRIPTION +This manual page documents briefly the +.B palbart +command. +It is a cross-assembler to for PDP/8 assembly language programs. +It will produce an output file in bin format, rim format, and using the +appropriate pseudo-ops, a combination of rim and bin formats. +A listing file is always produced and with an optional symbol table +and/or a symbol cross-reference (concordance). The permanent symbol +table can be output in a form that may be read back in so a customized +permanent symbol table can be produced. Any detected errors are output +to a separate file giving the filename in which they were detected +along with the line number, column number and error message as well as +marking the error in the listing file. +.PP +The following file name extensions are used: +.PP + .pal source code (input) +.PP + .lst assembly listing (output) +.PP + .bin assembly output in DEC's bin format (output) +.PP + .rim assembly output in DEC's rim format (output) +.PP + .err assembly errors detected (if any) (output) +.PP + .prm permanent symbol table in form suitable for reading after the EXPUNGE pseudo-op. + +.PP +.SH OPTIONS +A summary of options is included below. +.TP +.B \-d +Show symbol table at end of assembly +.TP +.B \-h +Display help. +.TP +.B \-l +Allow generation of literals (default is no literal generation) +Show version of program. +.TP +.B \-p +Generate a file with the permanent symbols in it. +(To get the current symbol table, assemble a file than has only +a $ in it.) +.TP +.B \-r +Produce output in rim format (default is bin format) +.TP +.B \-v +Display version information. +.TP +.B \-x +Generate a cross-reference (concordance) of user symbols. + +.SH DIAGNOSTICS +Assembler error diagnostics are output to an error file and inserted +in the listing file. Each line in the error file has the form +.PP +(:) : error: at Loc = +.PP +An example error message is: +.br +bintst.pal(17:9) : error: undefined symbol "UNDEF" at Loc = 07616 +.PP +The error diagnostics put in the listing start with a two character +error code (if appropriate) and a short message. A carat '^' is +placed under the item in error if appropriate. +An example error message is: +.PP + 17 07616 3000 DCA UNDEF +.br + UD undefined ^ +.br + 18 07617 1777 TAD I DUMMY +.PP +When an indirect is generated, an at character '@' is placed after the +the instruction value in the listing as an indicator as follows: +.PP + 14 03716 1777@ TAD OFFPAG +.PP +Undefined symbols are marked in the symbol table listing by prepending +a '?' to the symbol. Redefined symbols are marked in the symbol table +listing by prepending a '#' to the symbol. Examples are: +.PP + #REDEF 04567 +.br + SWITCH 07612 +.br + ?UNDEF 00000 +.PP +Refer to the code for the diagnostic messages generated. + +.SH BUGS +Only a minimal effort has been made to keep the listing format +anything like the PAL-8 listing format. +The operation of the conditional assembly pseudo-ops may not function +exactly as the DEC versions. I did not have any examples of these so +the implementation is my interpretation of how they should work. +.PP +The RIMPUNch and BINPUNch pseudo-ops do not change the binary output +file type that was specified on startup. This was intentional and +and allows rim formatted data to be output prior to the actual binary +formatted data. On UN*X style systems, the same effect can be achieved +ing the "cat" command, but on DOS/Windows systems, doing this was +a major chore. +.PP +The floating point input does not generate values exactly as the DEC +compiler does. I worked out several examples by hand and believe that +this implementation is slightly more accurate. If I am mistaken, +let me know and, if possible, a better method of generating the values. +.br + +.SH HISTORICAL NOTE +This assembler was written to support the fleet of PDP-8 systems +used by the Bay Area Rapid Transit System. As of early 1997, +this includes about 40 PDP-8/E systems driving the train destination +signs in passenger stations. + +.SH REFERENCES +This assembler is based on the pal assember by: +.br +Douglas Jones and +.br +Rich Coon + +.SH DISCLAIMER +See the symbol table for the set of pseudo-ops supported. +.PP +See the code for pseudo-ops that are not standard for PDP/8 assembly. +.PP +Refer to DEC's "Programming Languages (for the PDP/8)" for complete +documentation of pseudo-ops. +.PP +Refer to DEC's "Introduction to Programming (for the PDP/8)" or a +lower level introduction to the assembly language. + +.SH WARRANTY +If you don't like it the way it works or if it doesn't work, that's +tough. You're welcome to fix it yourself. That's what you get for +using free software. + +.SH COPYRIGHT NOTICE +This is free software. There is no fee for using it. You may make +any changes that you wish and also give it away. If you can make +a commercial product out of it, fine, but do not put any limits on +the purchaser's right to do the same. If you improve it or fix any +bugs, it would be nice if you told me and offered me a copy of the +new version. +Gary Messenbrink + +.SH VERSIONS + Version Date by Comments +.br + v1.0 12Apr96 GAM Original +.br + v1.1 18Nov96 GAM Permanent symbol table initialization error. +.br + v1.2 20Nov96 GAM Added BINPUNch and RIMPUNch pseudo-operators. +.br + v1.3 24Nov96 GAM Added DUBL pseudo-op (24 bit integer constants). +.br + v1.4 29Nov96 GAM Fixed bug in checksum generation. +.br + v2.1 08Dec96 GAM Added concordance processing (cross reference). +.br + v2.2 10Dec96 GAM Added FLTG psuedo-op (floating point constants). +.br + v2.3 2Feb97 GAM Fixed paging problem in cross reference output. +.br + v2.4 11Apr97 GAM Fixed problem with some labels being put in cross reference multiple times. + +.SH AUTHOR +This manual page was written by Vince Mulhollon , +for the Debian GNU/Linux system (but may be used by others). ADDED src/palbart/palbart.c Index: src/palbart/palbart.c ================================================================== --- /dev/null +++ src/palbart/palbart.c @@ -0,0 +1,4352 @@ +/******************************************************************************/ +/* */ +/* Program: PAL (BART version) */ +/* File: pal.c */ +/* Author: Gary A. Messenbrink */ +/* gam@rahul.net */ +/* */ +/* Purpose: A 2 pass PDP-8 pal-like assembler. */ +/* */ +/* PAL(1) */ +/* */ +/* NAME */ +/* pal - a PDP/8 pal-like assembler. */ +/* */ +/* SYNOPSIS: */ +/* pal [ -$ -d -h -e -l -p -r -t -v -x ] inputfile */ +/* */ +/* DESCRIPTION */ +/* This is a cross-assembler to for PDP/8 assembly language programs. */ +/* It will produce an output file in bin format, rim format, and using the */ +/* appropriate pseudo-ops, a combination of rim and bin formats. */ +/* A listing file is always produced and with an optional symbol table */ +/* and/or a symbol cross-reference (concordance). The permanent symbol */ +/* table can be output in a form that may be read back in so a customized */ +/* permanent symbol table can be produced. Any detected errors are output */ +/* to a separate file giving the filename in which they were detected */ +/* along with the line number, column number and error message as well as */ +/* marking the error in the listing file. */ +/* The following file name extensions are used: */ +/* .pal source code (input) */ +/* .lst assembly listing (output) */ +/* .bin assembly output in DEC's bin format (output) */ +/* .rim assembly output in DEC's rim format (output) */ +/* .err assembly errors detected (if any) (output) */ +/* .prm permanent symbol table in form suitable for reading after */ +/* the EXPUNGE pseudo-op. */ +/* */ +/* OPTIONS */ +/* -$ Allow files to not end with $ */ +/* -d Dump the symbol table at end of assembly */ +/* -h Show help */ +/* -e Don't allow generation of links */ +/* -l Allow generation of links (default is link generation) */ +/* -n No redefinition of permanent symbols with labels */ +/* -p Generate a file with the permanent symbols in it. */ +/* (To get the current symbol table, assemble a file than has only */ +/* a $ in it.) */ +/* -r Produce output in rim format (default is bin format) */ +/* -tN Set tab stops to N spaces (default is 8) */ +/* -v Display program version. */ +/* -x Generate a cross-reference (concordance) of user symbols. */ +/* */ +/* DIAGNOSTICS */ +/* Assembler error diagnostics are output to an error file and inserted */ +/* in the listing file. Each line in the error file has the form */ +/* */ +/* (:) : error: at Loc = */ +/* */ +/* An example error message is: */ +/* */ +/* bintst.pal(17:9) : error: undefined symbol "UNDEF" at Loc = 07616 */ +/* */ +/* The error diagnostics put in the listing start with a two character */ +/* error code (if appropriate) and a short message. A carat '^' is */ +/* placed under the item in error if appropriate. */ +/* An example error message is: */ +/* */ +/* 17 07616 3000 DCA UNDEF */ +/* UD undefined ^ */ +/* 18 07617 1777 TAD I DUMMY */ +/* */ +/* When an indirect is generated, an at character '@' is placed after the */ +/* the instruction value in the listing as an indicator as follows: */ +/* */ +/* 14 03716 1777@ TAD OFFPAG */ +/* */ +/* Undefined symbols are marked in the symbol table listing by prepending */ +/* a '?' to the symbol. Redefined symbols are marked in the symbol table */ +/* listing by prepending a '#' to the symbol. Examples are: */ +/* */ +/* #REDEF 04567 */ +/* SWITCH 07612 */ +/* ?UNDEF 00000 */ +/* */ +/* Refer to the code for the diagnostic messages generated. */ +/* */ +/* BUGS */ +/* This program will accept source that real PAL will not. To ensure */ +/* valid source assemble on real or simulated PDP-8. */ +/* Different PAL versions have different permanent symbols defined. This */ +/* program define more than and PAL version. By default redefining them */ +/* as a label is not an error. It is for normal PAL. The -n flag will */ +/* make redefining an error. */ +/* */ +/* Only a minimal effort has been made to keep the listing format */ +/* anything like the PAL-8 listing format. */ +/* The operation of the conditional assembly pseudo-ops may not function */ +/* exactly as the DEC versions. I did not have any examples of these so */ +/* the implementation is my interpretation of how they should work. */ +/* */ +/* The RIMPUNch and BINPUNch pseudo-ops do not change the binary output */ +/* file type that was specified on startup. This was intentional and */ +/* and allows rim formatted data to be output prior to the actual binary */ +/* formatted data. On UN*X style systems, the same effect can be achieved */ +/* by using the "cat" command, but on DOS/Windows systems, doing this was */ +/* a major chore. */ +/* */ +/* The floating point input does not generate values exactly as the DEC */ +/* compiler does. I worked out several examples by hand and believe that */ +/* this implementation is slightly more accurate. If I am mistaken, */ +/* let me know and, if possible, a better method of generating the values. */ +/* */ +/* CDF .-. */ +/* Generates 2201 when assembled at 5000. This looks like a bug in OS/8 */ +/* PAL */ +/* */ +/* BUILD and INSTALLATION */ +/* The current version has only been built under Linux. */ +/* Earlier versions have been built and successfully executed on: */ +/* a. Linux (80486 CPU)using gcc */ +/* b. RS/6000 (AIX 3.2.5) */ +/* c. Borland C++ version 3.1 (large memory model) */ +/* d. Borland C++ version 4.52 (large memory model) */ +/* with no modifications to the source code. */ +/* */ +/* On UNIX type systems, store the the program as the pal command */ +/* and on PC type systems, store it as pal.exe */ +/* */ +/* HISTORICAL NOTE: */ +/* This assembler was written to support the fleet of PDP-8 systems */ +/* used by the Bay Area Rapid Transit System. As of early 1997, */ +/* this includes about 40 PDP-8/E systems driving the train destination */ +/* signs in passenger stations. */ +/* */ +/* REFERENCES: */ +/* This assembler is based on the pal assembler by: */ +/* Douglas Jones and */ +/* Rich Coon */ +/* */ +/* DISCLAIMER: */ +/* See the symbol table for the set of pseudo-ops supported. */ +/* See the code for pseudo-ops that are not standard for PDP/8 assembly. */ +/* Refer to DEC's "Programming Languages (for the PDP/8)" for complete */ +/* documentation of pseudo-ops. */ +/* Refer to DEC's "Introduction to Programming (for the PDP/8)" or a */ +/* lower level introduction to the assembly language. */ +/* */ +/* WARRANTY: */ +/* If you don't like it the way it works or if it doesn't work, that's */ +/* tough. You're welcome to fix it yourself. That's what you get for */ +/* using free software. */ +/* */ +/* COPYRIGHT NOTICE: */ +/* This is free software. There is no fee for using it. You may make */ +/* any changes that you wish and also give it away. If you can make */ +/* a commercial product out of it, fine, but do not put any limits on */ +/* the purchaser's right to do the same. If you improve it or fix any */ +/* bugs, it would be nice if you told me and offered me a copy of the */ +/* new version. */ +/* */ +/* */ +/* Amendments Record: */ +/* Version Date by Comments */ +/* ------- ------- --- --------------------------------------------------- */ +/* v1.0 12Apr96 GAM Original */ +/* v1.1 18Nov96 GAM Permanent symbol table initialization error. */ +/* v1.2 20Nov96 GAM Added BINPUNch and RIMPUNch pseudo-operators. */ +/* v1.3 24Nov96 GAM Added DUBL pseudo-op (24 bit integer constants). */ +/* v1.4 29Nov96 GAM Fixed bug in checksum generation. */ +/* v2.1 08Dec96 GAM Added concordance processing (cross reference). */ +/* v2.2 10Dec96 GAM Added FLTG psuedo-op (floating point constants). */ +/* v2.3 2Feb97 GAM Fixed paging problem in cross reference output. */ +/* DJG: I started with the 2.5 RK version but found when looking on the net */ +/* later that multiple diverging version existed. I have tried to combine */ +/* the fixed into one version. I took the version info below from the versions*/ +/* I pulled from. */ +/* http://dustyoldcomputers.com/pdp-common/reference/host/index.html */ +/* http://www.dunnington.u-net.com/public/PDP-8/palbart.c */ +/* http://sourcecodebrowser.com/palbart/2.4/palbart-2_84_8c_source.html */ +/* http://packages.qa.debian.org/p/palbart.html */ +/* v2.4 11Apr97 GAM Fixed problem with some labels being put in cross */ +/* reference multiple times. */ +/* Started with RK version, Attempted to merge */ +/* GAM V2.4 and PNT change DJG */ +/* v2.4 29Oct07 RK Added 4 character tabstop; IOTs for TA8/E. */ +/* v2.4 19Jan03 PNT Added ASCII pseudo-op, like TEXT but not packed. */ +/* v2.5 03Nov07 RK Fixed buffer overflow problem in readLine and */ +/* increased symbol table size */ +/* v2.6 14Jul03 PNT Added missing TTY symbols, and "1st TTY" symbols. */ +/* v2.7 14Jun13 DJG David Gesswein djg@pdp8online.com */ +/* Merged other changes found online giving duplicate */ +/* Versions in the history */ +/* Didn't copy over deleting -l literal flag */ +/* All fixes to make it match OS/8 PAL8 better */ +/* Fixed handling of IFDEF type conditionals */ +/* Fixed excessive redefined symbol errors */ +/* PAL8 uses 12 bit symbols and this program label */ +/* symbols are 15 bit. */ +/* Added FILENAME and DEVNAME psuedo ops */ +/* Added OPR and KCF instructions. Fixed RMF */ +/* Allowed space after = */ +/* Prevented I and D from being deleted by EXPUNGE */ +/* Allowed permanent symbols to be redefined with error*/ +/* PAL8 updates without message. Error is just warning*/ +/* Fixed certain cases of memory reference generation */ +/* Allowed unary + */ +/* Fixed " character literal at end of line */ +/* Fixed errors in reloc handling */ +/* Fixed [CDF CIF type expressions */ +/* Made title default to first line */ +/* Fixed checksum when nopunch used */ +/* Fixed FIXTAB */ +/* Probably added more subtle bugs */ +/* v2.8 15Jun13 DJG Merged versions found on net. See above */ +/* Added * to RELOC addresses in listing */ +/* Changed default to literal/links on. Added -e to */ +/* turn off */ +/* Fixed PAGE when RELOC used */ +/* Changed SPF to TFL and SPI to TSK */ +/* Make error when changing permanent symbol to label */ +/* if -e flag is used */ +/* Allow space oring in IFZERO etc */ +/* Fixed handling of page zero overflow */ +/* v2.9 23Jun13 DJG Fixed properly all pages literal handling */ +/* changing page doesn't cause loss of last literal */ +/* location used. */ +/* Fixed bin generation if no origin set */ +/* v2.9a 01Jul13 DJG Fixed Comment. Binaries not updated */ +/* v2.10 08Feb14 DJG Changed trailer to 8 bytes since pip didn't like */ +/* trailer of one 0x80 */ +/* v2.11 19Apr15 DPI Fixed incorrect link generation with impled 0200 */ +/* starting address. Patch from Doug Ingrams */ +/* v2.12 28Apr15 DJG Fixed incorrect handling of reloc, expressions with */ +/* undefined symbols. Fixed conditional assembly with */ +/* undefined symbols. Added new flag to allow file to */ +/* not end with $ */ +/* v2.13 02May15 DPI Fixed bug in readLine when removing \r from a blank */ +/* line. Changed -s to -$ in -h display. Corrected */ +/* version comment. */ +/* v2.13 03May15 DJG Moved TITLE, BANK to new additional option. */ +/* Change release variable below when you update. Send changes back to */ +/* David Gesswein, djg@pdp8online.com. */ +/******************************************************************************/ + +#include +#include +#include +#include +#include + +char *release = "pal-2.13, 03 May 2015"; + +/* Set to 1 and use -e flag to make ( and [ literals errors */ +#define LITERAL_ERROR 0 + +#define LINELEN 132 +#define LIST_LINES_PER_PAGE 55 /* Includes 5 line page header. */ +#define NAMELEN 128 +#define SYMBOL_COLUMNS 5 +#define SYMLEN 7 +#define SYMBOL_TABLE_SIZE 4096 +#define TITLELEN 63 +#define XREF_COLUMNS 8 + +#define ADDRESS_FIELD 00177 +#define FIELD_FIELD 070000 +#define INDIRECT_BIT 00400 +#define LAST_PAGE_LOC 00177 +#define OP_CODE 07000 +#define PAGE_BIT 00200 + +#ifdef PAGE_SIZE +#undef PAGE_SIZE +#endif +#define PAGE_SIZE 00200 + +#define PAGE_FIELD 07600 +#define PAGE_ZERO_END 00200 + +/* Macro to get the number of elements in an array. */ +#define DIM(a) (sizeof(a)/sizeof(a[0])) + +/* Macro to get the address plus one of the end of an array. */ +#define BEYOND(a) ((a) + DIM(A)) + +#define is_blank(c) ((c==' ') || (c=='\t') || (c=='\f') || (c=='>')) +#define isend(c) ((c=='\0')|| (c=='\n')) +#define isdone(c) ((c=='/') || (isend(c)) || (c==';')) + +/* Macros for testing symbol attributes. Each macro evaluates to non-zero */ +/* (true) if the stated condition is met. */ +/* Use these to test attributes. The proper bits are extracted and then */ +/* tested. */ +#define M_CONDITIONAL(s) ((s & CONDITION) == CONDITION) +#define M_DEFINED(s) ((s & DEFINED) == DEFINED) +#define M_DUPLICATE(s) ((s & DUPLICATE) == DUPLICATE) +#define M_FIXED(s) ((s & FIXED) == FIXED) +#define M_LABEL(s) ((s & LABEL) == LABEL) +#define M_MRI(s) ((s & MRI) == MRI) +#define M_MRIFIX(s) ((s & MRIFIX) == MRIFIX) +#define M_PSEUDO(s) ((s & PSEUDO) == PSEUDO) +#define M_REDEFINED(s) ((s & REDEFINED) == REDEFINED) +#define M_UNDEFINED(s) (!M_DEFINED(s)) +#define M_PERM_REDEFINED(s) ((s & PERM_REDEFINED) == PERM_REDEFINED) + +/* This macro is used to test symbols by the conditional assembly pseudo-ops. */ +#define M_DEF(s) (M_DEFINED(s)) +#define M_COND(s) (M_CONDITIONAL(s)) +#define M_DEFINED_CONDITIONALLY(t) (M_DEF(t) && ((pass==1) ||!M_COND(t))) + +typedef unsigned char BOOL; +typedef unsigned char BYTE; +typedef short int WORD16; +typedef long int WORD32; + +#ifndef FALSE + #define FALSE 0 + #define TRUE (!FALSE) +#endif + +/* Line listing styles. Used to control listing of lines. */ +enum linestyle_t +{ + LINE, LINE_VAL, LINE_LOC_VAL, LOC_VAL +}; +typedef enum linestyle_t LINESTYLE_T; + +/* Symbol Types. */ +/* Note that the names that have FIX as the suffix contain the FIXED bit */ +/* included in the value. */ +/* */ +/* The CONDITION bit is used when processing the conditional assembly PSEUDO- */ +/* OPs (e.g., IFDEF). During pass 1 of the assembly, the symbol is either */ +/* defined or undefined. The condition bit is set when the symbol is defined */ +/* during pass 1 and reset on pass 2 at the location the symbol was defined */ +/* during pass 1. When processing conditionals during pass 2, if the symbol */ +/* is defined and the condition bit is set, the symbol is treated as if it */ +/* were undefined. This gives consistent behavior of the conditional */ +/* pseudo-ops during both pass 1 and pass 2. */ +enum symtyp +{ + UNDEFINED = 0000, + DEFINED = 0001, + FIXED = 0002, + MRI = 0004 | DEFINED, + LABEL = 0010 | DEFINED, + REDEFINED = 0020 | DEFINED, + DUPLICATE = 0040 | DEFINED, + PSEUDO = 0100 | FIXED | DEFINED, + CONDITION = 0200 | DEFINED, + PERM_REDEFINED = 0400, + MRIFIX = MRI | FIXED | DEFINED, + DEFFIX = DEFINED | FIXED +}; +typedef enum symtyp SYMTYP; + +enum pseudo_t +{ + BANK, BINPUNCH, DECIMAL, DUBL, EJECT, ENPUNCH, EXPUNGE, FIELD, + FIXMRI, FIXTAB, FLTG, IFDEF, IFNDEF, IFNZERO, IFZERO, NOPUNCH, + OCTAL, PAGE, PAUSE, RELOC, RIMPUNCH, SEGMNT, TEXT, TITLE, + XLIST, ZBLOCK, FILENAME, DEVICE, ASCII +}; +typedef enum pseudo_t PSEUDO_T; + +struct sym_t +{ + SYMTYP type; + char name[SYMLEN]; + WORD16 val; + int xref_index; + int xref_count; +}; +typedef struct sym_t SYM_T; + +struct lpool_t +{ + WORD16 loc; + WORD16 last_punched; + WORD16 pool[PAGE_SIZE]; +}; +typedef struct lpool_t LPOOL_T; + +struct emsg_t +{ + char *list; + char *file; +}; +typedef struct emsg_t EMSG_T; + +struct errsave_t +{ + char *mesg; + int col; +}; +typedef struct errsave_t ERRSAVE_T; + +struct fltg_ +{ + WORD16 exponent; + WORD32 mantissa; +}; +typedef struct fltg_ FLTG_T; + +/*----------------------------------------------------------------------------*/ + +/* Function Prototypes */ + +int binarySearch( char *name, int start, int symbol_count ); +int compareSymbols( const void *a, const void *b ); +void conditionFalse( void ); +void conditionTrue( void ); +SYM_T *defineLexeme( int start, int term, WORD16 val, SYMTYP type ); +SYM_T *defineSymbol( char *name, WORD16 val, SYMTYP type, WORD16 start); +void endOfBinary( void ); +void errorLexeme( EMSG_T *mesg, int col ); +void errorMessage( EMSG_T *mesg, int col ); +void errorSymbol( EMSG_T *mesg, char *name, int col ); +SYM_T *eval( void ); +WORD32 evalDubl( WORD32 initial_value ); +FLTG_T *evalFltg( void ); +SYM_T *evalSymbol( void ); +void getArgs( int argc, char *argv[] ); +WORD32 getDublExpr( void ); +WORD32 getDublExprs( void ); +FLTG_T *getFltgExpr( void ); +FLTG_T *getFltgExprs( void ); +SYM_T *getExpr( void ); +WORD16 getExprs( void ); +WORD16 incrementClc( void ); +void inputDubl( void ); +void inputFltg( void ); +WORD16 insertLiteral( LPOOL_T *pool, WORD16 value, int fieldpage_index ); +char *lexemeToName( char *name, int from, int term ); +void listLine( void ); +SYM_T *lookup( char *name ); +void moveToEndOfLine( void ); +void nextLexBlank( void ); +void nextLexeme( void ); +void normalizeFltg( FLTG_T *fltg ); +void onePass( void ); +void printCrossReference( void ); +void printErrorMessages( void ); +void printLine(char *line, WORD16 loc, WORD16 val, LINESTYLE_T linestyle); +void printPageBreak( void ); +void printPermanentSymbolTable( void ); +void printSymbolTable( void ); +BOOL pseudoOperators( PSEUDO_T val ); +void punchChecksum( void ); +void punchLocObject( WORD16 loc, WORD16 val ); +void punchLiteralPool( LPOOL_T *p, BOOL punch_page0 ); +void punchOutObject( WORD16 loc, WORD16 val ); +void punchLeader( int count ); +void punchObject( WORD16 val ); +void punchOrigin( WORD16 loc ); +void readLine( void ); +void saveError( char *mesg, int cc ); +BOOL testForLiteralCollision( WORD16 loc ); +void topOfForm( char *title, char *sub_title ); + +/*----------------------------------------------------------------------------*/ + +/* Table of pseudo-ops (directives) which are used to setup the symbol */ +/* table on startup and when the EXPUNGE pseudo-op is executed. */ +SYM_T pseudo[] = +{ + { PSEUDO, "ASCII", ASCII }, /* Put 8-bit ASCII into memory (see TEXT) */ + { PSEUDO, "BINPUN", BINPUNCH }, /* Output in Binary Loader format. */ + { PSEUDO, "DECIMA", DECIMAL }, /* Read literal constants in base 10. */ + { PSEUDO, "DEVICE", DEVICE }, /* Pack 6 bit device name into memory */ + { PSEUDO, "DUBL", DUBL }, /* Ignored (unsupported). */ + { PSEUDO, "EJECT", EJECT }, /* Eject a page in the listing. */ + { PSEUDO, "ENPUNC", ENPUNCH }, /* Turn on object code generation. */ + { PSEUDO, "EXPUNG", EXPUNGE }, /* Remove all symbols from symbol table. */ + { PSEUDO, "FIELD", FIELD }, /* Set origin to memory field. */ + { PSEUDO, "FILENA", FILENAME }, /* Pack 6 bit filename into memory. */ + { PSEUDO, "FIXMRI", FIXMRI }, /* Like =, but creates mem ref instruction*/ + { PSEUDO, "FIXTAB", FIXTAB }, /* Mark current symbols as permanent. */ + { PSEUDO, "FLTG", FLTG }, /* Ignored (unsupported). */ + { PSEUDO, "IFDEF", IFDEF }, /* Assemble if symbol is defined. */ + { PSEUDO, "IFNDEF", IFNDEF }, /* Assemble if symbol is not defined. */ + { PSEUDO, "IFNZER", IFNZERO }, /* Assemble if symbol value is not 0. */ + { PSEUDO, "IFNZRO", IFNZERO }, /* Assemble if symbol value is not 0. */ + { PSEUDO, "IFZERO", IFZERO }, /* Assemble if symbol value is 0. */ + { PSEUDO, "NOPUNC", NOPUNCH }, /* Turn off object code generation. */ + { PSEUDO, "OCTAL", OCTAL }, /* Read literal constants in base 8. */ + { PSEUDO, "PAGE", PAGE }, /* Set orign to page +1 or page n (0..37).*/ + { PSEUDO, "PAUSE", PAUSE }, /* Ignored */ + { PSEUDO, "RELOC", RELOC }, /* Assemble to run at a different address.*/ + { PSEUDO, "RIMPUN", RIMPUNCH }, /* Output in Read In Mode format. */ + { PSEUDO, "SEGMNT", SEGMNT }, /* Like page, but with page size=1K words.*/ + { PSEUDO, "TEXT", TEXT }, /* Pack 6 bit trimmed ASCII into memory. */ + { PSEUDO, "XLIST", XLIST }, /* Toggle listing generation. */ + { PSEUDO, "ZBLOCK", ZBLOCK }, /* Zero a block of memory. */ + { PSEUDO, "TITLE", TITLE }, /* Use the text string as a listing title.*/ + { PSEUDO, "BANK", BANK } /* Like field, select some 32K out of 128K*/ +}; +/* Number o extended pseudo operators to ignore unless command option specified + * to enable */ +#define NUMBER_ADDITIONAL_PSEUDO 2 + +/* Symbol Table */ +/* The table is put in lexical order on startup, so symbols can be */ +/* inserted as desired into the initial table. */ +/* really_permanent_symbols aren't removed by EXPUNGE */ +SYM_T really_permanent_symbols[] = +{ + { MRIFIX, "I", 00400 }, /* INDIRECT ADDRESSING */ + { MRIFIX, "Z", 00000 } /* PAGE ZERO ADDRESS */ +}; + +SYM_T permanent_symbols[] = +{ + /* Memory Reference Instructions */ + { MRIFIX, "AND", 00000 }, /* LOGICAL AND */ + { MRIFIX, "TAD", 01000 }, /* TWO'S COMPLEMENT ADD */ + { MRIFIX, "ISZ", 02000 }, /* INCREMENT AND SKIP IF ZERO */ + { MRIFIX, "DCA", 03000 }, /* DEPOSIT AND CLEAR ACC */ + { MRIFIX, "JMP", 05000 }, /* JUMP */ + { MRIFIX, "JMS", 04000 }, /* JUMP TO SUBROUTINE */ + /* Floating Point Interpreter Instructions */ + { MRIFIX, "FEXT", 00000 }, /* FLOATING EXIT */ + { MRIFIX, "FADD", 01000 }, /* FLOATING ADD */ + { MRIFIX, "FSUB", 02000 }, /* FLOATING SUBTRACT */ + { MRIFIX, "FMPY", 03000 }, /* FLOATING MULTIPLY */ + { MRIFIX, "FDIV", 04000 }, /* FLOATING DIVIDE */ + { MRIFIX, "FGET", 05000 }, /* FLOATING GET */ + { MRIFIX, "FPUT", 06000 }, /* FLOATING PUT */ + { FIXED, "FNOR", 07000 }, /* FLOATING NORMALIZE */ + { FIXED, "FEXT", 00000 }, /* EXIT FROM FLOATING POINT INTERPRETER */ + { FIXED, "SQUARE", 00001 }, /* SQUARE C(FAC) */ + { FIXED, "SQROOT", 00002 }, /* TAKE SQUARE ROOT OF C(FAC) */ + /* Group 1 Operate Microinstrcutions */ + { FIXED, "OPR", 07000 }, /* NO OPERATION */ + { FIXED, "NOP", 07000 }, /* NO OPERATION */ + { FIXED, "IAC", 07001 }, /* INCREMENT AC */ + { FIXED, "RAL", 07004 }, /* ROTATE AC AND LINK LEFT ONE */ + { FIXED, "RTL", 07006 }, /* ROTATE AC AND LINK LEFT TWO */ + { FIXED, "RAR", 07010 }, /* ROTATE AC AND LINK RIGHT ONE */ + { FIXED, "RTR", 07012 }, /* ROTATE AC AND LINK RIGHT TWO */ + { FIXED, "CML", 07020 }, /* COMPLEMENT LINK */ + { FIXED, "CMA", 07040 }, /* COMPLEMEMNT AC */ + { FIXED, "CLL", 07100 }, /* CLEAR LINK */ + { FIXED, "CLA", 07200 }, /* CLEAR AC */ + /* Group 2 Operate Microinstructions */ + { FIXED, "BSW", 07002 }, /* Swap bytes in AC (PDP/8e) */ + { FIXED, "HLT", 07402 }, /* HALT THE COMPUTER */ + { FIXED, "OSR", 07404 }, /* INCLUSIVE OR SR WITH AC */ + { FIXED, "SKP", 07410 }, /* SKIP UNCONDITIONALLY */ + { FIXED, "SNL", 07420 }, /* SKIP ON NON-ZERO LINK */ + { FIXED, "SZL", 07430 }, /* SKIP ON ZERO LINK */ + { FIXED, "SZA", 07440 }, /* SKIP ON ZERO AC */ + { FIXED, "SNA", 07450 }, /* SKIP ON NON=ZERO AC */ + { FIXED, "SMA", 07500 }, /* SKIP MINUS AC */ + { FIXED, "SPA", 07510 }, /* SKIP ON POSITIVE AC (ZERO IS POSITIVE) */ + /* Combined Operate Microinstructions */ + { FIXED, "CIA", 07041 }, /* COMPLEMENT AND INCREMENT AC */ + { FIXED, "STL", 07120 }, /* SET LINK TO 1 */ + { FIXED, "GLK", 07204 }, /* GET LINK (PUT LINK IN AC BIT 11) */ + { FIXED, "STA", 07240 }, /* SET AC TO -1 */ + { FIXED, "LAS", 07604 }, /* LOAD ACC WITH SR */ + /* MQ Instructions (PDP/8e) */ + { FIXED, "MQL", 07421 }, /* Load MQ from AC, then clear AC. */ + { FIXED, "MQA", 07501 }, /* Inclusive OR MQ with AC */ + { FIXED, "SWP", 07521 }, /* Swap AC and MQ */ + { FIXED, "ACL", 07701 }, /* Load MQ into AC */ + /* Program Interrupt */ + { FIXED, "IOT", 06000 }, + { FIXED, "ION", 06001 }, /* TURN INTERRUPT PROCESSOR ON */ + { FIXED, "IOF", 06002 }, /* TURN INTERRUPT PROCESSOR OFF */ + /* Program Interrupt, PDP-8/e */ + { FIXED, "SKON", 06000 }, /* Skip if interrupt on and turn int off. */ + { FIXED, "SRQ", 06003 }, /* Skip on interrupt request. */ + { FIXED, "GTF", 06004 }, /* Get interrupt flags. */ + { FIXED, "RTF", 06005 }, /* Restore interrupt flags. */ + { FIXED, "SGT", 06006 }, /* Skip on greater than flag. */ + { FIXED, "CAF", 06007 }, /* Clear all flags. */ + /* Keyboard/Reader */ + { FIXED, "KCF", 06030 }, /* CLEAR KEYBOAR FLAG */ + { FIXED, "KSF", 06031 }, /* SKIP ON KEYBOARD FLAG */ + { FIXED, "KCC", 06032 }, /* CLEAR KEYBOARD FLAG & READ CHAR */ + { FIXED, "KRS", 06034 }, /* READ KEYBOARD BUFFER (STATIC) */ + { FIXED, "KIE", 06035 }, /* AC11 TO KEYBD/RDR INT ENABLE F/F */ + { FIXED, "KRB", 06036 }, /* READ KEYBOARD BUFFER & CLEAR FLAG */ + /* Teleprinter/Punch */ + { FIXED, "TFL", 06040 }, /* SET TELEPRINTER/PUNCH FLAG */ + { FIXED, "TSF", 06041 }, /* SKIP ON TELEPRINTER FLAG */ + { FIXED, "TCF", 06042 }, /* CLEAR TELEPRINTER FLAG */ + { FIXED, "TPC", 06044 }, /* LOAD TELEPRINTER & PRINT */ + { FIXED, "TSK", 06045 }, /* SKIP IF TELETYPE INTERRUPT */ + { FIXED, "TLS", 06046 }, /* LOAD TELPRINTER & CLEAR FLAG */ + /* High Speed Paper Tape Reader */ + { FIXED, "RSF", 06011 }, /* SKIP ON READER FLAG */ + { FIXED, "RRB", 06012 }, /* READ READER BUFFER AND CLEAR FLAG */ + { FIXED, "RFC", 06014 }, /* READER FETCH CHARACTER */ + /* PC8-E High Speed Paper Tape Reader & Punch */ + { FIXED, "RPE", 06010 }, /* Set interrupt enable for reader/punch */ + { FIXED, "PCE", 06020 }, /* Clear interrupt enable for rdr/punch */ + { FIXED, "RCC", 06016 }, /* Read reader buffer, clear flags & buf, */ + /* and fetch character. */ + /* High Speed Paper Tape Punch */ + { FIXED, "PSF", 06021 }, /* SKIP ON PUNCH FLAG */ + { FIXED, "PCF", 06022 }, /* CLEAR ON PUNCH FLAG */ + { FIXED, "PPC", 06024 }, /* LOAD PUNCH BUFFER AND PUNCH CHARACTER* */ + { FIXED, "PLS", 06026 }, /* LOAD PUNCH BUFFER AND CLEAR FLAG */ + + /* DECassette TU60 (RK 20071008) */ + { FIXED, "KCLR", 06700 }, /* Clear all (clear A and B) */ + { FIXED, "KSDR", 06701 }, /* Skip if data flag set */ + { FIXED, "KSEN", 06702 }, /* Skip if EOT/BOT, not ready, or empty */ + { FIXED, "KSBF", 06703 }, /* Skip if ready flag set */ + { FIXED, "KLSA", 06704 }, /* AC4-11 -> A, clear A, -(AC4-11) -> A */ + { FIXED, "KSAF", 06705 }, /* Skip on any flag or error */ + { FIXED, "KGOA", 06706 }, /* Assert status A and transfer data to AC*/ + { FIXED, "KRSB", 06707 }, /* Transfer B -> AC4-11 */ + + /* DECtape Transport Type TU55 and DECtape Control Type TC01 */ + { FIXED, "DTRA", 06761 }, /* Contents of status register is ORed */ + /* into AC bits 0-9 */ + { FIXED, "DTCA", 06762 }, /* Clear status register A, all flags */ + /* undisturbed */ + { FIXED, "DTXA", 06764 }, /* Status register A loaded by exclusive */ + /* OR from AC. If AC bit 10=0, clear */ + /* error flags; if AC bit 11=0, DECtape */ + /* control flag is cleared. */ + { FIXED, "DTLA", 06766 }, /* Combination of DTCA and DTXA */ + { FIXED, "DTSF", 06771 }, /* Skip if error flag is 1 or if DECtape */ + /* control flag is 1 */ + { FIXED, "DTRB", 06772 }, /* Contents of status register B is */ + /* ORed into AC */ + { FIXED, "DTLB", 06774 }, /* Memory field portion of status */ + /* register B loaded from AC bits 6-8 */ + /* Disk File and Control, Type DF32 */ + { FIXED, "DCMA", 06601 }, /* CLEAR DISK MEMORY REQUEST AND */ + /* INTERRUPT FLAGS */ + { FIXED, "DMAR", 06603 }, /* LOAD DISK FROM AC, CLEAR AC READ */ + /* INTO CORE, CLEAR INTERRUPT FLAG */ + { FIXED, "DMAW", 06605 }, /* LOAD DISK FROM AC, WRITE ONTO DISK */ + /* FROM CORE, CLEAR INTERRUPT FLAG */ + { FIXED, "DCEA", 06611 }, /* CLEAR DISK EXTENDED ADDRESS AND */ + { FIXED, "DSAC", 06612 }, /* SKIP IF ADDRESS CONFIRMED FLAG = 1 */ + /* MEMORY ADDRESS EXTENSION REGISTER */ + { FIXED, "DEAL", 06615 }, /* CLEAR DISK EXTENDED ADDRESS AND */ + /* MEMORY ADDRESS EXTENSION REGISTER */ + /* AND LOAD SAME FROM AC */ + { FIXED, "DEAC", 06616 }, /* CLEAR AC, LOAD AC FROM DISK EXTENDED */ + /* ADDRESS REGISTER, SKIP IF ADDRESS */ + /* CONFIRMED FLAG = 1 */ + { FIXED, "DFSE", 06621 }, /* SKIP IF PARITY ERROR, DATA REQUEST */ + /* LATE, OR WRITE LOCK SWITCH FLAG = 0 */ + /* (NO ERROR) */ + { FIXED, "DFSC", 06622 }, /* SKIP IF COMPLETION FLAG = 1 (DATA */ + /* TRANSFER COMPLETE) */ + { FIXED, "DMAC", 06626 }, /* CLEAR AC, LOAD AC FROM DISK MEMORY */ + /* ADDRESS REGISTER */ + /* Disk File and Control, Type RF08 */ + { FIXED, "DCIM", 06611 }, + { FIXED, "DIML", 06615 }, + { FIXED, "DIMA", 06616 }, + { FIXED, "DISK", 06623 }, + { FIXED, "DCXA", 06641 }, + { FIXED, "DXAL", 06643 }, + { FIXED, "DXAC", 06645 }, + { FIXED, "DMMT", 06646 }, + /* Memory Extension Control, Type 183 */ + { FIXED, "CDF", 06201 }, /* CHANGE DATA FIELD */ + { FIXED, "CIF", 06202 }, /* CHANGE INSTRUCTION FIELD */ + { FIXED, "CDI", 06203 }, /* Change data & instrution field. */ + { FIXED, "RDF", 06214 }, /* READ DATA FIELD */ + { FIXED, "RIF", 06224 }, /* READ INSTRUCTION FIELD */ + { FIXED, "RIB", 06234 }, /* READ INTERRUPT BUFFER */ + { FIXED, "RMF", 06244 }, /* RESTORE MEMORY FIELD */ + /* Memory Parity, Type MP8/I (MP8/L) */ + { FIXED, "SMP", 06101 }, /* SKIP IF MEMORY PARITY FLAG = 0 */ + { FIXED, "CMP", 06104 }, /* CLEAR MEMORY PAIRTY FLAG */ + /* Memory Parity, Type MP8-E (PDP8/e) */ + { FIXED, "DPI", 06100 }, /* Disable parity interrupt. */ + { FIXED, "SNP", 06101 }, /* Skip if no parity error. */ + { FIXED, "EPI", 06103 }, /* Enable parity interrupt. */ + { FIXED, "CNP", 06104 }, /* Clear parity error flag. */ + { FIXED, "CEP", 06106 }, /* Check for even parity. */ + { FIXED, "SPO", 06107 }, /* Skip on parity option. */ + /* Data Communications Systems, Type 680I */ + { FIXED, "TTINCR", 06401 }, /* The content of the line select */ + /* register is incremented by one. */ + { FIXED, "TTI", 06402 }, /* The line status word is read and */ + /* sampled. If the line is active for */ + /* the fourth time, the line bit is */ + /* shifted into the character assembly */ + /* word. If the line bit is active for */ + /* a number of times less than four, */ + /* the count is incremented. If the */ + /* line is not active, the active/inac- */ + /* tive status of the line is recorded */ + { FIXED, "TTO", 06404 }, /* The character in the AC is shifted */ + /* right one position, zeros are shifted */ + /* into vacated positions, and the orig- */ + /* inal content of AC11 is transferred */ + /* out of the computer on the TTY line. */ + { FIXED, "TTCL", 06411 }, /* The line select register is cleared. */ + { FIXED, "TTSL", 06412 }, /* The line select register is loaded by */ + /* an OR transfer from the content of */ + /* of AC5-11, the the AC is cleared. */ + { FIXED, "TTRL", 06414 }, /* The content of the line select regis- */ + /* ter is read into AC5-11 by an OR */ + /* transfer. */ + { FIXED, "TTSKP", 06421 }, /* Skip if clock flag is a 1. */ + { FIXED, "TTXON", 06424 }, /* Clock 1 is enabled to request a prog- */ + /* ram interrupt and clock 1 flag is */ + /* cleared. */ + { FIXED, "TTXOF", 06422 }, /* Clock 1 is disabled from causing a */ + /* program interrupt and clock 1 flag */ + /* is cleared. */ +}; /* End-of-Symbols for Permanent Symbol Table */ + +/* Global variables */ +SYM_T *symtab; /* Symbol Table */ +int symbol_top; /* Number of entries in symbol table. */ + +SYM_T *fixed_symbols; /* Start of the fixed symbol table entries. */ +int number_of_fixed_symbols; + +/*----------------------------------------------------------------------------*/ + +WORD16 *xreftab; /* Start of the concordance table. */ + +ERRSAVE_T error_list[20]; +int save_error_count; + +#define GET_PAGE_INDEX(x) (((x) & 07600) >> 7) +#define MAX_PAGES 32 +LPOOL_T cp[MAX_PAGES]; /* Storage for page constants. */ +int max_page_used[MAX_PAGES]; + +char s_detected[] = "detected"; +char s_error[] = "error"; +char s_errors[] = "errors"; +char s_no[] = "No"; +char s_page[] = "Page"; +char s_symtable[] = "Symbol Table"; +char s_xref[] = "Cross Reference"; +char s_generated[] = "generated"; +char s_link[] = "link"; +char s_links[] = "links"; + +/* Assembler diagnostic messages. */ +/* Some attempt has been made to keep continuity with the PAL-III and */ +/* MACRO-8 diagnostic messages. If a diagnostic indicator, (e.g., IC) */ +/* exists, then the indicator is put in the listing as the first two */ +/* characters of the diagnostic message. The PAL-III indicators where used */ +/* when there was a choice between using MACRO-8 and PAL-III indicators. */ +/* The character pairs and their meanings are: */ +/* DT Duplicate Tag (symbol) */ +/* IC Illegal Character */ +/* ID Illegal Redefinition of a symbol. An attempt was made to give */ +/* a symbol a new value not via =. */ +/* IE Illegal Equals An equal sign was used in the wrong context, */ +/* (e.g., A+B=C, or TAD A+=B) */ +/* II Illegal Indirect An off page reference was made, but a literal */ +/* could not be generated because the indirect bit was already set. */ +/* IR Illegal Reference (address is not on current page or page zero) */ +/* ND No $ (the program terminator) at end of file. */ +/* PE Current, Non-Zero Page Exceeded (literal table flowed into code) */ +/* RD ReDefintion of a symbol */ +/* ST Symbol Table full */ +/* UA Undefined Address (undefined symbol) */ +/* ZE Zero Page Exceeded (see above, or out of space) */ +EMSG_T duplicate_label = { "DT duplicate", "duplicate label" }; +EMSG_T illegal_blank = { "IC illegal blank", "illegal blank" }; +EMSG_T illegal_character = { "IC illegal char", "illegal character" }; +EMSG_T illegal_expression = { "IC in expression", "illegal expression" }; +EMSG_T label_syntax = { "IC label syntax", "label syntax" }; +EMSG_T not_a_number = { "IC numeric syntax", "numeric syntax of" }; +EMSG_T number_not_radix = { "IC radix", "number not in current radix"}; +EMSG_T symbol_syntax = { "IC symbol syntax", "symbol syntax" }; +EMSG_T illegal_equals = { "IE illegal =", "illegal equals" }; +EMSG_T illegal_indirect = { "II off page", "illegal indirect" }; +EMSG_T illegal_reference = { "IR off page", "illegal reference" }; +EMSG_T undefined_symbol = { "UD undefined", "undefined symbol" }; +EMSG_T redefined_symbol = { "RD redefined", "redefined symbol" }; +EMSG_T illegal_redefine = { "ID redefined", "Illegal redefine of symbol" }; +EMSG_T literal_overflow = { "PE page exceeded", + "current page literal capacity exceeded" }; +EMSG_T pz_literal_overflow = { "ZE page exceeded", + "page zero capacity exceeded" }; +EMSG_T dubl_overflow = { "dubl overflow", "DUBL value overflow" }; +EMSG_T fltg_overflow = { "fltg overflow", "FLTG value overflow" }; +EMSG_T zblock_too_small = { "expr too small", "ZBLOCK value too small" }; +EMSG_T zblock_too_large = { "expr too large", "ZBLOCK value too large" }; +EMSG_T end_of_file = { "ND no $ at EOF", "No $ at End-of-File" }; +EMSG_T no_pseudo_op = { "not implemented", + "not implemented pseudo-op" }; +EMSG_T illegal_field_value = { "expr out of range", + "field value not in range of 0 through 7" }; +EMSG_T literal_gen_off = { "literals off", + "literal generation is off" }; +EMSG_T no_literal_value = { "no value", "no literal value" }; +EMSG_T text_string = { "no delimiter", + "text string delimiters not matched" }; +EMSG_T in_rim_mode = { "not OK in rim mode" + "FIELD pseudo-op not valid in RIM mode" }; +EMSG_T lt_expected = { "'<' expected", "'<' expected" }; +EMSG_T symbol_table_full = { "ST Symbol Tbl Full", + "Symbol Table Full" }; +/*----------------------------------------------------------------------------*/ + +FILE *errorfile; +FILE *infile; +FILE *listfile; +FILE *listsave; +FILE *objectfile; +FILE *objectsave; + +char errorpathname[NAMELEN]; +char filename[NAMELEN]; +char listpathname[NAMELEN]; +char objectpathname[NAMELEN]; +char *pathname; +char permpathname[NAMELEN]; + +int tabstops; /* number of characters to expand a tab to */ +int list_lineno; +int list_pageno; +char list_title[LINELEN]; +BOOL list_title_set; /* Set if TITLE pseudo-op used. */ +char line[LINELEN]; /* Input line. */ +int lineno; /* Current line number. */ +int page_lineno; /* print line number on current page. */ +BOOL listed; /* Listed flag. */ +BOOL listedsave; + +int cc; /* Column Counter (char position in line). */ +WORD16 checksum; /* Generated checksum */ +BOOL binary_data_output; /* Set true when data has been output. */ +WORD16 clc; /* Location counter */ +WORD16 cplc; /* Current page literal counter. */ +char delimiter; /* Character immediately after eval'd term. */ +int errors; /* Number of errors found so far. */ +int links; /* Number of links generated so far. */ +BOOL error_in_line; /* TRUE if error on current line. */ +int errors_pass_1; /* Number of errors on pass 1. */ +WORD16 field; /* Current field */ +WORD16 fieldlc; /* location counter without field portion. */ +BOOL fltg_input; /* TRUE when doing floating point input. */ +BOOL indirect_generated; /* TRUE if an off page address generated. */ +int last_xref_lexstart; /* Column where last xref symbol was located. */ +int last_xref_lineno; /* Line where last xref symbol was located. */ +int lexstartprev; /* Where previous lexeme started. */ +int lextermprev; /* Where previous lexeme ended. */ +int lexstart; /* Index of current lexeme on line. */ +int lexterm; /* Index of character after current lexeme. */ +BOOL literals_ok; /* Generate literals, ignore ID redefine err */ +BOOL perm_redef_error; /* Make redefining perm sym with labels error */ +int maxcc; /* Current line length. */ +BOOL overflow; /* Overflow flag for math routines. */ +int pass; /* Number of current pass. */ +BOOL print_permanent_symbols; +WORD16 pzlc; /* Page Zero literal counter. */ +WORD16 radix; /* Default number radix. */ +WORD16 reloc; /* The relocation distance. */ +BOOL rim_mode; /* Generate rim format, defaults to bin */ +BOOL dollar_not_required; /* $ not required at end of file */ +BOOL additional_enabled; /* True if extended functions over PAL8 */ + /* enabled */ +BOOL symtab_print; /* Print symbol table flag */ +BOOL xref; + +FLTG_T fltg_ac; /* Value holder for evalFltg() */ +SYM_T sym_eval = { DEFINED, "", 0 }; /* Value holder for eval() */ +SYM_T sym_getexpr = { DEFINED, "", 0 }; /* Value holder for getexpr() */ +SYM_T sym_undefined = { UNDEFINED, "", 0 };/* Symbol Table Terminator */ + + +/******************************************************************************/ +/* */ +/* Function: main */ +/* */ +/* Synopsis: Starting point. Controls order of assembly. */ +/* */ +/******************************************************************************/ +int main( int argc, char *argv[] ) +{ + int ix; + int space; + + /* Set the default values for global symbols. */ + binary_data_output = FALSE; + fltg_input = FALSE; + literals_ok = TRUE; + perm_redef_error = FALSE; + print_permanent_symbols = FALSE; + rim_mode = FALSE; + dollar_not_required = FALSE; + additional_enabled = FALSE; + symtab_print = FALSE; + xref = FALSE; + pathname = NULL; + + /* Get the options and pathnames */ + getArgs( argc, argv ); + + /* Setup the error file in case symbol table overflows while installing the */ + /* permanent symbols. */ + errorfile = fopen( errorpathname, "w" ); + if (errorfile == NULL) { + fprintf( stderr, "Could not open error file %s: %s\n", errorpathname, strerror(errno)); + exit( -1 ); + + } + errors = 0; + save_error_count = 0; + pass = 0; /* This is required for symbol table initialization. */ + symtab = (SYM_T *) malloc( sizeof( SYM_T ) * SYMBOL_TABLE_SIZE ); + + if( symtab == NULL ) + { + fprintf( stderr, "Could not allocate memory for symbol table.\n"); + exit( -1 ); + } + + /* Place end marker in symbol table. */ + symtab[0] = sym_undefined; + symbol_top = 0; + number_of_fixed_symbols = symbol_top; + fixed_symbols = &symtab[symbol_top - 1]; + + /* Enter the pseudo-ops into the symbol table */ + for( ix = 0; ix < DIM( pseudo ) - + (additional_enabled ? 0 : NUMBER_ADDITIONAL_PSEUDO) ; ix++ ) + { + defineSymbol( pseudo[ix].name, pseudo[ix].val, pseudo[ix].type, 0 ); + } + + /* Enter the predefined symbols into the table. */ + /* Also make them part of the permanent symbol table. */ + for( ix = 0; ix < DIM( really_permanent_symbols ); ix++ ) + { + defineSymbol( really_permanent_symbols[ix].name, + really_permanent_symbols[ix].val, + really_permanent_symbols[ix].type | DEFFIX , 0 ); + } + + /* Enter the predefined symbols into the table. */ + /* Also make them part of the permanent symbol table. */ + for( ix = 0; ix < DIM( permanent_symbols ); ix++ ) + { + defineSymbol( permanent_symbols[ix].name, + permanent_symbols[ix].val, + permanent_symbols[ix].type | DEFFIX , 0 ); + } + + number_of_fixed_symbols = symbol_top; + fixed_symbols = &symtab[symbol_top - 1]; + + /* Do pass one of the assembly */ + checksum = 0; + pass = 1; + page_lineno = LIST_LINES_PER_PAGE; + onePass(); + errors_pass_1 = errors; + + /* Set up for pass two */ + rewind( infile ); + /*Opened in main errorfile = fopen( errorpathname, "w" );*/ + objectfile = fopen( objectpathname, "wb" ); + if (objectfile == NULL) { + fprintf( stderr, "Could not open object file %s: %s\n", objectpathname, strerror(errno)); + exit( -1 ); + + } + objectsave = objectfile; + + listfile = fopen( listpathname, "w" ); + if (listfile == NULL) { + fprintf( stderr, "Could not open list file %s: %s\n", listpathname, strerror(errno)); + exit( -1 ); + + } + listsave = NULL; + + punchLeader( 0 ); + checksum = 0; + + /* Do pass two of the assembly */ + errors = 0; + save_error_count = 0; + page_lineno = LIST_LINES_PER_PAGE; + + if( xref ) + { + /* Get the amount of space that will be required for the concordance. */ + for( space = 0, ix = 0; ix < symbol_top; ix++ ) + { + symtab[ix].xref_index = space; /* Index into concordance table. */ + space += symtab[ix].xref_count + 1; + symtab[ix].xref_count = 0; /* Clear the count for pass 2. */ + + } + /* Allocate the necessary space. */ + xreftab = (WORD16 *) malloc( sizeof( WORD16 ) * space ); + + /* Clear the cross reference space. */ + for( ix = 0; ix < space; ix++ ) + { + xreftab[ix] = 0; + } + } + pass = 2; + onePass(); + + /* Undo effects of NOPUNCH for any following checksum */ + objectfile = objectsave; + punchChecksum(); + + /* Works great for trailer. */ + punchLeader( 8 ); + + /* undo effects of XLIST for any following output to listing file. */ + if( listfile == NULL ) + { + listfile = listsave; + } + + /* Display value of error counter. */ + if( errors == 0 ) + { + fprintf( listfile, "\n %s %s %s\n", s_no, s_detected, s_errors ); + } + else + { + fprintf( errorfile, "\n %d %s %s\n", errors, s_detected, + ( errors == 1 ? s_error : s_errors )); + fprintf( listfile, "\n %d %s %s\n", errors, s_detected, + ( errors == 1 ? s_error : s_errors )); + fprintf( stderr, " %d %s %s\n", errors, s_detected, + ( errors == 1 ? s_error : s_errors )); + } + /* Display value of link counter. */ + if( links == 0 ) + { + fprintf( listfile, " %s %s %s\n", s_no, s_links, s_generated ); + } + else + { + fprintf( errorfile, " %d %s %s\n", links, + ( links == 1 ? s_link : s_links ), + s_generated); + fprintf( listfile, " %d %s %s\n", links, + ( links == 1 ? s_link : s_links ), + s_generated); + fprintf( stderr, " %d %s %s\n", links, + ( links == 1 ? s_link : s_links ), + s_generated); + } + + if( symtab_print ) + { + printSymbolTable(); + } + + if( print_permanent_symbols ) + { + printPermanentSymbolTable(); + } + + if( xref ) + { + printCrossReference(); + } + + fclose( objectfile ); + fclose( listfile ); + fclose( errorfile ); + if( errors == 0 && errors_pass_1 == 0 ) + { + remove( errorpathname ); + } + + return( errors != 0 ); +} /* main() */ + +/******************************************************************************/ +/* */ +/* Function: getArgs */ +/* */ +/* Synopsis: Parse command line, set flags accordingly and setup input and */ +/* output files. */ +/* */ +/******************************************************************************/ +void getArgs( int argc, char *argv[] ) +{ + int len; + int ix, jx; + + /* Set the defaults */ + errorfile = NULL; + infile = NULL; + listfile = NULL; + listsave = NULL; + objectfile = NULL; + objectsave = NULL; + tabstops = 8; + + for( ix = 1; ix < argc; ix++ ) + { + if( argv[ix][0] == '-' ) + { + for( jx = 1; argv[ix][jx] != 0; jx++ ) + { + switch( argv[ix][jx] ) + { + case '$': + dollar_not_required = TRUE; + break; + + case 'd': + symtab_print = TRUE; + break; + + case 'a': + additional_enabled = TRUE; + break; + + case 'r': + rim_mode = TRUE; + break; + + case 'e': + literals_ok = FALSE; + break; + + case 'l': + literals_ok = TRUE; + break; + + case 'n': + perm_redef_error = TRUE; + break; + + case 'p': + print_permanent_symbols = TRUE; + break; + + /* Added -tN; RK 20071029 */ + /* Damn, this is ugly, we should use getopt() */ + case 't': + if (argv [ix][jx + 1]) { + tabstops = atoi (argv [ix] + (jx + 1)); + /* advance past numbers */ + for (jx++; argv [ix][jx]; jx++) ; + jx--; + } else { + ix++; + if (ix >= argc) { + fprintf( stderr, "%s: missing argument for -t, expected number of tabsopts\n", argv[0] ); + exit( -1 ); + } + for (jx = 0; argv [ix][jx]; jx++) ; + jx--; + tabstops = atoi (argv [ix]); + } + break; + + case 'x': + xref = TRUE; + break; + + case 'v': + fprintf( stderr, "%s\n", release ); + fflush( stderr ); + exit( -1 ); + break; + + default: + fprintf( stderr, "%s: unknown flag: %s\n", argv[0], argv[ix] ); + case 'h': + fprintf( stderr, " -$ -- allow file to not end with $\n" ); + fprintf( stderr, " -a -- enable additional function not in PAL8\n" ); + fprintf( stderr, " -d -- dump symbol table\n" ); + fprintf( stderr, " -e -- error if link generated\n" ); + fprintf( stderr, " -h -- show this help\n" ); + fprintf( stderr, " -l -- generate literal/link (default)\n" ); + fprintf( stderr, " -n -- no redefining with label permanent symbols\n" ); + fprintf( stderr, " -p -- output permanent symbols to file\n" ); + fprintf( stderr, " -r -- output rim format file\n" ); + fprintf( stderr, " -t N -- set tab stops to N\n" ); + fprintf( stderr, " -v -- display version\n" ); + fprintf( stderr, " -x -- output cross reference to file\n" ); + fflush( stderr ); + exit( -1 ); + } /* end switch */ + } /* end for */ + } + else + { + if( pathname != NULL ) + { + fprintf( stderr, "%s: too many input files\n", argv[0] ); + exit( -1 ); + } + pathname = &argv[ix][0]; + } + } /* end for */ + + if( pathname == NULL ) + { + fprintf( stderr, "%s: no input file specified\n", argv[0] ); + exit( -1 ); + } + + len = strlen( pathname ); + if( len > NAMELEN - 5 ) + { + fprintf( stderr, "%s: pathname \"%s\" too long\n", argv[0], pathname ); + exit( -1 ); + } + + /* Now open the input file. */ + if(( infile = fopen( pathname, "r" )) == NULL ) + { + fprintf( stderr, "%s: cannot open \"%s\": %s\n", argv[0], pathname, strerror(errno) ); + exit( -1 ); + } + + /* Now make the pathnames */ + /* Find last '.', if it exists. */ + jx = len - 1; + while( pathname[jx] != '.' && pathname[jx] != '/' + && pathname[jx] != '\\' && jx >= 0 ) + { + jx--; + } + + switch( pathname[jx] ) + { + case '.': + break; + + case '/': + case '\\': + jx = len; + break; + + default: + break; + } + + /* Add the pathname extensions. */ + strncpy( objectpathname, pathname, jx ); + objectpathname[jx] = '\0'; + strcat( objectpathname, rim_mode ? ".rim" : ".bin" ); + + strncpy( listpathname, pathname, jx ); + listpathname[jx] = '\0'; + strcat( listpathname, ".lst" ); + + strncpy( errorpathname, pathname, jx ); + errorpathname[jx] = '\0'; + strcat( errorpathname, ".err" ); + + strncpy( permpathname, pathname, jx ); + permpathname[jx] = '\0'; + strcat( permpathname, ".prm" ); + + /* Extract the filename from the path. */ + if( isalpha( pathname[0] ) && pathname[1] == ':' && pathname[2] != '\\' ) + { + pathname[1] = '\\'; /* MS-DOS style pathname */ + } + + jx = len - 1; + while( pathname[jx] != '/' && pathname[jx] != '\\' && jx >= 0 ) + { + jx--; + } + strcpy( filename, &pathname[jx + 1] ); + +} /* getArgs() */ + +/******************************************************************************/ +/* */ +/* Function: clearLiteralTable */ +/* */ +/* Synopsis: Clear the cp and max_page_used data storing literal */ +/* information. */ +/* */ +/******************************************************************************/ +void clearLiteralTable() +{ + int i; + + for (i = 0; i < DIM(cp); i++) + { + cp[i].loc = 0200; /* Points to end of page for [] operands. */ + cp[i].last_punched = 0200; /* Points to end of page for [] operands. */ + } + memset(max_page_used, 0, sizeof(max_page_used)); +} + +/******************************************************************************/ +/* */ +/* Function: onePass */ +/* */ +/* Synopsis: Do one assembly pass. */ +/* */ +/******************************************************************************/ +void onePass() +{ + char name[SYMLEN]; + WORD16 newclc; + BOOL scanning_line; + int start; + SYM_T *sym; + int term; + WORD16 val; + + clc = 0200; /* Default starting address is 200 octal. */ + field = 0; + fieldlc = clc & 07777; + reloc = 0; + clearLiteralTable(); + listed = TRUE; + lineno = 0; + list_pageno = 0; + list_lineno = 0; + last_xref_lexstart = 0; + last_xref_lineno = 0; + list_title_set = FALSE; + radix = 8; /* Initial radix is octal (base 8). */ + + if( !rim_mode ) + { + /* Put out initial origin if not in rim mode. */ + punchOrigin( clc ); + } + + while( TRUE ) + { + readLine(); + nextLexeme(); + + scanning_line = TRUE; + while( scanning_line ) + { + if( isend( line[lexstart] )) + { + scanning_line = FALSE; + } + else + { + switch( line[lexstart] ) + { + case '/': + scanning_line = FALSE; + break; + + case ';': + nextLexeme(); + break; + + case '$': + endOfBinary(); + return; + + case '*': + nextLexeme(); /* Skip '*', (set origin symbol) */ + newclc = ((getExpr())->val & 07777 ) | field; + /* Do not change Current Location Counter if an error occurred. */ + if( !error_in_line ) + { + if(( newclc & 07600 ) != ( clc & 07600 ) ) + { + /* Current page has changed. */ + punchLiteralPool( cp, 0 ); + } + clc = newclc - reloc; + fieldlc = clc & 07777; + + if( !rim_mode ) + { + /* Not rim mode, put out origin. */ + punchOrigin( clc ); + } + printLine( line, 0, fieldlc, LINE_VAL ); + } + break; + + default: + switch( line[lexterm] ) + { + case ',': + if( isalpha( line[lexstart] )) + { + /* Use lookup so symbol will not be counted as reference. */ + sym = lookup( lexemeToName( name, lexstart, lexterm )); + if( M_DEFINED( sym->type )) + { + if( (sym->val & 07777) != ( ( clc+reloc ) & 07777) && pass == 2 ) + { + errorSymbol( &duplicate_label, sym->name, lexstart ); + } + sym->type = sym->type | DUPLICATE; + } + /* Must call define on pass 2 to generate concordance. */ + defineLexeme( lexstart, lexterm, ( clc + reloc ), LABEL ); + } + else + { + errorLexeme( &label_syntax, lexstart ); + } + nextLexeme(); /* skip label */ + nextLexeme(); /* skip comma */ + break; + + case '=': + if( isalpha( line[lexstart] )) + { + start = lexstart; + term = lexterm; + delimiter = line[lexterm]; + nextLexBlank(); /* skip symbol */ + nextLexeme(); /* skip trailing =, allow blank */ + delimiter = line[lexterm]; + val = getExprs(); + defineLexeme( start, term, val, DEFINED ); + printLine( line, 0, val, LINE_VAL ); + } + else + { + errorLexeme( &symbol_syntax, lexstartprev ); + nextLexeme(); /* skip symbol */ + nextLexeme(); /* skip trailing = */ + getExprs(); /* skip expression */ + } + break; + + default: + if( isalpha( line[lexstart] )) + { + sym = evalSymbol(); + val = sym->val; + if( M_PSEUDO( sym->type )) + { + nextLexeme(); /* Skip symbol */ + scanning_line = pseudoOperators( (PSEUDO_T)val & 07777 ); + } + else + { + /* Identifier is not a pseudo-op, interpret as load value */ + punchOutObject( clc, getExprs() & 07777 ); + incrementClc(); + } + } + else + { + /* Identifier is a value, interpret as load value */ + punchOutObject( clc, getExprs() & 07777 ); + incrementClc(); + } + break; + } /* end switch */ + break; + } /* end switch */ + } /* end if */ + } /* end while( scanning_line ) */ + } /* end while( TRUE ) */ +} /* onePass() */ + + +/******************************************************************************/ +/* */ +/* Function: fixMRIInstruction */ +/* */ +/* Synopsis: Now that we have the final value figure out if page 0, current */ +/* page, or indirect needed and max final instruction */ +/* */ +/******************************************************************************/ +WORD16 fixMRIInstruction(WORD16 instruction, WORD16 value) +{ + /* Now have the address part of the MRI instruction. */ + if( value < 00200 ) + { + instruction |= value; /* Page zero MRI. */ + } + else if( (( fieldlc + reloc ) & 07600 ) <= value + && value <= ((( fieldlc + reloc ) & 07600) | 0177 )) + { + instruction |= ( PAGE_BIT | (value & ADDRESS_FIELD )); /* Current page MRI */ + } + else + { + if(( instruction & INDIRECT_BIT ) == INDIRECT_BIT ) + { + /* Already indirect, can't generate */ + errorSymbol( &illegal_indirect, NULL, lexstartprev ); + } + else + { + if( literals_ok ) + { + /* Now fix off page reference. */ + /* Search current page literal pool for needed instruction. */ + /* Set Indirect Current Page */ + instruction |= ( 00600 | insertLiteral( cp, value, GET_PAGE_INDEX(clc) )); + indirect_generated = TRUE; + if (pass == 2) + { + links++; + } + } + else + { + errorSymbol( &illegal_reference, NULL, lexstartprev ); + instruction |= ( value & 0177 ); + } + } + } + return instruction; +} +/******************************************************************************/ +/* */ +/* Function: getExprs */ +/* */ +/* Synopsis: Or together a list of blank separated expressions, from the */ +/* current lexeme onward. Leave the current lexeme as */ +/* the last one in the list. */ +/* */ +/******************************************************************************/ +WORD16 getExprs() +{ + SYM_T *symv; + SYM_T *symt; + WORD16 temp; + SYMTYP temp_type; + WORD16 value; + SYMTYP value_type; + BOOL MRI_held = FALSE; + WORD16 held_value = 0; + + symv = getExpr(); + value = symv->val; + value_type = symv->type; + + while( TRUE ) + { + if( isdone( line[lexstart] )) + { + if (MRI_held) + { + value = fixMRIInstruction(value, held_value); + } + return( value ); + } + switch( line[lexstart] ) + { + case ')': + case ']': + case '<': + if (MRI_held) + { + value = fixMRIInstruction(value, held_value); + } + return( value ); + + default: + break; + } + + /* Interpret space as logical or */ + symt = getExpr(); + temp = symt->val & 07777; + temp_type = symt->type; + + switch( value_type & (MRI | MRIFIX)) + { + case MRI: + case MRIFIX: + /* Previous symbol was a Memory Reference Instruction. */ + switch( temp_type & (MRI | MRIFIX) ) + { + case MRI: + case MRIFIX: + /* If we have held value don't or in more MRI's to instuction, they */ + /* are now instuction value */ + if (MRI_held) + { + held_value |= temp; + } + else + { + /* Current symbol is also a Memory Reference Instruction. */ + value |= temp; /* Just OR the MRI instructions. */ + } + break; + + default: + held_value |= temp; + MRI_held = TRUE; + break; + } + break; + + default: + if (value_type == UNDEFINED || temp_type == UNDEFINED) { + value = 0; + } else { + value |= temp; /* Normal 12 bit value. */ + } + break; + } + } /* end while */ +} /* getExprs() */ + + +/******************************************************************************/ +/* */ +/* Function: getExpr */ +/* */ +/* Synopsis: Get an expression, from the current lexeme onward, leave the */ +/* current lexeme as the one after the expression. Expressions */ +/* contain terminal symbols (identifiers) separated by operators. */ +/* */ +/******************************************************************************/ +SYM_T *getExpr() +{ + SYM_T *sym; + delimiter = line[lexterm]; + + + if( line[lexstart] == '-' ) + { + nextLexBlank(); + sym_getexpr = *(eval()); + sym_getexpr.val = ( - sym_getexpr.val ) & 07777; + } + else + { + if( line[lexstart] == '+' ) + { + nextLexBlank(); + } + sym_getexpr = *(eval()); + sym_getexpr.val = sym_getexpr.val & 07777; + } + + if( is_blank( delimiter )) + { + return( &sym_getexpr ); + } + + /* Here we assume the current lexeme is the operator separating the */ + /* previous operator from the next, if any. */ + while( TRUE ) + { + /* assert line[lexstart] == delimiter */ + if( is_blank( delimiter )) + { + return( &sym_getexpr ); + } + + switch( line[lexstart] ) + { + case '+': /* add */ + nextLexBlank(); /* skip over the operator */ + sym = eval(); + sym_getexpr.val += sym->val; + if (sym_getexpr.type == UNDEFINED || sym->type == UNDEFINED) { + sym_getexpr.val = 0; + sym_getexpr.type = UNDEFINED; + } + break; + + case '-': /* subtract */ + nextLexBlank(); /* skip over the operator */ + sym = eval(); + sym_getexpr.val -= sym->val; + if (sym_getexpr.type == UNDEFINED || sym->type == UNDEFINED) { + sym_getexpr.val = 0; + sym_getexpr.type = UNDEFINED; + } + break; + + case '^': /* multiply */ + nextLexBlank(); /* skip over the operator */ + sym = eval(); + sym_getexpr.val *= sym->val; + if (sym_getexpr.type == UNDEFINED || sym->type == UNDEFINED) { + sym_getexpr.val = 0; + sym_getexpr.type = UNDEFINED; + } + break; + + case '%': /* divide */ + nextLexBlank(); /* skip over the operator */ + sym = eval(); + sym_getexpr.val /= sym->val; + if (sym_getexpr.type == UNDEFINED || sym->type == UNDEFINED) { + sym_getexpr.val = 0; + sym_getexpr.type = UNDEFINED; + } + break; + + case '&': /* and */ + nextLexBlank(); /* skip over the operator */ + sym = eval(); + sym_getexpr.val &= sym->val; + if (sym_getexpr.type == UNDEFINED || sym->type == UNDEFINED) { + sym_getexpr.val = 0; + sym_getexpr.type = UNDEFINED; + } + break; + + case '!': /* or */ + nextLexBlank(); /* skip over the operator */ + sym = eval(); + sym_getexpr.val |= sym->val; + if (sym_getexpr.type == UNDEFINED || sym->type == UNDEFINED) { + sym_getexpr.val = 0; + sym_getexpr.type = UNDEFINED; + } + break; + + default: + if( isend( line[lexstart] )) + { + return( &sym_getexpr ); + } + + switch( line[lexstart] ) + { + case '/': + case ';': + case ')': + case ']': + case '<': + break; + + case '=': + errorMessage( &illegal_equals, lexstart ); + moveToEndOfLine(); + sym_getexpr.val = 0; + break; + + default: + errorMessage( &illegal_expression, lexstart ); + moveToEndOfLine(); + sym_getexpr.val = 0; + break; + } + return( &sym_getexpr ); + } + } /* end while */ +} /* getExpr() */ + + +/******************************************************************************/ +/* */ +/* Function: eval */ +/* */ +/* Synopsis: Get the value of the current lexeme, set delimiter and advance.*/ +/* */ +/******************************************************************************/ +SYM_T *eval() +{ + WORD16 digit; + int from; + WORD16 loc; + SYM_T *sym; + WORD16 val; + + val = 0; + + delimiter = line[lexterm]; + if( isalpha( line[lexstart] )) + { + sym = evalSymbol(); + if( M_UNDEFINED( sym->type ) && pass == 2 ) + { + errorSymbol( &undefined_symbol, sym->name, lexstart ); + nextLexeme(); + return( sym ); + } + else + { + nextLexeme(); + return( sym ); + } + } + else if( isdigit( line[lexstart] )) + { + from = lexstart; + val = 0; + while( from < lexterm ) + { + if( isdigit( line[from] )) + { + digit = (WORD16) line[from++] - (WORD16) '0'; + if( digit < radix ) + { + val = val * radix + digit; + } + else + { + errorLexeme( &number_not_radix, from - 1 ); + val = 0; + from = lexterm; + } + } + else + { + errorLexeme( ¬_a_number, lexstart ); + val = 0; + from = lexterm; + } + } + nextLexeme(); + sym_eval.val = val; + return( &sym_eval ); + } + else + { + switch( line[lexstart] ) + { + case '"': /* Character literal */ + if( cc + 1 <= maxcc ) + { + val = line[lexstart + 1] | 0200; + delimiter = line[lexstart + 2]; + cc = lexstart + 2; + } + else + { + errorMessage( &no_literal_value, lexstart ); + } + nextLexeme(); + break; + + case '.': /* Value of Current Location Counter */ + val = (clc & 07777) + reloc; + nextLexeme(); + break; + + case '[': /* Generate literal on page zero. */ + if( !literals_ok && LITERAL_ERROR) + { + errorMessage( &literal_gen_off, lexstart ); + } + nextLexBlank(); /* Skip bracket */ + val = getExprs() & 07777; + + if( line[lexstart] == ']' ) + { + nextLexBlank(); /* Skip end bracket */ + } + + sym_eval.val = (literals_ok || !LITERAL_ERROR) ? insertLiteral( cp , val, + GET_PAGE_INDEX(field)) : 0; + return( &sym_eval ); + + case '(': /* Generate literal on current page. */ + if( !literals_ok && LITERAL_ERROR) + { + errorMessage( &literal_gen_off, lexstart ); + } + + nextLexBlank(); /* Skip paren */ + val = getExprs() & 07777; + + if( line[lexstart] == ')' ) + { + nextLexBlank(); /* Skip end paren */ + } + + loc = (literals_ok || !LITERAL_ERROR) ? insertLiteral( cp, val, GET_PAGE_INDEX(clc) ) : 0; + sym_eval.val = loc + (( clc + reloc ) & 077600 ); + return( &sym_eval ); + + default: + switch( line[lexstart] ) + { + case '=': + errorMessage( &illegal_equals, lexstart ); + moveToEndOfLine(); + break; + + default: + errorMessage( &illegal_character, lexstart ); + break; + } + val = 0; /* On error, set value to zero. */ + nextLexBlank(); /* Go past illegal character. */ + } + } + sym_eval.val = val; + return( &sym_eval ); +} /* eval() */ + + +/******************************************************************************/ +/* */ +/* Function: inputDubl */ +/* */ +/* Synopsis: Get the value of the current lexeme as a double word. */ +/* */ +/******************************************************************************/ +void inputDubl() +{ + WORD32 dublvalue; + BOOL scanning_line; + + scanning_line = TRUE; + do + { + while( scanning_line ) + { + if( isend( line[lexstart] )) + { + scanning_line = FALSE; + } + else + { + switch( line[lexstart] ) + { + case '/': + scanning_line = FALSE; + break; + + case ';': + nextLexeme(); + break; + + case '+': + delimiter = line[lexterm]; + nextLexBlank(); + case '-': + default: + if( isdigit( line[lexstart] ) || line[lexstart] == '-' ) + { + dublvalue = getDublExprs(); + punchOutObject( clc, (WORD16)(( dublvalue >> 12 ) & 07777 )); + incrementClc(); + punchOutObject( clc, (WORD16)( dublvalue & 07777 )); + incrementClc(); + } + else + { + return; /* Non-numeric input, back to assembly. */ + } + break; + } /* end switch */ + } /* end if */ + + if( error_in_line ) + { + return; /* Error occurred, exit DUBL input mode. */ + } + } /* end while( scanning_line ) */ + readLine(); + nextLexeme(); + scanning_line = TRUE; + } + while( TRUE ); +} /* inputDubl() */ + + +/******************************************************************************/ +/* */ +/* Function: getDublExprs */ +/* */ +/* Synopsis: Get a DUBL expression. */ +/* */ +/******************************************************************************/ +WORD32 getDublExprs() +{ + WORD32 dublvalue; + + dublvalue = getDublExpr(); + + while( TRUE ) + { + if( isdone( line[lexstart] )) + { + return( dublvalue ); + } + else + { + errorMessage( &illegal_expression, lexstart - 1 ); + return( 0 ); + } + } /* end while */ +} /* getDublExprs() */ + + +/******************************************************************************/ +/* */ +/* Function: getDublExpr */ +/* */ +/* Synopsis: Get the value of the current lexeme as a double word. The */ +/* number is always considered to have a decimal radix. */ +/* */ +/******************************************************************************/ +WORD32 getDublExpr() +{ + WORD32 dublvalue; + + delimiter = line[lexterm]; + if( line[lexstart] == '-' ) + { + nextLexBlank(); + dublvalue = evalDubl( 0 ); + nextLexeme(); + /* Test for any value greater than 23 bits in length. */ + if( (unsigned long int)dublvalue > 040000000L ) + { + errorMessage( &dubl_overflow, lexstart ); + dublvalue = 0; + } + dublvalue = -dublvalue; + } + else + { + dublvalue = evalDubl( 0 ); + nextLexeme(); + /* Test for any value greater than 23 bits in length. */ + if( (unsigned long int)dublvalue > 037777777L ) + { + errorMessage( &dubl_overflow, lexstart ); + dublvalue = 0; + } + } + + if( is_blank( delimiter )) + { + return( dublvalue ); + } + + /* Here we assume the current lexeme is the operator separating the */ + /* previous operator from the next, if any. */ + while( TRUE ) + { + /* assert line[lexstart] == delimiter */ + if( is_blank( delimiter )) + { + errorMessage( &illegal_expression, lexstart ); + moveToEndOfLine(); + dublvalue = 0; + return( dublvalue ); + } + + switch( line[lexstart] ) + { + case '+': /* add */ + case '-': /* subtract */ + case '^': /* multiply */ + case '%': /* divide */ + case '&': /* and */ + case '!': /* or */ + errorMessage( &illegal_expression, lexstart ); + moveToEndOfLine(); + dublvalue = 0; + break; + + default: + if( isend( line[lexstart] )) + { + return( dublvalue ); + } + + switch( line[lexstart] ) + { + case '/': + case ';': + break; + + default: + errorMessage( &illegal_expression, lexstart ); + moveToEndOfLine(); + dublvalue = 0; + break; + } + return( dublvalue ); + } + } /* end while */ +} /* getDublExpr() */ + + +/******************************************************************************/ +/* */ +/* Function: evalDubl */ +/* */ +/* Synopsis: Get the value of the current lexeme as a double word. The */ +/* number is always considered to have a decimal radix. */ +/* */ +/******************************************************************************/ +WORD32 evalDubl( WORD32 initial_value ) +{ + WORD32 digit; + int from; + WORD32 dublvalue; + WORD32 olddublvalue; + + overflow = FALSE; + delimiter = line[lexterm]; + from = lexstart; + dublvalue = initial_value; + + while( from < lexterm ) + { + if( isdigit( line[from] )) + { + olddublvalue = dublvalue; + digit = (WORD32)( line[from++] - '0' ); + dublvalue = dublvalue * 10 + digit; + if( dublvalue < olddublvalue ) + { + overflow = TRUE; + } + } + else + { + errorLexeme( ¬_a_number, from ); + dublvalue = 0; + from = lexterm; + } + } + return( dublvalue ); +} /* evalDubl() */ + + +/******************************************************************************/ +/* */ +/* Function: inputFltg */ +/* */ +/* Synopsis: Get the value of the current lexeme as a Floating Point const. */ +/* */ +/******************************************************************************/ +void inputFltg() +{ + FLTG_T *fltg; + BOOL scanning_line; + + fltg_input = TRUE; /* Set lexeme scanner for floating point. */ + scanning_line = TRUE; + while( TRUE ) + { + while( scanning_line ) + { + if( isend( line[lexstart] )) + { + scanning_line = FALSE; + } + else + { + switch( line[lexstart] ) + { + case '/': + scanning_line = FALSE; + break; + + case ';': + nextLexeme(); + break; + + case '+': + delimiter = line[lexterm]; + nextLexBlank(); + case '-': + default: + if( isdigit( line[lexstart] ) || line[lexstart] == '-' ) + { + fltg = getFltgExprs(); + punchOutObject( clc, ( fltg->exponent & 07777 )); + incrementClc(); + punchOutObject( clc, (WORD16)(( fltg->mantissa >> 12 ) & 07777 )); + incrementClc(); + punchOutObject( clc, (WORD16)( fltg->mantissa & 07777 )); + incrementClc(); + } + else + { + fltg_input = FALSE; /* Reset lexeme scanner. */ + return; /* Non-numeric input, back to assembly. */ + } + break; + } /* end switch */ + } /* end if */ + + if( error_in_line ) + { + fltg_input = FALSE; /* Reset lexeme scanner. */ + return; /* Error occurred, exit FLTG input mode. */ + } + } /* end while( scanning_line ) */ + readLine(); + nextLexeme(); + scanning_line = TRUE; + } +} /* inputFltg() */ + + +/******************************************************************************/ +/* */ +/* Function: getFltgExprs */ +/* */ +/* Synopsis: Get a FLTG expression. */ +/* */ +/******************************************************************************/ +FLTG_T *getFltgExprs() +{ + FLTG_T *fltg; + + fltg = getFltgExpr(); + + while( TRUE ) + { + if( isdone( line[lexstart] )) + { + return( fltg ); + } + else + { + errorMessage( &illegal_expression, lexstart - 1 ); + return( 0 ); + } + } /* end while */ +} /* getFltgExprs() */ + + +/******************************************************************************/ +/* */ +/* Function: getFltgExpr */ +/* */ +/* Synopsis: Get the value of the current lexeme as a double word. The */ +/* number is always considered to have a decimal radix. */ +/* */ +/******************************************************************************/ +FLTG_T *getFltgExpr() +{ + FLTG_T *fltg; + + delimiter = line[lexterm]; + fltg = evalFltg(); + /* Test for any value greater than 23 bits in length. */ + if( (unsigned long int)fltg->mantissa> 077777777L ) + { + errorMessage( &fltg_overflow, lexstart ); + } + + if( is_blank( delimiter )) + { + return( fltg ); + } + + /* Here we assume the current lexeme is the operator separating the */ + /* previous operator from the next, if any. */ + while( TRUE ) + { + /* assert line[lexstart] == delimiter */ + if( is_blank( delimiter )) + { + errorMessage( &illegal_expression, lexstart ); + moveToEndOfLine(); + fltg = 0; + return( fltg ); + } + + switch( line[lexstart] ) + { + case '+': /* add */ + case '-': /* subtract */ + case '^': /* multiply */ + case '%': /* divide */ + case '&': /* and */ + case '!': /* or */ + errorMessage( &illegal_expression, lexstart ); + moveToEndOfLine(); + fltg = NULL; + break; + + default: + if( isend( line[lexstart] )) + { + return( fltg ); + } + + switch( line[lexstart] ) + { + case '/': + case ';': + break; + + default: + errorMessage( &illegal_expression, lexstart ); + moveToEndOfLine(); + fltg = NULL; + break; + } + return( fltg ); + } + } /* end while */ +} /* getFltgExpr() */ + + +/******************************************************************************/ +/* */ +/* Function: evalFltg */ +/* */ +/* Synopsis: Get the value of the current lexeme as a floating point value. */ +/* Floating point input is alwasy considered decimal. */ +/* The general format of a floating point number is: */ +/* +-ddd.dddE+-dd where each d is a decimal digit. */ +/* */ +/******************************************************************************/ +FLTG_T *evalFltg() +{ + int current_state; + int current_col; + WORD16 exponent; + FLTG_T *fltg; + WORD32 input_value; + BOOL negate; + BOOL negate_exponent; + int next_state; + int right_digits; + + /* This uses a lexical analyzer to parse the floating point format. */ + static BYTE state_table[][10] = + { + /* 0 1 2 3 4 5 6 Oolumn index */ + /* + - d . E sp p State Comment */ + { 2, 1, 3, 4, 10, 10, 10 }, /* 0 Initial state. */ + { 11, 11, 3, 4, 11, 11, 11 }, /* 1 - */ + { 11, 11, 3, 4, 11, 11, 11 }, /* 2 + */ + { 10, 10, 10, 4, 6, 10, 10 }, /* 3 # (+-ddd) */ + { 11, 11, 5, 11, 11, 10, 10 }, /* 4 . (+-ddd.) */ + { 11, 11, 11, 11, 6, 10, 11 }, /* 5 # (+-ddd.ddd) */ + { 8, 7, 9, 11, 11, 11, 11 }, /* 6 E (+-ddd.dddE) */ + { 11, 11, 9, 11, 11, 11, 11 }, /* 7 - (+-ddd.dddE- */ + { 11, 11, 9, 11, 11, 11, 11 }, /* 8 + (+-ddd.dddE+ */ + { 11, 11, 11, 11, 11, 10, 11 } /* 9 # (+-ddd.dddE+-dd */ + /* 10 Completion state */ + /* 11 Error state. */ + }; + + delimiter = line[lexterm]; + fltg = &fltg_ac; + fltg->exponent = 0; + fltg->mantissa = 0; + input_value = 0; + negate = FALSE; + negate_exponent = FALSE; + next_state = 0; + exponent = 0; + right_digits = 0; + current_state = 0; + + while( TRUE ) + { + /* Classify character. This is the column index. */ + switch( line[lexstart] ) + { + case '+': + current_col = 0; + break; + + case '-': + current_col = 1; + break; + + case '.': + current_col = 3; + break; + + case 'E': case 'e': + current_col = 4; + break; + + default: + if( isdigit( line[lexstart] )) + { + current_col = 2; + } + else if( isdone( line[lexstart] )) + { + current_col = 5; + } + else + { + current_col = 6; + } + break; + } + + next_state = state_table[current_state][current_col]; + + switch( next_state ) + { + case 1: /* - */ + negate = TRUE; + case 2: /* + */ + delimiter = line[lexterm]; /* Move past the + or - character. */ + nextLexBlank(); + break; + + case 3: /* Number (+-ddd) */ + input_value = evalDubl( 0 ); /* Integer part of the number. */ + nextLexeme(); /* Move past previous lexeme. */ + break; + + case 4: + delimiter = line[lexterm]; + nextLexBlank(); /* Move past the . character. */ + break; + + case 5: /* . (+-ddd.ddd) */ + /* Fractional part of the number. */ + input_value = evalDubl( input_value ); + right_digits = lexterm - lexstart;/* Digit count to right of decimal. */ + nextLexeme(); /* Move past previous lexeme. */ + break; + + case 6: /* E (+-ddd.dddE) */ + delimiter = line[lexterm]; /* Move past the E. */ + nextLexBlank(); + break; + + case 7: /* - (+-ddd.dddE-) */ + negate_exponent = TRUE; + case 8: /* + (+-ddd.dddE+) */ + delimiter = line[lexterm]; /* Move past the + or - character. */ + nextLexBlank(); + break; + + case 9: /* # (+-ddd.dddE+-dd) */ + exponent = (int)evalDubl( 0 ); /* Exponent of floating point number. */ + if( negate_exponent ) { exponent = - exponent; } + nextLexeme(); /* Move past previous lexeme. */ + break; + + case 10: /* Floating number parsed, convert */ + /* the number. */ + /* Get the exponent for the number as input. */ + exponent -= right_digits; + + /* Remove trailing zeros and adjust the exponent accordingly. */ + while(( input_value % 10 ) == 0 ) + { + input_value /= 10; + exponent++; + } + + /* Convert the number to floating point. The number is calculated with */ + /* a 27 bit mantissa to improve precision. The extra 3 bits are */ + /* discarded after the result has been calculated. */ + fltg->exponent = 26; + fltg->mantissa = input_value << 3; + normalizeFltg( fltg ); + + + while( exponent != 0 ) + { + if( exponent < 0 ) + { + /* Decimal point is to the left. */ + fltg->mantissa /= 10; + normalizeFltg( fltg ); + exponent++; + } + else if( exponent > 0 ) + { + /* Decimal point is to the right. */ + fltg->mantissa *= 10; + normalizeFltg( fltg ); + exponent--; + } + } + + /* Discard the extra precsion used for calculating the number. */ + fltg->mantissa >>= 3; + fltg->exponent -= 3; + if( negate ) + { + fltg->mantissa = (- fltg->mantissa ) & 077777777L; + } + return( fltg ); + + case 11: /* Error in format. */ + /* Not a properly constructued floating point number. */ + return( fltg ); + default: + break; + } + /* Set state for next pass through the loop. */ + current_state = next_state; + } +} /* evalFltg() */ + + + +/******************************************************************************/ +/* */ +/* Function: normalizeFltg */ +/* */ +/* Synopsis: Normalize a PDP-8 double precision floating point number. */ +/* */ +/******************************************************************************/ +void normalizeFltg( FLTG_T *fltg ) +{ + /* Normalize the floating point number. */ + if( fltg->mantissa != 0 ) + { + if(( fltg->mantissa & ~0x3FFFFFFL ) == 0 ) + { + while(( fltg->mantissa & ~0x1FFFFFFL ) == 0 ) + + { + fltg->mantissa <<= 1; + fltg->exponent--; + } + } + else + { + while(( fltg->mantissa & ~0x3FFFFFFL ) != 0 ) + { + fltg->mantissa >>= 1; + fltg->exponent++; + } + } + } + else + { + fltg->exponent = 0; + } + return; +} + + +/******************************************************************************/ +/* */ +/* Function: incrementClc */ +/* */ +/* Synopsis: Set the next assembly location. Test for collision with */ +/* the literal tables. */ +/* */ +/******************************************************************************/ +WORD16 incrementClc() +{ + testForLiteralCollision( clc ); + + /* Incrementing the location counter is not to change field setting. */ + clc = ( clc & 070000 ) + (( clc + 1 ) & 07777 ); + fieldlc = clc & 07777; + return( clc ); +} /* incrementClc() */ + + +/******************************************************************************/ +/* */ +/* Function: testForLiteralCollision */ +/* */ +/* Synopsis: Test the given location for collision with the literal tables. */ +/* */ +/******************************************************************************/ +BOOL testForLiteralCollision( WORD16 loc ) +{ + WORD16 pagelc; + BOOL result = FALSE; + WORD16 tmppage; + int tmpfield; + + tmpfield = GET_PAGE_INDEX(loc); + tmppage = loc & 07600; + pagelc = loc & 00177; + + if ( pagelc > max_page_used[tmpfield] ) + { + max_page_used[tmpfield] = pagelc; + } + if ( pagelc >= cp[tmpfield].loc ) + { + if ( tmppage == 0 ) + { + errorMessage( &pz_literal_overflow, -1 ); + } + else + { + errorMessage( &literal_overflow, -1 ); + } + result = TRUE; + } + + return( result ); +} /* testForLiteralCollision() */ + + +/******************************************************************************/ +/* */ +/* Function: readLine */ +/* */ +/* Synopsis: Get next line of input. Print previous line if needed. */ +/* */ +/******************************************************************************/ +void readLine() +{ + WORD16 ix; + WORD16 iy; + char inpline[LINELEN]; + + listLine(); /* List previous line if needed. */ + lineno++; /* Count lines read. */ + indirect_generated = FALSE; /* Mark no indirect address generated. */ + listed = FALSE; /* Mark as not listed. */ + cc = 0; /* Initialize column counter. */ + lexstartprev = 0; + + error_in_line = FALSE; + if(( fgets( inpline, LINELEN - 1, infile )) == NULL ) + { + inpline[0] = '$'; + inpline[1] = '\n'; + inpline[2] = '\0'; + if (!dollar_not_required) { + error_in_line = TRUE; + } + } + + /* Remove any tabs from the input line by inserting the required number */ + /* of spaces to simulate N character tab stops, where N defaults to 8 and */ + /* is set by the command line option -t. (RK 20071029) */ + /* Ignore \r if there is one. (DPI 20150501) */ + for( ix = 0, iy = 0; inpline[ix] != '\0' && iy < (LINELEN - 2); ix++ ) + { + switch( inpline[ix] ) + { + case '\t': + do + { + line[iy] = ' '; + iy++; + } + while(( iy % tabstops ) != 0 && iy < (LINELEN - 2)); + break; + + case '\r': /* dont copy the carriage return */ + break; + + default: + line[iy] = inpline[ix]; + iy++; + break; + } + } + if (iy >= (LINELEN - 2)) { + line [iy] = '\n'; + iy++; + } + line[iy] = '\0'; + + maxcc = iy; /* Save the current line length. */ + /* Save the first line for possible use as the listing title. */ + if( lineno == 1 ) + { + strcpy( list_title, line ); + } +} /* readLine() */ + + +/******************************************************************************/ +/* */ +/* Function: listLine */ +/* */ +/* Synopsis: Output a line to the listing file. */ +/* */ +/******************************************************************************/ +void listLine() +/* generate a line of listing if not already done! */ +{ + if( listfile != NULL && listed == FALSE ) + { + printLine( line, 0, 0, LINE ); + } +} /* listLine() */ + + +/******************************************************************************/ +/* */ +/* Function: printPageBreak */ +/* */ +/* Synopsis: Output a Top of Form and listing header if new page necessary. */ +/* */ +/******************************************************************************/ +void printPageBreak() +{ + if( page_lineno >= LIST_LINES_PER_PAGE ) + /* ( list_lineno % LIST_LINES_PER_PAGE ) == 0 ) */ + { + if( !list_title_set ) + { + /* strcpy( list_title, line ); */ + if( list_title[strlen(list_title) - 1] == '\n' ) + { + list_title[strlen(list_title) - 1] = '\0'; + } + if( strlen( list_title ) > TITLELEN ) + { + list_title[TITLELEN] = '\0'; + } + list_title_set = TRUE; + } + topOfForm( list_title, NULL ); + + } +} /* printPageBreak() */ + + +/******************************************************************************/ +/* */ +/* Function: printLine */ +/* */ +/* Synopsis: Output a line to the listing file with new page if necessary. */ +/* */ +/******************************************************************************/ +void printLine( char *line, WORD16 loc, WORD16 val, LINESTYLE_T linestyle ) +{ + char rlc; + + if( listfile == NULL ) + { + save_error_count = 0; + return; + } + + printPageBreak(); + + list_lineno++; + page_lineno++; + + if (reloc == 0) + { + rlc = ' '; + } + else + { + rlc = '*'; + } + + switch( linestyle ) + { + default: + case LINE: + fprintf(listfile, "%5d ", lineno ); + fputs( line, listfile ); + listed = TRUE; + break; + + case LINE_VAL: + fprintf(listfile, "%5d %4.4o ", lineno, val ); + fputs( line, listfile ); + listed = TRUE; + break; + + case LINE_LOC_VAL: + if( !listed ) + { + if( indirect_generated ) + { + fprintf( listfile, "%5d %5.5o%c %4.4o@ ", lineno, loc, rlc, val ); + } + else + { + fprintf( listfile, "%5d %5.5o%c %4.4o ", lineno, loc, rlc, val ); + } + fputs( line, listfile ); + listed = TRUE; + } + else + { + fprintf( listfile, " %5.5o%c %4.4o\n", loc, rlc, val ); + } + break; + + case LOC_VAL: + fprintf( listfile, " %5.5o%c %4.4o\n", loc, rlc, val ); + break; + } + printErrorMessages(); +} /* printLine() */ + + +/******************************************************************************/ +/* */ +/* Function: printErrorMessages */ +/* */ +/* Synopsis: Output any error messages from the current list of errors. */ +/* */ +/******************************************************************************/ +void printErrorMessages() +{ + WORD16 ix; + WORD16 iy; + + if( listfile != NULL ) + { + /* If any errors, display them now. */ + for( iy = 0; iy < save_error_count; iy++ ) + { + printPageBreak(); + fprintf( listfile, "%-18.18s", error_list[iy].mesg ); + if( error_list[iy].col >= 0 ) + { + for( ix = 0; ix < error_list[iy].col; ix++ ) + { + if( line[ix] == '\t' ) + { + putc( '\t', listfile ); + } + else + { + putc( ' ', listfile ); + } + } + fputs( "^", listfile ); + list_lineno++; + page_lineno++; + } + fputs( "\n", listfile ); + } + } + save_error_count = 0; +} /* printErrorMessages() */ + + +/******************************************************************************/ +/* */ +/* Function: endOfBinary */ +/* */ +/* Synopsis: Outputs both literal tables at the end of a binary segment. */ +/* */ +/******************************************************************************/ +void endOfBinary() +{ + /* Punch page 0 also. */ + punchLiteralPool( cp, 1 ); + if( error_in_line ) + { + listed = TRUE; + clc = ( clc & 070000 ) + (( clc - 1 ) & 07777 ); + errorMessage( &end_of_file, -1 ); + clc = ( clc & 070000 ) + (( clc + 1 ) & 07777 ); + } + else + { + listLine(); /* List line if not done yet. */ + } + return; +} /* endOfBinary() */ + + +/******************************************************************************/ +/* */ +/* Function: punchChecksum */ +/* */ +/* Synopsis: Output a checksum if the current mode requires it and an */ +/* object file exists. */ +/* */ +/******************************************************************************/ +void punchChecksum() +{ + /* If the assembler has output any BIN data output the checksum. */ + if( binary_data_output && !rim_mode ) + { + punchLocObject( 0, checksum ); + } + binary_data_output = FALSE; + checksum = 0; +} /* punchChecksum() */ + + +/******************************************************************************/ +/* */ +/* Function: punchLeader */ +/* */ +/* Synopsis: Generate 2 feet of leader on object file, as per DEC */ +/* documentation. Paper tape has 10 punches per inch. */ +/* */ +/******************************************************************************/ +void punchLeader( int count ) +{ + int ix; + + /* If value is zero, set to the default of 2 feet of leader. */ + count = ( count == 0 ) ? 240 : count; + + if( objectfile != NULL ) + { + for( ix = 0; ix < count; ix++ ) + { + fputc( 0200, objectfile ); + } + } +} /* punchLeader() */ + + +/******************************************************************************/ +/* */ +/* Function: punchOrigin */ +/* */ +/* Synopsis: Output an origin to the object file. */ +/* */ +/******************************************************************************/ +void punchOrigin( WORD16 loc ) +{ + punchObject((( loc >> 6 ) & 0077 ) | 0100 ); + punchObject( loc & 0077 ); +} /* punchOrigin() */ + + +/******************************************************************************/ +/* */ +/* Function: punchObject */ +/* */ +/* Synopsis: Put one character to object file and include it in checksum. */ +/* */ +/******************************************************************************/ +void punchObject( WORD16 val ) +{ + val &= 0377; + if( objectfile != NULL ) + { + fputc( val, objectfile ); + checksum += val; + } + binary_data_output = TRUE; +} /* punchObject() */ + + +/******************************************************************************/ +/* */ +/* Function: punchOutObject */ +/* */ +/* Synopsis: Output the current line and then then punch value to the */ +/* object file. */ +/* */ +/******************************************************************************/ +void punchOutObject( WORD16 loc, WORD16 val ) +{ + /* Adding reloc makes printout agree with PAL8 where is prints the */ + /* relocated address, not the address in the BIN file */ + printLine( line,( ( field | loc ) + reloc ), val, LINE_LOC_VAL ); + punchLocObject( loc, val ); +} /* punchOutObject() */ + +/******************************************************************************/ +/* */ +/* Function: punchLocObject */ +/* */ +/* Synopsis: Output the word (with origin if rim format) to the object file.*/ +/* */ +/******************************************************************************/ +void punchLocObject( WORD16 loc, WORD16 val ) +{ + if( rim_mode ) + { + punchOrigin( loc ); + } + punchObject(( val >> 6 ) & 0077 ); + punchObject( val & 0077 ); +} /* punchLocObject() */ + + +/******************************************************************************/ +/* */ +/* Function: punchLiteralPool */ +/* */ +/* Synopsis: Output the current page data. */ +/* */ +/******************************************************************************/ +void punchLiteralPool( LPOOL_T *p, BOOL punch_page0 ) +{ + WORD16 loc; + WORD16 tmplc; + int lpool_page = 0; /* Silence false uninitialized error from GCC */ + int i; + + for (i = MAX_PAGES-1; i >= 0; i--) { + lpool_page = (i << 7) & 07600; + + if ( p[i].loc != p[i].last_punched && (punch_page0 || lpool_page != 0) ) + { + if( !rim_mode ) + { + /* Put out origin if not in rim mode. */ + punchOrigin( p[i].loc | lpool_page ); + } + /* Put the literals in the object file. */ + for( loc = p[i].loc; loc < p[i].last_punched; loc++ ) + { + tmplc = loc + lpool_page; + printLine( line, (field | tmplc), p[i].pool[loc], LOC_VAL ); + punchLocObject( tmplc, p[i].pool[loc] ); + } + p[i].last_punched = p[i].loc; + } + } +} /* punchLiteralPool() */ + + +/******************************************************************************/ +/* */ +/* Function: insertLiteral */ +/* */ +/* Synopsis: Add a value to the given literal pool if not already in pool. */ +/* Return the location of the value in the pool. */ +/* */ +/******************************************************************************/ +WORD16 insertLiteral( LPOOL_T *pool, WORD16 value, int fieldpage_index ) +{ + WORD16 ix; + LPOOL_T *p; + + p = &pool[fieldpage_index]; + + /* Search the literal pool for any occurence of the needed value. */ + ix = PAGE_SIZE - 1; + while( ix >= p->loc && p->pool[ix] != value ) + { + ix--; + } + + /* Check if value found in literal pool. If not, then insert value. */ + if( ix < p->loc ) + { + (p->loc)--; + p->pool[p->loc] = value; + ix = p->loc; + if( max_page_used[fieldpage_index] >= p->loc ) { + if ( (fieldpage_index & 017) == 0 ) + { + errorMessage( &pz_literal_overflow, -1 ); + } + else + { + errorMessage( &literal_overflow, -1 ); + } + } + } + return( ix ); +} /* insertLiteral() */ + + +/******************************************************************************/ +/* */ +/* Function: printSymbolTable */ +/* */ +/* Synopsis: Output the symbol table. */ +/* */ +/******************************************************************************/ +void printSymbolTable() +{ + int col; + int cx; + char *fmt; + int ix; + char mark; + int page; + int row; + int symbol_base; + int symbol_lines; + + symbol_base = number_of_fixed_symbols; + + for( page=0, list_lineno=0, col=0, ix=symbol_base; ix < symbol_top; page++ ) + { + topOfForm( list_title, s_symtable ); + symbol_lines = LIST_LINES_PER_PAGE - page_lineno; + + for( row = 0; page_lineno < LIST_LINES_PER_PAGE && ix < symbol_top; row++) + { + list_lineno++; + page_lineno++; + fprintf( listfile, "%5d", list_lineno ); + + for( col = 0; col < SYMBOL_COLUMNS && ix < symbol_top; col++ ) + { + /* Get index of symbol for the current line and column */ + cx = symbol_lines * ( SYMBOL_COLUMNS * page + col ) + row; + cx += symbol_base; + + /* Make sure that there is a symbol to be printed. */ + if( number_of_fixed_symbols <= cx && cx < symbol_top ) + { + switch( symtab[cx].type & LABEL ) + { + case LABEL: + fmt = " %c%-6.6s %5.5o "; + break; + + default: + fmt = " %c%-6.6s %4.4o "; + break; + } + + switch( symtab[cx].type & ( DEFINED | REDEFINED )) + { + case UNDEFINED: + mark = '?'; + break; + + case REDEFINED: + mark = '#'; + break; + + default: + mark = ' '; + break; + } + fprintf( listfile, fmt, mark, symtab[cx].name, symtab[cx].val ); + ix++; + } + } + fprintf( listfile, "\n" ); + } + } +} /* printSymbolTable() */ + + +/******************************************************************************/ +/* */ +/* Function: printPermanentSymbolTable */ +/* */ +/* Synopsis: Output the permanent symbol table to a file suitable for */ +/* being input after the EXPUNGE pseudo-op. */ +/* */ +/******************************************************************************/ +void printPermanentSymbolTable() +{ + int ix; + FILE *permfile; + char *s_type; + + if(( permfile = fopen( permpathname, "w" )) == NULL ) + { + exit( 2 ); + } + + fprintf( permfile, "/ PERMANENT SYMBOL TABLE\n/\n" ); + fprintf( permfile, " EXPUNGE\n/\n" ); + /* Print the memory reference instructions first. */ + s_type = "FIXMRI"; + for( ix = 0; ix < symbol_top; ix++ ) + { + if( M_MRI( symtab[ix].type )) + { + fprintf( permfile, "%-7s %s=%4.4o\n", + s_type, symtab[ix].name, symtab[ix].val ); + } + } + + s_type = " "; + for( ix = 0; ix < symbol_top; ix++ ) + { + if( M_FIXED( symtab[ix].type )) + { + if( !M_MRI( symtab[ix].type ) && !M_PSEUDO( symtab[ix].type )) + { + fprintf( permfile, "%-7s %s=%4.4o\n", + s_type, symtab[ix].name, symtab[ix].val ); + } + } + } + fprintf( permfile, "/\n FIXTAB\n" ); + fclose( permfile ); +} /* printPermanentSymbolTable() */ + + +/******************************************************************************/ +/* */ +/* Function: printCrossReference */ +/* */ +/* Synopsis: Output a cross reference (concordance) for the file being */ +/* assembled. */ +/* */ +/******************************************************************************/ +void printCrossReference() +{ + int ix; + int symbol_base; + int xc; + int xc_index; + int xc_refcount; + int xc_cols; + + /* Force top of form for first page. */ + page_lineno = LIST_LINES_PER_PAGE; + + list_lineno = 0; + symbol_base = number_of_fixed_symbols; + + for( ix = symbol_base; ix < symbol_top; ix++ ) + { + list_lineno++; + page_lineno++; + if( page_lineno >= LIST_LINES_PER_PAGE ) + { + topOfForm( list_title, s_xref ); + } + + fprintf( listfile, "%5d", list_lineno ); + + /* Get reference count & index into concordance table for this symbol. */ + xc_refcount = symtab[ix].xref_count; + xc_index = symtab[ix].xref_index; + /* Determine how to label symbol on concordance. */ + switch( symtab[ix].type & ( DEFINED | REDEFINED )) + { + case UNDEFINED: + fprintf( listfile, " U "); + break; + + case REDEFINED: + fprintf( listfile, " M %5d ", xreftab[xc_index] ); + break; + + default: + fprintf( listfile, " A %5d ", xreftab[xc_index] ); + break; + } + fprintf( listfile, "%-6.6s ", symtab[ix].name ); + + /* Output the references, 8 numbers per line after symbol name. */ + for( xc_cols = 0, xc = 1; xc < xc_refcount + 1; xc++, xc_cols++ ) + { + if( xc_cols >= XREF_COLUMNS ) + { + xc_cols = 0; + page_lineno++; + if( page_lineno >= LIST_LINES_PER_PAGE ) + { + topOfForm( list_title, s_xref); + } + list_lineno++; + fprintf( listfile, "\n%5d%-19s", list_lineno, " " ); + } + fprintf( listfile, " %5d", xreftab[xc_index + xc] ); + } + fprintf( listfile, "\n" ); + } +} /* printCrossReference() */ + + +/******************************************************************************/ +/* */ +/* Function: topOfForm */ +/* */ +/* Synopsis: Prints title and sub-title on top of next page of listing. */ +/* */ +/******************************************************************************/ +void topOfForm( char *title, char *sub_title ) +{ + char temp[10]; + + list_pageno++; + strcpy( temp, s_page ); + sprintf( temp, "%s %d", s_page, list_pageno ); + + /* Output a top of form if not the first page of the listing. */ + if( list_pageno > 1 ) + { + fprintf( listfile, "\f" ); + } + fprintf( listfile, "\n\n\n %-63s %10s\n", title, temp ); + + /* Reset the current page line counter. */ + page_lineno = 3; + if( sub_title != NULL ) + { + fprintf( listfile, "%80s\n", sub_title ); + page_lineno++; + } + else + { + fprintf( listfile, "\n" ); + page_lineno++; + } + fprintf( listfile, "\n" ); + page_lineno++; +} /* topOfForm() */ + + +/******************************************************************************/ +/* */ +/* Function: lexemeToName */ +/* */ +/* Synopsis: Convert the current lexeme into a string. */ +/* */ +/******************************************************************************/ +char *lexemeToName( char *name, int from, int term ) +{ + int to; + + to = 0; + + while( from < term && to < ( SYMLEN - 1 )) + { + name[to++] = toupper( line[from++] ); + } + + while( to < SYMLEN ) + { + name[to++] = '\0'; + } + return( name ); +} /* lexemeToName() */ + +/******************************************************************************/ +/* */ +/* Function: defineLexeme */ +/* */ +/* Synopsis: Put lexeme into symbol table with a value. */ +/* */ +/******************************************************************************/ +SYM_T *defineLexeme( int start, /* start of lexeme being defined. */ + int term, /* end+1 of lexeme being defined. */ + WORD16 val, /* value of lexeme being defined. */ + SYMTYP type ) /* how symbol is being defined. */ +{ + char name[SYMLEN]; + + lexemeToName( name, start, term); + return( defineSymbol( name, val, type, start )); +} /* defineLexeme() */ + + +/******************************************************************************/ +/* */ +/* Function: defineSymbol */ +/* */ +/* Synopsis: Define a symbol in the symbol table, enter symbol name if not */ +/* not already in table. */ +/* */ +/******************************************************************************/ +SYM_T *defineSymbol( char *name, WORD16 val, SYMTYP type, WORD16 start ) +{ + SYM_T *sym; + int xref_count; + + if( strlen( name ) < 1 ) + { + return( &sym_undefined ); /* Protect against non-existent names. */ + } + sym = lookup( name ); + /* OS/8 PAL8 seems to allow permanent symbold to be redefined without error */ + if( ( M_FIXED( sym->type ) && pass == 1 && perm_redef_error ) || + (M_PERM_REDEFINED( sym->type ) && (sym->val != val)) ) + { + type |= PERM_REDEFINED; + } + + xref_count = 0; /* Set concordance for normal defintion. */ + + if( M_DEFINED( sym->type )) + { + if( pass == 2 && ( (sym->val & 07777) != (val & 07777) || M_PERM_REDEFINED(sym->type)) ) + { + /* Generate diagnostic if redefining a symbol. */ + if( M_PERM_REDEFINED( sym->type ) && (M_LABEL(sym->type) || M_LABEL(type)) ) + { + errorSymbol( &illegal_redefine, sym->name, start ); + } else + { + /* Generate diagnostic if redefining a symbol. */ + if( M_REDEFINED( sym->type ) && (M_LABEL(sym->type) || M_LABEL(type)) ) + { + errorSymbol( &redefined_symbol, sym->name, start ); + } + } + type = type | REDEFINED; + sym->xref_count++; /* Referenced suymbol, count it. */ + xref_count = sym->xref_count; + } + } + + if( pass == 2 && xref ) + { + /* Put the definition line number in the concordance table. */ + /* Defined symbols are not counted as references. */ + xreftab[sym->xref_index] = lineno; + /* Put the line number in the concordance table. */ + xreftab[sym->xref_index + xref_count] = lineno; + } + + /* Now set the value and the type. */ + sym->val = ( M_LABEL(type) ) ? val : val & 07777; + sym->type = ( pass == 1 ) ? ( type | CONDITION ) : type; + return( sym ); +} /* defineSymbol() */ + + +/******************************************************************************/ +/* */ +/* Function: lookup */ +/* */ +/* Synopsis: Find a symbol in table. If not in table, enter symbol in */ +/* table as undefined. Return address of symbol in table. */ +/* */ +/******************************************************************************/ +SYM_T *lookup( char *name ) +{ + int ix; /* Insertion index */ + int lx; /* Left index */ + int rx; /* Right index */ + + /* First search the permanent symbols. */ + lx = 0; + ix = binarySearch( name, lx, number_of_fixed_symbols ); + + /* If symbol not in permanent symbol table. */ + if( ix < 0 ) + { + /* Now try the user symbol table. */ + ix = binarySearch( name, number_of_fixed_symbols, symbol_top ); + + /* If symbol not in user symbol table. */ + if( ix < 0 ) + { + /* Must put symbol in table if index is negative. */ + ix = ~ix; + if( symbol_top + 1 >= SYMBOL_TABLE_SIZE ) + { + errorSymbol( &symbol_table_full, name, lexstart ); + exit( 1 ); + } + + for( rx = symbol_top; rx >= ix; rx-- ) + { + symtab[rx + 1] = symtab[rx]; + } + symbol_top++; + + /* Enter the symbol as UNDEFINED with a value of zero. */ + strcpy( symtab[ix].name, name ); + symtab[ix].type = UNDEFINED; + symtab[ix].val = 0; + symtab[ix].xref_count = 0; + if( xref && pass == 2 ) + { + xreftab[symtab[ix].xref_index] = 0; + } + } + } + + return( &symtab[ix] ); /* Return the location of the symbol. */ +} /* lookup() */ + + +/******************************************************************************/ +/* */ +/* Function: binarySearch */ +/* */ +/* Synopsis: Searches the symbol table within the limits given. If the */ +/* symbol is not in the table, it returns the insertion point. */ +/* */ +/******************************************************************************/ +int binarySearch( char *name, int start, int symbol_count ) +{ + int lx; /* Left index */ + int mx; /* Middle index */ + int rx; /* Right index */ + int compare; /* Results of comparison */ + + lx = start; + rx = symbol_count - 1; + while( lx <= rx ) + { + mx = ( lx + rx ) / 2; /* Find center of search area. */ + + compare = strcmp( name, symtab[mx].name ); + + if( compare < 0 ) + { + rx = mx - 1; + } + else if( compare > 0 ) + { + lx = mx + 1; + } + else + { + return( mx ); /* Found a match in symbol table. */ + } + } /* end while */ + return( ~lx ); /* Return insertion point. */ +} /* binarySearch() */ + + +/******************************************************************************/ +/* */ +/* Function: compareSymbols */ +/* */ +/* Synopsis: Used to presort the symbol table when starting assembler. */ +/* */ +/******************************************************************************/ +int compareSymbols( const void *a, const void *b ) +{ + return( strcmp( ((SYM_T *) a)->name, ((SYM_T *) b)->name )); +} /* compareSymbols() */ + + +/******************************************************************************/ +/* */ +/* Function: evalSymbol */ +/* */ +/* Synopsis: Get the pointer for the symbol table entry if exists. */ +/* If symbol doesn't exist, return a pointer to the undefined sym */ +/* */ +/******************************************************************************/ +SYM_T *evalSymbol() +{ + char name[SYMLEN]; + SYM_T *sym; + + sym = lookup( lexemeToName( name, lexstart, lexterm )); + + /* The symbol goes in the concordance iff it is in a different position in */ + /* the assembler source file. */ + if( lexstart != last_xref_lexstart || lineno != last_xref_lineno ) + { + sym->xref_count++; /* Count the number of references to symbol. */ + last_xref_lexstart = lexstart; + last_xref_lineno = lineno; + + /* Put the line number in the concordance table. */ + if( xref && pass == 2 ) + { + xreftab[sym->xref_index + sym->xref_count] = lineno; + } + } + + return( sym ); +} /* evalSymbol() */ + + +/******************************************************************************/ +/* */ +/* Function: moveToEndOfLine */ +/* */ +/* Synopsis: Move the parser input to the end of the current input line. */ +/* */ +/******************************************************************************/ +void moveToEndOfLine() +{ + while( !isend( line[cc] )) cc++; + lexstart = cc; + lexterm = cc; + lexstartprev = lexstart; +} /* moveToEndOfLine() */ + +/******************************************************************************/ +/* */ +/* Function: nextLexeme */ +/* */ +/* Synopsis: Get the next lexical element from input line. */ +/* */ +/******************************************************************************/ +void nextLexeme() +{ + /* Save start column of previous lexeme for diagnostic messages. */ + lexstartprev = lexstart; + lextermprev = lexterm; + + while( is_blank( line[cc] )) { cc++; } + lexstart = cc; + + if( isalnum( line[cc] )) + { + while( isalnum( line[cc] )) { cc++; } + } + else if( isend( line[cc] )) + { + /* End-of-Line, don't advance cc! */ + } + else + { + switch( line[cc] ) + { + case '"': /* Quoted letter */ + if( cc + 2 < maxcc ) + { + cc++; + cc++; + } + else + { + errorMessage( &no_literal_value, lexstart ); + cc++; + } + break; + + case '/': /* Comment, don't advance cc! */ + break; + + default: /* All other punctuation. */ + cc++; + break; + } + } + lexterm = cc; +} /* nextLexeme() */ + + +/******************************************************************************/ +/* */ +/* Function: nextLexBlank */ +/* */ +/* Synopsis: Used to prevent illegal blanks in expressions. */ +/* */ +/******************************************************************************/ +void nextLexBlank() +{ + nextLexeme(); + if( is_blank( delimiter )) + { + errorMessage( &illegal_blank, lexstart - 1 ); + } + delimiter = line[lexterm]; +} /* nextLexBlank() */ + + +/******************************************************************************/ +/* */ +/* Function: pseudoOperators */ +/* */ +/* Synopsis: Process pseudo-ops (directives). */ +/* */ +/******************************************************************************/ +BOOL pseudoOperators( PSEUDO_T val ) +{ + int count, count2; + int delim; + int index; + int ix; + int lexstartsave; + WORD16 newfield; + WORD16 oldclc; + int pack; + BOOL status; + SYM_T *sym; + FILE *temp; + int term; + WORD16 value; + char os8_name[8]; + int reloc_clc; + + status = TRUE; + switch( (PSEUDO_T) val ) + { + case ASCII: + /* added 18-Jan-2003 PNT -- derived from TEXT */ + delim = line[lexstart]; + index = lexstart + 1; + while( line[index] != delim && !isend( line[index] )) + { + punchOutObject( clc, (line[index] & 127) | 128 ); + incrementClc(); + index++; + } + if( isend( line[index] )) + { + cc = index; + lexterm = cc; + errorMessage( &text_string, cc ); + } + else + { + cc = index + 1; + lexterm = cc; + } + nextLexeme(); + break; + + case BANK: + errorSymbol( &no_pseudo_op, "BANK", lexstartprev ); + /* should select a different 32K out of 128K */ + break; + + case BINPUNCH: + /* If there has been data output and this is a mode switch, set up to */ + /* output data in BIN mode. */ + if( binary_data_output && rim_mode ) + { + clearLiteralTable(); + punchLeader( 8 ); /* Generate a short leader/trailer. */ + checksum = 0; + binary_data_output = FALSE; + } + rim_mode = FALSE; + break; + + case DECIMAL: + radix = 10; + break; + + case DUBL: + inputDubl(); + break; + + case EJECT: + page_lineno = LIST_LINES_PER_PAGE; /* This will force a page break. */ + status = FALSE; /* This will force reading of next line */ + break; + + case ENPUNCH: + if( pass == 2 ) + { + objectfile = objectsave; + } + break; + + case EXPUNGE: /* Erase symbol table */ + if( pass == 1 ) + { + symtab[0] = sym_undefined; + symbol_top = 0; + number_of_fixed_symbols = symbol_top; + fixed_symbols = &symtab[symbol_top - 1]; + + /* Enter the pseudo-ops into the symbol table. */ + for( ix = 0; ix < DIM( pseudo ); ix++ ) + { + defineSymbol( pseudo[ix].name, pseudo[ix].val, pseudo[ix].type, 0 ); + } + /* Enter the really permanent symbols into the table. */ + /* Also make them part of the permanent symbol table. */ + for( ix = 0; ix < DIM( really_permanent_symbols ); ix++ ) + { + defineSymbol( really_permanent_symbols[ix].name, + really_permanent_symbols[ix].val, + really_permanent_symbols[ix].type | DEFFIX , 0 ); + } + number_of_fixed_symbols = symbol_top; + fixed_symbols = &symtab[symbol_top - 1]; + + } + break; + + case FIELD: + /* Punch page 0 also */ + punchLiteralPool( cp, 1 ); + newfield = field >> 12; + lexstartsave = lexstartprev; + if( isdone( line[lexstart] )) + { + newfield += 1; /* Blank FIELD directive. */ + } + else + { + newfield = (getExpr())->val; /* FIELD with argument. */ + } + + if( rim_mode ) + { + errorMessage( &in_rim_mode, lexstartsave ); /* Can't change fields. */ + } + else if( newfield > 7 || newfield < 0 ) + { + errorMessage( &illegal_field_value, lexstartprev ); + } + else + { + value = (( newfield & 0007 ) << 3 ) | 00300; + punchObject( value ); + if( objectfile != NULL ) /* Only fix checksum if punching */ + { + checksum -= value; /* Field punches are not added to checksum. */ + } + field = newfield << 12; + } + + clc = 0200 | field; + fieldlc = clc & 07777; + + if( !rim_mode ) + { + punchOrigin( clc ); + } + + clearLiteralTable(); + + break; + + case FIXMRI: + if( line[lexterm] == '=' && isalpha( line[lexstart] )) + { + lexstartsave = lexstart; + term = lexterm; + nextLexeme(); /* Skip symbol. */ + nextLexeme(); /* Skip trailing = */ + defineLexeme( lexstartsave, term, getExprs(), MRI ); + } + else + { + errorLexeme( &symbol_syntax, lexstart ); + nextLexeme(); /* Skip symbol. */ + nextLexeme(); /* Skip trailing = */ + (void) getExprs(); /* Skip expression. */ + } + break; + + case FIXTAB: + if (pass == 1) /* Only fix on first pass, on second all are defined */ + { + /* Mark all current symbols as permanent symbols. */ + for( ix = 0; ix < symbol_top; ix++ ) + { + symtab[ix].type = symtab[ix].type | FIXED; + } + number_of_fixed_symbols = symbol_top; + fixed_symbols = &symtab[symbol_top - 1]; + + /* Re-sort the symbol table */ + qsort( symtab, symbol_top, sizeof(symtab[0]), compareSymbols ); + } + break; + + case FLTG: + inputFltg(); + /* errorSymbol( &no_pseudo_op, "FLTG", lexstartprev ); */ + break; + + case IFDEF: + if( isalpha( line[lexstart] )) + { + sym = evalSymbol(); + nextLexeme(); + if( M_DEFINED_CONDITIONALLY( sym->type )) + { + conditionTrue(); + } + else + { + conditionFalse(); + } + } + else + { + errorLexeme( &label_syntax, lexstart ); + } + break; + + case IFNDEF: + if( isalpha( line[lexstart] )) + { + sym = evalSymbol(); + nextLexeme(); + if( M_DEFINED_CONDITIONALLY( sym->type )) + { + conditionFalse(); + } + else + { + conditionTrue(); + } + } + else + { + errorLexeme( &label_syntax, lexstart ); + } + break; + + case IFNZERO: + if( getExprs() == 0 ) + { + conditionFalse(); + } + else + { + conditionTrue(); + } + break; + + case IFZERO: + if( getExprs() == 0 ) + { + conditionTrue(); + } + else + { + conditionFalse(); + } + break; + + case NOPUNCH: + if( pass == 2 ) + { + objectfile = NULL; + } + break; + + case OCTAL: + radix = 8; + break; + + case PAGE: + reloc_clc = clc + reloc; + punchLiteralPool( cp, 0 ); + oldclc = clc; + if( isdone( line[lexstart] )) + { + clc = (( reloc_clc + 0177 ) & 077600) - reloc; /* No argumnet. */ + fieldlc = clc & 07777; + } + else + { + value = (getExpr())->val; + clc = field + (( value & 037 ) << 7 ) - reloc; + fieldlc = clc & 07777; + } + testForLiteralCollision( clc + reloc ); + + if( !rim_mode && clc != oldclc ) + { + punchOrigin( clc ); + } + break; + + case PAUSE: + break; + + case RELOC: + if( isdone( line[lexstart] )) + { + reloc = 0; /* Blank RELOC directive. */ + } + else + { + value = (getExpr())->val; /* RELOC with argument. */ + reloc = (value & 07777) - ( clc & 07777); + } + break; + + case RIMPUNCH: + /* If the assembler has output any BIN data, output the literal tables */ + /* and the checksum for what has been assembled and setup for RIM mode. */ + if( binary_data_output && !rim_mode ) + { + endOfBinary(); + clearLiteralTable(); + punchChecksum(); + punchLeader( 8 ); /* Generate a short leader/trailer. */ + } + rim_mode = TRUE; + break; + + case SEGMNT: + punchLiteralPool( cp, 0 ); + if( isdone( line[lexstart] )) + { /* No argument. */ + clc = ( clc & 06000 ) + 02000; + fieldlc = clc & 07777; + } + else + { + getExpr(); + clc = ( val & 003 ) << 10; + fieldlc = clc & 07777; + } + if( !rim_mode ) + { + punchOrigin( clc ); + } + testForLiteralCollision( clc ); + break; + + case TEXT: + delim = line[lexstart]; + pack = 0; + count = 0; + index = lexstart + 1; + while( line[index] != delim && !isend( line[index] )) + { + pack = ( pack << 6 ) | ( line[index] & 077 ); + count++; + if( count > 1 ) + { + punchOutObject( clc, pack ); + incrementClc(); + count = 0; + pack = 0; + } + index++; + } + + if( count != 0 ) + { + punchOutObject( clc, pack << 6 ); + incrementClc(); + } + else + { + punchOutObject( clc, 0 ); + incrementClc(); + } + + if( isend( line[index] )) + { + cc = index; + lexterm = cc; + errorMessage( &text_string, cc ); + } + else + { + cc = index + 1; + lexterm = cc; + } + nextLexeme(); + break; + + case FILENAME: + memset(os8_name, 0, sizeof(os8_name)); + delimiter=line[lexstart]; + if (delimiter != '.') + { + for (index = lexstart, count = 0; index < lexterm && count < 6; index++) + { + os8_name[count++] = line[index]; + } + delimiter=line[lexterm]; + if (delimiter == '.') + { + nextLexeme(); /* Skip . */ + } + } + nextLexeme(); + if (delimiter == '.') + { + for (index = lexstart, count = 6; index < lexterm && count < 8; index++) + { + os8_name[count++] = line[index]; + } + } + + pack = 0; + count = 0; + for (count2 = 0; count2 < 8; count2++) + { + pack = ( pack << 6 ) | ( os8_name[count2] & 077 ); + count++; + if( count > 1 ) + { + punchOutObject( clc, pack ); + incrementClc(); + count = 0; + pack = 0; + } + } + nextLexeme(); + break; + + case DEVICE: + memset(os8_name, 0, sizeof(os8_name)); + for (index = lexstart, count = 0; index < lexterm && count < 4; index++) + { + os8_name[count++] = line[index]; + } + + pack = 0; + count = 0; + for (count2 = 0; count2 < 4; count2++) + { + pack = ( pack << 6 ) | ( os8_name[count2] & 077 ); + count++; + if( count > 1 ) + { + punchOutObject( clc, pack ); + incrementClc(); + count = 0; + pack = 0; + } + } + + nextLexeme(); + break; + + case TITLE: + delim = line[lexstart]; + ix = lexstart + 1; + /* Find string delimiter. */ + do + { + if( list_title[ix] == delim && list_title[ix + 1] == delim ) + { + ix++; + } + ix++; + } while( line[ix] != delim && !isend(line[ix]) ); + + if( line[ix] == delim ) + { + count = 0; + ix = lexstart + 1; + do + { + if( list_title[ix] == delim && list_title[ix + 1] == delim ) + { + ix++; + } + list_title[count] = line[ix]; + count++; + ix++; + list_title[count] = '\0'; + } while( line[ix] != delim && !isend(line[ix]) ); + + if( strlen( list_title ) > TITLELEN ) + { + list_title[TITLELEN] = '\0'; + } + + cc = ix + 1; + lexterm = cc; + page_lineno = LIST_LINES_PER_PAGE;/* Force top of page for new titles. */ + list_title_set = TRUE; + } + else + { + cc = ix; + lexterm = cc; + errorMessage( &text_string, cc ); + } + + nextLexeme(); + break; + + case XLIST: + if( isdone( line[lexstart] )) + { + temp = listfile; /* Blank XLIST directive. */ + listfile = listsave; + listsave = temp; + } + else + { + if( (getExpr())->val == 0 ) + { + if( listfile == NULL ) + { + listfile = listsave; + listsave = NULL; + } + } + else + { + if( listfile != NULL ) + { + listsave = listfile; + listfile = NULL; + } + } + } + break; + + case ZBLOCK: + value = (getExpr())->val; + if( value < 0 ) + { + errorMessage( &zblock_too_small, lexstartprev ); + } + else if( value + ( clc & 07777 ) - 1 > 07777 ) + { + errorMessage( &zblock_too_large, lexstartprev ); + } + else + { + for( ; value > 0; value-- ) + { + punchOutObject( clc, 0 ); + incrementClc(); + } + } + + break; + + default: + break; + } /* end switch for pseudo-ops */ + return( status ); +} /* pseudoOperators() */ + + +/******************************************************************************/ +/* */ +/* Function: conditionFalse */ +/* */ +/* Synopsis: Called when a false conditional has been evaluated. */ +/* Lex should be the opening <; ignore all text until */ +/* the closing >. */ +/* */ +/******************************************************************************/ +void conditionFalse() +{ + int level; + + if( line[lexstart] == '<' ) + { + /* Invariant: line[cc] is the next unexamined character. */ + level = 1; + while( level > 0 ) + { + if( isend( line[cc] )) + { + readLine(); + } + else + { + switch( line[cc] ) + { + case '>': + level--; + cc++; + break; + + case '<': + level++; + cc++; + break; + + case '$': + level = 0; + cc++; + break; + + default: + cc++; + break; + } /* end switch */ + } /* end if */ + } /* end while */ + nextLexeme(); + } + else + { + errorMessage( <_expected, lexstart ); + } +} /* conditionFalse() */ + +/******************************************************************************/ +/* */ +/* Function: conditionTrue */ +/* */ +/* Synopsis: Called when a true conditional has been evaluated. */ +/* Lex should be the opening <; skip it and setup for */ +/* normal assembly. */ +/* */ +/******************************************************************************/ +void conditionTrue() +{ + if( line[lexstart] == '<' ) + { + nextLexeme(); /* Skip the opening '<' */ + } + else + { + errorMessage( <_expected, lexstart ); + } +} /* conditionTrue() */ + + +/******************************************************************************/ +/* */ +/* Function: errorLexeme */ +/* */ +/* Synopsis: Display an error message using the current lexical element. */ +/* */ +/******************************************************************************/ +void errorLexeme( EMSG_T *mesg, int col ) +{ + char name[SYMLEN]; + + errorSymbol( mesg, lexemeToName( name, lexstart, lexterm ), col ); +} /* errorLexeme() */ + + +/******************************************************************************/ +/* */ +/* Function: errorSymbol */ +/* */ +/* Synopsis: Display an error message with a given string. */ +/* */ +/******************************************************************************/ +void errorSymbol( EMSG_T *mesg, char *name, int col ) +{ + char linecol[12]; + char *s; + + if( pass == 2 ) + { + s = ( name == NULL ) ? "" : name ; + errors++; + sprintf( linecol, "(%d:%d)", lineno, col + 1 ); + fprintf( errorfile, "%s%-9s : error: %s \"%s\" at Loc = %5.5o\n", + filename, linecol, mesg->file, s, clc ); + saveError( mesg->list, col ); + } + error_in_line = TRUE; +} /* errorSymbol() */ + + +/******************************************************************************/ +/* */ +/* Function: errorMessage */ +/* */ +/* Synopsis: Display an error message without a name argument. */ +/* */ +/******************************************************************************/ +void errorMessage( EMSG_T *mesg, int col ) +{ + char linecol[12]; + + if( pass == 2 ) + { + errors++; + sprintf( linecol, "(%d:%d)", lineno, col + 1 ); + fprintf( errorfile, "%s%-9s : error: %s at Loc = %5.5o\n", + filename, linecol, mesg->file, clc ); + saveError( mesg->list, col ); + } + error_in_line = TRUE; +} /* errorMessage() */ + +/******************************************************************************/ +/* */ +/* Function: saveError */ +/* */ +/* Synopsis: Save the current error in a list so it may displayed after the */ +/* the current line is printed. */ +/* */ +/******************************************************************************/ +void saveError( char *mesg, int col ) +{ + if( save_error_count < DIM( error_list )) + { + error_list[save_error_count].mesg = mesg; + error_list[save_error_count].col = col; + save_error_count++; + } + error_in_line = TRUE; + + if( listed ) + { + printErrorMessages(); + } +} /* saveError() */ +/* End-of-File */ ADDED src/ptp2txt.c Index: src/ptp2txt.c ================================================================== --- /dev/null +++ src/ptp2txt.c @@ -0,0 +1,211 @@ +/* + * Program to convert between POSIX ASCII text files + * and the output of OS/8 PIP to the Paper Tape Punch. + + * The OS/8 paper tape punch format is: + * + * leader: a bunch of ASCII NUL chars to be ignored. + * ASCII with the 8th bit set, CR+LF line endings. + * trailer: a bunch of ASCII NUL chars to be ignored. + + * This program can be used as a filter from stdin to stdout or + * it will create a new file with name ending in .txt if going to + * POSIX text or .ptp if going to OS/8 PIP Paper Tape format. + + * If the program is called with the name "txt2ptp" then + * LTCOUNT (default 100) bytes of leader is prepended to the + * output file and LTCOUNT bytes of leader are appended. + * The 8th bit of every output character is set, and LF-only + * input is turned into CR+LF output. CR+LF input is passed + * as-is. + + * If called by any other name, the ASCII NUL character is + * ignored anywhere in the file, and the 8th bit is cleared. + * Line endings are untouched in this case. + + * This program helps work around the issue that the + * OS/8 Paper Tape reader handler assumes the last + * character in the buffer is junk, so that when you send + * a plain text file into PDP-8 SIMH OS/8 with the + * ptr device, the last character is lost. + */ + +/* + * Author: Bill Cattey + * License: The SIMH License: + + * Copyright © 2015-2017 + * by Bill Cattey et. ux. William Cattey et. ux. Poetnerd + + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + + * The above copyright notice and this permission notice shall be include + * in all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR + * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + + * Except as contained in this notice, the names of the authors + * above shall not be used in advertising or otherwise to promote + * the sale, use or other dealings in this Software without + * prior written authorization from those authors. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define BLOCK_SIZE 256 +#define TO_PTP 1 +#define TO_TXT 2 +#define LTCHAR '\0' +#define LTCOUNT 100 + +/* global variable: ltbuf */ +int global_ltbuf[LTCOUNT]; + + +void make_txt (FILE *fpin, FILE *fpout) +{ + int inchar, outchar; + int read_ct, n; + char *obuffp; + char ibuff[BLOCK_SIZE], obuff[BLOCK_SIZE]; + + while ((read_ct = fread (ibuff, sizeof(char), BLOCK_SIZE, fpin))) { + obuffp = obuff; + for (n = 0; n < read_ct; n++) { + inchar = *(ibuff + n); + if (inchar == LTCHAR) continue; + *obuffp++ = inchar & 0177; + } + fwrite (obuff, sizeof(char), obuffp - obuff, fpout); + } +} + +/* We could just create an empty buffer and output it, + but this is better if for some reason LTCHAR changes. */ +void init_ltbuf () +{ + int n; + + for (n = 0; n < LTCOUNT; n++) { + global_ltbuf[n] = LTCHAR; + } +} + +void make_lt (FILE *fpout) +{ + fwrite (global_ltbuf, sizeof(char), LTCOUNT, fpout); +} + +void make_ptp (FILE *fpin, FILE *fpout) +{ + int inchar, outchar, prior = '\0'; + int read_ct, n; + char *obuffp; + char ibuff[BLOCK_SIZE]; + /* Every \n might add a \r to the output. + Worst case is obuff doubles in size. */ + char obuff[2*BLOCK_SIZE]; + + make_lt (fpout); + + while ((read_ct = fread (ibuff, sizeof(char), BLOCK_SIZE, fpin))) { + obuffp = obuff; + for (n = 0; n < read_ct; n++) { + inchar = *(ibuff + n); + if (inchar == '\n' && prior != '\r') { + *obuffp++ = (char)('\r' | 0200); + } + *obuffp++ = inchar | 0200; + prior = inchar; + } + fwrite (obuff, sizeof(char), obuffp - obuff, fpout); + } + /* If we don't already have an EOF, add one. */ + if (inchar != '\032') { + fwrite ("\232", sizeof(char), 1, fpout); + } + make_lt (fpout); +} + + +void process_file (char *fname, int flag) +{ + FILE *fpin, *fpout; + + char *ofname; + char *fend; + + if (flag == TO_PTP) fend = ".ptp"; + else fend = ".txt"; + + ofname = malloc (((strlen (fname) + strlen(fend)) * sizeof (char)) + 1); + strcpy (ofname, fname); + strcat (ofname, fend); + + /* printf ("Filename is: %s.\n", ofname); */ + + if ((fpin = fopen (fname, "r")) == NULL) { + printf ("Open of input file %s failed with status %d. Skipping.\n", + fname, errno); + return; + } + if ((fpout = fopen (ofname, "w")) == NULL) { + printf ("Open of output file %s failed with status %d. Skipping.\n", + ofname, errno); + return; + } + if (flag == TO_PTP) + make_ptp (fpin, fpout); + else + make_txt (fpin, fpout); + + fclose (fpin); + fclose (fpout); + free (ofname); +} + +int main (int argc, char *argv[]) +{ + int i, flag; + char *ltbuf; + + if (strcmp (basename (argv[0]), "txt2ptp") == 0) { + /* printf ("Flag is TO_PTP"); */ + flag = TO_PTP; + init_ltbuf (); + } + else { + flag = TO_TXT; + } + + if (argc == 1) { + if (flag == TO_PTP) make_ptp (stdin, stdout); + else make_txt (stdin, stdout); + } + else { + for (i = 1; i < argc; i++) { + process_file (argv[i], flag); + } + } +} + Index: src/scanswitch.c ================================================================== --- src/scanswitch.c +++ src/scanswitch.c @@ -38,66 +38,19 @@ int main() { int i,j,k,switchscan[2], tmp; - struct bcm2835_peripheral gpio; - - // ------------ Find gpio address (different for Pi 2) ------------- - - gpio.addr_p = bcm_host_get_peripheral_address() + 0x200000; - - if (gpio.addr_p== 0x20200000) printf("scanswitch - RPi Plus\n"); - else printf("scanswitch - RPi 2\n"); - - if (map_peripheral(&gpio, 0) != 0) - { printf("Failed to map the physical GPIO registers into the virtual memory space.\n"); + + extern struct bcm2835_peripheral gpio; + if (map_gpio_for_pidp8i (1) != 0) + { printf("Failed to map the GPIO SoC peripheral into our VM space.\n"); return -1; } - - // initialise GPIO (all pins used as inputs, with pull-ups enabled on cols) - for (i=0;i #if defined(HAVE_DLOPEN) /* Dynamic Readline support */ #include #endif + +#ifdef PIDP8I +#include "gpio-common.h" // for start/stop_pidp8i_gpio_thread() +#include "PDP8/pidp8i.h" // for build_pidp8i_scp_cmd() +#endif #ifndef MAX #define MAX(a,b) (((a) >= (b)) ? (a) : (b)) #endif @@ -346,10 +348,11 @@ if ((cp = get_sim_sw (cp)) == NULL) return SCPE_INVSW #define GET_RADIX(val,dft) \ if (sim_switches & SWMASK ('O')) val = 8; \ else if (sim_switches & SWMASK ('D')) val = 10; \ else if (sim_switches & SWMASK ('H')) val = 16; \ + else if ((sim_switch_number >= 2) && (sim_switch_number <= 36)) val = sim_switch_number; \ else val = dft; /* Asynch I/O support */ #if defined (SIM_ASYNCH_IO) pthread_mutex_t sim_asynch_lock = PTHREAD_MUTEX_INITIALIZER; @@ -497,11 +500,16 @@ SCHTAB *get_rsearch (CONST char *cptr, int32 radix, SCHTAB *schptr); SCHTAB *get_asearch (CONST char *cptr, int32 radix, SCHTAB *schptr); int32 test_search (t_value *val, SCHTAB *schptr); static const char *get_glyph_gen (const char *iptr, char *optr, char mchar, t_bool uc, t_bool quote, char escape_char); -int32 get_switches (const char *cptr); +typedef enum { + SW_ERROR, /* Parse Error */ + SW_BITMASK, /* Bitmask Value or Not a switch */ + SW_NUMBER /* Numeric Value */ + } SWITCH_PARSE; +SWITCH_PARSE get_switches (const char *cptr, int32 *sw_val, int32 *sw_number); CONST char *get_sim_sw (CONST char *cptr); t_stat get_aval (t_addr addr, DEVICE *dptr, UNIT *uptr); t_value get_rval (REG *rptr, uint32 idx); void put_rval (REG *rptr, uint32 idx, t_value val); void fprint_help (FILE *st); @@ -554,10 +562,11 @@ DEVICE *sim_dflt_dev = NULL; UNIT *sim_clock_queue = QUEUE_LIST_END; int32 sim_interval = 0; int32 sim_switches = 0; +int32 sim_switch_number = 0; FILE *sim_ofile = NULL; TMLN *sim_oline = NULL; MEMFILE *sim_mfile = NULL; SCHTAB *sim_schrptr = FALSE; SCHTAB *sim_schaptr = FALSE; @@ -580,10 +589,14 @@ int32 sim_brk_ent = 0; int32 sim_brk_lnt = 0; int32 sim_brk_ins = 0; int32 sim_quiet = 0; int32 sim_step = 0; +char *sim_sub_instr = NULL; +char *sim_sub_instr_buf = NULL; +size_t sim_sub_instr_size = 0; +size_t *sim_sub_instr_off = NULL; static double sim_time; static uint32 sim_rtime; static int32 noqueue_time; volatile int32 stop_cpu = 0; static char **sim_argv; @@ -601,10 +614,11 @@ static int32 sim_goto_line[MAX_DO_NEST_LVL+1]; /* the current line number in the currently open do file */ static int32 sim_do_echo = 0; /* the echo status of the currently open do file */ static int32 sim_show_message = 1; /* the message display status of the currently open do file */ static int32 sim_on_inherit = 0; /* the inherit status of on state and conditions when executing do files */ static int32 sim_do_depth = 0; +static t_bool sim_cmd_echoed = FALSE; /* Command was emitted already prior to message output */ static int32 sim_on_check[MAX_DO_NEST_LVL+1]; static char *sim_on_actions[MAX_DO_NEST_LVL+1][SCPE_MAX_ERR+1]; static char sim_do_filename[MAX_DO_NEST_LVL+1][CBUFSIZE]; static const char *sim_do_ocptr[MAX_DO_NEST_LVL+1]; @@ -612,10 +626,17 @@ t_stat sim_last_cmd_stat; /* Command Status */ static SCHTAB sim_stabr; /* Register search specifier */ static SCHTAB sim_staba; /* Memory search specifier */ + +static DEBTAB sim_dflt_debug[] = { + {"EVENT", SIM_DBG_EVENT, "Event Dispatching"}, + {"ACTIVATE", SIM_DBG_ACTIVATE, "Event Scheduling"}, + {"AIO_QUEUE", SIM_DBG_AIO_QUEUE, "Asynchronous Event Queueing"}, + {0} +}; static UNIT sim_step_unit = { UDATA (&step_svc, 0, 0) }; static UNIT sim_expect_unit = { UDATA (&expect_svc, 0, 0) }; #if defined USE_INT64 static const char *sim_si64 = "64b data"; @@ -804,13 +825,14 @@ " Switches can be used to control the format of display information:\n\n" /***************** 80 character line width template *************************/ "++-a display as ASCII\n" "++-c display as character string\n" "++-m display as instruction mnemonics\n" - "++-o display as octal\n" - "++-d display as decimal\n" - "++-h display as hexadecimal\n\n" + "++-o or -8 display as octal\n" + "++-d or -10 display as decimal\n" + "++-h or -16 display as hexadecimal\n" + "++-2 display as binary\n\n" " The simulators typically accept symbolic input (see documentation with each\n" " simulator).\n\n" "3Examples\n" " Examples:\n\n" "++ex 1000-1100 examine 1000 to 1100\n" @@ -996,11 +1018,11 @@ " as paper-tape readers, and devices with write lock switches, such as disks\n" " and tapes, support read only operation; other devices do not. If a file is\n" " attached read only, its contents can be examined but not modified.\n" "5-q\n" " If the -q switch is specified when creating a new file (-n) or opening one\n" - " read only (-r), the message announcing this fact is suppressed.\n" + " read only (-r), any messages announcing these facts will be suppressed.\n" "5-f\n" " For simulated magnetic tapes, the ATTACH command can specify the format of\n" " the attached tape image file:\n\n" "++ATTACH -f \n\n" " The currently supported tape image file formats are:\n\n" @@ -1061,30 +1083,39 @@ "3LS\n" "++LS {path} list directory files\n" "2Displaying Files\n" #define HLP_TYPE "*Commands Displaying_Files TYPE" "3TYPE\n" - "++TYPE {file} display a file contents\n" + "++TYPE file display a file contents\n" #define HLP_CAT "*Commands Displaying_Files CAT" "3CAT\n" - "++CAT {file} display a file contents\n" + "++CAT file display a file contents\n" + "2Removing Files\n" #define HLP_DELETE "*Commands Removing_Files DEL" "3DELETE\n" - "++DEL{ete} {file} deletes a file\n" + "++DEL{ete} file deletes a file\n" #define HLP_RM "*Commands Removing_Files RM" "3RM\n" - "++RM {file} deletes a file\n" + "++RM file deletes a file\n" + "2Copying Files\n" +#define HLP_COPY "*Commands Copying_Files COPY" + "3COPY\n" + "++COPY sfile dfile copies a file\n" +#define HLP_CP "*Commands Copying_Files CP" + "3CP\n" + "++CP sfile dfile copies a file\n" #define HLP_SET "*Commands SET" "2SET\n" /***************** 80 character line width template *************************/ #define HLP_SET_CONSOLE "*Commands SET CONSOLE" "3Console\n" "+set console arg{,arg...} set console options\n" - "+set console WRU specify console drop to simh character\n" - "+set console BRK specify console Break character\n" - "+set console DEL specify console delete character\n" - "+set console PCHAR specify console printable characters\n" + "+set console WRU=value specify console drop to simh character\n" + "+set console BRK=value specify console Break character\n" + "+set console DEL=value specify console delete character\n" + "+set console PCHAR=bitmask bit mask of printable characters in\n" + "++++++++ range [31,0]\n" "+set console SPEED=speed{*factor}\n" "++++++++ specify console input data rate\n" "+set console TELNET=port specify console telnet port\n" "+set console TELNET=LOG=log_file\n" "++++++++ specify console telnet logging to the\n" @@ -1181,18 +1212,30 @@ "+set clock noasynch disable asynchronous clocks\n" #endif "+set clock nocatchup disable catchup clock ticks\n" "+set clock catchup enable catchup clock ticks\n" "+set clock calib=n%% specify idle calibration skip %%\n" + "+set clock stop=n stop execution after n instructions\n\n" + " The set clock stop command allows execution to have a bound when\n" + " execution starts with a BOOT, NEXT or CONTINUE command.\n" #define HLP_SET_ASYNCH "*Commands SET Asynch" "3Asynch\n" "+set asynch enable asynchronous I/O\n" "+set noasynch disable asynchronous I/O\n" -#define HLP_SET_ENVIRON "*Commands SET Asynch" +#define HLP_SET_ENVIRON "*Commands SET Environment" "3Environment\n" + "4Explicitily Changing A Variable\n" "+set environment name=val set environment variable\n" "+set environment name clear environment variable\n" + "4Gathering Input From A User\n" + " Input from a user can be obtained by:\n\n" + "+set environment -p \"Prompt String\" name=default\n\n" + " The -p switch indicates that the user should be prompted\n" + " with the indicated prompt string and the input provided\n" + " will be saved in the environment variable 'name'. If no\n" + " input is provided, the value specified as 'default' will be\n" + " used.\n" #define HLP_SET_ON "*Commands SET Command_Status_Trap_Dispatching" "3Command Status Trap Dispatching\n" "+set on enables error checking after command\n" "++++++++ execution\n" "+set noon disables error checking after command\n" @@ -1226,11 +1269,11 @@ "++++++++ messages\n" #define HLP_SET_PROMPT "*Commands SET Command_Prompt" "3Command Prompt\n" "+set prompt \"string\" sets an alternate simulator prompt string\n" "3Device and Unit\n" - "+set OCT|DEC|HEX set device display radix\n" + "+set OCT|DEC|HEX|BIN set device display radix\n" "+set ENABLED enable device\n" "+set DISABLED disable device\n" "+set DEBUG{=arg} set device debug flags\n" "+set NODEBUG={arg} clear device debug flags\n" "+set arg{,arg...} set device parameters (see show modifiers)\n" @@ -1341,10 +1384,11 @@ " The simulator can execute command files with the DO command:\n\n" "++DO {arguments...} execute commands in file\n\n" " The DO command allows command files to contain substitutable arguments.\n" " The string %%n, where n is between 1 and 9, is replaced with argument n\n" " from the DO command line. The string %%0 is replaced with .\n" + " The string %%* is replaced by the whole set of arguments (%%1 ... %%9).\n" " The sequences \\%% and \\\\ are replaced with the literal characters %% and \\,\n" " respectively. Arguments with spaces can be enclosed in matching single\n" " or double quotation marks.\n\n" " DO commands may be nested up to ten invocations deep.\n\n" "3Switches\n" @@ -1359,10 +1403,68 @@ " calling command file will be inherited in the command file being invoked.\n" " If the switch -q is specified, the quiet mode will be explicitly enabled\n" " for the called command file, otherwise quiet mode is inherited from the\n" " calling context.\n" /***************** 80 character line width template *************************/ + "3Variable_Insertion\n" + " Built In variables %%DATE%%, %%TIME%%, %%DATETIME%%, %%LDATE%%, %%LTIME%%,\n" + " %%CTIME%%, %%DATE_YYYY%%, %%DATE_YY%%, %%DATE_YC%%, %%DATE_MM%%, %%DATE_MMM%%,\n" + " %%DATE_MONTH%%, %%DATE_DD%%, %%DATE_D%%, %%DATE_WYYYY%%, %%DATE_WW%%,\n" + " %%TIME_HH%%, %%TIME_MM%%, %%TIME_SS%%, %%STATUS%%, %%TSTATUS%%, %%SIM_VERIFY%%,\n" + " %%SIM_QUIET%%, %%SIM_MESSAGE%% %%SIM_MESSAGE%%\n" + " %%SIM_NAME%%, %%SIM_BIN_NAME%%, %%SIM_BIN_PATH%%m %%SIM_OSTYPE%%\n\n" + "+Token %%0 expands to the command file name.\n" + "+Token %%n (n being a single digit) expands to the n'th argument\n" + "+Token %%* expands to the whole set of arguments (%%1 ... %%9)\n\n" + "+The input sequence \"%%%%\" represents a literal \"%%\". All other\n" + "+character combinations are rendered literally.\n\n" + "+Omitted parameters result in null-string substitutions.\n\n" + "+Tokens preceeded and followed by %% characters are expanded as environment\n" + "+variables, and if an environment variable isn't found then it can be one of\n" + "+several special variables:\n\n" + "++%%DATE%% yyyy-mm-dd\n" + "++%%TIME%% hh:mm:ss\n" + "++%%DATETIME%% yyyy-mm-ddThh:mm:ss\n" + "++%%LDATE%% mm/dd/yy (Locale Formatted)\n" + "++%%LTIME%% hh:mm:ss am/pm (Locale Formatted)\n" + "++%%CTIME%% Www Mmm dd hh:mm:ss yyyy (Locale Formatted)\n" + "++%%UTIME%% nnnn (Unix time - seconds since 1/1/1970)\n" + "++%%DATE_YYYY%% yyyy (0000-9999)\n" + "++%%DATE_YY%% yy (00-99)\n" + "++%%DATE_MM%% mm (01-12)\n" + "++%%DATE_MMM%% mmm (JAN-DEC)\n" + "++%%DATE_MONTH%% month (January-December)\n" + "++%%DATE_DD%% dd (01-31)\n" + "++%%DATE_WW%% ww (01-53) ISO 8601 week number\n" + "++%%DATE_WYYYY%% yyyy (0000-9999) ISO 8601 week year number\n" + "++%%DATE_D%% d (1-7) ISO 8601 day of week\n" + "++%%DATE_JJJ%% jjj (001-366) day of year\n" + "++%%DATE_19XX_YY%% yy A year prior to 2000 with the same\n" + "++++++++++ calendar days as the current year\n" + "++%%DATE_19XX_YYYY%% yyyy A year prior to 2000 with the same\n" + "++++++++++ calendar days as the current year\n" + "++%%TIME_HH%% hh (00-23)\n" + "++%%TIME_MM%% mm (00-59)\n" + "++%%TIME_SS%% ss (00-59)\n" + "++%%STATUS%% Status value from the last command executed\n" + "++%%TSTATUS%% The text form of the last status value\n" + "++%%SIM_VERIFY%% The Verify/Verbose mode of the current Do command file\n" + "++%%SIM_VERBOSE%% The Verify/Verbose mode of the current Do command file\n" + "++%%SIM_QUIET%% The Quiet mode of the current Do command file\n" + "++%%SIM_MESSAGE%% The message display status of the current Do command file\n" + "++%%SIM_NAME%% The name of the current simulator\n" + "++%%SIM_BIN_NAME%% The program name of the current simulator\n" + "++%%SIM_BIN_PATH%% The program path that invoked the current simulator\n" + "++%%SIM_OSTYPE%% The Operating System running the current simulator\n\n" + "+Environment variable lookups are done first with the precise name between\n" + "+the %% characters and if that fails, then the name between the %% characters\n" + "+is upcased and a lookup of that valus is attempted.\n\n" + "+The first Space delimited token on the line is extracted in uppercase and\n" + "+then looked up as an environment variable. If found it the value is\n" + "+supstituted for the original string before expanding everything else. If\n" + "+it is not found, then the original beginning token on the line is left\n" + "+untouched.\n" #define HLP_GOTO "*Commands Executing_Command_Files GOTO" "3GOTO\n" " Commands in a command file execute in sequence until either an error\n" " trap occurs (when a command completes with an error status), or when an\n" " explict request is made to start command execution elsewhere with the\n" @@ -1556,33 +1658,43 @@ /***************** 80 character line width template *************************/ "3Injecting Console Input\n" " The SEND command provides a way to insert input into the console device of\n" " a simulated system as if it was entered by a user.\n\n" "++SEND {-t} {after=nn,}{delay=nn,}\"\"\n\n" + "++NOSEND\n\n" + "++SHOW SEND\n\n" " The string argument must be delimited by quote characters. Quotes may\n" " be either single or double but the opening and closing quote characters\n" " must match. Data in the string may contain escaped character strings.\n\n" " The SEND command can also insert input into any serial device on a\n" " simulated system as if it was entered by a user.\n\n" - "++SEND {-t} :line {after=nn,}{delay=nn,}\"\"\n\n" + "++SEND {-t} {:line} {after=nn,}{delay=nn,}\"\"\n\n" + "++NOSEND {:line}\n\n" + "++SHOW SEND {:line}\n\n" + " The NOSEND command removes any undelivered input data which may be\n" + " pending on the CONSOLE or a specific multiplexer line.\n\n" + " The SHOW SEND command displays any pending SEND activity for the\n" + " CONSOLE or a specific multiplexer line.\n" "4Delay\n" - " Specifies a positive integer representing a minimal instruction delay\n" - " between characters being sent. The value specified in a delay\n" - " argument persists across SEND commands to the same device (console or\n" - " serial device). The delay parameter can be set by itself with:\n\n" + " Specifies an integer (>=0) representing a minimal instruction delay\n" + " between characters being sent. The delay parameter can be set by\n" + " itself with:\n\n" "++SEND DELAY=n\n\n" - " The default value of the delay parameter is 1000.\n" + " which will set the default delay value for subsequent SEND commands\n" + " which don't specify an explicit DELAY parameter along with a string\n" + " If a SEND command is processed and no DELAY value has been specified,\n" + " the default value of the delay parameter is 1000.\n" /***************** 80 character line width template *************************/ "4After\n" - " Specifies a positive integer representing a minimal number of instructions\n" + " Specifies an integer (>=0) representing a minimal number of instructions\n" " which must execute before the first character in the string is sent.\n" - " The value specified as the after parameter persists across SEND commands\n" - " to the same device (console or serial device). The after parameter value\n" - " can be set by itself with:\n\n" + " The after parameter value can be set by itself with:\n\n" "++SEND AFTER=n\n\n" - " If the after parameter isn't explicitly set, it defaults to the value of\n" - " the delay parameter.\n" + " which will set the default after value for subsequent SEND commands\n" + " which don't specify an explicit AFTER parameter along with a string\n" + " If a SEND command is processed and no AFTER value has been specified,\n" + " the default value of the delay parameter is the DELAY parameter value.\n" "4Escaping String Data\n" " The following character escapes are explicitly supported:\n" "++\\r Sends the ASCII Carriage Return character (Decimal value 13)\n" "++\\n Sends the ASCII Linefeed character (Decimal value 10)\n" "++\\f Sends the ASCII Formfeed character (Decimal value 12)\n" @@ -1609,10 +1721,11 @@ "3Reacting To Console Output\n" " The EXPECT command provides a way to stop execution and take actions\n" " when specific output has been generated by the simulated system.\n\n" "++EXPECT {dev:line} {[count]} {HALTAFTER=n,}\"\" {actioncommand {; actioncommand}...}\n\n" "++NOEXPECT {dev:line} \"\"\n\n" + "++SHOW EXPECT {dev:line}\n\n" " The string argument must be delimited by quote characters. Quotes may\n" " be either single or double but the opening and closing quote characters\n" " must match. Data in the string may contain escaped character strings.\n" " If a [count] is specified, the rule will match after the match string\n" " has matched count times.\n\n" @@ -1626,11 +1739,14 @@ " ending character sequence (i.e. \"\\r\\n\").\n" " Once data has matched any expect rule, that data is no longer eligible\n" " to match other expect rules which may already be defined.\n" " Data which is output prior to the definition of an expect rule is not\n" " eligible to be matched against.\n\n" - " The NOEXPECT command removes a previously defined EXPECT command.\n" + " The NOEXPECT command removes a previously defined EXPECT command for the\n" + " console or a specific multiplexer line.\n\n" + " The SHOW EXPECT command displays all of the pending EXPECT state for\n" + " the console or a specific multiplexer line.\n" /***************** 80 character line width template *************************/ "4Switches\n" " Switches can be used to influence the behavior of EXPECT rules\n\n" "5-p\n" " EXPECT rules default to be one shot activities. That is a rule is\n" @@ -1697,19 +1813,31 @@ "++\\xh{h} where each h is a hex digit (0-9A-Fa-f)\n" "4HaltAfter\n" " Specifies the number of instructions which should be executed before\n" " simulator instruction execution should stop. The default is to stop\n" " executing instructions immediately (i.e. HALTAFTER=0).\n" - " The HaltAfter delay, once set, persists for all expect behaviors for\n" - " that device.\n" - " The HaltAfter parameter value can be set by itself with:\n\n" + " The default HaltAfter delay, once set, persists for all expect behaviors\n" + " for that device.\n" + " The default HaltAfter parameter value can be set by itself with:\n\n" "++EXPECT HALTAFTER=n\n\n" + " A unique HaltAfter value can be specified with each expect matching rule\n" + " which if it is not specified then the default value will be used.\n" " To avoid potentially unpredictable system hehavior that will happen\n" " if multiple expect rules are in effect and a haltafter value is large\n" " enough for more than one expect rule to match before an earlier haltafter\n" " delay has expired, only a single EXPECT rule can be defined if a non-zero\n" " HaltAfter parameter has been set.\n" + /***************** 80 character line width template *************************/ +#define HLP_SLEEP "*Commands Executing_Command_Files Pausing_Command_Execution" + "3Pausing Command Execution\n" + " A simulator command file may wait for a specific period of time with the\n\n" + "++SLEEP NUMBER[SUFFIX]...\n\n" + " Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default),\n" + " 'm' for minutes, 'h' for hours or 'd' for days. NUMBER may be an\n" + " arbitrary floating point number. Given two or more arguments, pause\n" + " for the amount of time specified by the sum of their values.\n" + " NOTE: A SLEEP command is interruptable with SIGINT (^C).\n\n" /***************** 80 character line width template *************************/ #define HLP_ASSERT "*Commands Executing_Command_Files Testing_Simulator_State" #define HLP_IF "*Commands Executing_Command_Files Testing_Simulator_State" "3Testing Simulator State\n" " There are two ways for a command file to examine simulator state and\n" @@ -1729,10 +1857,12 @@ " halts after the initial load from disk. The ASSERT command is then\n" " used to confirm that the load completed successfully by examining the\n" " CPU's \"A\" register for the expected value:\n\n" "++; OS bootstrap command file\n" "++;\n" + "++IF EXIST \"os.disk\" echo os.disk exists\n" + "++IF NOT EXIST os.disk echo os.disk not existing\n" "++ATTACH DS0 os.disk\n" "++BOOT DS\n" "++; A register contains error code; 0 = good boot\n" "++ASSERT A=0\n" "++ATTACH MT0 sys.tape\n" @@ -1744,18 +1874,20 @@ " message. Otherwise, the command file will continue to bring up the\n" " operating system.\n" "4IF\n" " The IF command tests a simulator state condition and executes additional\n" " commands if the condition is true:\n\n" - "++IF commandtoprocess{; additionalcommandtoprocess}...\n\n" + "++IF commandtoprocess{; additionalcommandtoprocess}...\n\n" "5Examples:\n" " A command file might be used to bootstrap an operating system that\n" " halts after the initial load from disk. The ASSERT command is then\n" " used to confirm that the load completed successfully by examining the\n" " CPU's \"A\" register for the expected value:\n\n" "++; OS bootstrap command file\n" "++;\n" + "++IF EXIST \"os.disk\" echo os.disk exists\n" + "++IF NOT EXIST os.disk echo os.disk not existing\n" "++ATTACH DS0 os.disk\n" "++BOOT DS\n" "++; A register contains error code; 0 = good boot\n" "++IF NOT A=0 echo Boot failed - Failure Code; EX A; exit AFAIL\n" "++ATTACH MT0 sys.tape\n" @@ -1766,11 +1898,11 @@ " Failure Code:\" command will be displayed, the contents of the A register\n" " will be displayed and the command file will be aborted with an \"Assertion\n" " failed\" message. Otherwise, the command file will continue to bring up\n" " the operating system.\n" "4Conditional Expressions\n" - " The IF and ASSERT commands evaluate two different forms of conditional\n" + " The IF and ASSERT commands evaluate three different forms of conditional\n" " expressions.:\n\n" "5Simulator State Expressions\n" " The values of simulator registers can be evaluated with:\n\n" "++{NOT} {} |{}\n\n" " If is not specified, CPU is assumed. is a register (scalar\n" @@ -1807,10 +1939,15 @@ "++GEQ - greater than or equal\n\n" " Comparisons are generic. This means that if both string1 and string2 are\n" " comprised of all numeric digits, then the strings are converted to numbers\n" " and a numeric comparison is performed. For example: \"+1\" EQU \"1\" will be\n" " true.\n" + "5File Existence Expressions\n" + " File existence can be determined with:\n" + "++{NOT} EXIST \"\"\n\n" + "++{NOT} EXIST \n\n" + " Specifies a true (false {NOT}) condition if the file exists.\n" /***************** 80 character line width template *************************/ #define HLP_EXIT "*Commands Exiting_The_Simulator" "2Exiting The Simulator\n" " EXIT (synonyms QUIT and BYE) returns control to the operating system.\n" /***************** 80 character line width template *************************/ @@ -1871,10 +2008,12 @@ { "LS", &dir_cmd, 0, HLP_LS }, { "TYPE", &type_cmd, 0, HLP_TYPE }, { "CAT", &type_cmd, 0, HLP_CAT }, { "DELETE", &delete_cmd, 0, HLP_DELETE }, { "RM", &delete_cmd, 0, HLP_RM }, + { "COPY", ©_cmd, 0, HLP_COPY }, + { "CP", ©_cmd, 0, HLP_CP }, { "SET", &set_cmd, 0, HLP_SET }, { "SHOW", &show_cmd, 0, HLP_SHOW }, { "DO", &do_cmd, 1, HLP_DO }, { "GOTO", &goto_cmd, 1, HLP_GOTO }, { "RETURN", &return_cmd, 0, HLP_RETURN }, @@ -1884,13 +2023,15 @@ { "IF", &assert_cmd, 0, HLP_IF }, { "PROCEED", &noop_cmd, 0, HLP_PROCEED }, { "IGNORE", &noop_cmd, 0, HLP_IGNORE }, { "ECHO", &echo_cmd, 0, HLP_ECHO }, { "ASSERT", &assert_cmd, 1, HLP_ASSERT }, - { "SEND", &send_cmd, 0, HLP_SEND }, + { "SEND", &send_cmd, 1, HLP_SEND }, + { "NOSEND", &send_cmd, 0, HLP_SEND }, { "EXPECT", &expect_cmd, 1, HLP_EXPECT }, { "NOEXPECT", &expect_cmd, 0, HLP_EXPECT }, + { "SLEEP", &sleep_cmd, 0, HLP_SLEEP }, { "!", &spawn_cmd, 0, HLP_SPAWN }, { "HELP", &help_cmd, 0, HLP_HELP }, #if defined(USE_SIM_VIDEO) { "SCREENSHOT", &screenshot_cmd,0, HLP_SCREENSHOT }, #endif @@ -1931,10 +2072,11 @@ static C1TAB set_dev_tab[] = { { "OCTAL", &set_dev_radix, 8 }, { "DECIMAL", &set_dev_radix, 10 }, { "HEX", &set_dev_radix, 16 }, + { "BINARY", &set_dev_radix, 2 }, { "ENABLED", &set_dev_enbdis, 1 }, { "DISABLED", &set_dev_enbdis, 0 }, { "DEBUG", &set_dev_debug, 1 }, { "NODEBUG", &set_dev_debug, 0 }, { NULL, NULL, 0 } @@ -2051,11 +2193,11 @@ stdnul = fopen(NULL_DEVICE,"wb"); for (i = 1; i < argc; i++) { /* loop thru args */ if (argv[i] == NULL) /* paranoia */ continue; if ((*argv[i] == '-') && lookswitch) { /* switch? */ - if ((sw = get_switches (argv[i])) < 0) { + if (get_switches (argv[i], &sw, NULL) == SW_ERROR) { fprintf (stderr, "Invalid switch %s\n", argv[i]); return 0; } sim_switches = sim_switches | sw; } @@ -2063,17 +2205,18 @@ if ((strlen (argv[i]) + strlen (cbuf) + 3) >= sizeof(cbuf)) { fprintf (stderr, "Argument string too long\n"); return 0; } if (*cbuf) /* concat args */ - sim_strlcat (cbuf, " ", sizeof(cbuf)); + strlcat (cbuf, " ", sizeof (cbuf)); sprintf(&cbuf[strlen(cbuf)], "%s%s%s", strchr(argv[i], ' ') ? "\"" : "", argv[i], strchr(argv[i], ' ') ? "\"" : ""); lookswitch = FALSE; /* no more switches */ } } /* end for */ sim_quiet = sim_switches & SWMASK ('Q'); /* -q means quiet */ sim_on_inherit = sim_switches & SWMASK ('O'); /* -o means inherit on state */ + sim_init_sock (); /* init socket capabilities */ AIO_INIT; /* init Asynch I/O */ if (sim_vm_init != NULL) /* call once only */ (*sim_vm_init)(); @@ -2111,12 +2254,18 @@ } if (!sim_quiet) { printf ("\n"); show_version (stdout, NULL, NULL, 0, NULL); } +show_version (stdnul, NULL, NULL, 1, NULL); /* Quietly set SIM_OSTYPE */ if (sim_dflt_dev == NULL) /* if no default */ sim_dflt_dev = sim_devices[0]; +if (((sim_dflt_dev->flags & DEV_DEBUG) == 0) && /* default device without debug? */ + (sim_dflt_dev->debflags == NULL)) { + sim_dflt_dev->flags |= DEV_DEBUG; /* connect default event debugging */ + sim_dflt_dev->debflags = sim_dflt_debug; + } if (*argv[0]) { /* sim name arg? */ char *np; /* "path.ini" */ strncpy (nbuf, argv[0], PATH_MAX + 1); /* copy sim name */ if ((np = (char *)match_ext (nbuf, "EXE"))) /* remove .exe */ @@ -2126,10 +2275,11 @@ np = strrchr (nbuf, '\\'); /* windows path separator */ if (np == NULL) np = strrchr (nbuf, ']'); /* VMS path separator */ if (np != NULL) setenv ("SIM_BIN_NAME", np+1, 1); /* Publish simulator binary name */ + setenv ("SIM_BIN_PATH", argv[0], 1); } sim_argv = argv; cptr = getenv("HOME"); if (cptr == NULL) { cptr = getenv("HOMEPATH"); @@ -2149,11 +2299,11 @@ char *np; /* "path.ini" */ nbuf[0] = '"'; /* starting " */ strncpy (nbuf + 1, argv[0], PATH_MAX + 1); /* copy sim name */ if ((np = (char *)match_ext (nbuf, "EXE"))) /* remove .exe */ *np = 0; - sim_strlcat (nbuf, ".ini\"", sizeof(nbuf)); /* add .ini" */ + strlcat (nbuf, ".ini\"", sizeof (nbuf)); /* add .ini" */ stat = do_cmd (-1, nbuf) & ~SCPE_NOMESSAGE; /* proc default cmd file */ if (stat == SCPE_OPENERR) { /* didn't exist/can't open? */ np = strrchr (nbuf, '/'); /* stript path and try again in cwd */ if (np == NULL) np = strrchr (nbuf, '\\'); /* windows path separator */ @@ -2211,10 +2361,11 @@ if (sim_ttisatty()) continue; /* ignore tty EOF */ else break; /* otherwise exit */ } if (*cptr == 0) /* ignore blank */ continue; + sim_cmd_echoed = TRUE; sim_sub_args (cbuf, sizeof(cbuf), argv); if (sim_log) /* log cmd */ fprintf (sim_log, "%s%s\n", sim_prompt, cptr); if (sim_deb && (sim_deb != sim_log) && (sim_deb != stdout)) fprintf (sim_deb, "%s%s\n", sim_prompt, cptr); @@ -2251,11 +2402,11 @@ return SCPE_ARG; cptr = get_glyph_nc (cptr, gbuf, '"'); /* get quote delimited token */ if (gbuf[0] == '\0') { /* Token started with quote */ gbuf[sizeof (gbuf)-1] = '\0'; - strncpy (gbuf, cptr, sizeof (gbuf)-1); + strlcpy (gbuf, cptr, sizeof (gbuf)); gptr = strchr (gbuf, '"'); if (gptr) *gptr = '\0'; } sim_prompt = (char *)realloc (sim_prompt, strlen (gbuf) + 2); /* nul terminator and trailing blank */ @@ -2477,11 +2628,11 @@ sprintf (buf, "set %s ENABLE", sim_dname (dptr)); fprintf (st, "%-30s\tEnables device %s\n", buf, sim_dname (dptr)); sprintf (buf, "set %s DISABLE", sim_dname (dptr)); fprintf (st, "%-30s\tDisables device %s\n", buf, sim_dname (dptr)); } -if (dptr->flags & DEV_DEBUG) { +if ((dptr->flags & DEV_DEBUG) || (dptr->debflags)) { fprint_header (st, &found, header); sprintf (buf, "set %s DEBUG", sim_dname (dptr)); fprintf (st, "%-30s\tEnables debugging for device %s\n", buf, sim_dname (dptr)); sprintf (buf, "set %s NODEBUG", sim_dname (dptr)); fprintf (st, "%-30s\tDisables debugging for device %s\n", buf, sim_dname (dptr)); @@ -2555,11 +2706,11 @@ fprint_header (st, &found, header); sprintf (buf, "show %s %s%s", sim_dname (dptr), mptr->pstring, MODMASK(mptr,MTAB_SHP) ? "=arg" : ""); fprintf (st, "%-30s\t%s\n", buf, mptr->help ? mptr->help : ""); } } -if (dptr->flags & DEV_DEBUG) { +if ((dptr->flags & DEV_DEBUG) || (dptr->debflags)) { fprint_header (st, &found, header); sprintf (buf, "show %s DEBUG", sim_dname (dptr)); fprintf (st, "%-30s\tDisplays debugging status for device %s\n", buf, sim_dname (dptr)); } if ((dptr->modifiers) && (dptr->units) && (dptr->numunits != 1)) { @@ -2929,11 +3080,11 @@ sim_on_inherit =(sim_switches & SWMASK ('O')) || sim_on_inherit; /* -o means inherit ON condition actions */ errabort = sim_switches & SWMASK ('E'); /* -e means abort on error */ abuf[sizeof(abuf)-1] = '\0'; -strncpy (abuf, fcptr, sizeof(abuf)-1); +strlcpy (abuf, fcptr, sizeof(abuf)); c = abuf; do_arg[10] = NULL; /* make sure the argument list always ends with a NULL */ for (nargs = 0; nargs < 10; ) { /* extract arguments */ while (sim_isspace (*c)) /* skip blanks */ c++; @@ -2952,12 +3103,12 @@ } /* end for */ if (do_arg [0] == NULL) /* need at least 1 */ return SCPE_2FARG; if ((fpin = fopen (do_arg[0], "r")) == NULL) { /* file failed to open? */ - sim_strlcpy (cbuf, do_arg[0], sizeof (cbuf)); /* try again with .sim extension */ - sim_strlcat (cbuf, ".sim", sizeof (cbuf)); + strlcpy (cbuf, do_arg[0], sizeof (cbuf)); /* try again with .sim extension */ + strlcat (cbuf, ".sim", sizeof (cbuf)); if ((fpin = fopen (cbuf, "r")) == NULL) { /* failed a second time? */ if (flag == 0) /* cmd line file? */ fprintf (stderr, "Can't open file %s\n", do_arg[0]); return SCPE_OPENERR; /* return failure */ } @@ -2984,12 +3135,12 @@ } } } } -sim_strlcpy( sim_do_filename[sim_do_depth], do_arg[0], - sizeof (sim_do_filename[sim_do_depth])); /* stash away do file name for possible use by 'call' command */ +strlcpy( sim_do_filename[sim_do_depth], do_arg[0], + sizeof (sim_do_filename[sim_do_depth])); /* stash away do file name for possible use by 'call' command */ sim_do_label[sim_do_depth] = label; /* stash away do label for possible use in messages */ sim_goto_line[sim_do_depth] = 0; if (label) { sim_gotofile = fpin; sim_do_echo = echo; @@ -3017,10 +3168,11 @@ } if (*cptr == 0) /* ignore blank */ continue; if (echo) /* echo if -v */ sim_printf("%s> %s\n", do_position(), cptr); + sim_cmd_echoed = echo; if (*cptr == ':') /* ignore label */ continue; cptr = get_glyph_cmd (cptr, gbuf); /* get command glyph */ sim_switches = 0; /* init switches */ sim_gotofile = fpin; @@ -3065,11 +3217,11 @@ default: break; } if ((stat >= SCPE_BASE) && (stat != SCPE_EXIT) && /* error from cmd? */ (stat != SCPE_STEP)) { - if (!echo && !sim_quiet && /* report if not echoing */ + if (!echo && /* report if not echoing */ !stat_nomessage && /* and not suppressing messages */ !(cmdp && cmdp->message)) { /* and not handling them specially */ sim_printf("%s> %s\n", do_position(), sim_do_ocptr[sim_do_depth]); } } @@ -3099,12 +3251,12 @@ sim_gotofile = NULL; if (flag >= 0) { sim_do_echo = saved_sim_do_echo; /* restore echo state we entered with */ sim_show_message = saved_sim_show_message; /* restore message display state we entered with */ sim_on_inherit = saved_sim_on_inherit; /* restore ON inheritance state we entered with */ + sim_quiet = saved_sim_quiet; /* restore quiet mode we entered with */ } -sim_quiet = saved_sim_quiet; /* restore quiet mode we entered with */ if ((flag >= 0) || (!sim_on_inherit)) { for (i=0; i sim_sub_instr_size) { + sim_sub_instr = (char *)realloc (sim_sub_instr, instr_size*sizeof(*sim_sub_instr)); + sim_sub_instr_off = (size_t *)realloc (sim_sub_instr_off, instr_size*sizeof(*sim_sub_instr_off)); + sim_sub_instr_size = instr_size; + } +sim_sub_instr_buf = instr; +strlcpy (sim_sub_instr, instr, instr_size*sizeof(*sim_sub_instr)); +while (sim_isspace (*ip)) { /* skip leading spaces */ + sim_sub_instr_off[outstr_off++] = ip - instr; *op++ = *ip++; + } istart = ip; for (; *ip && (op < oend); ) { - if ((ip [0] == '\\') && /* literal escape? */ - ((ip [1] == '%') || (ip [1] == '\\'))) { /* and followed by '%' or '\'? */ - ip++; /* skip '\' */ - *op++ = *ip++; /* copy escaped char */ + if ((ip [0] == '%') && (ip [1] == '%')) { /* literal % insert? */ + sim_sub_instr_off[outstr_off++] = ip - instr; + ip++; /* skip one */ + *op++ = *ip++; /* copy insert % */ } else if ((*ip == '%') && (sim_isalnum(ip[1]) || (ip[1] == '*') || (ip[1] == '_'))) {/* sub? */ if ((ip[1] >= '0') && (ip[1] <= ('9'))) { /* %n = sub */ @@ -3244,11 +3409,11 @@ else if (!strcmp ("DATETIME", gbuf)) { sprintf (rbuf, "%04d-%02d-%02dT%02d:%02d:%02d", tmnow->tm_year+1900, tmnow->tm_mon+1, tmnow->tm_mday, tmnow->tm_hour, tmnow->tm_min, tmnow->tm_sec); ap = rbuf; } /* Locale oriented formatted date/time info */ - if (!strcmp ("LDATE", gbuf)) { + else if (!strcmp ("LDATE", gbuf)) { strftime (rbuf, sizeof(rbuf), "%x", tmnow); ap = rbuf; } else if (!strcmp ("LTIME", gbuf)) { #if defined(HAVE_C99_STRFTIME) @@ -3268,10 +3433,14 @@ #else strcpy (rbuf, ctime(&now)); rbuf[strlen (rbuf)-1] = '\0'; /* remove trailing \n */ #endif ap = rbuf; + } + else if (!strcmp ("UTIME", gbuf)) { + sprintf (rbuf, "%" LL_FMT "d", (LL_TYPE)now); + ap = rbuf; } /* Separate Date/Time info */ else if (!strcmp ("DATE_YYYY", gbuf)) {/* Year (0000-9999) */ strftime (rbuf, sizeof(rbuf), "%Y", tmnow); ap = rbuf; @@ -3383,30 +3552,38 @@ ap = rbuf; } } } if (ap) { /* non-null arg? */ - while (*ap && (op < oend)) /* copy the argument */ + while (*ap && (op < oend)) { /* copy the argument */ + sim_sub_instr_off[outstr_off++] = ip - instr; *op++ = *ap++; + } } } else if (ip == istart) { /* at beginning of input? */ get_glyph (istart, gbuf, 0); /* substitute initial token */ ap = getenv(gbuf); /* if it is an environment variable name */ if (!ap) { /* nope? */ + sim_sub_instr_off[outstr_off++] = ip - instr; *op++ = *ip++; /* press on with literal character */ continue; } - while (*ap && (op < oend)) /* copy the translation */ + while (*ap && (op < oend)) { /* copy the translation */ + sim_sub_instr_off[outstr_off++] = ip - instr; *op++ = *ap++; + } ip += strlen(gbuf); } - else + else { + sim_sub_instr_off[outstr_off++] = ip - instr; *op++ = *ip++; /* literal character */ + } } *op = 0; /* term buffer */ +sim_sub_instr_off[outstr_off] = 0; strcpy (instr, tmpbuf); free (tmpbuf); return; } @@ -3474,13 +3651,13 @@ { char gbuf[CBUFSIZE], gbuf2[CBUFSIZE]; CONST char *tptr, *gptr; REG *rptr; uint32 idx; -t_value val; t_stat r; t_bool Not = FALSE; +t_bool Exist = FALSE; t_bool result; t_addr addr; t_stat reason; cptr = (CONST char *)get_sim_opt (CMD_OPT_SW|CMD_OPT_DFT, (CONST char *)cptr, &r); @@ -3490,12 +3667,17 @@ return SCPE_2FARG; tptr = get_glyph (cptr, gbuf, 0); /* get token */ if (!strcmp (gbuf, "NOT")) { /* Conditional Inversion? */ Not = TRUE; /* remember that, and */ cptr = (CONST char *)tptr; + tptr = get_glyph (cptr, gbuf, 0); /* get next token */ } -if (*cptr == '"') { /* quoted string comparison? */ +if (!strcmp (gbuf, "EXIST")) { /* File Exist Test? */ + Exist = TRUE; /* remember that, and */ + cptr = (CONST char *)tptr; + } +if (Exist || (*cptr == '"')) { /* quoted string comparison? */ char op[CBUFSIZE]; static struct { const char *op; int aval; int bval; @@ -3521,33 +3703,41 @@ if (!*tptr) return SCPE_2FARG; cptr += strlen (gbuf); while (sim_isspace (*cptr)) /* skip spaces */ ++cptr; - get_glyph (cptr, op, '"'); - for (optr = compare_ops; optr->op; optr++) - if (0 == strcmp (op, optr->op)) - break; - if (!optr->op) - return sim_messagef (SCPE_ARG, "Invalid operator: %s\n", op); - cptr += strlen (op); - while (sim_isspace (*cptr)) /* skip spaces */ - ++cptr; - cptr = (CONST char *)get_glyph_gen (cptr, gbuf2, 0, (sim_switches & SWMASK ('I')), TRUE, '\\'); - /* get second string */ - if (*cptr) { /* more? */ - if (flag) /* ASSERT has no more args */ - return SCPE_2MARG; + if (!Exist) { + get_glyph (cptr, op, '"'); + for (optr = compare_ops; optr->op; optr++) + if (0 == strcmp (op, optr->op)) + break; + if (!optr->op) + return sim_messagef (SCPE_ARG, "Invalid operator: %s\n", op); + cptr += strlen (op); + while (sim_isspace (*cptr)) /* skip spaces */ + ++cptr; + cptr = (CONST char *)get_glyph_gen (cptr, gbuf2, 0, (sim_switches & SWMASK ('I')), TRUE, '\\'); + /* get second string */ + if (*cptr) { /* more? */ + if (flag) /* ASSERT has no more args */ + return SCPE_2MARG; + } + else { + if (!flag) + return SCPE_2FARG; /* IF needs actions! */ + } + result = sim_cmp_string (gbuf, gbuf2); + result = ((result == optr->aval) || (result == optr->bval)); + if (optr->invert) + result = !result; } else { - if (!flag) - return SCPE_2FARG; /* IF needs actions! */ + FILE *f = fopen (gbuf, "r"); + if (f) + fclose (f); + result = (f != NULL); } - result = sim_cmp_string (gbuf, gbuf2); - result = ((result == optr->aval) || (result == optr->bval)); - if (optr->invert) - result = !result; } else { cptr = get_glyph (cptr, gbuf, 0); /* get register */ rptr = find_reg (gbuf, &gptr, sim_dfdev); /* parse register */ if (rptr) { /* got register? */ @@ -3588,12 +3778,12 @@ } if (rptr) { /* Handle register case */ if (!get_rsearch (gbuf, rptr->radix, &sim_stabr) || /* parse condition */ (sim_stabr.boolop == -1)) /* relational op reqd */ return SCPE_MISVAL; - val = get_rval (rptr, idx); /* get register value */ - result = test_search (&val, &sim_stabr); /* test condition */ + sim_eval[0] = get_rval (rptr, idx); /* get register value */ + result = test_search (sim_eval, &sim_stabr); /* test condition */ } else { /* Handle memory case */ if (!get_asearch (gbuf, sim_dfdev ? sim_dfdev->dradix : sim_dflt_dev->dradix, &sim_staba) || /* parse condition */ (sim_staba.boolop == -1)) /* relational op reqd */ return SCPE_MISVAL; @@ -3615,18 +3805,24 @@ /* Send command Syntax: SEND {After=m},{Delay=n},"string-to-send" - After - is a positive integer representing a number of instruction delay - before the initial characters is sent. The value specified - in a after argument persists across SEND commands. The after - parameter can be set by itself with SEND AFTER=n - Delay - is a positive integer representing a minimal instruction delay - before and between characters being sent. The value specified - in a delay argument persists across SEND commands. The delay - parameter can be set by itself with SEND DELAY=n + After - is an integer (>= 0) representing a number of instruction + delay before the initial characters is sent. The after + parameter can is set by itself (with SEND AFTER=n). + The value specified then persists across SEND commands, + and is the default value used in subsequent SEND commands + which don't specify an explicit AFTER parameter. This default + value is visible externally via an environment variable. + Delay - is an integer (>= 0) representing a number of instruction + delay before and between characters being sent. The + delay parameter can is set by itself (with SEND DELAY=n) + The value specified persists across SEND commands, and is + the default value used in subsequent SEND commands which + don't specify an explicit DELAY parameter. This default + value is visible externally via an environment variable. String - must be quoted. Quotes may be either single or double but the opening anc closing quote characters must match. Within quotes C style character escapes are allowed. The following character escapes are explicitly supported: \r Sends the ASCII Carriage Return character (Decimal value 13) @@ -3643,19 +3839,58 @@ as well as octal character values of the form: \n{n{n}} where each n is an octal digit (0-7) and hext character values of the form: \xh{h} where each h is a hex digit (0-9A-Fa-f) */ + +static uint32 get_default_env_parameter (const char *dev_name, const char *param_name, uint32 default_value) +{ +char varname[CBUFSIZE]; +uint32 val; +char *endptr; +const char *colon = strchr (dev_name, ':'); + +if (colon) + snprintf (varname, sizeof(varname), "%s_%*.*s_%s", param_name, (int)(colon-dev_name), (int)(colon-dev_name), dev_name, colon + 1); +else + snprintf (varname, sizeof(varname), "%s_%s", param_name, dev_name); +if (!getenv (varname)) + val = default_value; +else { + val = strtoul (getenv (varname), &endptr, 0); + if (*endptr) + val = default_value; + } +return val; +} + +static void set_default_env_parameter (const char *dev_name, const char *param_name, uint32 value) +{ +char varname[CBUFSIZE]; +char valbuf[CBUFSIZE]; + +const char *colon = strchr (dev_name, ':'); + +if (colon) + snprintf (varname, sizeof(varname), "%s_%*.*s_%s", param_name, (int)(colon-dev_name), (int)(colon-dev_name), dev_name, colon + 1); +else + snprintf (varname, sizeof(varname), "%s_%s", param_name, dev_name); +snprintf (valbuf, sizeof(valbuf), "%u", value); +setenv(varname, valbuf, 1); +} t_stat send_cmd (int32 flag, CONST char *cptr) { char gbuf[CBUFSIZE]; CONST char *tptr; uint8 dbuf[CBUFSIZE]; uint32 dsize = 0; -uint32 delay = 0; -uint32 after = 0; +const char *dev_name; +uint32 delay; +t_bool delay_set = FALSE; +uint32 after; +t_bool after_set = FALSE; t_stat r; SEND *snd; GET_SWITCHES (cptr); /* get switches */ tptr = get_glyph (cptr, gbuf, ','); @@ -3666,44 +3901,55 @@ cptr = tptr; tptr = get_glyph (tptr, gbuf, ','); } else snd = sim_cons_get_send (); - +dev_name = tmxr_send_line_name (snd); +if (!flag) + return sim_send_clear (snd); +delay = get_default_env_parameter (dev_name, "SIM_SEND_DELAY", SEND_DEFAULT_DELAY); +after = get_default_env_parameter (dev_name, "SIM_SEND_AFTER", delay); while (*cptr) { if ((!strncmp(gbuf, "DELAY=", 6)) && (gbuf[6])) { delay = (uint32)get_uint (&gbuf[6], 10, 10000000, &r); if (r != SCPE_OK) return sim_messagef (SCPE_ARG, "Invalid Delay Value\n"); cptr = tptr; tptr = get_glyph (cptr, gbuf, ','); + delay_set = TRUE; + if (!after_set) + after = delay; continue; } if ((!strncmp(gbuf, "AFTER=", 6)) && (gbuf[6])) { after = (uint32)get_uint (&gbuf[6], 10, 10000000, &r); if (r != SCPE_OK) return sim_messagef (SCPE_ARG, "Invalid After Value\n"); cptr = tptr; tptr = get_glyph (cptr, gbuf, ','); + after_set = TRUE; continue; } if ((*cptr == '"') || (*cptr == '\'')) break; return SCPE_ARG; } -if (*cptr) { - if ((*cptr != '"') && (*cptr != '\'')) - return sim_messagef (SCPE_ARG, "String must be quote delimited\n"); - cptr = get_glyph_quoted (cptr, gbuf, 0); - if (*cptr != '\0') - return SCPE_2MARG; /* No more arguments */ - - if (SCPE_OK != sim_decode_quoted_string (gbuf, dbuf, &dsize)) - return sim_messagef (SCPE_ARG, "Invalid String\n"); - } -if ((dsize == 0) && (delay == 0) && (after == 0)) - return SCPE_2FARG; +if (!*cptr) { + if ((!delay_set) && (!after_set)) + return SCPE_2FARG; + set_default_env_parameter (dev_name, "SIM_SEND_DELAY", delay); + set_default_env_parameter (dev_name, "SIM_SEND_AFTER", after); + return SCPE_OK; + } +if ((*cptr != '"') && (*cptr != '\'')) + return sim_messagef (SCPE_ARG, "String must be quote delimited\n"); +cptr = get_glyph_quoted (cptr, gbuf, 0); +if (*cptr != '\0') + return SCPE_2MARG; /* No more arguments */ + +if (SCPE_OK != sim_decode_quoted_string (gbuf, dbuf, &dsize)) + return sim_messagef (SCPE_ARG, "Invalid String\n"); return sim_send_input (snd, dbuf, dsize, after, delay); } t_stat sim_show_send (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr) { @@ -3736,11 +3982,11 @@ GET_SWITCHES (cptr); /* get switches */ tptr = get_glyph (cptr, gbuf, ','); if (sim_isalpha(gbuf[0]) && (strchr (gbuf, ':'))) { r = tmxr_locate_line_expect (gbuf, &exp); if (r != SCPE_OK) - return r; + return sim_messagef (r, "No such active line: %s\n", gbuf); cptr = tptr; } else exp = sim_cons_get_expect (); if (flag) @@ -3773,10 +4019,61 @@ if (*cptr && (cptr[strlen(cptr)-1] != '"') && (cptr[strlen(cptr)-1] != '\'')) return SCPE_ARG; /* String must be quote delimited */ return sim_exp_show (st, exp, gbuf); } + +/* Sleep command */ + +t_stat sleep_cmd (int32 flag, CONST char *cptr) +{ +char *tptr; +double wait; + +stop_cpu = 0; +signal (SIGINT, int_handler); +while (*cptr) { + wait = strtod (cptr, &tptr); + switch (*tptr) { + case ' ': + case '\t': + case '\0': + break; + case 's': + case 'S': + ++tptr; + break; + case 'm': + case 'M': + ++tptr; + wait *= 60.0; + break; + case 'h': + case 'H': + ++tptr; + wait *= (60.0*60.0); + break; + case 'd': + case 'D': + ++tptr; + wait *= (24.0*60.0*60.0); + break; + default: + signal (SIGINT, SIG_DFL); /* cancel WRU */ + return sim_messagef (SCPE_ARG, "Invalid Sleep unit '%c'\n", *cptr); + } + wait *= 1000.0; /* Convert to Milliseconds */ + cptr = tptr; + while ((wait > 1000.0) && (!stop_cpu)) + wait -= sim_os_ms_sleep (1000); + if ((wait > 0.0) && (!stop_cpu)) + sim_os_ms_sleep ((unsigned)wait); + } +signal (SIGINT, SIG_DFL); /* cancel WRU */ +stop_cpu = 0; +return SCPE_OK; +} /* Goto command */ t_stat goto_cmd (int32 flag, CONST char *fcptr) { @@ -4032,15 +4329,46 @@ /* Set environment routine */ t_stat sim_set_environment (int32 flag, CONST char *cptr) { -char varname[CBUFSIZE]; +char varname[CBUFSIZE], prompt[CBUFSIZE], cbuf[CBUFSIZE]; if ((!cptr) || (*cptr == 0)) /* now eol? */ return SCPE_2FARG; -cptr = get_glyph (cptr, varname, '='); /* get environment variable name */ +if (sim_switches & SWMASK ('P')) { + CONST char *deflt = NULL; + + cptr = get_glyph_quoted (cptr, prompt, 0); /* get prompt */ + if (prompt[0] == '\0') + return sim_messagef (SCPE_2FARG, "Missing Prompt and Environment Variable Name\n"); + if ((prompt[0] == '"') || (prompt[0] == '\'')) { + prompt[strlen (prompt) - 1] = '\0'; + memmove (prompt, prompt + 1, strlen (prompt)); + } + deflt = get_glyph (cptr, varname, '='); /* get environment variable name */ + if (deflt == NULL) + deflt = ""; + if (*deflt) { + strlcat (prompt, " [", sizeof (prompt)); + strlcat (prompt, deflt, sizeof (prompt)); + strlcat (prompt, "] ", sizeof (prompt)); + } + else + strlcat (prompt, " ", sizeof (prompt)); + if (sim_rem_cmd_active_line == -1) { + cptr = read_line_p (prompt, cbuf, sizeof(cbuf), stdin); + if ((cptr == NULL) || (*cptr == 0)) + cptr = deflt; + else + cptr = cbuf; + } + else + cptr = deflt; + } +else + cptr = get_glyph (cptr, varname, '='); /* get environment variable name */ setenv(varname, cptr, 1); return SCPE_OK; } /* Set command */ @@ -4577,10 +4905,11 @@ fprintf (st, " %s", SIM_VERSION_MODE); #endif if (flag) { t_bool idle_capable; uint32 os_ms_sleep_1, os_tick_size; + char os_type[128] = "Unknown"; fprintf (st, "\n Simulator Framework Capabilities:"); fprintf (st, "\n %s", sim_si64); fprintf (st, "\n %s", sim_sa64); fprintf (st, "\n %s", eth_capabilities()); @@ -4670,10 +4999,12 @@ #elif defined(__ALPHA) "Alpha"; #else "VAX"; #endif + strlcpy (os_type, "OpenVMS ", sizeof (os_type)); + strlcat (os_type, arch, sizeof (os_type)); fprintf (st, "\n OS: OpenVMS %s %s", arch, __VMS_VERSION); } #elif defined(_WIN32) if (1) { char *proc_id = getenv ("PROCESSOR_IDENTIFIER"); @@ -4695,28 +5026,41 @@ _pclose (f); } fprintf (st, "\n OS: %s", osversion); fprintf (st, "\n Architecture: %s%s%s, Processors: %s", arch, proc_arch3264 ? " on " : "", proc_arch3264 ? proc_arch3264 : "", procs); fprintf (st, "\n Processor Id: %s, Level: %s, Revision: %s", proc_id ? proc_id : "", proc_level ? proc_level : "", proc_rev ? proc_rev : ""); + strlcpy (os_type, "Windows", sizeof (os_type)); } #else if (1) { char osversion[2*PATH_MAX+1] = ""; FILE *f; if ((f = popen ("uname -a", "r"))) { - memset (osversion, 0, sizeof(osversion)); + memset (osversion, 0, sizeof (osversion)); do { - if (NULL == fgets (osversion, sizeof(osversion)-1, f)) + if (NULL == fgets (osversion, sizeof (osversion)-1, f)) break; sim_trim_endspc (osversion); } while (osversion[0] == '\0'); pclose (f); } fprintf (st, "\n OS: %s", osversion); + if ((f = popen ("uname", "r"))) { + memset (os_type, 0, sizeof (os_type)); + do { + if (NULL == fgets (os_type, sizeof (os_type)-1, f)) + break; + sim_trim_endspc (os_type); + } while (os_type[0] == '\0'); + pclose (f); + } } #endif + if ((!strcmp (os_type, "Unknown")) && (getenv ("OSTYPE"))) + strlcpy (os_type, getenv ("OSTYPE"), sizeof (os_type)); + setenv ("SIM_OSTYPE", os_type, 1); } #if defined(SIM_GIT_COMMIT_ID) #define S_xstr(a) S_str(a) #define S_str(a) #a fprintf (st, "%sgit commit id: %8.8s", flag ? "\n " : " ", S_xstr(SIM_GIT_COMMIT_ID)); @@ -4877,11 +5221,11 @@ t_stat show_dev_debug (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr) { int32 any = 0; DEBTAB *dep; -if (dptr->flags & DEV_DEBUG) { +if ((dptr->flags & DEV_DEBUG) || (dptr->debflags)) { if (dptr->dctrl == 0) fputs ("Debugging disabled", st); else if (dptr->debflags == NULL) fputs ("Debugging enabled", st); else { @@ -5038,11 +5382,11 @@ if (sim_is_running) return SCPE_INVREM; if ((!cptr) || (*cptr == 0)) return SCPE_2FARG; gbuf[sizeof(gbuf)-1] = '\0'; -strncpy (gbuf, cptr, sizeof(gbuf)-1); +strlcpy (gbuf, cptr, sizeof(gbuf)); sim_trim_endspc(gbuf); if (chdir(gbuf) != 0) return sim_messagef(SCPE_IOERR, "Unable to directory change to: %s\n", gbuf); return SCPE_OK; } @@ -5126,27 +5470,27 @@ char *c; char DirName[PATH_MAX + 1], WholeName[PATH_MAX + 1], WildName[PATH_MAX + 1]; memset (DirName, 0, sizeof(DirName)); memset (WholeName, 0, sizeof(WholeName)); -sim_strlcpy (WildName, cptr, sizeof(WildName)); +strlcpy (WildName, cptr, sizeof(WildName)); cptr = WildName; sim_trim_endspc (WildName); if ((!stat (WildName, &filestat)) && (filestat.st_mode & S_IFDIR)) - sim_strlcat (WildName, "/*", sizeof(WildName)); + strlcat (WildName, "/*", sizeof (WildName)); if ((*cptr != '/') || (0 == memcmp (cptr, "./", 2)) || (0 == memcmp (cptr, "../", 3))) { #if defined (VMS) - getcwd (WholeName, sizeof(WholeName)-1, 0); + getcwd (WholeName, sizeof (WholeName)-1, 0); #else - getcwd (WholeName, sizeof(WholeName)-1); + getcwd (WholeName, sizeof (WholeName)-1); #endif - sim_strlcat (WholeName, "/", sizeof(WholeName)); - sim_strlcat (WholeName, cptr, sizeof(WholeName)); + strlcat (WholeName, "/", sizeof (WholeName)); + strlcat (WholeName, cptr, sizeof (WholeName)); sim_trim_endspc (WholeName); } else - sim_strlcpy (WholeName, cptr, sizeof(WholeName)); + strlcpy (WholeName, cptr, sizeof (WholeName)); while ((c = strstr (WholeName, "/./"))) memmove (c + 1, c + 3, 1 + strlen (c + 3)); while ((c = strstr (WholeName, "//"))) memmove (c + 1, c + 2, 1 + strlen (c + 2)); while ((c = strstr (WholeName, "/../"))) { @@ -5163,18 +5507,18 @@ memmove (DirName, WholeName, 1+c-WholeName); DirName[1+c-WholeName] = '\0'; } else { #if defined (VMS) - getcwd (WholeName, sizeof(WholeName)-1, 0); + getcwd (WholeName, sizeof (WholeName)-1, 0); #else - getcwd (WholeName, sizeof(WholeName)-1); + getcwd (WholeName, sizeof (WholeName)-1); #endif } cptr = WholeName; #if defined (HAVE_GLOB) -memset (&paths, 0, sizeof(paths)); +memset (&paths, 0, sizeof (paths)); if (0 == glob (cptr, 0, NULL, &paths)) { #else dir = opendir(DirName[0] ? DirName : "/."); if (dir) { struct dirent *ent; @@ -5334,11 +5678,11 @@ char lbuf[4*CBUFSIZE]; if ((!cptr) || (*cptr == 0)) return SCPE_2FARG; lbuf[sizeof(lbuf)-1] = '\0'; -strncpy (lbuf, cptr, sizeof(lbuf)-1); +strlcpy (lbuf, cptr, sizeof(lbuf)); sim_trim_endspc(lbuf); file = sim_fopen (lbuf, "r"); if (file == NULL) { /* open failed? */ TYPE_CTX type_state; t_stat stat; @@ -5387,10 +5731,66 @@ stat = sim_dir_scan (cptr, sim_delete_entry, &del_state); if (stat == SCPE_OK) return del_state.stat; return sim_messagef (SCPE_ARG, "No such file or directory: %s\n", cptr); } + +typedef struct { + t_stat stat; + int count; + char destname[CBUFSIZE]; + } COPY_CTX; + +static void sim_copy_entry (const char *directory, + const char *filename, + t_offset FileSize, + const struct stat *filestat, + void *context) +{ +COPY_CTX *ctx = (COPY_CTX *)context; +struct stat deststat; +char FullPath[PATH_MAX + 1]; +char dname[CBUFSIZE];\ +t_stat st; + +strlcpy (dname, ctx->destname, sizeof (dname)); + +sprintf (FullPath, "%s%s", directory, filename); + +if ((dname[strlen (dname) - 1] == '/') || (dname[strlen (dname) - 1] == '\\')) + dname[strlen (dname) - 1] = '\0'; +if ((!stat (dname, &deststat)) && (deststat.st_mode & S_IFDIR)) { + const char *dslash = (strrchr (dname, '/') ? "/" : (strrchr (dname, '\\') ? "\\" : "/")); + + dname[sizeof (dname) - 1] = '\0'; + snprintf (&dname[strlen (dname)], sizeof (dname) - strlen (dname), "%s%s", dslash, filename); + } +st = sim_copyfile (FullPath, dname, TRUE); +if (SCPE_OK == st) + ++ctx->count; +else + ctx->stat = st; +} + +t_stat copy_cmd (int32 flg, CONST char *cptr) +{ +char sname[CBUFSIZE]; +COPY_CTX copy_state; +t_stat stat; + +memset (©_state, 0, sizeof (copy_state)); +if ((!cptr) || (*cptr == 0)) + return SCPE_2FARG; +cptr = get_glyph_quoted (cptr, sname, 0); +if ((!cptr) || (*cptr == 0)) + return SCPE_2FARG; +cptr = get_glyph_quoted (cptr, copy_state.destname, 0); +stat = sim_dir_scan (sname, sim_copy_entry, ©_state); +if ((stat == SCPE_OK) && (copy_state.count)) + return sim_messagef (SCPE_OK, " %3d file(s) copied\n", copy_state.count); +return copy_state.stat; +} /* Breakpoint commands */ t_stat brk_cmd (int32 flg, CONST char *cptr) { @@ -5415,17 +5815,20 @@ uptr = dptr->units; if (uptr == NULL) return SCPE_IERR; max = uptr->capac - 1; abuf[sizeof(abuf)-1] = '\0'; -strncpy (abuf, cptr, sizeof(abuf)-1); -cptr = abuf; +strlcpy (abuf, cptr, sizeof(abuf)); if ((aptr = strchr (abuf, ';'))) { /* ;action? */ + cptr += aptr - abuf + 1; if (flg != SSH_ST) /* only on SET */ - return sim_messagef (SCPE_ARG, "Invalid argument: %s\n", aptr); + return sim_messagef (SCPE_ARG, "Invalid argument: %s\n", cptr); *aptr++ = 0; /* separate strings */ + if ((cptr > sim_sub_instr_buf) && ((size_t)(cptr - sim_sub_instr_buf) < sim_sub_instr_size)) + aptr = &sim_sub_instr[sim_sub_instr_off[cptr - sim_sub_instr_buf]]; /* get un-substituted string */ } +cptr = abuf; if (*cptr == 0) { /* no argument? */ lo = (t_addr) get_rval (sim_PC, 0); /* use PC */ return ssh_break_one (st, flg, lo, 0, aptr); } while (*cptr) { @@ -5663,19 +6066,21 @@ if (!(uptr->dynflags & UNIT_ATTMULT)) return SCPE_ALATT; /* Already attached */ } } gbuf[sizeof(gbuf)-1] = '\0'; -strncpy (gbuf, cptr, sizeof(gbuf)-1); +strlcpy (gbuf, cptr, sizeof(gbuf)); sim_trim_endspc (gbuf); /* trim trailing spc */ return scp_attach_unit (dptr, uptr, gbuf); /* attach */ } /* Call device-specific or file-oriented attach unit routine */ t_stat scp_attach_unit (DEVICE *dptr, UNIT *uptr, const char *cptr) { +if (uptr->flags & UNIT_DIS) /* disabled? */ + return SCPE_UDIS; if (dptr->attach != NULL) /* device routine? */ return dptr->attach (uptr, (CONST char *)cptr); /* call it */ return attach_unit (uptr, (CONST char *)cptr); /* no, std routine */ } @@ -5683,41 +6088,35 @@ t_stat attach_unit (UNIT *uptr, CONST char *cptr) { DEVICE *dptr; -if (uptr->flags & UNIT_DIS) /* disabled? */ - return SCPE_UDIS; if (!(uptr->flags & UNIT_ATTABLE)) /* not attachable? */ return SCPE_NOATT; if ((dptr = find_dev_from_unit (uptr)) == NULL) return SCPE_NOATT; uptr->filename = (char *) calloc (CBUFSIZE, sizeof (char)); /* alloc name buf */ if (uptr->filename == NULL) return SCPE_MEM; -strncpy (uptr->filename, cptr, CBUFSIZE); /* save name */ +strlcpy (uptr->filename, cptr, CBUFSIZE); /* save name */ if ((sim_switches & SWMASK ('R')) || /* read only? */ ((uptr->flags & UNIT_RO) != 0)) { if (((uptr->flags & UNIT_ROABLE) == 0) && /* allowed? */ ((uptr->flags & UNIT_RO) == 0)) return attach_err (uptr, SCPE_NORO); /* no, error */ uptr->fileref = sim_fopen (cptr, "rb"); /* open rd only */ if (uptr->fileref == NULL) /* open fail? */ return attach_err (uptr, SCPE_OPENERR); /* yes, error */ uptr->flags = uptr->flags | UNIT_RO; /* set rd only */ - if (!sim_quiet && !(sim_switches & SWMASK ('Q'))) { - sim_printf ("%s: unit is read only\n", sim_dname (dptr)); - } + sim_messagef (SCPE_OK, "%s: unit is read only\n", sim_dname (dptr)); } else { if (sim_switches & SWMASK ('N')) { /* new file only? */ uptr->fileref = sim_fopen (cptr, "wb+"); /* open new file */ if (uptr->fileref == NULL) /* open fail? */ return attach_err (uptr, SCPE_OPENERR); /* yes, error */ - if (!sim_quiet && !(sim_switches & SWMASK ('Q'))) { - sim_printf ("%s: creating new file\n", sim_dname (dptr)); - } + sim_messagef (SCPE_OK, "%s: creating new file\n", sim_dname (dptr)); } else { /* normal */ uptr->fileref = sim_fopen (cptr, "rb+"); /* open r/w */ if (uptr->fileref == NULL) { /* open fail? */ #if defined(EPERM) @@ -5729,23 +6128,19 @@ return attach_err (uptr, SCPE_NORO);/* no error */ uptr->fileref = sim_fopen (cptr, "rb"); /* open rd only */ if (uptr->fileref == NULL) /* open fail? */ return attach_err (uptr, SCPE_OPENERR); /* yes, error */ uptr->flags = uptr->flags | UNIT_RO; /* set rd only */ - if (!sim_quiet) { - sim_printf ("%s: unit is read only\n", sim_dname (dptr)); - } + sim_messagef (SCPE_OK, "%s: unit is read only\n", sim_dname (dptr)); } else { /* doesn't exist */ if (sim_switches & SWMASK ('E')) /* must exist? */ return attach_err (uptr, SCPE_OPENERR); /* yes, error */ uptr->fileref = sim_fopen (cptr, "wb+");/* open new file */ if (uptr->fileref == NULL) /* open fail? */ return attach_err (uptr, SCPE_OPENERR); /* yes, error */ - if (!sim_quiet) { - sim_printf ("%s: creating new file\n", sim_dname (dptr)); - } + sim_messagef (SCPE_OK, "%s: creating new file\n", sim_dname (dptr)); } } /* end if null */ } /* end else */ } if (uptr->flags & UNIT_BUFABLE) { /* buffer? */ @@ -5752,13 +6147,11 @@ uint32 cap = ((uint32) uptr->capac) / dptr->aincr; /* effective size */ if (uptr->flags & UNIT_MUSTBUF) /* dyn alloc? */ uptr->filebuf = calloc (cap, SZ_D (dptr)); /* allocate */ if (uptr->filebuf == NULL) /* no buffer? */ return attach_err (uptr, SCPE_MEM); /* error */ - if (!sim_quiet) { - sim_printf ("%s: buffering file in memory\n", sim_dname (dptr)); - } + sim_messagef (SCPE_OK, "%s: buffering file in memory\n", sim_dname (dptr)); uptr->hwmark = (uint32)sim_fread (uptr->filebuf, /* read file */ SZ_D (dptr), cap, uptr->fileref); uptr->flags = uptr->flags | UNIT_BUF; /* set buffered */ } uptr->flags = uptr->flags | UNIT_ATT; @@ -5870,13 +6263,11 @@ if ((dptr = find_dev_from_unit (uptr)) == NULL) return SCPE_OK; if ((uptr->flags & UNIT_BUF) && (uptr->filebuf)) { uint32 cap = (uptr->hwmark + dptr->aincr - 1) / dptr->aincr; if (uptr->hwmark && ((uptr->flags & UNIT_RO) == 0)) { - if (!sim_quiet) { - sim_printf ("%s: writing buffer to file\n", sim_dname (dptr)); - } + sim_messagef (SCPE_OK, "%s: writing buffer to file\n", sim_dname (dptr)); rewind (uptr->fileref); sim_fwrite (uptr->filebuf, SZ_D (dptr), cap, uptr->fileref); if (ferror (uptr->fileref)) sim_printf ("%s: I/O error - %s", sim_dname (dptr), strerror (errno)); } @@ -6007,11 +6398,11 @@ GET_SWITCHES (cptr); /* get switches */ if (*cptr == 0) /* must be more */ return SCPE_2FARG; gbuf[sizeof(gbuf)-1] = '\0'; -strncpy (gbuf, cptr, sizeof(gbuf)-1); +strlcpy (gbuf, cptr, sizeof(gbuf)); sim_trim_endspc (gbuf); if ((sfile = sim_fopen (gbuf, "wb")) == NULL) return SCPE_OPENERR; r = sim_save (sfile); fclose (sfile); @@ -6162,11 +6553,11 @@ GET_SWITCHES (cptr); /* get switches */ if (*cptr == 0) /* must be more */ return SCPE_2FARG; gbuf[sizeof(gbuf)-1] = '\0'; -strncpy (gbuf, cptr, sizeof(gbuf)-1); +strlcpy (gbuf, cptr, sizeof(gbuf)); sim_trim_endspc (gbuf); if ((rfile = sim_fopen (gbuf, "rb")) == NULL) return SCPE_OPENERR; r = sim_rest (rfile); fclose (rfile); @@ -6491,10 +6882,11 @@ sim_printf (", now unattached\n"); } } } free (attnames[j]); + attnames[j] = NULL; } Cleanup_Return: for (j=0; j < attcnt; j++) free (attnames[j]); free (attnames); @@ -6562,11 +6954,12 @@ if (MATCH_CMD (gbuf, "UNTIL") != 0) return sim_messagef (SCPE_2MARG, "Unexpected %s command argument: %s %s\n", (flag == RU_RUN) ? "RUN" : "GO", gbuf, cptr); sim_switches = 0; GET_SWITCHES (cptr); - if ((*cptr == '\'') || (*cptr == '"')) { /* Expect UNTIL condition */ + if (((*cptr == '\'') || (*cptr == '"')) || /* Expect UNTIL condition */ + (!sim_strncasecmp(cptr, "HALTAFTER=", 10))) { r = expect_cmd (1, cptr); if (r != SCPE_OK) return r; } else { /* BREAK UNTIL condition */ @@ -6667,36 +7060,36 @@ return sim_messagef (SCPE_IERR, "Can't seek to %u in %s for %s\n", (unsigned)uptr->pos, uptr->filename, sim_uname (uptr)); } } stop_cpu = 0; sim_is_running = 1; /* flag running */ -if (sim_ttrun () != SCPE_OK) { /* set console mode */ +if ((r = sim_ttrun ()) != SCPE_OK) { /* set console mode */ sim_is_running = 0; /* flag idle */ sim_ttcmd (); - return SCPE_TTYERR; + return sim_messagef (SCPE_TTYERR, "sim_ttrun() returned: %s\n", sim_error_text (r)); } if ((r = sim_check_console (30)) != SCPE_OK) { /* check console, error? */ sim_is_running = 0; /* flag idle */ sim_ttcmd (); - return r; + sim_messagef (r, "sim_check_console () returned: %s\n", sim_error_text (r)); } if (signal (SIGINT, int_handler) == SIG_ERR) { /* set WRU */ sim_is_running = 0; /* flag idle */ sim_ttcmd (); - return SCPE_SIGERR; + return sim_messagef (SCPE_SIGERR, "Can't establish SIGINT"); } #ifdef SIGHUP if (signal (SIGHUP, int_handler) == SIG_ERR) { /* set WRU */ sim_is_running = 0; /* flag idle */ sim_ttcmd (); - return SCPE_SIGERR; + return sim_messagef (SCPE_SIGERR, "Can't establish SIGHUP"); } #endif if (signal (SIGTERM, int_handler) == SIG_ERR) { /* set WRU */ sim_is_running = 0; /* flag idle */ sim_ttcmd (); - return SCPE_SIGERR; + return sim_messagef (SCPE_SIGERR, "Can't establish SIGTERM"); } if (sim_step) /* set step timer */ sim_activate (&sim_step_unit, sim_step); fflush(stdout); /* flush stdout */ if (sim_log) /* flush log if enabled */ @@ -7011,11 +7404,11 @@ } if (*tptr && (*tptr++ != ',')) return SCPE_ARG; reason = exdep_reg_loop (ofile, sim_schrptr, flag, cptr, lowr, highr, (uint32) low, (uint32) high); - if ((!sim_oline) && (sim_log && (ofile == stdout))) + if ((flag & EX_E) && (!sim_oline) && (sim_log && (ofile == stdout))) exdep_reg_loop (sim_log, sim_schrptr, EX_E, cptr, lowr, highr, (uint32) low, (uint32) high); continue; } @@ -7026,11 +7419,11 @@ return (tstat ? tstat : SCPE_ARG); if (*tptr && (*tptr++ != ',')) return SCPE_ARG; reason = exdep_addr_loop (ofile, sim_schaptr, flag, cptr, low, high, sim_dfdev, sim_dfunit); - if ((!sim_oline) && (sim_log && (ofile == stdout))) + if ((flag & EX_E) && (!sim_oline) && (sim_log && (ofile == stdout))) exdep_addr_loop (sim_log, sim_schaptr, EX_E, cptr, low, high, sim_dfdev, sim_dfunit); } /* end for */ if (sim_ofile) /* close output file */ fclose (sim_ofile); @@ -7062,13 +7455,13 @@ continue; val = last_val = 0; for (idx = lows; idx <= highs; idx++) { if (idx >= rptr->depth) return SCPE_SUB; - val = get_rval (rptr, idx); + sim_eval[0] = val = get_rval (rptr, idx); sim_switches = saved_switches; - if (schptr && !test_search (&val, schptr)) + if (schptr && !test_search (sim_eval, schptr)) continue; if (flag == EX_E) { if ((idx > lows) && (val == last_val)) continue; if (idx > val_start+1) { @@ -7179,15 +7572,16 @@ fprintf (ofile, "%s[%d]:\t", rptr->name, idx); else fprintf (ofile, "%s:\t", rptr->name); if (!(flag & EX_E)) return SCPE_OK; +sim_eval[0] = val; GET_RADIX (rdx, rptr->radix); if ((rptr->flags & REG_VMAD) && sim_vm_fprint_addr) sim_vm_fprint_addr (ofile, sim_dflt_dev, (t_addr) val); else if (!(rptr->flags & REG_VMFLAGS) || - (fprint_sym (ofile, (rptr->flags & REG_UFMASK) | rdx, &val, + (fprint_sym (ofile, (rptr->flags & REG_UFMASK) | rdx, sim_eval, NULL, sim_switches | SIM_SW_REG) > 0)) { fprint_val (ofile, val, rdx, rptr->width, rptr->flags & REG_FMT); if (rptr->fields) { fprintf (ofile, "\t"); fprint_fields (ofile, val, val, rptr->fields); @@ -7382,11 +7776,10 @@ PUT_RVAL (uint32, rptr, idx, (int32) val, (uint32) mask); else PUT_RVAL (t_uint64, rptr, idx, val, mask); #else else PUT_RVAL (uint32, rptr, idx, val, mask); #endif -return; } /* Examine address routine Inputs: (sim_eval is an implicit argument) @@ -7664,10 +8057,12 @@ #define S__STR(tok) S__STR_QUOTE(tok) handle = dlopen("libncurses." S__STR(HAVE_DLOPEN), RTLD_NOW|RTLD_GLOBAL); handle = dlopen("libcurses." S__STR(HAVE_DLOPEN), RTLD_NOW|RTLD_GLOBAL); handle = dlopen("libreadline." S__STR(HAVE_DLOPEN), RTLD_NOW|RTLD_GLOBAL); if (!handle) + handle = dlopen("libreadline." S__STR(HAVE_DLOPEN) ".7", RTLD_NOW|RTLD_GLOBAL); + if (!handle) handle = dlopen("libreadline." S__STR(HAVE_DLOPEN) ".6", RTLD_NOW|RTLD_GLOBAL); if (!handle) handle = dlopen("libreadline." S__STR(HAVE_DLOPEN) ".5", RTLD_NOW|RTLD_GLOBAL); if (handle) { p_readline = (readline_func)((size_t)dlsym(handle, "readline")); @@ -7678,11 +8073,11 @@ if (p_readline) { char *tmpc = p_readline (prompt); /* get cmd line */ if (tmpc == NULL) /* bad result? */ cptr = NULL; else { - strncpy (cptr, tmpc, size); /* copy result */ + strlcpy (cptr, tmpc, size); /* copy result */ free (tmpc) ; /* free temp */ } } else { printf ("%s", prompt); /* display prompt */ @@ -7769,11 +8164,11 @@ quote_char = *iptr; } } } if (sim_islower (*iptr) && uc) - *optr = (char)toupper (*iptr); + *optr = (char)sim_toupper (*iptr); else *optr = *iptr; iptr++; optr++; } *optr = 0; if (mchar && (*iptr == mchar)) /* skip terminator */ @@ -7824,43 +8219,58 @@ while ((--tptr >= cptr) && sim_isspace (*tptr)) *tptr = 0; return cptr; } -int sim_isspace (char c) -{ -return (c & 0x80) ? 0 : isspace (c); -} - -int sim_islower (char c) -{ -return (c & 0x80) ? 0 : islower (c); -} - -int sim_isalpha (char c) -{ -return (c & 0x80) ? 0 : isalpha (c); -} - -int sim_isprint (char c) -{ -return (c & 0x80) ? 0 : isprint (c); -} - -int sim_isdigit (char c) -{ -return (c & 0x80) ? 0 : isdigit (c); -} - -int sim_isgraph (char c) -{ -return (c & 0x80) ? 0 : isgraph (c); -} - -int sim_isalnum (char c) -{ -return (c & 0x80) ? 0 : isalnum (c); +int sim_isspace (int c) +{ +return ((c < 0) || (c >= 128)) ? 0 : isspace (c); +} + +int sim_islower (int c) +{ +return (c >= 'a') && (c <= 'z'); +} + +int sim_isupper (int c) +{ +return (c >= 'A') && (c <= 'Z'); +} + +int sim_toupper (int c) +{ +return ((c >= 'a') && (c <= 'z')) ? ((c - 'a') + 'A') : c; +} + +int sim_tolower (int c) +{ +return ((c >= 'A') && (c <= 'Z')) ? ((c - 'A') + 'a') : c; +} + +int sim_isalpha (int c) +{ +return ((c < 0) || (c >= 128)) ? 0 : isalpha (c); +} + +int sim_isprint (int c) +{ +return ((c < 0) || (c >= 128)) ? 0 : isprint (c); +} + +int sim_isdigit (int c) +{ +return ((c < 0) || (c >= 128)) ? 0 : isdigit (c); +} + +int sim_isgraph (int c) +{ +return ((c < 0) || (c >= 128)) ? 0 : isgraph (c); +} + +int sim_isalnum (int c) +{ +return ((c < 0) || (c >= 128)) ? 0 : isalnum (c); } /* strncasecmp() is not available on all platforms */ int sim_strncasecmp (const char* string1, const char* string2, size_t len) { @@ -7868,14 +8278,12 @@ unsigned char s1, s2; for (i=0; i s2) return 1; if (s1 == 0) @@ -7891,14 +8299,12 @@ unsigned char s1, s2; while (1) { s1 = (unsigned char)string1[i]; s2 = (unsigned char)string2[i]; - if (sim_islower (s1)) - s1 = (unsigned char)toupper (s1); - if (sim_islower (s2)) - s2 = (unsigned char)toupper (s2); + s1 = (unsigned char)sim_toupper (s1); + s2 = (unsigned char)sim_toupper (s2); if (s1 == s2) { if (s1 == 0) return 0; i++; continue; @@ -8207,16 +8613,16 @@ static const char *hex_digits = "0123456789ABCDEF"; const char *c; ++iptr; *optr = 0; - c = strchr (hex_digits, toupper(*iptr)); + c = strchr (hex_digits, sim_toupper(*iptr)); if (c) { *optr = ((*optr)<<4) + (uint8)(c-hex_digits); ++iptr; } - c = strchr (hex_digits, toupper(*iptr)); + c = strchr (hex_digits, sim_toupper(*iptr)); if (c) { *optr = ((*optr)<<4) + (uint8)(c-hex_digits); ++iptr; } ++optr; @@ -8570,27 +8976,39 @@ /* get_switches get switches from input string Inputs: cptr = pointer to input string Outputs: - sw = switch bit mask - 0 if no switches, -1 if error + *sw = switch bit mask + *mumber = numeric value + Return value: SW_ERROR if error + SW_BITMASK if switch bitmask or not a switch + SW_NUMBER if numeric */ -int32 get_switches (const char *cptr) +SWITCH_PARSE get_switches (const char *cptr, int32 *sw, int32 *number) { -int32 sw; - +*sw = 0; if (*cptr != '-') - return 0; -sw = 0; + return SW_BITMASK; +if (number) + *number = 0; +if (sim_isdigit(cptr[1])) { + char *end; + long val = strtol (1+cptr, &end, 10); + + if ((*end != 0) || (number == NULL)) + return SW_ERROR; + *number = (int32)val; + return SW_NUMBER; + } for (cptr++; (sim_isspace (*cptr) == 0) && (*cptr != 0); cptr++) { if (sim_isalpha (*cptr) == 0) - return -1; - sw = sw | SWMASK (toupper (*cptr)); + return SW_ERROR; + *sw = *sw | SWMASK (sim_toupper (*cptr)); } -return sw; +return SW_BITMASK; } /* get_sim_sw accumulate sim_switches Inputs: @@ -8600,19 +9018,25 @@ NULL if error */ CONST char *get_sim_sw (CONST char *cptr) { -int32 lsw; +int32 lsw, lnum; char gbuf[CBUFSIZE]; while (*cptr == '-') { /* while switches */ cptr = get_glyph (cptr, gbuf, 0); /* get switch glyph */ - lsw = get_switches (gbuf); /* parse */ - if (lsw <= 0) /* invalid? */ - return NULL; - sim_switches = sim_switches | lsw; /* accumulate */ + switch (get_switches (gbuf, &lsw, &lnum)) { /* parse */ + case SW_ERROR: + return NULL; + case SW_BITMASK: + sim_switches = sim_switches | lsw; /* accumulate */ + break; + case SW_NUMBER: + sim_switch_number = lnum; /* set number */ + break; + } } return cptr; } /* get_sim_opt get simulator command options @@ -8625,17 +9049,18 @@ *stat = error status */ CONST char *get_sim_opt (int32 opt, CONST char *cptr, t_stat *st) { -int32 t; +int32 t, n; char gbuf[CBUFSIZE]; CONST char *svptr; DEVICE *tdptr; UNIT *tuptr; sim_switches = 0; /* no switches */ +sim_switch_number = 0; /* no numberuc switch */ sim_ofile = NULL; /* no output file */ sim_schrptr = NULL; /* no search */ sim_schaptr = NULL; /* no search */ sim_stabr.logic = sim_staba.logic = SCH_OR; /* default search params */ sim_stabr.boolop = sim_staba.boolop = SCH_GE; @@ -8669,18 +9094,25 @@ } sim_opt_out |= CMD_OPT_OF; /* got output file */ continue; } cptr = get_glyph (cptr, gbuf, 0); - if ((t = get_switches (gbuf)) != 0) { /* try for switches */ - if (t < 0) { /* err if bad switch */ + switch (get_switches (gbuf, &t, &n)) { /* try for switches */ + case SW_ERROR: /* err if bad switch */ *st = SCPE_INVSW; return NULL; - } - sim_switches = sim_switches | t; /* or in new switches */ + case SW_NUMBER: + sim_switch_number = n; /* set number */ + continue; + case SW_BITMASK: + if (t != 0) { + sim_switches = sim_switches | t;/* or in new switches */ + continue; + } + break; } - else if ((opt & CMD_OPT_SCH) && /* if allowed, */ + if ((opt & CMD_OPT_SCH) && /* if allowed, */ get_rsearch (gbuf, sim_dfdev->dradix, &sim_stabr)) { /* try for search */ sim_schrptr = &sim_stabr; /* set search */ sim_schaptr = get_asearch (gbuf, sim_dfdev->dradix, &sim_staba);/* populate memory version of the same expression */ sim_opt_out |= CMD_OPT_SCH; /* got search */ } @@ -8748,11 +9180,11 @@ (*fptr != 0) && (*fptr != ';'); #else *fptr != 0; /* others: stop at null */ #endif fptr++, eptr++) { - if (toupper (*fptr) != toupper (*eptr)) + if (sim_toupper (*fptr) != sim_toupper (*eptr)) return NULL; } if (*eptr != 0) /* ext exhausted? */ return NULL; } @@ -9013,12 +9445,11 @@ while (sim_isspace (*inptr)) /* bypass white space */ inptr++; val = 0; nodigit = 1; for (c = *inptr; sim_isalnum(c); c = *++inptr) { /* loop through char */ - if (sim_islower (c)) - c = toupper (c); + c = sim_toupper (c); if (sim_isdigit (c)) /* digit? */ digit = c - (uint32) '0'; else if (radix <= 10) /* stop if not expected */ break; else digit = c + 10 - (uint32) 'A'; /* convert letter */ @@ -9817,11 +10248,11 @@ } if ((act != NULL) && (*act != 0)) { /* new action? */ char *newp = (char *) calloc (CBUFSIZE+1, sizeof (char)); /* alloc buf */ if (newp == NULL) /* mem err? */ return SCPE_MEM; - strncpy (newp, act, CBUFSIZE); /* copy action */ + strlcpy (newp, act, CBUFSIZE); /* copy action */ bp->act = newp; /* set pointer */ } sim_brk_summ = sim_brk_summ | (sw & ~BRK_TYP_TEMP); return SCPE_OK; } @@ -10045,11 +10476,11 @@ memcpy (buf, sim_brk_act[sim_do_depth], lnt + 1); /* copy with ; */ buf[lnt] = 0; /* erase ; */ sim_brk_act[sim_do_depth] += lnt + 1; /* adv ptr */ } else { - strncpy (buf, sim_brk_act[sim_do_depth], size); /* copy action */ + strlcpy (buf, sim_brk_act[sim_do_depth], size); /* copy action */ sim_brk_clract (); /* no more */ } return buf; } @@ -10182,17 +10613,20 @@ t_stat sim_set_expect (EXPECT *exp, CONST char *cptr) { char gbuf[CBUFSIZE]; CONST char *tptr; CONST char *c1ptr; +const char *dev_name; +uint32 after; t_bool after_set = FALSE; -uint32 after = exp->after; int32 cnt = 0; t_stat r; if ((cptr == NULL) || (*cptr == 0)) return SCPE_2FARG; +dev_name = tmxr_expect_line_name (exp); +after = get_default_env_parameter (dev_name, "SIM_EXPECT_HALTAFTER", 0); if (*cptr == '[') { cnt = (int32) strtotv (cptr + 1, &c1ptr, 10); if ((cptr == c1ptr) || (*c1ptr != ']')) return sim_messagef (SCPE_ARG, "Invalid Repeat count specification\n"); cptr = c1ptr + 1; @@ -10202,18 +10636,24 @@ tptr = get_glyph (cptr, gbuf, ','); if ((!strncmp(gbuf, "HALTAFTER=", 10)) && (gbuf[10])) { after = (uint32)get_uint (&gbuf[10], 10, 100000000, &r); if (r != SCPE_OK) return sim_messagef (SCPE_ARG, "Invalid Halt After Value\n"); - after_set = TRUE; cptr = tptr; + after_set = TRUE; } -if ((*cptr != '"') && (*cptr != '\'')) +if ((*cptr != '\0') && (*cptr != '"') && (*cptr != '\'')) return sim_messagef (SCPE_ARG, "String must be quote delimited\n"); cptr = get_glyph_quoted (cptr, gbuf, 0); -return sim_exp_set (exp, gbuf, cnt, (after_set ? after : exp->after), sim_switches, cptr); +/* Hsndle a bare HALTAFTER=nnn command */ +if ((gbuf[0] == '\0') && (*cptr == '\0') && after_set) { + set_default_env_parameter (dev_name, "SIM_EXPECT_HALTAFTER", after); + return SCPE_OK; + } + +return sim_exp_set (exp, gbuf, cnt, after, sim_switches, cptr); } /* Clear expect */ t_stat sim_set_noexpect (EXPECT *exp, const char *cptr) @@ -10295,11 +10735,11 @@ exp->rules = NULL; exp->size = 0; free (exp->buf); exp->buf = NULL; exp->buf_size = 0; -exp->buf_ins = 0; +exp->buf_data = exp->buf_ins = 0; return SCPE_OK; } /* Set/Add an expect rule */ @@ -10361,12 +10801,12 @@ if (after && exp->size) return sim_messagef (SCPE_ARG, "Multiple concurrent EXPECT rules aren't valid when a HALTAFTER parameter is non-zero\n"); exp->rules = (EXPTAB *) realloc (exp->rules, sizeof (*exp->rules)*(exp->size + 1)); ep = &exp->rules[exp->size]; exp->size += 1; -exp->after = after; /* set halt after value */ memset (ep, 0, sizeof(*ep)); +ep->after = after; /* set halt after value */ ep->match_pattern = (char *)malloc (strlen (match) + 1); if (ep->match_pattern) strcpy (ep->match_pattern, match); ep->cnt = cnt; /* set proceed count */ ep->switches = switches; /* set switches */ @@ -10398,11 +10838,15 @@ ep->act = NULL; /* now no action */ } if (act) while (sim_isspace(*act)) ++act; /* skip leading spaces in action string */ if ((act != NULL) && (*act != 0)) { /* new action? */ - char *newp = (char *) calloc (strlen (act)+1, sizeof (*act)); /* alloc buf */ + char *newp; + + if ((act > sim_sub_instr_buf) && ((size_t)(act - sim_sub_instr_buf) < sim_sub_instr_size)) + act = &sim_sub_instr[sim_sub_instr_off[act - sim_sub_instr_buf]]; /* get un-substituted string */ + newp = (char *) calloc (strlen (act)+1, sizeof (*act)); /* alloc buf */ if (newp == NULL) /* mem err? */ return SCPE_MEM; strcpy (newp, act); /* copy action */ ep->act = newp; /* set pointer */ } @@ -10419,21 +10863,26 @@ /* Show an expect rule */ t_stat sim_exp_show_tab (FILE *st, const EXPECT *exp, const EXPTAB *ep) { +const char *dev_name = tmxr_expect_line_name (exp); +uint32 default_haltafter = get_default_env_parameter (dev_name, "SIM_EXPECT_HALTAFTER", 0); + if (!ep) return SCPE_OK; -fprintf (st, "EXPECT"); +fprintf (st, " EXPECT"); if (ep->switches & EXP_TYP_PERSIST) fprintf (st, " -p"); if (ep->switches & EXP_TYP_CLEARALL) fprintf (st, " -c"); if (ep->switches & EXP_TYP_REGEX) fprintf (st, " -r"); if (ep->switches & EXP_TYP_REGEX_I) fprintf (st, " -i"); +if (ep->after != default_haltafter) + fprintf (st, " HALTAFTER=%d", (int)ep->after); fprintf (st, " %s", ep->match_pattern); if (ep->cnt > 0) fprintf (st, " [%d]", ep->cnt); if (ep->act) fprintf (st, " %s", ep->act); @@ -10442,28 +10891,30 @@ } t_stat sim_exp_show (FILE *st, CONST EXPECT *exp, const char *match) { CONST EXPTAB *ep = (CONST EXPTAB *)sim_exp_fnd (exp, match, 0); +const char *dev_name = tmxr_expect_line_name (exp); +uint32 default_haltafter = get_default_env_parameter (dev_name, "SIM_EXPECT_HALTAFTER", 0); if (exp->buf_size) { char *bstr = sim_encode_quoted_string (exp->buf, exp->buf_ins); - fprintf (st, "Match Buffer Size: %d\n", exp->buf_size); - fprintf (st, "Buffer Insert Offset: %d\n", exp->buf_ins); - fprintf (st, "Buffer Contents: %s\n", bstr); + fprintf (st, " Match Buffer Size: %d\n", exp->buf_size); + fprintf (st, " Buffer Insert Offset: %d\n", exp->buf_ins); + fprintf (st, " Buffer Contents: %s\n", bstr); + if (default_haltafter) + fprintf (st, " Default HaltAfter: %u instructions\n", (unsigned)default_haltafter); free (bstr); } -if (exp->after) - fprintf (st, "Halt After: %d instructions\n", exp->after); -if (exp->dptr && exp->dbit) - fprintf (st, "Debugging via: SET %s DEBUG%s%s\n", sim_dname(exp->dptr), exp->dptr->debflags ? "=" : "", exp->dptr->debflags ? get_dbg_verb (exp->dbit, exp->dptr) : ""); -fprintf (st, "Match Rules:\n"); +if (exp->dptr && (exp->dbit & exp->dptr->dctrl)) + fprintf (st, " Expect Debugging via: SET %s DEBUG%s%s\n", sim_dname(exp->dptr), exp->dptr->debflags ? "=" : "", exp->dptr->debflags ? get_dbg_verb (exp->dbit, exp->dptr) : ""); +fprintf (st, " Match Rules:\n"); if (!*match) return sim_exp_showall (st, exp); if (!ep) { - fprintf (st, "No Rules match '%s'\n", match); + fprintf (st, " No Rules match '%s'\n", match); return SCPE_ARG; } do { sim_exp_show_tab (st, exp, ep); ep = (CONST EXPTAB *)sim_exp_fnd (exp, match, 1 + (ep - exp->rules)); @@ -10494,10 +10945,12 @@ if ((!exp) || (!exp->rules)) /* Anying to check? */ return SCPE_OK; exp->buf[exp->buf_ins++] = data; /* Save new data */ exp->buf[exp->buf_ins] = '\0'; /* Nul terminate for RegEx match */ +if (exp->buf_data < exp->buf_size) + ++exp->buf_data; /* Record amount of data in buffer */ for (i=0; i < exp->size; i++) { ep = &exp->rules[i]; if (ep->switches & EXP_TYP_REGEX) { #if defined (USE_REGEX) @@ -10508,12 +10961,12 @@ if (tstr) cbuf = tstr; else { if (strlen ((char *)exp->buf) != exp->buf_ins) { /* Nul characters in buffer? */ size_t off; + tstr = (char *)malloc (exp->buf_ins + 1); - tstr[0] = '\0'; for (off=0; off < exp->buf_ins; off += 1 + strlen ((char *)&exp->buf[off])) strcpy (&tstr[strlen (tstr)], (char *)&exp->buf[off]); cbuf = tstr; } @@ -10552,11 +11005,13 @@ } free (matches); #endif } else { - if (exp->buf_ins < ep->size) { /* Match stradle end of buffer */ + if (exp->buf_data < ep->size) /* Too little data to match yet? */ + continue; /* Yes, Try next one. */ + if (exp->buf_ins < ep->size) { /* Match might stradle end of buffer */ /* * First compare the newly deposited data at the beginning * of buffer with the end of the match string */ if (exp->buf_ins) { @@ -10567,12 +11022,12 @@ sim_debug (exp->dbit, exp->dptr, "Checking String[0:%d]: %s\n", exp->buf_ins, estr); sim_debug (exp->dbit, exp->dptr, "Against Match Data: %s\n", mstr); free (estr); free (mstr); } - if (memcmp (exp->buf, &ep->match[ep->size-exp->buf_ins], exp->buf_ins)) - continue; + if (memcmp (exp->buf, &ep->match[ep->size-exp->buf_ins], exp->buf_ins)) /* Tail Match? */ + continue; /* Nope, Try next one. */ } if (sim_deb && exp->dptr && (exp->dptr->dctrl & exp->dbit)) { char *estr = sim_encode_quoted_string (&exp->buf[exp->buf_size-(ep->size-exp->buf_ins)], ep->size-exp->buf_ins); char *mstr = sim_encode_quoted_string (ep->match, ep->size-exp->buf_ins); @@ -10579,12 +11034,12 @@ sim_debug (exp->dbit, exp->dptr, "Checking String[%d:%d]: %s\n", exp->buf_size-(ep->size-exp->buf_ins), ep->size-exp->buf_ins, estr); sim_debug (exp->dbit, exp->dptr, "Against Match Data: %s\n", mstr); free (estr); free (mstr); } - if (memcmp (&exp->buf[exp->buf_size-(ep->size-exp->buf_ins)], ep->match, ep->size-exp->buf_ins)) - continue; + if (memcmp (&exp->buf[exp->buf_size-(ep->size-exp->buf_ins)], ep->match, ep->size-exp->buf_ins)) /* Front Match? */ + continue; /* Nope, Try next one. */ break; } else { if (sim_deb && exp->dptr && (exp->dptr->dctrl & exp->dbit)) { char *estr = sim_encode_quoted_string (&exp->buf[exp->buf_ins-ep->size], ep->size); @@ -10593,12 +11048,12 @@ sim_debug (exp->dbit, exp->dptr, "Checking String[%d:%d]: %s\n", exp->buf_ins-ep->size, ep->size, estr); sim_debug (exp->dbit, exp->dptr, "Against Match Data: %s\n", mstr); free (estr); free (mstr); } - if (memcmp (&exp->buf[exp->buf_ins-ep->size], ep->match, ep->size)) - continue; + if (memcmp (&exp->buf[exp->buf_ins-ep->size], ep->match, ep->size)) /* Whole string match? */ + continue; /* Nope, Try next one. */ break; } } } if (exp->buf_ins == exp->buf_size) { /* At end of match buffer? */ @@ -10608,10 +11063,11 @@ so that the regular expression has a single contiguous buffer to match against instead of the wrapping buffer paradigm which is used when no regular expression rules are in effect */ memmove (exp->buf, &exp->buf[exp->buf_size/2], exp->buf_size-(exp->buf_size/2)); exp->buf_ins -= exp->buf_size/2; + exp->buf_data = exp->buf_ins; sim_debug (exp->dbit, exp->dptr, "Buffer Full - sliding the last %d bytes to start of buffer new insert at: %d\n", (exp->buf_size/2), exp->buf_ins); } else { exp->buf_ins = 0; /* wrap around to beginning */ sim_debug (exp->dbit, exp->dptr, "Buffer wrapping\n"); @@ -10639,15 +11095,15 @@ if (!(ep->switches & EXP_TYP_PERSIST)) /* One shot expect rule? */ sim_exp_clr_tab (exp, ep); /* delete it */ } sim_activate (&sim_expect_unit, /* schedule simulation stop when indicated */ (ep->switches & EXP_TYP_TIME) ? - (int32)((sim_timer_inst_per_sec ()*exp->after)/1000000.0) : - exp->after); + (int32)((sim_timer_inst_per_sec ()*ep->after)/1000000.0) : + ep->after); } /* Matched data is no longer available for future matching */ - exp->buf_ins = 0; + exp->buf_data = exp->buf_ins = 0; } free (tstr); return SCPE_OK; } @@ -10665,16 +11121,12 @@ snd->bufsize = snd->insoff+size; snd->buffer = (uint8 *)realloc(snd->buffer, snd->bufsize); } memcpy(snd->buffer+snd->insoff, data, size); snd->insoff += size; -if (delay) - snd->delay = (sim_switches & SWMASK ('T')) ? (uint32)((sim_timer_inst_per_sec()*delay)/1000000.0) : delay; -if (after) - snd->after = (sim_switches & SWMASK ('T')) ? (uint32)((sim_timer_inst_per_sec()*after)/1000000.0) : after; -if (snd->after == 0) - snd->after = snd->delay; +snd->delay = (sim_switches & SWMASK ('T')) ? (uint32)((sim_timer_inst_per_sec()*delay)/1000000.0) : delay; +snd->after = (sim_switches & SWMASK ('T')) ? (uint32)((sim_timer_inst_per_sec()*after)/1000000.0) : after; snd->next_time = sim_gtime() + snd->after; return SCPE_OK; } /* Cancel Queued input data */ @@ -10687,30 +11139,31 @@ /* Display console Queued input data status */ t_stat sim_show_send_input (FILE *st, const SEND *snd) { +fprintf (st, "%s\n", tmxr_send_line_name (snd)); if (snd->extoff < snd->insoff) { - fprintf (st, "%d bytes of pending input Data:\n ", snd->insoff-snd->extoff); + fprintf (st, " %d bytes of pending input Data:\n ", snd->insoff-snd->extoff); fprint_buffer_string (st, snd->buffer+snd->extoff, snd->insoff-snd->extoff); fprintf (st, "\n"); } else - fprintf (st, "No Pending Input Data\n"); + fprintf (st, " No Pending Input Data\n"); if ((snd->next_time - sim_gtime()) > 0) { - if ((snd->next_time - sim_gtime()) > (sim_timer_inst_per_sec()/1000000.0)) - fprintf (st, "Minimum of %d instructions (%d microseconds) before sending first character\n", (int)(snd->next_time - sim_gtime()), + if (((snd->next_time - sim_gtime()) > (sim_timer_inst_per_sec()/1000000.0)) && ((sim_timer_inst_per_sec()/1000000.0) > 0.0)) + fprintf (st, " Minimum of %d instructions (%d microseconds) before sending first character\n", (int)(snd->next_time - sim_gtime()), (int)((snd->next_time - sim_gtime())/(sim_timer_inst_per_sec()/1000000.0))); else - fprintf (st, "Minimum of %d instructions before sending first character\n", (int)(snd->next_time - sim_gtime())); + fprintf (st, " Minimum of %d instructions before sending first character\n", (int)(snd->next_time - sim_gtime())); } -if (snd->delay > (sim_timer_inst_per_sec()/1000000.0)) - fprintf (st, "Minimum of %d instructions (%d microseconds) between characters\n", (int)snd->delay, (int)(snd->delay/(sim_timer_inst_per_sec()/1000000.0))); +if ((snd->delay > (sim_timer_inst_per_sec()/1000000.0)) && ((sim_timer_inst_per_sec()/1000000.0) > 0.0)) + fprintf (st, " Minimum of %d instructions (%d microseconds) between characters\n", (int)snd->delay, (int)(snd->delay/(sim_timer_inst_per_sec()/1000000.0))); else - fprintf (st, "Minimum of %d instructions between characters\n", (int)snd->delay); -if (snd->dptr && snd->dbit) - fprintf (st, "Debugging via: SET %s DEBUG%s%s\n", sim_dname(snd->dptr), snd->dptr->debflags ? "=" : "", snd->dptr->debflags ? get_dbg_verb (snd->dbit, snd->dptr) : ""); + fprintf (st, " Minimum of %d instructions between characters\n", (int)snd->delay); +if (snd->dptr && (snd->dbit & snd->dptr->dctrl)) + fprintf (st, " Send Debugging via: SET %s DEBUG%s%s\n", sim_dname(snd->dptr), snd->dptr->debflags ? "=" : "", snd->dptr->debflags ? get_dbg_verb (snd->dbit, snd->dptr) : ""); return SCPE_OK; } /* Poll for Queued input data */ @@ -10793,11 +11246,11 @@ int32 offset = 0; if (dptr->debflags == 0) return debtab_none; -dbits &= dptr->dctrl; /* Look for just the bits tha matched */ +dbits &= dptr->dctrl; /* Look for just the bits that matched */ /* Find matching words for bitmask */ while (dptr->debflags[offset].name && (offset < 32)) { if (dptr->debflags[offset].mask == dbits) /* All Bits Match */ @@ -10996,10 +11449,12 @@ char *buf = stackbuf; int32 len; va_list arglist; t_bool inhibit_message = (!sim_show_message || (stat & SCPE_NOMESSAGE)); +if ((stat == SCPE_OK) && (sim_quiet || (sim_switches & SWMASK ('Q')))) + return stat; while (1) { /* format passed string, args */ va_start (arglist, fmt); #if defined(NO_vsnprintf) len = vsprintf (buf, fmt, arglist); #else /* !defined(NO_vsnprintf) */ @@ -11023,12 +11478,14 @@ } break; } if (sim_do_ocptr[sim_do_depth]) { - if (!sim_do_echo && !sim_quiet && !inhibit_message) + if (!sim_do_echo && !inhibit_message && !sim_cmd_echoed) { sim_printf("%s> %s\n", do_position(), sim_do_ocptr[sim_do_depth]); + sim_cmd_echoed = TRUE; + } else { if (sim_deb) { /* Always put context in debug output */ TMLN *saved_oline = sim_oline; sim_oline = NULL; /* avoid potential debug to active socket */ @@ -11075,15 +11532,11 @@ device is set into 'raw' mode when the cpu is booted, and the extra returns don't hurt any other systems. Callers should be calling sim_debug() which is a macro defined in scp.h which evaluates the action condition before incurring call overhead. */ -#if defined(__cplusplus) -void _sim_debug (uint32 dbits, void* vdptr, const char* fmt, ...) -#else void _sim_debug (uint32 dbits, DEVICE* vdptr, const char* fmt, ...) -#endif { DEVICE *dptr = (DEVICE *)vdptr; if (sim_deb && dptr && (dptr->dctrl & dbits)) { TMLN *saved_oline = sim_oline; char stackbuf[STACKBUFSIZE]; @@ -11577,11 +12030,11 @@ if (!*start || *start == '\n'|| n == 0 || n >= VSMAX) FAIL (SCPE_ARG, Invalid parameter number, start); while (n > vsnum) /* Get arg pointer if not cached */ vstrings[vsnum++] = va_arg (ap, char *); end = vstrings[n-1]; /* Check for True */ - if (!end || !(toupper (*end) == 'T' || *end == '1')) { + if (!end || !(sim_toupper (*end) == 'T' || *end == '1')) { excluded = TRUE; /* False, skip topic this time */ if (*htext) htext++; continue; } @@ -11806,11 +12259,11 @@ while (*cptr) { if (blankch (*cptr)) { *cptr++ = '_'; } else { - *cptr = (char)toupper (*cptr); + *cptr = (char)sim_toupper (*cptr); cptr++; } } if (!strncmp (cbuf, token, strlen (token))) { if (match) @@ -11878,11 +12331,11 @@ } else p = sim_name; top.title = (char *) malloc (strlen (p) + ((flag & SCP_HELP_ATTACH)? sizeof (attach_help)-1: 0) +1); for (i = 0; p[i]; i++ ) - top.title[i] = (char)toupper (p[i]); + top.title[i] = (char)sim_toupper (p[i]); top.title[i] = '\0'; if (flag & SCP_HELP_ATTACH) strcpy (top.title+i, attach_help); top.label = (char *) malloc (sizeof ("1")); @@ -12103,11 +12556,11 @@ * path. Having failed in the CWD, try to find the location * of the executable. Failing that, try the 'help' subdirectory * of the executable. Failing that, we're out of luck. */ fbuf[sizeof(fbuf)-1] = '\0'; - strncpy (fbuf, sim_argv[0], sizeof (fbuf)-1); + strlcpy (fbuf, sim_argv[0], sizeof (fbuf)); if ((p = (char *)match_ext (fbuf, "EXE"))) *p = '\0'; if ((p = strrchr (fbuf, '\\'))) { p[1] = '\0'; d = "%s\\"; Index: src/scp.h ================================================================== --- src/scp.h +++ src/scp.h @@ -89,10 +89,11 @@ t_stat set_default_cmd (int32 flg, CONST char *cptr); t_stat pwd_cmd (int32 flg, CONST char *cptr); t_stat dir_cmd (int32 flg, CONST char *cptr); t_stat type_cmd (int32 flg, CONST char *cptr); t_stat delete_cmd (int32 flg, CONST char *cptr); +t_stat copy_cmd (int32 flg, CONST char *cptr); t_stat brk_cmd (int32 flag, CONST char *ptr); t_stat do_cmd (int32 flag, CONST char *ptr); t_stat goto_cmd (int32 flag, CONST char *ptr); t_stat return_cmd (int32 flag, CONST char *ptr); t_stat shift_cmd (int32 flag, CONST char *ptr); @@ -100,10 +101,11 @@ t_stat on_cmd (int32 flag, CONST char *ptr); t_stat noop_cmd (int32 flag, CONST char *ptr); t_stat assert_cmd (int32 flag, CONST char *ptr); t_stat send_cmd (int32 flag, CONST char *ptr); t_stat expect_cmd (int32 flag, CONST char *ptr); +t_stat sleep_cmd (int32 flag, CONST char *ptr); t_stat help_cmd (int32 flag, CONST char *ptr); t_stat screenshot_cmd (int32 flag, CONST char *ptr); t_stat spawn_cmd (int32 flag, CONST char *ptr); t_stat echo_cmd (int32 flag, CONST char *ptr); @@ -146,21 +148,85 @@ const char *sim_dname (DEVICE *dptr); const char *sim_uname (UNIT *dptr); const char *sim_set_uname (UNIT *uptr, const char *uname); t_stat get_yn (const char *ques, t_stat deflt); char *sim_trim_endspc (char *cptr); -int sim_isspace (char c); -int sim_islower (char c); -int sim_isalpha (char c); -int sim_isprint (char c); -int sim_isdigit (char c); -int sim_isgraph (char c); -int sim_isalnum (char c); +int sim_isspace (int c); +#ifdef isspace +#undef isspace +#endif +#ifndef IN_SCP_C +#define isspace(chr) sim_isspace (chr) +#endif +int sim_islower (int c); +#ifdef islower +#undef islower +#endif +#ifndef IN_SCP_C +#define islower(chr) sim_islower (chr) +#endif +int sim_isalpha (int c); +#ifdef isalpha +#undef isalpha +#endif +#ifndef IN_SCP_C +#define isalpha(chr) sim_isalpha (chr) +#endif +int sim_isprint (int c); +#ifdef isprint +#undef isprint +#endif +#ifndef IN_SCP_C +#define isprint(chr) sim_isprint (chr) +#endif +int sim_isdigit (int c); +#ifdef isdigit +#undef isdigit +#endif +#ifndef IN_SCP_C +#define isdigit(chr) sim_isdigit (chr) +#endif +int sim_isgraph (int c); +#ifdef isgraph +#undef isgraph +#endif +#ifndef IN_SCP_C +#define isgraph(chr) sim_isgraph (chr) +#endif +int sim_isalnum (int c); +#ifdef isalnum +#undef isalnum +#endif +#ifndef IN_SCP_C +#define isalnum(chr) sim_isalnum (chr) +#endif +int sim_toupper (int c); +int sim_tolower (int c); +#ifdef toupper +#undef toupper +#endif +#define toupper(chr) sim_toupper(chr) +#ifdef tolower +#undef tolower +#endif +#define tolower(chr) sim_tolower(chr) int sim_strncasecmp (const char *string1, const char *string2, size_t len); int sim_strcasecmp (const char *string1, const char *string2); size_t sim_strlcat (char *dst, const char *src, size_t size); size_t sim_strlcpy (char *dst, const char *src, size_t size); +#ifndef strlcpy +#define strlcpy(dst, src, size) sim_strlcpy((dst), (src), (size)) +#endif +#ifndef strlcat +#define strlcat(dst, src, size) sim_strlcat((dst), (src), (size)) +#endif +#ifndef strncasecmp +#define strncasecmp(str1, str2, len) sim_strncasecmp((str1), (str2), (len)) +#endif +#ifndef strcasecmp +#define strcasecmp(str1, str2) sim_strcasecmp ((str1), (str2)) +#endif CONST char *get_sim_opt (int32 opt, CONST char *cptr, t_stat *st); const char *put_switches (char *buf, size_t bufsize, uint32 sw); CONST char *get_glyph (const char *iptr, char *optr, char mchar); CONST char *get_glyph_nc (const char *iptr, char *optr, char mchar); CONST char *get_glyph_quoted (const char *iptr, char *optr, char mchar); @@ -184,10 +250,11 @@ t_stat sim_print_val (t_value val, uint32 radix, uint32 width, uint32 format); const char *sim_fmt_secs (double seconds); const char *sim_fmt_numeric (double number); const char *sprint_capac (DEVICE *dptr, UNIT *uptr); char *read_line (char *cptr, int32 size, FILE *stream); +char *read_line_p (const char *prompt, char *ptr, int32 size, FILE *stream); void fprint_reg_help (FILE *st, DEVICE *dptr); void fprint_set_help (FILE *st, DEVICE *dptr); void fprint_show_help (FILE *st, DEVICE *dptr); CTAB *find_cmd (const char *gbuf); DEVICE *find_dev (const char *ptr); @@ -235,26 +302,16 @@ void sim_debug_bits (uint32 dbits, DEVICE* dptr, BITFIELD* bitdefs, uint32 before, uint32 after, int terminate); #if defined (__DECC) && defined (__VMS) && (defined (__VAX) || (__DECC_VER < 60590001)) #define CANT_USE_MACRO_VA_ARGS 1 #endif -#if defined(__cplusplus) -#ifdef CANT_USE_MACRO_VA_ARGS -#define _sim_debug sim_debug -void sim_debug (uint32 dbits, void* dptr, const char *fmt, ...) GCC_FMT_ATTR(3, 4); -#else -void _sim_debug (uint32 dbits, void* dptr, const char *fmt, ...) GCC_FMT_ATTR(3, 4); -#define sim_debug(dbits, dptr, ...) do { if (sim_deb && dptr && ((dptr)->dctrl & dbits)) _sim_debug (dbits, dptr, __VA_ARGS__);} while (0) -#endif -#else #ifdef CANT_USE_MACRO_VA_ARGS #define _sim_debug sim_debug void sim_debug (uint32 dbits, DEVICE* dptr, const char *fmt, ...) GCC_FMT_ATTR(3, 4); #else void _sim_debug (uint32 dbits, DEVICE* dptr, const char *fmt, ...) GCC_FMT_ATTR(3, 4); -#define sim_debug(dbits, dptr, ...) do { if (sim_deb && dptr && ((dptr)->dctrl & dbits)) _sim_debug (dbits, dptr, __VA_ARGS__);} while (0) -#endif +#define sim_debug(dbits, dptr, ...) do { if (sim_deb && dptr && ((dptr)->dctrl & (dbits))) _sim_debug (dbits, dptr, __VA_ARGS__);} while (0) #endif void fprint_stopped_gen (FILE *st, t_stat v, REG *pc, DEVICE *dptr); #define SCP_HELP_FLAT (1u << 31) /* Force flat help when prompting is not possible */ #define SCP_HELP_ONECMD (1u << 30) /* Display one topic, do not prompt */ #define SCP_HELP_ATTACH (1u << 29) /* Top level topic is ATTACH help */ @@ -272,10 +329,11 @@ extern DEVICE *sim_dflt_dev; extern DEVICE *sim_dfdev; extern UNIT *sim_dfunit; extern int32 sim_interval; extern int32 sim_switches; +extern int32 sim_switch_number; extern int32 sim_quiet; extern int32 sim_step; extern t_stat sim_last_cmd_stat; /* Command Status */ extern FILE *sim_log; /* log file */ extern FILEREF *sim_log_ref; /* log file file reference */ Index: src/sim_console.c ================================================================== --- src/sim_console.c +++ src/sim_console.c @@ -226,17 +226,17 @@ DEVICE sim_con_telnet = { "CON-TELNET", sim_con_units, sim_con_reg, sim_con_mod, 2, 0, 0, 0, 0, 0, NULL, NULL, sim_con_reset, NULL, sim_con_attach, sim_con_detach, - NULL, DEV_DEBUG, 0, sim_con_debug, + NULL, DEV_DEBUG | DEV_NOSAVE, 0, sim_con_debug, NULL, NULL, NULL, NULL, NULL, sim_con_telnet_description}; TMLN sim_con_ldsc = { 0 }; /* console line descr */ TMXR sim_con_tmxr = { 1, 0, 0, &sim_con_ldsc, NULL, &sim_con_telnet };/* console line mux */ -SEND sim_con_send = {SEND_DEFAULT_DELAY, &sim_con_telnet, DBG_SND}; +SEND sim_con_send = {0, &sim_con_telnet, DBG_SND}; EXPECT sim_con_expect = {&sim_con_telnet, DBG_EXP}; static t_bool sim_con_console_port = TRUE; /* Enable automatic WRU console polling */ @@ -338,14 +338,14 @@ { "LOG", &sim_show_cons_log, 0 }, { "TELNET", &sim_show_telnet, 0 }, { "DEBUG", &sim_show_cons_debug, 0 }, { "BUFFERED", &sim_show_cons_buff, 0 }, { "EXPECT", &sim_show_cons_expect, 0 }, - { "HALT", &sim_show_cons_expect, 0 }, + { "HALT", &sim_show_cons_expect, -1 }, { "INPUT", &sim_show_cons_send_input, 0 }, - { "RESPONSE", &sim_show_cons_send_input, 0 }, - { "DELAY", &sim_show_cons_expect, 0 }, + { "RESPONSE", &sim_show_cons_send_input, -1 }, + { "DELAY", &sim_show_cons_expect, -1 }, { NULL, NULL, 0 } }; static CTAB set_con_telnet_tab[] = { { "LOG", &sim_set_cons_log, 0 }, @@ -409,11 +409,12 @@ SHTAB *shptr; int32 i; if (*cptr == 0) { /* show all */ for (i = 0; show_con_tab[i].name; i++) - show_con_tab[i].action (st, dptr, uptr, show_con_tab[i].arg, cptr); + if (show_con_tab[i].arg != -1) + show_con_tab[i].action (st, dptr, uptr, show_con_tab[i].arg, cptr); return SCPE_OK; } while (*cptr != 0) { cptr = get_glyph (cptr, gbuf, ','); /* get modifier */ if ((shptr = find_shtab (show_con_tab, gbuf))) @@ -477,10 +478,11 @@ int *vals; /* values */ }; typedef struct BITSAMPLE_REG BITSAMPLE_REG; struct BITSAMPLE_REG { REG *reg; /* Register to be sampled */ + uint32 idx; /* Register index */ t_bool indirect; /* Register value points at memory */ DEVICE *dptr; /* Device register is part of */ UNIT *uptr; /* Unit Register is related to */ uint32 width; /* number of bits to sample */ BITSAMPLE *bits; @@ -500,18 +502,18 @@ UNIT *uptr; /* remote console unit */ uint32 repeat_interval; /* usecs between repeat execution */ t_bool repeat_pending; /* repeat delivery pending */ char *repeat_action; /* command(s) to repeatedly execute */ int smp_sample_interval; /* cycles between samples */ + int smp_sample_dither_pct; /* dithering of cycles interval */ uint32 smp_reg_count; /* sample register count */ BITSAMPLE_REG *smp_regs; /* registers being sampled */ }; REMOTE *sim_rem_consoles = NULL; static TMXR sim_rem_con_tmxr = { 0, 0, 0, NULL, NULL, &sim_remote_console };/* remote console line mux */ static uint32 sim_rem_read_timeout = 30; /* seconds before automatic continue */ -static uint32 *sim_rem_read_timeouts = NULL;/* per line read timeout (default from sim_rem_read_timeout) */ static int32 sim_rem_active_number = -1; /* -1 - not active, >= 0 is index of active console */ int32 sim_rem_cmd_active_line = -1; /* step in progress on line # */ static CTAB *sim_rem_active_command = NULL; /* active command */ static char *sim_rem_command_buf; /* active command buffer */ static t_bool sim_log_temp = FALSE; /* temporary log file active */ @@ -531,11 +533,14 @@ return SCPE_OK; } for (reg = 0; reg < rem->smp_reg_count; reg++) { uint32 bit; - fprintf (st, "}%s %s%s %d:", rem->smp_regs[reg].dptr->name, rem->smp_regs[reg].reg->name, rem->smp_regs[reg].indirect ? " -I" : "", rem->smp_regs[reg].bits[0].depth); + if (rem->smp_regs[reg].reg->depth > 1) + fprintf (st, "}%s %s[%d] %s %d:", rem->smp_regs[reg].dptr->name, rem->smp_regs[reg].reg->name, rem->smp_regs[reg].idx, rem->smp_regs[reg].indirect ? " -I" : "", rem->smp_regs[reg].bits[0].depth); + else + fprintf (st, "}%s %s%s %d:", rem->smp_regs[reg].dptr->name, rem->smp_regs[reg].reg->name, rem->smp_regs[reg].indirect ? " -I" : "", rem->smp_regs[reg].bits[0].depth); for (bit = 0; bit < rem->smp_regs[reg].width; bit++) fprintf (st, "%s%d", (bit != 0) ? "," : "", rem->smp_regs[reg].bits[bit].tot); fprintf (st, "\n"); } return SCPE_OK; @@ -612,18 +617,24 @@ } if (rem->smp_reg_count) { uint32 reg; DEVICE *dptr = NULL; - fprintf (st, "Register Bit Sampling is occurring every %d cycles\n", rem->smp_sample_interval); + if (rem->smp_sample_dither_pct) + fprintf (st, "Register Bit Sampling is occurring every %d cycles (dithered %d percent)\n", rem->smp_sample_interval, rem->smp_sample_dither_pct); + else + fprintf (st, "Register Bit Sampling is occurring every %d cycles\n", rem->smp_sample_interval); fprintf (st, " Registers being sampled are: "); for (reg = 0; reg < rem->smp_reg_count; reg++) { if (rem->smp_regs[reg].indirect) fprintf (st, " indirect "); if (dptr != rem->smp_regs[reg].dptr) fprintf (st, "%s ", rem->smp_regs[reg].dptr->name); - fprintf (st, "%s%s", rem->smp_regs[reg].reg->name, ((reg + 1) < rem->smp_reg_count) ? ", " : ""); + if (rem->smp_regs[reg].reg->depth > 1) + fprintf (st, "%s[%d]%s", rem->smp_regs[reg].reg->name, rem->smp_regs[reg].idx, ((reg + 1) < rem->smp_reg_count) ? ", " : ""); + else + fprintf (st, "%s%s", rem->smp_regs[reg].reg->name, ((reg + 1) < rem->smp_reg_count) ? ", " : ""); dptr = rem->smp_regs[reg].dptr; } fprintf (st, "\n"); if (sim_switches & SWMASK ('D')) sim_rem_sample_output (st, rem->line); @@ -859,11 +870,11 @@ char cbuf[4*CBUFSIZE], gbuf[CBUFSIZE], *argv[1] = {NULL}; CONST char *cptr; int32 saved_switches = sim_switches; t_stat stat; -sim_strlcpy (cbuf, sim_rem_command_buf, sizeof (cbuf)); +strlcpy (cbuf, sim_rem_command_buf, sizeof (cbuf)); while (isspace(cbuf[0])) memmove (cbuf, cbuf+1, strlen(cbuf+1)+1); /* skip leading whitespace */ sim_sub_args (cbuf, sizeof(cbuf), argv); cptr = cbuf; cptr = get_glyph (cptr, gbuf, 0); /* get command glyph */ @@ -873,10 +884,12 @@ sim_ttcmd (); /* restore console */ stat = sim_rem_active_command->action (sim_rem_active_command->arg, cptr);/* execute command */ if (stat != SCPE_OK) stat = _sim_rem_message (gbuf, stat); /* display results */ sim_last_cmd_stat = SCPE_BARE_STATUS(stat); +if (sim_vm_post != NULL) /* optionally let the simulator know */ + (*sim_vm_post) (TRUE); /* something might have changed */ if (!sim_processing_event) { sim_ttrun (); /* set console mode */ sim_cancel (rem_con_data_unit); /* force immediate activation of sim_rem_con_data_svc */ sim_activate (rem_con_data_unit, -1); } @@ -1025,11 +1038,11 @@ COLLECT nnn SAMPLES EVERY nnn CYCLES reg{,reg...} */ static t_stat sim_rem_collect_cmd_setup (int32 line, CONST char **iptr) { char gbuf[CBUFSIZE]; -int32 samples, cycles; +int32 samples, cycles, dither_pct; t_bool all_stop = FALSE; t_stat stat = SCPE_OK; CONST char *cptr = *iptr; REMOTE *rem = &sim_rem_consoles[line]; @@ -1071,10 +1084,11 @@ else stat = sim_messagef (SCPE_ARG, "Expected value or STOP found: %s\n", gbuf); } else { const char *tptr; + int32 event_time = rem->smp_sample_interval; cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */ if (MATCH_CMD (gbuf, "SAMPLES") != 0) { *iptr = cptr; return sim_messagef (SCPE_ARG, "Expected SAMPLES found: %s\n", gbuf); @@ -1093,19 +1107,37 @@ cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */ if ((MATCH_CMD (gbuf, "CYCLES") != 0) || (*cptr == 0)) { *iptr = cptr; return sim_messagef (SCPE_ARG, "Expected CYCLES found: %s\n", gbuf); } + cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */ + if ((MATCH_CMD (gbuf, "DITHER") != 0) || (*cptr == 0)) { + *iptr = cptr; + return sim_messagef (SCPE_ARG, "Expected DITHER found: %s\n", gbuf); + } + cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */ + dither_pct = (int32) get_uint (gbuf, 10, INT_MAX, &stat); + if ((stat != SCPE_OK) || /* error? */ + (dither_pct < 0) || (dither_pct > 25)) { + *iptr = cptr; + return sim_messagef (SCPE_ARG, "Expected value found: %s\n", gbuf); + } + cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */ + if ((MATCH_CMD (gbuf, "PERCENT") != 0) || (*cptr == 0)) { + *iptr = cptr; + return sim_messagef (SCPE_ARG, "Expected PERCENT found: %s\n", gbuf); + } tptr = strcpy (gbuf, "STOP"); /* Start from a clean slate */ sim_rem_collect_cmd_setup (rem->line, &tptr); rem->smp_sample_interval = cycles; rem->smp_reg_count = 0; while (cptr && *cptr) { const char *comma = strchr (cptr, ','); char tbuf[2*CBUFSIZE]; uint32 bit, width; REG *reg; + uint32 idx; int32 saved_switches = sim_switches; t_bool indirect = FALSE; BITSAMPLE_REG *smp_regs; if (comma) { @@ -1130,17 +1162,37 @@ reg = find_reg (gbuf, &tptr, sim_dfdev); if (reg == NULL) { stat = sim_messagef (SCPE_NXREG, "Nonexistent Register: %s\n", gbuf); break; } + if (*tptr == '[') { /* subscript? */ + const char *tgptr = ++tptr; + + if (reg->depth <= 1) { /* array register? */ + stat = sim_messagef (SCPE_SUB, "Not Array Register: %s\n", reg->name); + break; + } + idx = (uint32) strtotv (tgptr, &tptr, 10); /* convert index */ + if ((tgptr == tptr) || (*tptr++ != ']')) { + stat = sim_messagef (SCPE_SUB, "Missing or Invalid Register Subscript: %s[%s\n", reg->name, tgptr); + break; + } + if (idx >= reg->depth) { /* validate subscript */ + stat = sim_messagef (SCPE_SUB, "Invalid Register Subscript: %s[%d]\n", reg->name, idx); + break; + } + } + else + idx = 0; /* not array */ smp_regs = (BITSAMPLE_REG *)realloc (rem->smp_regs, (rem->smp_reg_count + 1) * sizeof(*smp_regs)); if (smp_regs == NULL) { stat = SCPE_MEM; break; } rem->smp_regs = smp_regs; smp_regs[rem->smp_reg_count].reg = reg; + smp_regs[rem->smp_reg_count].idx = idx; smp_regs[rem->smp_reg_count].dptr = sim_dfdev; smp_regs[rem->smp_reg_count].uptr = sim_dfunit; smp_regs[rem->smp_reg_count].indirect = indirect; width = indirect ? sim_dfdev->dwidth : reg->width; smp_regs[rem->smp_reg_count].width = width; @@ -1165,11 +1217,13 @@ *iptr = cptr; cptr = strcpy (gbuf, "STOP"); sim_rem_collect_cmd_setup (line, &cptr);/* Cleanup mess */ return stat; } - sim_activate (&rem_con_smp_smpl_units[rem->line], rem->smp_sample_interval); + if (rem->smp_sample_dither_pct) + event_time = (((rand() % (2 * rem->smp_sample_dither_pct)) - rem->smp_sample_dither_pct) * event_time) / 100; + sim_activate (&rem_con_smp_smpl_units[rem->line], event_time); } *iptr = cptr; return stat; } @@ -1207,11 +1261,11 @@ } static void sim_rem_collect_reg_bits (BITSAMPLE_REG *reg) { uint32 i; -t_value val = get_rval (reg->reg, 0); +t_value val = get_rval (reg->reg, reg->idx); if (reg->indirect) val = get_aval ((t_addr)val, reg->dptr, reg->uptr); val = val >> reg->reg->offset; for (i = 0; i < reg->width; i++) { @@ -1242,14 +1296,18 @@ t_stat sim_rem_con_smp_collect_svc (UNIT *uptr) { int line = uptr - rem_con_smp_smpl_units; REMOTE *rem = &sim_rem_consoles[line]; -sim_debug (DBG_SAM, &sim_remote_console, "sim_rem_con_smp_collect_svc(line=%d) - interval=%d\n", line, rem->smp_sample_interval); +sim_debug (DBG_SAM, &sim_remote_console, "sim_rem_con_smp_collect_svc(line=%d) - interval=%d, dither=%d%%\n", line, rem->smp_sample_interval, rem->smp_sample_dither_pct); if (rem->smp_sample_interval && (rem->smp_reg_count != 0)) { + int32 event_time = rem->smp_sample_interval; + + if (rem->smp_sample_dither_pct) + event_time = (((rand() % (2 * rem->smp_sample_dither_pct)) - rem->smp_sample_dither_pct) * event_time) / 100; sim_rem_collect_registers (rem); - sim_activate (uptr, rem->smp_sample_interval); /* reschedule */ + sim_activate (uptr, event_time); /* reschedule */ } return SCPE_OK; } /* Unit service for remote console data polling */ @@ -1347,11 +1405,11 @@ } lp = &sim_rem_con_tmxr.ldsc[i]; if (!master_session) tmxr_linemsg (lp, "\r\nSimulator paused.\r\n"); if (!master_session && rem->read_timeout) { - tmxr_linemsgf (lp, "Simulation will resume automatically if input is not received in %d seconds\n", sim_rem_read_timeouts[i]); + tmxr_linemsgf (lp, "Simulation will resume automatically if input is not received in %d seconds\n", rem->read_timeout); tmxr_linemsgf (lp, "\r\n"); tmxr_send_buffered_data (lp); /* flush any buffered data */ } } else { @@ -1914,21 +1972,28 @@ t_stat stat_nomessage; sim_printf ("Command input starting on Master Remote Console Session\n"); stat = sim_run_boot_prep (0); sim_rem_master_was_enabled = TRUE; + sim_last_cmd_stat = SCPE_OK; while (sim_rem_master_mode) { sim_rem_consoles[0].single_mode = FALSE; sim_cancel (rem_con_data_unit); sim_activate (rem_con_data_unit, -1); stat = run_cmd (RU_GO, ""); if (stat != SCPE_TTMO) { stat_nomessage = stat & SCPE_NOMESSAGE; /* extract possible message supression flag */ stat = _sim_rem_message ("RUN", stat); } + sim_debug (DBG_MOD, &sim_remote_console, "Master Session Returned: Status - %d Active_Line: %d, Mode: %s, Active Cmd: %s\n", stat, sim_rem_cmd_active_line, sim_rem_consoles[0].single_mode ? "Single" : "^E Stopped", sim_rem_active_command ? sim_rem_active_command->name : ""); if (stat == SCPE_EXIT) sim_rem_master_mode = FALSE; + sim_rem_cmd_active_line = 0; /* Make it look like */ + sim_rem_consoles[0].single_mode = FALSE; + if (stat != SCPE_STEP) + sim_rem_active_command = &allowed_single_remote_cmds[0];/* Dummy */ + sim_last_cmd_stat = SCPE_BARE_STATUS(stat); /* make exit status available to remote console */ } sim_rem_master_was_enabled = FALSE; sim_rem_master_was_connected = FALSE; if (sim_log_temp) { /* If we setup a temporary log, clean it now */ int32 save_quiet = sim_quiet; @@ -2204,19 +2269,19 @@ fprintf (st, " Debug messages display time of day as hh:mm:ss.msec%s\n", sim_deb_switches & SWMASK ('R') ? " relative to the start of debugging" : ""); if (sim_deb_switches & SWMASK ('A')) fprintf (st, " Debug messages display time of day as seconds.msec%s\n", sim_deb_switches & SWMASK ('R') ? " relative to the start of debugging" : ""); for (i = 0; (dptr = sim_devices[i]) != NULL; i++) { if (!(dptr->flags & DEV_DIS) && - (dptr->flags & DEV_DEBUG) && + ((dptr->flags & DEV_DEBUG) || (dptr->debflags)) && (dptr->dctrl)) { fprintf (st, "Device: %-6s ", dptr->name); show_dev_debug (st, dptr, NULL, 0, NULL); } } for (i = 0; sim_internal_device_count && (dptr = sim_internal_devices[i]); ++i) { if (!(dptr->flags & DEV_DIS) && - (dptr->flags & DEV_DEBUG) && + ((dptr->flags & DEV_DEBUG) || (dptr->debflags)) && (dptr->dctrl)) { fprintf (st, "Device: %-6s ", dptr->name); show_dev_debug (st, dptr, NULL, 0, NULL); } } @@ -2431,10 +2496,11 @@ /* Show the console expect rules and state */ t_stat sim_show_cons_expect (FILE *st, DEVICE *dunused, UNIT *uunused, int32 flag, CONST char *cptr) { +fprintf (st, "Console Expect processing:\n"); return sim_exp_show (st, &sim_con_expect, cptr); } /* Log File Open/Close/Show Support */ @@ -2626,10 +2692,11 @@ /* Display console Queued input data status */ t_stat sim_show_cons_send_input (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr) { +fprintf (st, "Console Send processing:\n"); return sim_show_send_input (st, &sim_con_send); } /* Poll for character */ @@ -2641,11 +2708,14 @@ return c; if (!sim_rem_master_mode) { if ((sim_con_ldsc.rxbps) && /* rate limiting && */ (sim_gtime () < sim_con_ldsc.rxnexttime)) /* too soon? */ return SCPE_OK; /* not yet */ - c = sim_os_poll_kbd (); /* get character */ + if (sim_ttisatty ()) + c = sim_os_poll_kbd (); /* get character */ + else + c = SCPE_OK; if (c == SCPE_STOP) { /* ^E */ stop_cpu = 1; /* Force a stop (which is picked up by sim_process_event */ return SCPE_OK; } if ((sim_con_tmxr.master == 0) && /* not Telnet? */ @@ -2980,11 +3050,15 @@ return r2; } t_bool sim_ttisatty (void) { -return sim_os_ttisatty (); +static int answer = -1; + +if (answer == -1) + answer = sim_os_ttisatty (); +return (t_bool)answer; } /* Platform specific routine definitions */ @@ -3216,22 +3290,24 @@ return SCPE_OK; } static t_stat sim_os_ttrun (void) { -if ((std_input) && /* If Not Background process? */ +if ((sim_ttisatty ()) && + (std_input) && /* If Not Background process? */ (std_input != INVALID_HANDLE_VALUE)) { if (!GetConsoleMode(std_input, &saved_input_mode)) - return SCPE_TTYERR; + return sim_messagef (SCPE_TTYERR, "GetConsoleMode() error: 0x%X\n", (unsigned int)GetLastError ()); if ((!SetConsoleMode(std_input, ENABLE_VIRTUAL_TERMINAL_INPUT)) && (!SetConsoleMode(std_input, RAW_MODE))) - return SCPE_TTYERR; + return sim_messagef (SCPE_TTYERR, "SetConsoleMode() error: 0x%X\n", (unsigned int)GetLastError ()); } if ((std_output) && /* If Not Background process? */ (std_output != INVALID_HANDLE_VALUE)) { if (GetConsoleMode(std_output, &saved_output_mode)) - SetConsoleMode(std_output, ENABLE_VIRTUAL_TERMINAL_PROCESSING|ENABLE_PROCESSED_OUTPUT); + if (!SetConsoleMode(std_output, ENABLE_VIRTUAL_TERMINAL_PROCESSING|ENABLE_PROCESSED_OUTPUT)) + SetConsoleMode(std_output, ENABLE_PROCESSED_OUTPUT); } if (sim_log) { fflush (sim_log); _setmode (_fileno (sim_log), _O_BINARY); } @@ -3244,11 +3320,12 @@ if (sim_log) { fflush (sim_log); _setmode (_fileno (sim_log), _O_TEXT); } sim_os_set_thread_priority (PRIORITY_NORMAL); -if ((std_input) && /* If Not Background process? */ +if ((sim_ttisatty ()) && + (std_input) && /* If Not Background process? */ (std_input != INVALID_HANDLE_VALUE) && (!SetConsoleMode(std_input, saved_input_mode))) /* Restore Normal mode */ return SCPE_TTYERR; if ((std_output) && /* If Not Background process? */ (std_output != INVALID_HANDLE_VALUE) && @@ -3596,12 +3673,12 @@ SIOUXSettings.columns = 80; SIOUXSettings.rows = 40; SIOUXSettings.toppixel = 42; SIOUXSettings.leftpixel = 6; iBeamCursorH = GetCursor(iBeamCursor); - sim_strlcat(title, sim_name, sizeof(title)); - sim_strlcat(title, " Simulator", sizeof(title)); + strlcat(title, sim_name, sizeof(title)); + strlcat(title, " Simulator", sizeof(title)); title[0] = strlen(title) - 1; /* Pascal string done */ for (i = 0; i <= title[0]; i++) { /* copy to unsigned char */ ptitle[i] = title[i]; } SIOUXSetTitle(ptitle); @@ -3881,11 +3958,11 @@ static t_bool sim_os_poll_kbd_ready (int ms_timeout) { fd_set readfds; struct timeval timeout; -if (!sim_os_ttisatty()) { /* skip if !tty */ +if (!sim_ttisatty()) { /* skip if !tty */ sim_os_ms_sleep (ms_timeout); return FALSE; } FD_ZERO (&readfds); FD_SET (0, &readfds); @@ -3955,11 +4032,12 @@ sprintf (mbuf2, "%s%s%s", (sim_switches & SWMASK ('A')) ? "\n" : "", mbuf, (sim_switches & SWMASK ('I')) ? "" : "\n"); free (mbuf); mbuf = sim_encode_quoted_string ((uint8 *)mbuf2, strlen (mbuf2)); - sim_exp_set (&sim_con_expect, mbuf, 0, sim_con_expect.after, EXP_TYP_PERSIST, NULL); + sim_switches = EXP_TYP_PERSIST; + sim_set_expect (&sim_con_expect, mbuf); free (mbuf); free (mbuf2); } return SCPE_OK; @@ -3978,11 +4056,11 @@ if (cptr == NULL || *cptr == 0) return SCPE_2FARG; /* need arg */ rbuf = (uint8 *)malloc (1 + strlen(cptr)); - decode ((char *)rbuf, cptr); /* decod string */ + decode ((char *)rbuf, cptr); /* decode string */ sim_send_input (&sim_con_send, rbuf, strlen((char *)rbuf), 0, 0); /* queue it for output */ free (rbuf); } return SCPE_OK; @@ -3997,11 +4075,14 @@ if (cptr == NULL || *cptr == 0) /* no argument string? */ return SCPE_2FARG; /* need an argument */ val = (int32) get_uint (cptr, 10, INT_MAX, &r); /* parse the argument */ +if (r == SCPE_OK) { /* parse OK? */ + char gbuf[CBUFSIZE]; -if (r == SCPE_OK) /* parse OK? */ - sim_con_expect.after = val; /* save the delay value */ + snprintf (gbuf, sizeof (gbuf), "HALTAFTER=%d", val); + expect_cmd (1, gbuf); + } return r; } Index: src/sim_defs.h ================================================================== --- src/sim_defs.h +++ src/sim_defs.h @@ -106,20 +106,26 @@ */ #ifndef SIM_DEFS_H_ #define SIM_DEFS_H_ 0 +#include "sim_rev.h" #include #include #include #if defined(_MSC_VER) && (_MSC_VER < 1900) #define snprintf _snprintf /* poor man's snprintf which will work most of the time but has different return value */ #endif +#if defined(__VAX) +extern int sim_vax_snprintf(char *buf, size_t buf_size, const char *fmt, ...); +#define snprintf sim_vax_snprintf +#endif #include #include #include #include +#include #ifdef _WIN32 #include #undef PACKED /* avoid macro name collision */ #undef ERROR /* avoid macro name collision */ @@ -177,20 +183,32 @@ #define CONST const #endif /* Length specific integer declarations */ +/* Handle the special/unusual cases first with everything else leveraging stdints.h */ #if defined (VMS) #include -#else -typedef signed char int8; -typedef signed short int16; -typedef signed int int32; -typedef unsigned char uint8; -typedef unsigned short uint16; -typedef unsigned int uint32; -#endif +#elif defined(_MSC_VER) && (_MSC_VER < 1600) +typedef __int8 int8; +typedef __int16 int16; +typedef __int32 int32; +typedef unsigned __int8 uint8; +typedef unsigned __int16 uint16; +typedef unsigned __int32 uint32; +#else +/* All modern/standard compiler environments */ +/* any other environment needa a special case above */ +#include +typedef int8_t int8; +typedef int16_t int16; +typedef int32_t int32; +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +#endif /* end standard integers */ + typedef int t_stat; /* status */ typedef int t_bool; /* boolean */ /* 64b integers */ @@ -215,13 +233,15 @@ #endif #if defined (USE_INT64) /* 64b data */ typedef t_int64 t_svalue; /* signed value */ typedef t_uint64 t_value; /* value */ +#define T_VALUE_MAX 0xffffffffffffffffuLL #else /* 32b data */ typedef int32 t_svalue; typedef uint32 t_value; +#define T_VALUE_MAX 0xffffffffUL #endif /* end 64b data */ #if defined (USE_INT64) && defined (USE_ADDR64) /* 64b address */ typedef t_uint64 t_addr; #define T_ADDR_W 64 @@ -407,11 +427,11 @@ #define SWMASK(x) (1u << (((int) (x)) - ((int) 'A'))) /* String match - at least one character required */ -#define MATCH_CMD(ptr,cmd) ((NULL == (ptr)) || (!*(ptr)) || sim_strncasecmp ((ptr), (cmd), strlen (ptr))) +#define MATCH_CMD(ptr,cmd) ((NULL == (ptr)) || (!*(ptr)) || strncasecmp ((ptr), (cmd), strlen (ptr))) /* End of Linked List/Queue value */ /* Chosen for 2 reasons: */ /* 1 - to not be NULL, this allowing the NULL value to */ /* indicate inclusion on a list */ @@ -632,13 +652,14 @@ uint32 width; /* width */ uint32 offset; /* starting bit */ uint32 depth; /* save depth */ const char *desc; /* description */ BITFIELD *fields; /* bit fields */ - uint32 flags; /* flags */ uint32 qptr; /* circ q ptr */ size_t str_size; /* structure size */ + /* NOTE: Flags MUST always be last since it is initialized outside of macro definitions */ + uint32 flags; /* flags */ }; /* Register flags */ #define REG_FMT 00003 /* see PV_x */ @@ -757,10 +778,11 @@ struct EXPTAB { uint8 *match; /* match string */ uint32 size; /* match string size */ char *match_pattern; /* match pattern for format */ int32 cnt; /* proceed count */ + uint32 after; /* delay before halting */ int32 switches; /* flags */ #define EXP_TYP_PERSIST (SWMASK ('P')) /* rule persists after match, default is once a rule matches, it is removed */ #define EXP_TYP_CLEARALL (SWMASK ('C')) /* clear all rules after matching this rule, default is to once a rule matches, it is removed */ #define EXP_TYP_REGEX (SWMASK ('R')) /* rule pattern is a regular expression */ #define EXP_TYP_REGEX_I (SWMASK ('I')) /* regular expression pattern matching should be case independent */ @@ -776,14 +798,14 @@ struct EXPECT { DEVICE *dptr; /* Device (for Debug) */ uint32 dbit; /* Debugging Bit */ EXPTAB *rules; /* match rules */ int32 size; /* count of match rules */ - uint32 after; /* delay before halting */ uint8 *buf; /* buffer of output data which has produced */ uint32 buf_ins; /* buffer insertion point for the next output data */ uint32 buf_size; /* buffer size */ + uint32 buf_data; /* count of data in buffer */ }; /* Send Context */ struct SEND { @@ -837,102 +859,172 @@ of the structures they exist to populate. */ #define UDATA(act,fl,cap) NULL,act,NULL,NULL,NULL,0,0,(fl),0,(cap),0,NULL,0,0 + +/* Internal use ONLY (see below) Generic Register declaration for all fields */ +#define _REGDATANF(nm,loc,rdx,wd,off,dep,desc,flds,qptr,siz) \ + nm, (loc), (rdx), (wd), (off), (dep), (desc), (flds), (qptr), (siz) #if defined (__STDC__) || defined (_WIN32) /* Variants which depend on how macro arguments are convered to strings */ /* Generic Register declaration for all fields. If the register structure is extended, this macro will be retained and a - new macro will be provided that populates the new register structure */ + new internal macro will be provided that populates the new register structure */ #define REGDATA(nm,loc,rdx,wd,off,dep,desc,flds,fl,qptr,siz) \ - #nm, &(loc), (rdx), (wd), (off), (dep), (desc), (flds), (fl), (qptr), (siz) + _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,flds,qptr,siz),(fl) +#define REGDATAC(nm,loc,rdx,wd,off,dep,desc,flds,fl,qptr,siz) \ + _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,flds,qptr,siz),(fl) /* Right Justified Octal Register Data */ -#define ORDATA(nm,loc,wd) #nm, &(loc), 8, (wd), 0, 1, NULL, NULL +#define ORDATA(nm,loc,wd) \ + _REGDATANF(#nm,&(loc),8,wd,0,1,NULL,NULL,0,0) +#define ORDATAD(nm,loc,wd,desc) \ + _REGDATANF(#nm,&(loc),8,wd,0,1,desc,NULL,0,0) +#define ORDATADF(nm,loc,wd,desc,flds) \ + _REGDATANF(#nm,&(loc),8,wd,0,1,desc,flds,0,0) /* Right Justified Decimal Register Data */ -#define DRDATA(nm,loc,wd) #nm, &(loc), 10, (wd), 0, 1, NULL, NULL +#define DRDATA(nm,loc,wd) \ + _REGDATANF(#nm,&(loc),10,wd,0,1,NULL,NULL,0,0) +#define DRDATAD(nm,loc,wd,desc) \ + _REGDATANF(#nm,&(loc),10,wd,0,1,desc,NULL,0,0) +#define DRDATADF(nm,loc,wd,desc,flds) \ + _REGDATANF(#nm,&(loc),10,wd,0,1,desc,flds,0,0) /* Right Justified Hexadecimal Register Data */ -#define HRDATA(nm,loc,wd) #nm, &(loc), 16, (wd), 0, 1, NULL, NULL +#define HRDATA(nm,loc,wd) \ + _REGDATANF(#nm,&(loc),16,wd,0,1,NULL,NULL,0,0) +#define HRDATAD(nm,loc,wd,desc) \ + _REGDATANF(#nm,&(loc),16,wd,0,1,desc,NULL,0,0) +#define HRDATADF(nm,loc,wd,desc,flds) \ + _REGDATANF(#nm,&(loc),16,wd,0,1,desc,flds,0,0) /* Right Justified Binary Register Data */ -#define BINRDATA(nm,loc,wd) #nm, &(loc), 2, (wd), 0, 1, NULL, NULL +#define BINRDATA(nm,loc,wd) \ + _REGDATANF(#nm,&(loc),2,wd,0,1,NULL,NULL,0,0) +#define BINRDATAD(nm,loc,wd,desc) \ + _REGDATANF(#nm,&(loc),2,wd,0,1,desc,NULL,0,0) +#define BINRDATADF(nm,loc,wd,desc,flds) \ + _REGDATANF(#nm,&(loc),2,wd,0,1,desc,flds,0,0) /* One-bit binary flag at an arbitrary offset in a 32-bit word Register */ -#define FLDATA(nm,loc,pos) #nm, &(loc), 2, 1, (pos), 1, NULL, NULL +#define FLDATA(nm,loc,pos) \ + _REGDATANF(#nm,&(loc),2,1,pos,1,NULL,NULL,0,0) +#define FLDATAD(nm,loc,pos,desc) \ + _REGDATANF(#nm,&(loc),2,1,pos,1,desc,NULL,0,0) +#define FLDATADF(nm,loc,pos,desc,flds) \ + _REGDATANF(#nm,&(loc),2,1,pos,1,desc,flds,0,0) /* Arbitrary location and Radix Register */ -#define GRDATA(nm,loc,rdx,wd,pos) #nm, &(loc), (rdx), (wd), (pos), 1, NULL, NULL +#define GRDATA(nm,loc,rdx,wd,pos) \ + _REGDATANF(#nm,&(loc),rdx,wd,pos,1,NULL,NULL,0,0) +#define GRDATAD(nm,loc,rdx,wd,pos,desc) \ + _REGDATANF(#nm,&(loc),rdx,wd,pos,1,desc,NULL,0,0) +#define GRDATADF(nm,loc,rdx,wd,pos,desc,flds) \ + _REGDATANF(#nm,&(loc),rdx,wd,pos,1,desc,flds,0,0) /* Arrayed register whose data is kept in a standard C array Register */ -#define BRDATA(nm,loc,rdx,wd,dep) #nm, (loc), (rdx), (wd), 0, (dep), NULL, NULL -/* Same as above, but with additional description initializer */ -#define ORDATAD(nm,loc,wd,desc) #nm, &(loc), 8, (wd), 0, 1, (desc), NULL -#define DRDATAD(nm,loc,wd,desc) #nm, &(loc), 10, (wd), 0, 1, (desc), NULL -#define HRDATAD(nm,loc,wd,desc) #nm, &(loc), 16, (wd), 0, 1, (desc), NULL -#define BINRDATAD(nm,loc,wd,desc) #nm, &(loc), 2, (wd), 0, 1, (desc), NULL -#define FLDATAD(nm,loc,pos,desc) #nm, &(loc), 2, 1, (pos), 1, (desc), NULL -#define GRDATAD(nm,loc,rdx,wd,pos,desc) #nm, &(loc), (rdx), (wd), (pos), 1, (desc), NULL -#define BRDATAD(nm,loc,rdx,wd,dep,desc) #nm, (loc), (rdx), (wd), 0, (dep), (desc), NULL -/* Same as above, but with additional description initializer, and bitfields */ -#define ORDATADF(nm,loc,wd,desc,flds) #nm, &(loc), 8, (wd), 0, 1, (desc), (flds) -#define DRDATADF(nm,loc,wd,desc,flds) #nm, &(loc), 10, (wd), 0, 1, (desc), (flds) -#define HRDATADF(nm,loc,wd,desc,flds) #nm, &(loc), 16, (wd), 0, 1, (desc), (flds) -#define BINRDATADF(nm,loc,wd) #nm, &(loc), 2, (wd), 0, 1, NULL, NULL -#define FLDATADF(nm,loc,pos,desc,flds) #nm, &(loc), 2, 1, (pos), 1, (desc), (flds) -#define GRDATADF(nm,loc,rdx,wd,pos,desc,flds) #nm, &(loc), (rdx), (wd), (pos), 1, (desc), (flds) -#define BRDATADF(nm,loc,rdx,wd,dep,desc,flds) #nm, (loc), (rdx), (wd), 0, (dep), (desc), (flds) +#define BRDATA(nm,loc,rdx,wd,dep) \ + _REGDATANF(#nm,loc,rdx,wd,0,dep,NULL,NULL,0,0) +#define BRDATAD(nm,loc,rdx,wd,dep,desc) \ + _REGDATANF(#nm,loc,rdx,wd,0,dep,desc,NULL,0,0) +#define BRDATADF(nm,loc,rdx,wd,dep,desc,flds) \ + _REGDATANF(#nm,loc,rdx,wd,0,dep,desc,flds,0,0) +/* Arrayed register whose data is part of the UNIT structure */ +#define URDATA(nm,loc,rdx,wd,off,dep,fl) \ + _REGDATANF(#nm,&(loc),rdx,wd,off,dep,NULL,NULL,0,0),((fl) | REG_UNIT) +#define URDATAD(nm,loc,rdx,wd,off,dep,fl,desc) \ + _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,NULL,0,0),((fl) | REG_UNIT) +#define URDATADF(nm,loc,rdx,wd,off,dep,fl,desc,flds) \ + _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,flds,0,0),((fl) | REG_UNIT) +/* Arrayed register whose data is part of an arbitrary structure */ +#define STRDATA(nm,loc,rdx,wd,off,dep,siz,fl) \ + _REGDATANF(#nm,&(loc),rdx,wd,off,dep,NULL,NULL,0,siz),((fl) | REG_STRUCT) +#define STRDATAD(nm,loc,rdx,wd,off,dep,siz,fl,desc) \ + _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,NULL,0,siz),((fl) | REG_STRUCT) +#define STRDATADF(nm,loc,rdx,wd,off,dep,siz,fl,desc,flds) \ + _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,flds,0,siz),((fl) | REG_STRUCT) #define BIT(nm) {#nm, 0xffffffff, 1} /* Single Bit definition */ #define BITNC {"", 0xffffffff, 1} /* Don't care Bit definition */ #define BITF(nm,sz) {#nm, 0xffffffff, sz} /* Bit Field definition */ #define BITNCF(sz) {"", 0xffffffff, sz} /* Don't care Bit Field definition */ #define BITFFMT(nm,sz,fmt) {#nm, 0xffffffff, sz, NULL, #fmt}/* Bit Field definition with Output format */ #define BITFNAM(nm,sz,names) {#nm, 0xffffffff, sz, names} /* Bit Field definition with value->name map */ #else /* For non-STD-C compiler which can't stringify macro arguments with # */ +/* Generic Register declaration for all fields. + If the register structure is extended, this macro will be retained and a + new macro will be provided that populates the new register structure */ #define REGDATA(nm,loc,rdx,wd,off,dep,desc,flds,fl,qptr,siz) \ - "nm", &(loc), (rdx), (wd), (off), (dep), (desc), (flds), (fl), (qptr), (siz) -#define ORDATA(nm,loc,wd) "nm", &(loc), 8, (wd), 0, 1, NULL, NULL -#define DRDATA(nm,loc,wd) "nm", &(loc), 10, (wd), 0, 1, NULL, NULL -#define HRDATA(nm,loc,wd) "nm", &(loc), 16, (wd), 0, 1, NULL, NULL -#define BINRDATA(nm,loc,wd) "nm", &(loc), 2, (wd), 0, 1, NULL, NULL -#define FLDATA(nm,loc,pos) "nm", &(loc), 2, 1, (pos), 1, NULL, NULL -#define GRDATA(nm,loc,rdx,wd,pos) "nm", &(loc), (rdx), (wd), (pos), 1, NULL, NULL -#define BRDATA(nm,loc,rdx,wd,dep) "nm", (loc), (rdx), (wd), 0, (dep), NULL, NULL -#define ORDATAD(nm,loc,wd,desc) "nm", &(loc), 8, (wd), 0, 1, (desc), NULL -#define DRDATAD(nm,loc,wd,desc) "nm", &(loc), 10, (wd), 0, 1, (desc), NULL -#define HRDATAD(nm,loc,wd,desc) "nm", &(loc), 16, (wd), 0, 1, (desc), NULL -#define BINRDATAD(nm,loc,wd,desc) "nm", &(loc), 2, (wd), 0, 1, (desc), NULL -#define FLDATAD(nm,loc,pos,desc) "nm", &(loc), 2, 1, (pos), 1, (desc), NULL -#define GRDATAD(nm,loc,rdx,wd,pos,desc) "nm", &(loc), (rdx), (wd), (pos), 1, (desc), NULL -#define BRDATAD(nm,loc,rdx,wd,dep,desc) "nm", (loc), (rdx), (wd), 0, (dep), (desc), NULL -#define ORDATADF(nm,loc,wd,desc,flds) "nm", &(loc), 8, (wd), 0, 1, (desc), (flds) -#define DRDATADF(nm,loc,wd,desc,flds) "nm", &(loc), 10, (wd), 0, 1, (desc), (flds) -#define HRDATADF(nm,loc,wd,desc,flds) "nm", &(loc), 16, (wd), 0, 1, (desc), (flds) -#define BINRDATADF(nm,loc,wd,desc,flds) "nm", &(loc), 2, (wd), 0, 1, (desc), (flds) -#define FLDATADF(nm,loc,pos,desc,flds) "nm", &(loc), 2, 1, (pos), 1, (desc), (flds) -#define GRDATADF(nm,loc,rdx,wd,pos,desc,flds) "nm", &(loc), (rdx), (wd), (pos), 1, (desc), (flds) -#define BRDATADF(nm,loc,rdx,wd,dep,desc,flds) "nm", (loc), (rdx), (wd), 0, (dep), (desc), (flds) + _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,flds,qptr,siz),(fl) +#define REGDATAC(nm,loc,rdx,wd,off,dep,desc,flds,fl,qptr,siz) \ + _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,flds,qptr,siz),(fl) +/* Right Justified Octal Register Data */ +#define ORDATA(nm,loc,wd) \ + _REGDATANF("nm",&(loc),8,wd,0,1,NULL,NULL,0,0) +#define ORDATAD(nm,loc,wd,desc) \ + _REGDATANF("nm",&(loc),8,wd,0,1,desc,NULL,0,0) +#define ORDATADF(nm,loc,wd,desc,flds) \ + _REGDATANF("nm",&(loc),8,wd,0,1,desc,flds,0,0) +/* Right Justified Decimal Register Data */ +#define DRDATA(nm,loc,wd) \ + _REGDATANF("nm",&(loc),10,wd,0,1,NULL,NULL,0,0) +#define DRDATAD(nm,loc,wd,desc) \ + _REGDATANF("nm",&(loc),10,wd,0,1,desc,NULL,0,0) +#define DRDATADF(nm,loc,wd,desc,flds) \ + _REGDATANF("nm",&(loc),10,wd,0,1,desc,flds,0,0) +/* Right Justified Hexadecimal Register Data */ +#define HRDATA(nm,loc,wd) \ + _REGDATANF("nm",&(loc),16,wd,0,1,NULL,NULL,0,0) +#define HRDATAD(nm,loc,wd,desc) \ + _REGDATANF("nm",&(loc),16,wd,0,1,desc,NULL,0,0) +#define HRDATADF(nm,loc,wd,desc,flds) \ + _REGDATANF("nm",&(loc),16,wd,0,1,desc,flds,0,0) +/* Right Justified Binary Register Data */ +#define BINRDATA(nm,loc,wd) \ + _REGDATANF("nm",&(loc),2,wd,0,1,NULL,NULL,0,0) +#define BINRDATAD(nm,loc,wd,desc) \ + _REGDATANF("nm",&(loc),2,wd,0,1,desc,NULL,0,0) +#define BINRDATADF(nm,loc,wd,desc,flds) \ + _REGDATANF("nm",&(loc),2,wd,0,1,desc,flds,0,0) +/* One-bit binary flag at an arbitrary offset in a 32-bit word Register */ +#define FLDATA(nm,loc,pos) \ + _REGDATANF("nm",&(loc),2,1,pos,1,NULL,NULL,0,0) +#define FLDATAD(nm,loc,pos,desc) \ + _REGDATANF("nm",&(loc),2,1,pos,1,desc,NULL,0,0) +#define FLDATADF(nm,loc,pos,desc,flds) \ + _REGDATANF("nm",&(loc),2,1,pos,1,desc,flds,0,0) +/* Arbitrary location and Radix Register */ +#define GRDATA(nm,loc,rdx,wd,pos) \ + _REGDATANF("nm",&(loc),rdx,wd,pos,1,NULL,NULL,0,0) +#define GRDATAD(nm,loc,rdx,wd,pos,desc) \ + _REGDATANF("nm",&(loc),rdx,wd,pos,1,desc,NULL,0,0) +#define GRDATADF(nm,loc,rdx,wd,pos,desc,flds) \ + _REGDATANF("nm",&(loc),rdx,wd,pos,1,desc,flds,0,0) +/* Arrayed register whose data is kept in a standard C array Register */ +#define BRDATA(nm,loc,rdx,wd,dep) \ + _REGDATANF("nm",loc,rdx,wd,0,dep,NULL,NULL,0,0) +#define BRDATAD(nm,loc,rdx,wd,dep,desc) \ + _REGDATANF("nm",loc,rdx,wd,0,dep,desc,NULL,0,0) +#define BRDATADF(nm,loc,rdx,wd,dep,desc,flds) \ + _REGDATANF("nm",loc,rdx,wd,0,dep,desc,flds,0,0) +/* Arrayed register whose data is part of the UNIT structure */ +#define URDATA(nm,loc,rdx,wd,off,dep,fl) \ + _REGDATANF("nm",&(loc),rdx,wd,off,dep,NULL,NULL,0,0),((fl) | REG_UNIT) +#define URDATAD(nm,loc,rdx,wd,off,dep,fl,desc) \ + _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,NULL,0,0),((fl) | REG_UNIT) +#define URDATADF(nm,loc,rdx,wd,off,dep,fl,desc,flds) \ + _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,flds,0,0),((fl) | REG_UNIT) +/* Arrayed register whose data is part of an arbitrary structure */ +#define STRDATA(nm,loc,rdx,wd,off,dep,siz,fl) \ + _REGDATANF("nm",&(loc),rdx,wd,off,dep,NULL,NULL,0,siz),((fl) | REG_STRUCT) +#define STRDATAD(nm,loc,rdx,wd,off,dep,siz,fl,desc) \ + _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,NULL,0,siz),((fl) | REG_STRUCT) +#define STRDATADF(nm,loc,rdx,wd,off,dep,siz,fl,desc,flds) \ + _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,flds,0,siz),((fl) | REG_STRUCT) #define BIT(nm) {"nm", 0xffffffff, 1} /* Single Bit definition */ #define BITNC {"", 0xffffffff, 1} /* Don't care Bit definition */ #define BITF(nm,sz) {"nm", 0xffffffff, sz} /* Bit Field definition */ #define BITNCF(sz) {"", 0xffffffff, sz} /* Don't care Bit Field definition */ #define BITFFMT(nm,sz,fmt) {"nm", 0xffffffff, sz, NULL, "fmt"}/* Bit Field definition with Output format */ #define BITFNAM(nm,sz,names) {"nm", 0xffffffff, sz, names} /* Bit Field definition with value->name map */ #endif #define ENDBITS {NULL} /* end of bitfield list */ -/* Arrayed register whose data is part of the UNIT structure */ -#define URDATA(nm,loc,rdx,wd,off,dep,fl) \ - REGDATA(nm,(loc),(rdx),(wd),(off),(dep),NULL,NULL,((fl) | REG_UNIT),0,0) -/* Arrayed register whose data is part of an arbitrary structure */ -#define STRDATA(nm,loc,rdx,wd,off,dep,siz,fl) \ - REGDATA(nm,(loc),(rdx),(wd),(off),(dep),NULL,NULL,((fl) | REG_STRUCT),0,(siz)) -/* Same as above, but with additional description initializer */ -#define URDATAD(nm,loc,rdx,wd,off,dep,fl,desc) \ - REGDATA(nm,(loc),(rdx),(wd),(off),(dep),(desc),NULL,((fl) | REG_UNIT),0,0) -#define STRDATAD(nm,loc,rdx,wd,off,dep,siz,fl,desc) \ - REGDATA(nm,(loc),(rdx),(wd),(off),(dep),(desc),NULL,((fl) | REG_STRUCT),0,(siz)) -/* Same as above, but with additional description initializer, and bitfields */ -#define URDATADF(nm,loc,rdx,wd,off,dep,fl,desc,flds) \ - REGDATA(nm,(loc),(rdx),(wd),(off),(dep),(desc),(flds),((fl) | REG_UNIT),0,0) -#define STRDATADF(nm,loc,rdx,wd,off,dep,siz,fl,desc,flds) \ - REGDATA(nm,(loc),(rdx),(wd),(off),(dep),(desc),(flds),((fl) | REG_STRUCT),0,(siz)) /* Function prototypes */ #include "scp.h" #include "sim_console.h" Index: src/sim_disk.c ================================================================== --- src/sim_disk.c +++ src/sim_disk.c @@ -1360,13 +1360,11 @@ sim_quiet = saved_sim_quiet; if (r != SCPE_OK) { sim_switches = saved_sim_switches; return sim_messagef (r, "Can't open source VHD: %s\n", cptr); } - if (!sim_quiet) { - sim_printf ("%s%d: creating new virtual disk '%s'\n", sim_dname (dptr), (int)(uptr-dptr->units), gbuf); - } + sim_messagef (SCPE_OK, "%s%d: creating new virtual disk '%s'\n", sim_dname (dptr), (int)(uptr-dptr->units), gbuf); capac_factor = ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (word: 2, byte: 1) */ vhd = sim_vhd_disk_create (gbuf, ((t_offset)uptr->capac)*capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1)); if (!vhd) { return sim_messagef (r, "%s%d: can't create virtual disk '%s'\n", sim_dname (dptr), (int)(uptr-dptr->units), gbuf); } @@ -1381,12 +1379,11 @@ sim_vhd_disk_close(vhd); (void)remove (gbuf); return SCPE_MEM; } for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects) { - if (!sim_quiet) - sim_printf ("%s%d: Copied %dMB. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000), (int)((((float)lba)*100)/total_sectors)); + sim_messagef (SCPE_OK, "%s%d: Copied %dMB. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000), (int)((((float)lba)*100)/total_sectors)); sects = sectors_per_buffer; if (lba + sects > total_sectors) sects = total_sectors - lba; r = sim_disk_rdsect (uptr, lba, copy_buf, NULL, sects); if (r == SCPE_OK) { @@ -1398,16 +1395,14 @@ r = sim_disk_wrsect (uptr, lba, copy_buf, NULL, sects); uptr->fileref = save_unit_fileref; uptr->flags = saved_unit_flags; } } - if (!sim_quiet) { - if (r == SCPE_OK) - sim_printf ("\n%s%d: Copied %dMB. Done.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)(((t_offset)lba*sector_size)/1000000)); - else - sim_printf ("\n%s%d: Error copying: %s.\n", sim_dname (dptr), (int)(uptr-dptr->units), sim_error_text (r)); - } + if (r == SCPE_OK) + sim_messagef (SCPE_OK, "\n%s%d: Copied %dMB. Done.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)(((t_offset)lba*sector_size)/1000000)); + else + sim_messagef (r, "\n%s%d: Error copying: %s.\n", sim_dname (dptr), (int)(uptr-dptr->units), sim_error_text (r)); if ((r == SCPE_OK) && (sim_switches & SWMASK ('V'))) { uint8 *verify_buf = (uint8*) malloc (1024*1024); if (!verify_buf) { sim_vhd_disk_close(vhd); @@ -1414,12 +1409,11 @@ (void)remove (gbuf); free (copy_buf); return SCPE_MEM; } for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects) { - if (!sim_quiet) - sim_printf ("%s%d: Verified %dMB. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000), (int)((((float)lba)*100)/total_sectors)); + sim_messagef (SCPE_OK, "%s%d: Verified %dMB. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000), (int)((((float)lba)*100)/total_sectors)); sects = sectors_per_buffer; if (lba + sects > total_sectors) sects = total_sectors - lba; r = sim_disk_rdsect (uptr, lba, copy_buf, NULL, sects); if (r == SCPE_OK) { @@ -1437,11 +1431,11 @@ } } } if (!sim_quiet) { if (r == SCPE_OK) - sim_printf ("\n%s%d: Verified %dMB. Done.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)(((t_offset)lba*sector_size)/1000000)); + sim_messagef (r, "\n%s%d: Verified %dMB. Done.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)(((t_offset)lba*sector_size)/1000000)); else { t_lba i; uint32 save_dctrl = dptr->dctrl; FILE *save_sim_deb = sim_deb; @@ -1542,13 +1536,11 @@ return _err_return (uptr, SCPE_NORO); /* no, error */ uptr->fileref = open_function (cptr, "rb"); /* open rd only */ if (uptr->fileref == NULL) /* open fail? */ return _err_return (uptr, SCPE_OPENERR); /* yes, error */ uptr->flags = uptr->flags | UNIT_RO; /* set rd only */ - if (!sim_quiet) { - sim_printf ("%s%d: unit is read only\n", sim_dname (dptr), (int)(uptr-dptr->units)); - } + sim_messagef (SCPE_OK, "%s%d: unit is read only\n", sim_dname (dptr), (int)(uptr-dptr->units)); } else { /* normal */ uptr->fileref = open_function (cptr, "rb+"); /* open r/w */ if (uptr->fileref == NULL) { /* open fail? */ if ((errno == EROFS) || (errno == EACCES)) { /* read only? */ @@ -1556,12 +1548,11 @@ return _err_return (uptr, SCPE_NORO); /* no error */ uptr->fileref = open_function (cptr, "rb"); /* open rd only */ if (uptr->fileref == NULL) /* open fail? */ return _err_return (uptr, SCPE_OPENERR);/* yes, error */ uptr->flags = uptr->flags | UNIT_RO; /* set rd only */ - if (!sim_quiet) - sim_printf ("%s%d: unit is read only\n", sim_dname (dptr), (int)(uptr-dptr->units)); + sim_messagef (SCPE_OK, "%s%d: unit is read only\n", sim_dname (dptr), (int)(uptr-dptr->units)); } else { /* doesn't exist */ if (sim_switches & SWMASK ('E')) /* must exist? */ return _err_return (uptr, SCPE_OPENERR); /* yes, error */ if (create_function) @@ -1568,12 +1559,11 @@ uptr->fileref = create_function (cptr, ((t_offset)uptr->capac)*ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1));/* create new file */ else uptr->fileref = open_function (cptr, "wb+");/* open new file */ if (uptr->fileref == NULL) /* open fail? */ return _err_return (uptr, SCPE_OPENERR);/* yes, error */ - if (!sim_quiet) - sim_printf ("%s%d: creating new file\n", sim_dname (dptr), (int)(uptr-dptr->units)); + sim_messagef (SCPE_OK, "%s%d: creating new file\n", sim_dname (dptr), (int)(uptr-dptr->units)); created = TRUE; } } /* end if null */ } /* end else */ if (DK_GET_FMT (uptr) == DKUF_F_VHD) { @@ -1653,15 +1643,13 @@ free (init_buf); sim_disk_detach (uptr); /* report error now */ (void)remove (cptr); /* remove the created file */ return SCPE_OPENERR; } - if (!sim_quiet) - sim_printf ("%s%d: Initialized To Sector Address %dMB. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000), (int)((((float)lba)*100)/total_sectors)); + sim_messagef (SCPE_OK, "%s%d: Initialized To Sector Address %dMB. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000), (int)((((float)lba)*100)/total_sectors)); } - if (!sim_quiet) - sim_printf ("%s%d: Initialized To Sector Address %dMB. 100%% complete.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000)); + sim_messagef (SCPE_OK, "%s%d: Initialized To Sector Address %dMB. 100%% complete.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000)); free (init_buf); } if (pdp11tracksize) sim_disk_pdp11_bad_block (uptr, pdp11tracksize, sector_size/sizeof(uint16)); } @@ -1706,15 +1694,13 @@ dptr->dctrl = save_dctrl; sim_deb = save_sim_deb; } } } - if (!sim_quiet) - sim_printf ("%s%d: Verified containing Sector Address %dMB. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000), (int)((((float)lba)*100)/total_sectors)); + sim_messagef (SCPE_OK, "%s%d: Verified containing Sector Address %dMB. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000), (int)((((float)lba)*100)/total_sectors)); } - if (!sim_quiet) - sim_printf ("%s%d: Verified containing Sector Address %dMB. 100%% complete.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000)); + sim_messagef (SCPE_OK, "%s%d: Verified containing Sector Address %dMB. 100%% complete.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000)); free (verify_buf); uptr->dynflags |= UNIT_DISK_CHK; } filesystem_capac = get_filesystem_size (uptr); @@ -2155,11 +2141,11 @@ errno = ENOEXEC; return; } errno = EINVAL; } -#if defined(__GNUC__) +#if defined(__GNUC__) && defined(HAVE_NTDDDISK_H) #include #include #else #include #endif @@ -3641,12 +3627,11 @@ for (BlockNumber=BlocksToMerge=0; BlockNumber< NtoHl (hVHD->Dynamic.MaxTableEntries); ++BlockNumber) { if (hVHD->BAT[BlockNumber] == VHD_BAT_FREE_ENTRY) continue; ++BlocksToMerge; } - if (!sim_quiet) - sim_printf ("Merging %s\ninto %s\n", szVHDPath, hVHD->ParentVHDPath); + sim_messagef (SCPE_OK, "Merging %s\ninto %s\n", szVHDPath, hVHD->ParentVHDPath); for (BlockNumber=NeededBlock=0; BlockNumber < NtoHl (hVHD->Dynamic.MaxTableEntries); ++BlockNumber) { uint32 BlockSectors = SectorsPerBlock; if (hVHD->BAT[BlockNumber] == VHD_BAT_FREE_ENTRY) continue; @@ -3665,21 +3650,19 @@ BlockSectors, &SectorsWritten, SectorSize, SectorsPerBlock*BlockNumber)) break; - if (!sim_quiet) - sim_printf ("Merged %dMB. %d%% complete.\r", (int)((((float)NeededBlock)*SectorsPerBlock)*SectorSize/1000000), (int)((((float)NeededBlock)*100)/BlocksToMerge)); + sim_messagef (SCPE_OK, "Merged %dMB. %d%% complete.\r", (int)((((float)NeededBlock)*SectorsPerBlock)*SectorSize/1000000), (int)((((float)NeededBlock)*100)/BlocksToMerge)); hVHD->BAT[BlockNumber] = VHD_BAT_FREE_ENTRY; } if (BlockNumber < NtoHl (hVHD->Dynamic.MaxTableEntries)) { Status = errno; } else { Status = 0; - if (!sim_quiet) - sim_printf ("Merged %dMB. 100%% complete.\n", (int)((((float)NeededBlock)*SectorsPerBlock)*SectorSize/1000000)); + sim_messagef (SCPE_OK, "Merged %dMB. 100%% complete.\n", (int)((((float)NeededBlock)*SectorsPerBlock)*SectorSize/1000000)); fclose (hVHD->File); hVHD->File = NULL; (void)remove (szVHDPath); *ParentVHD = (char*) malloc (strlen (hVHD->ParentVHDPath)+1); strcpy (*ParentVHD, hVHD->ParentVHDPath); Index: src/sim_ether.c ================================================================== --- src/sim_ether.c +++ src/sim_ether.c @@ -709,11 +709,11 @@ found = 0; n = strlen(name); for (i=0; iitem[que->tail]; item->type = type; item->packet.len = len; item->packet.used = used; item->packet.crc_len = crc_len; - if (MAX (len, crc_len) <= sizeof (item->packet.msg) - ETH_CRC_SIZE) { + if (MAX (len, crc_len) <= sizeof (item->packet.msg)) { memcpy(item->packet.msg, data, ((len > crc_len) ? len : crc_len)); if (crc_data && (crc_len > len)) memcpy(&item->packet.msg[len], crc_data, ETH_CRC_SIZE); } else { @@ -1136,17 +1136,17 @@ #ifdef _WIN32 if (1) { BOOL(WINAPI *p_SetDllDirectory)(LPCTSTR); UINT(WINAPI *p_GetSystemDirectory)(LPTSTR lpBuffer, UINT uSize); - p_SetDllDirectory = (BOOL(WINAPI *)(LPCTSTR)) GetProcAddress(GetModuleHandle("kernel32.dll"), "SetDllDirectoryA"); - p_GetSystemDirectory = (UINT(WINAPI *)(LPTSTR, UINT)) GetProcAddress(GetModuleHandle("kernel32.dll"), "GetSystemDirectoryA"); + p_SetDllDirectory = (BOOL(WINAPI *)(LPCTSTR)) GetProcAddress(GetModuleHandleA("kernel32.dll"), "SetDllDirectoryA"); + p_GetSystemDirectory = (UINT(WINAPI *)(LPTSTR, UINT)) GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetSystemDirectoryA"); if (p_SetDllDirectory && p_GetSystemDirectory) { char npcap_path[512] = ""; if (p_GetSystemDirectory (npcap_path, sizeof(npcap_path) - 7)) - sim_strlcat (npcap_path, "\\Npcap", sizeof(npcap_path)); + strlcat (npcap_path, "\\Npcap", sizeof(npcap_path)); if (p_SetDllDirectory(npcap_path)) hLib = LoadLibraryA(lib_name); p_SetDllDirectory (NULL); } if (hLib == NULL) @@ -1701,12 +1701,10 @@ _eth_callback((u_char *)opaque, &header, buf); } #endif #if defined (USE_READER_THREAD) -#include - static void * _eth_reader(void *arg) { ETH_DEV* volatile dev = (ETH_DEV*)arg; int status = 0; @@ -3615,11 +3613,10 @@ ETH_BOOL all_multicast, ETH_BOOL promiscuous, ETH_MULTIHASH* const hash) { int i; char buf[116+66*ETH_FILTER_MAX]; -char errbuf[PCAP_ERRBUF_SIZE]; char mac[20]; char* buf2; t_stat status; #ifdef USE_BPF struct bpf_program bpf; @@ -3760,10 +3757,11 @@ in our case isn't actually interesting since the filters we generate aren't referencing IP fields, networks or values */ #ifdef USE_BPF if (dev->eth_api == ETH_API_PCAP) { + char errbuf[PCAP_ERRBUF_SIZE]; bpf_u_int32 bpf_subnet, bpf_netmask; if (pcap_lookupnet(dev->name, &bpf_subnet, &bpf_netmask, errbuf)<0) bpf_netmask = 0; /* compile filter string */ @@ -3822,15 +3820,16 @@ */ int eth_host_devices(int used, int max, ETH_LIST* list) { pcap_t* conn = NULL; int i, j, datalink = 0; -char errbuf[PCAP_ERRBUF_SIZE]; for (i=0; i #if defined (USE_SETNONBLOCK) #undef USE_SETNONBLOCK #endif /* USE_SETNONBLOCK */ #undef PCAP_READ_TIMEOUT #define PCAP_READ_TIMEOUT 15 #if (!defined (xBSD) && !defined(_WIN32) && !defined(VMS) && !defined(__CYGWIN__)) || defined (HAVE_TAP_NETWORK) || defined (HAVE_VDE_NETWORK) #define MUST_DO_SELECT 1 #endif +#else +#include #endif /* USE_READER_THREAD */ /* give priority to USE_NETWORK over USE_SHARED */ #if defined(USE_NETWORK) && defined(USE_SHARED) #undef USE_SHARED Index: src/sim_fio.c ================================================================== --- src/sim_fio.c +++ src/sim_fio.c @@ -373,10 +373,38 @@ { return sim_fseeko (st, (t_offset)offset, whence); } #if defined(_WIN32) +static const char * +GetErrorText(DWORD dwError) +{ +static char szMsgBuffer[2048]; +DWORD dwStatus; + +dwStatus = FormatMessageA (FORMAT_MESSAGE_FROM_SYSTEM| + FORMAT_MESSAGE_IGNORE_INSERTS, // __in DWORD dwFlags, + NULL, // __in_opt LPCVOID lpSource, + dwError, // __in DWORD dwMessageId, + 0, // __in DWORD dwLanguageId, + szMsgBuffer, // __out LPTSTR lpBuffer, + sizeof (szMsgBuffer) -1, // __in DWORD nSize, + NULL); // __in_opt va_list *Arguments +if (0 == dwStatus) + snprintf(szMsgBuffer, sizeof(szMsgBuffer) - 1, "Error Code: 0x%lX", dwError); +while (sim_isspace (szMsgBuffer[strlen (szMsgBuffer)-1])) + szMsgBuffer[strlen (szMsgBuffer) - 1] = '\0'; +return szMsgBuffer; +} + +t_stat sim_copyfile (const char *source_file, const char *dest_file, t_bool overwrite_existing) +{ +if (CopyFileA (source_file, dest_file, !overwrite_existing)) + return SCPE_OK; +return sim_messagef (SCPE_ARG, "Error Copying '%s' to '%s': %s\n", source_file, dest_file, GetErrorText (GetLastError ())); +} + #include int sim_set_fsize (FILE *fptr, t_addr size) { return _chsize(_fileno(fptr), (long)size); } @@ -437,10 +465,58 @@ return ftruncate(fileno(fptr), (off_t)size); } #include #include +#if HAVE_UTIME +#include +#endif + +t_stat sim_copyfile (const char *source_file, const char *dest_file, t_bool overwrite_existing) +{ +FILE *fIn = NULL, *fOut = NULL; +t_stat st = SCPE_OK; +char *buf = NULL; +size_t bytes; + +fIn = sim_fopen (source_file, "rb"); +if (!fIn) { + st = sim_messagef (SCPE_ARG, "Can't open '%s' for input: %s\n", source_file, strerror (errno)); + goto Cleanup_Return; + } +fOut = sim_fopen (dest_file, "wb"); +if (!fOut) { + st = sim_messagef (SCPE_ARG, "Can't open '%s' for output: %s\n", dest_file, strerror (errno)); + goto Cleanup_Return; + } +buf = (char *)malloc (BUFSIZ); +while ((bytes = fread (buf, 1, BUFSIZ, fIn))) + fwrite (buf, 1, bytes, fOut); +Cleanup_Return: +free (buf); +if (fIn) + fclose (fIn); +if (fOut) + fclose (fOut); +#if defined(HAVE_UTIME) +if (st == SCPE_OK) { + struct stat statb; + + if (!stat (source_file, &statb)) { + struct utimbuf utim; + + utim.actime = statb.st_atime; + utim.modtime = statb.st_mtime; + if (utime (dest_file, &utim)) + st = SCPE_IOERR; + } + else + st = SCPE_IOERR; + } +#endif +return st; +} int sim_set_fifo_nonblock (FILE *fptr) { struct stat stbuf; @@ -521,5 +597,22 @@ close (shmem->shm_fd); free (shmem); } #endif + +#if defined(__VAX) +/* + * We privide a 'basic' snprintf, which 'might' overrun a buffer, but + * the actual use cases don't on other platforms and none of the callers + * care about the function return value. + */ +int sim_vax_snprintf(char *buf, size_t buf_size, const char *fmt, ...) +{ +va_list arglist; + +va_start (arglist, fmt); +vsprintf (buf, fmt, arglist); +va_end (arglist); +return 0; +} +#endif Index: src/sim_fio.h ================================================================== --- src/sim_fio.h +++ src/sim_fio.h @@ -64,10 +64,11 @@ uint32 sim_fsize (FILE *fptr); uint32 sim_fsize_name (const char *fname); t_offset sim_ftell (FILE *st); t_offset sim_fsize_ex (FILE *fptr); t_offset sim_fsize_name_ex (const char *fname); +t_stat sim_copyfile (const char *source_file, const char *dest_file, t_bool overwrite_existing); void sim_buf_swap_data (void *bptr, size_t size, size_t count); void sim_buf_copy_swapped (void *dptr, const void *bptr, size_t size, size_t count); typedef struct SHMEM SHMEM; t_stat sim_shmem_open (const char *name, size_t size, SHMEM **shmem, void **addr); void sim_shmem_close (SHMEM *shmem); Index: src/sim_frontpanel.h ================================================================== --- src/sim_frontpanel.h +++ src/sim_frontpanel.h @@ -54,11 +54,11 @@ #include #if !defined(__VAX) /* Unsupported platform */ -#define SIM_FRONTPANEL_VERSION 4 +#define SIM_FRONTPANEL_VERSION 8 /** sim_panel_start_simulator A starts a simulator with a particular configuration @@ -66,11 +66,11 @@ sim_path the path to the simulator binary sim_config the configuration to run the simulator with device_panel_count the number of sub panels for connected devices Note 1: - The path specified must be either a fully specified path or - it could be merey the simulator name if the simulator binary + it could be merely the simulator name if the simulator binary is located in the current PATH. - The simulator binary must be built from the same version simh source code that the frontpanel API was acquired fron (the API and the simh framework must speak the same language) @@ -192,11 +192,11 @@ A panel application has a choice of two different methods of getting the values contained in the set of registers it has declared interest in via the sim_panel_add_register APIs. - 1) The values can be polled (when ever it is desired) by calling + 1) The values can be polled (whenever it is desired) by calling sim_panel_get_registers(). 2) The panel can call sim_panel_set_display_callback_interval() to specify a callback routine and a periodic rate that the callback routine should be called. The panel API will make a best effort to deliver the current register state at the desired rate. @@ -227,44 +227,59 @@ /** When a front panel application wants to get averaged bit sample values, it must first declare the sampling parameters that will - be used while collecting the bit values. + be used while collecting the bit values. The dithering + percentage must be 25% or less and when non 0 causes the sample + frequency to vary by plus or minus a random percentage value up + to the specified value. sim_panel_set_sampling_parameters + sim_panel_set_sampling_parameters_ex sample_frequency cycles/instructions between sample captures + sample_dither_pct percentage of sample_frequency to vary randomly sample_depth how many samples to accumulate in the rolling average for each bit sample. Returned bit sample values will range from 0 thru this value. */ +int +sim_panel_set_sampling_parameters_ex (PANEL *panel, + unsigned int sample_frequency, + unsigned int sample_dither_pct, + unsigned int sample_depth); + int sim_panel_set_sampling_parameters (PANEL *panel, unsigned int sample_frequency, unsigned int sample_depth); - /** When a front panel application needs to change the running state of a simulator one of the following routines should be called: sim_panel_exec_halt - Stop instruction execution sim_panel_exec_boot - Boot a simulator from a specific device sim_panel_exec_run - Start/Resume a simulator running instructions + sim_panel_exec_start - Start a simulator running instructions + after resetting all devices sim_panel_exec_step - Have a simulator execute a single step */ int sim_panel_exec_halt (PANEL *panel); int sim_panel_exec_boot (PANEL *panel, const char *device); +int +sim_panel_exec_start (PANEL *panel); + int sim_panel_exec_run (PANEL *panel); int sim_panel_exec_step (PANEL *panel); @@ -460,11 +475,13 @@ /** All APIs routines which return an int return 0 for success and -1 for an error. - An API which returns an error (-1), will not change the panel state. + An API which returns an error (-1), will not change the panel state + except to possibly set the panel state to Error if the panel + condition is no longer useful. sim_panel_get_error - the details of the most recent error sim_panel_clear_error - clears the error buffer */ @@ -473,26 +490,28 @@ /** The panek<->simulator wire protocol can be traced if protocol problems arise. - sim_panel_set_debug_file - Specifies the log file to record debug traffic sim_panel_set_debug_mode - Specifies the debug detail to be recorded sim_panel_flush_debug - Flushes debug output to disk + sim_panel_debug - Write message to the debug file */ -void -sim_panel_set_debug_file (PANEL *panel, const char *debug_file); - #define DBG_XMT 1 /* Transmit Data */ #define DBG_RCV 2 /* Receive Data */ #define DBG_REQ 4 /* Request Data */ #define DBG_RSP 8 /* Response Data */ +#define DBG_THR 16 /* Thread Activities */ +#define DBG_APP 32 /* Application Activities */ void sim_panel_set_debug_mode (PANEL *panel, int debug_bits); +void +sim_panel_debug (PANEL *panel, const char *fmt, ...); + void sim_panel_flush_debug (PANEL *panel); #endif /* !defined(__VAX) */ Index: src/sim_serial.c ================================================================== --- src/sim_serial.c +++ src/sim_serial.c @@ -1,8 +1,8 @@ /* sim_serial.c: OS-dependent serial port routines - Copyright (c) 2008, J. David Bryan + Copyright (c) 2008, J. David Bryan, Mark Pizzolato Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, @@ -25,10 +25,11 @@ The author gratefully acknowledges the assistance of Holger Veit with the UNIX-specific code and testing. 07-Oct-08 JDB [serial] Created file + 22-Apr-12 MP Adapted from code originally written by J. David Bryan This module provides OS-dependent routines to access serial ports on the host machine. The terminal multiplexer library uses these routines to provide serial connections to simulated terminal interfaces. @@ -300,11 +301,11 @@ found = 0; n = strlen(name); for (i=0; ioWriteReady.hEvent, 0) == WAIT_TIMEOUT) - return 0; if ((!WriteFile (port->hPort, (LPVOID) buffer, /* write the buffer to the serial port */ (DWORD) count, NULL, &port->oWriteSync)) && (GetLastError () != ERROR_IO_PENDING)) { sim_error_serial ("WriteFile", /* function failed; report unexpected error */ (int) GetLastError ()); - return -1; /* return failure to caller */ - } -if ((!WaitCommEvent (port->hPort, &port->dwEvtMask, &port->oWriteReady)) && - (GetLastError () != ERROR_IO_PENDING)) { - sim_error_serial ("WaitCommEvent", /* function failed; report unexpected error */ - (int) GetLastError ()); return -1; /* return failure to caller */ } return count; /* return number of characters written/queued */ } Index: src/sim_serial.h ================================================================== --- src/sim_serial.h +++ src/sim_serial.h @@ -1,8 +1,8 @@ /* sim_serial.h: OS-dependent serial port routines header file - Copyright (c) 2008, J. David Bryan + Copyright (c) 2008, J. David Bryan, Mark Pizzolato Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, @@ -22,10 +22,12 @@ Except as contained in this notice, the name of the author shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the author. 07-Oct-08 JDB [serial] Created file + 22-Apr-12 MP Adapted from code originally written by J. David Bryan + */ #ifndef SIM_SERIAL_H_ #define SIM_SERIAL_H_ 0 Index: src/sim_tape.c ================================================================== --- src/sim_tape.c +++ src/sim_tape.c @@ -66,10 +66,12 @@ sim_tape_sprecr space tape record reverse sim_tape_wrtmk write tape mark sim_tape_wreom erase remainder of tape sim_tape_wreomrw erase remainder of tape & rewind sim_tape_wrgap write erase gap + sim_tape_errecf erase record forward + sim_tape_errecr erase record reverse sim_tape_sprecsf space records forward sim_tape_spfilef space files forward sim_tape_sprecsr space records reverse sim_tape_spfiler space files reverse sim_tape_position generalized position @@ -318,14 +320,23 @@ sim_debug (ctx->dbit, ctx->dptr, "_tape_completion_dispatch(unit=%d, top=%d, callback=%p)\n", (int)(uptr-ctx->dptr->units), ctx->io_top, ctx->callback); if (ctx->io_top != TOP_DONE) abort(); /* horribly wrong, stop */ -if (ctx->callback && ctx->io_top == TOP_DONE) { +if (ctx->asynch_io) + pthread_mutex_lock (&ctx->io_lock); + +if (ctx->callback) { ctx->callback = NULL; + if (ctx->asynch_io) + pthread_mutex_unlock (&ctx->io_lock); callback (uptr, ctx->io_status); } +else { + if (ctx->asynch_io) + pthread_mutex_unlock (&ctx->io_lock); + } } static t_bool _tape_is_active (UNIT *uptr) { struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx; @@ -365,12 +376,11 @@ /* Enable asynchronous operation */ t_stat sim_tape_set_async (UNIT *uptr, int latency) { #if !defined(SIM_ASYNCH_IO) -sim_printf ("Tape: can't operate asynchronously\r\n"); -return SCPE_NOFNC; +return sim_messagef (SCPE_NOFNC, "Tape: can't operate asynchronously\r\n"); #else struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx; pthread_attr_t attr; ctx->asynch_io = sim_asynch_enabled; @@ -444,11 +454,11 @@ { DEVICE *dptr; if ((dptr = find_dev_from_unit (uptr)) == NULL) return SCPE_NOATT; -return sim_tape_attach_ex (uptr, cptr, (dptr->flags & DEV_DEBUG) ? 0xFFFFFFFF : 0, 0); +return sim_tape_attach_ex (uptr, cptr, ((dptr->flags & DEV_DEBUG) || (dptr->debflags)) ? 0xFFFFFFFF : 0, 0); } t_stat sim_tape_attach_ex (UNIT *uptr, const char *cptr, uint32 dbit, int completion_delay) { struct tape_context *ctx; @@ -605,11 +615,11 @@ return; if (sim_deb && (ctx->dptr->dctrl & reason)) sim_data_trace(ctx->dptr, uptr, (detail ? data : NULL), "", len, txt, reason); } -/* Read record length forward (internal routine) +/* Read record length forward (internal routine). Inputs: uptr = pointer to tape unit bc = pointer to returned record length Outputs: @@ -628,11 +638,11 @@ direction. On return, status is MTSE_OK and the tape is positioned at the first data byte if a record was encountered, or status is an MTSE error code giving the reason that the operation did not succeed and the tape position is as indicated above. - The ANSI standards for magnetic tape recording (X3.32, X3.39, and X3.54) and + The ANSI standards for magnetic tape recording (X3.22, X3.39, and X3.54) and the equivalent ECMA standard (ECMA-62) specify a maximum erase gap length of 25 feet (7.6 meters). While gaps of any length may be written, gaps longer than this are non-standard and may indicate that an unrecorded or erased tape is being read. @@ -639,11 +649,11 @@ If the tape density has been set via a previous "sim_tape_set_dens" call, then the length is monitored when skipping over erase gaps. If the length reaches 25 feet, motion is terminated, and MTSE_RUNAWAY status is returned. Runaway status is also returned if an end-of-medium marker or the physical end of file is encountered while spacing over a gap; however, MTSE_EOM is - returned if the tape is positioned at the EOM on entry. + returned if the tape is positioned at the EOM or EOF on entry. If the density has not been set, then a gap of any length is skipped, and MTSE_RUNAWAY status is never returned. In effect, erase gaps present in the tape image file will be transparent to the caller. @@ -654,25 +664,30 @@ established at 256 elements but may be set to any size desired. To avoid a large read for the typical case where an erase gap is not present, the first read is of a single metadatum marker. If that is a gap marker, then additional buffered reads are performed. - See the notes at "sim_tape_wrgap" regarding the erase gap implementation. + See the notes at "tape_erase_fwd" regarding the erase gap implementation. + Implementation notes: 1. For programming convenience, erase gap processing is performed for both SIMH standard and E11 tape formats, although the latter will never - contain erase gaps, as the "sim_tape_wrgap" call takes no action for the + contain erase gaps, as the "tape_erase_fwd" call takes no action for the E11 format. 2. The "feof" call cannot return a non-zero value on the first pass through the loop, because the "sim_fseek" call resets the internal end-of-file indicator. Subsequent passes only occur if an erase gap is present, so a non-zero return indicates an EOF was seen while reading through a gap. - 3. The dynamic start/stop test of the HP 3000 magnetic tape diagnostic + 3. The "runaway_counter" cannot decrement to zero (or below) in the presence + of an error that terminates the gap-search loop. Therefore, the test + after the loop exit need not check for error status. + + 4. The dynamic start/stop test of the HP 3000 magnetic tape diagnostic heavily exercises the erase gap scanning code. Sample test execution times for various buffer sizes on a 2 GHz host platform are: buffer size execution time (elements) (CPU seconds) @@ -682,50 +697,51 @@ 128 237 256 203 512 186 1024 171 - 4. Because an erase gap may precede the logical end-of-medium, represented + 5. Because an erase gap may precede the logical end-of-medium, represented either by the physical end-of-file or by an EOM marker, the "position not updated" flag is set only if the tape is positioned at the EOM when the routine is entered. If at least one gap marker precedes the EOM, then the PNU flag is not set. This ensures that a backspace-and-retry sequence will work correctly in both cases. */ static t_stat sim_tape_rdlntf (UNIT *uptr, t_mtrlnt *bc) { -struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx; -uint8 c; -t_bool all_eof; -uint32 f = MT_GET_FMT (uptr); +uint8 c; +t_bool all_eof; +uint32 f = MT_GET_FMT (uptr); t_mtrlnt sbc; t_tpclnt tpcbc; t_mtrlnt buffer [256]; /* local tape buffer */ -uint32 bufcntr, bufcap; /* buffer counter and capacity */ -int32 runaway_counter, sizeof_gap; /* bytes remaining before runaway and bytes per gap */ -t_stat r = MTSE_OK; +uint32 bufcntr, bufcap; /* buffer counter and capacity */ +int32 runaway_counter, sizeof_gap; /* bytes remaining before runaway and bytes per gap */ +t_stat status = MTSE_OK; MT_CLR_PNU (uptr); /* clear the position-not-updated flag */ if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */ return MTSE_UNATT; /* then quit with an error */ -if (ctx == NULL) /* if not properly attached? */ - return sim_messagef (SCPE_IERR, "Bad Attach\n"); /* that's a problem */ -sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* set the initial tape position */ +if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* set the initial tape position; if it fails */ + MT_SET_PNU (uptr); /* then set position not updated */ + status = sim_tape_ioerr (uptr); /* and quit with I/O error status */ + } -switch (f) { /* the read method depends on the tape format */ +else switch (f) { /* otherwise the read method depends on the tape format */ case MTUF_F_STD: case MTUF_F_E11: runaway_counter = 25 * 12 * bpi [MT_DENS (uptr->dynflags)]; /* set the largest legal gap size in bytes */ if (runaway_counter == 0) { /* if tape density has not been not set */ sizeof_gap = 0; /* then disable runaway detection */ runaway_counter = INT_MAX; /* to allow gaps of any size */ } + else /* otherwise */ sizeof_gap = sizeof (t_mtrlnt); /* set the size of the gap */ bufcntr = 0; /* force an initial read */ bufcap = 0; /* but of just one metadata marker */ @@ -732,13 +748,13 @@ do { /* loop until a record, gap, or error is seen */ if (bufcntr == bufcap) { /* if the buffer is empty then refill it */ if (feof (uptr->fileref)) { /* if we hit the EOF while reading a gap */ if (sizeof_gap > 0) /* then if detection is enabled */ - r = MTSE_RUNAWAY; /* then report a tape runaway */ + status = MTSE_RUNAWAY; /* then report a tape runaway */ else /* otherwise report the physical EOF */ - r = MTSE_EOM; /* as the end-of-medium */ + status = MTSE_EOM; /* as the end-of-medium */ break; } else if (bufcap == 0) /* otherwise if this is the initial read */ bufcap = 1; /* then start with just one marker */ @@ -754,27 +770,27 @@ if (ferror (uptr->fileref)) { /* if a file I/O error occurred */ if (bufcntr == 0) /* then if this is the initial read */ MT_SET_PNU (uptr); /* then set position not updated */ - r = sim_tape_ioerr (uptr); /* report the error and quit */ + status = sim_tape_ioerr (uptr); /* report the error and quit */ break; } else if (bufcap == 0 /* otherwise if positioned at the physical EOF */ || buffer [0] == MTR_EOM) /* or at the logical EOM */ if (bufcntr == 0) { /* then if this is the initial read */ MT_SET_PNU (uptr); /* then set position not updated */ - r = MTSE_EOM; /* and report the end-of-medium and quit */ + status = MTSE_EOM; /* and report the end-of-medium and quit */ break; } else { /* otherwise some gap has already been skipped */ if (sizeof_gap > 0) /* so if detection is enabled */ - r = MTSE_RUNAWAY; /* then report a tape runaway */ + status = MTSE_RUNAWAY; /* then report a tape runaway */ else /* otherwise report the physical EOF */ - r = MTSE_EOM; /* as the end-of-medium */ + status = MTSE_EOM; /* as the end-of-medium */ break; } else /* otherwise reset the index */ bufcntr = 0; /* to the start of the buffer */ @@ -782,101 +798,132 @@ *bc = buffer [bufcntr++]; /* store the metadata marker value */ if (*bc == MTR_EOM) { /* if an end-of-medium marker is seen */ if (sizeof_gap > 0) /* then if detection is enabled */ - r = MTSE_RUNAWAY; /* then report a tape runaway */ + status = MTSE_RUNAWAY; /* then report a tape runaway */ else /* otherwise report the physical EOF */ - r = MTSE_EOM; /* as the end-of-medium */ + status = MTSE_EOM; /* as the end-of-medium */ break; } uptr->pos = uptr->pos + sizeof (t_mtrlnt); /* space over the marker */ if (*bc == MTR_TMK) { /* if the value is a tape mark */ - r = MTSE_TMK; /* then quit with tape mark status */ + status = MTSE_TMK; /* then quit with tape mark status */ break; } else if (*bc == MTR_GAP) /* otherwise if the value is a full gap */ runaway_counter -= sizeof_gap; /* then decrement the gap counter */ else if (*bc == MTR_FHGAP) { /* otherwise if the value if a half gap */ - uptr->pos = uptr->pos - sizeof (t_mtrlnt) / 2; /* then back up */ - sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* to resync */ - bufcntr = bufcap; /* mark the buffer as invalid to force a read */ - - *bc = MTR_GAP; /* reset the marker */ - runaway_counter -= sizeof_gap / 2; /* and decrement the gap counter */ - } - - else { /* otherwise it's a record marker */ - if (bufcntr < bufcap) /* if the position is within the buffer */ - sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* then seek to the data area */ - - sbc = MTR_L (*bc); /* extract the record length */ - uptr->pos = uptr->pos + sizeof (t_mtrlnt) /* position to the start */ - + (f == MTUF_F_STD ? (sbc + 1) & ~1 : sbc); /* of the record */ + uptr->pos = uptr->pos - sizeof (t_mtrlnt) / 2; /* then back up and resync */ + + if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* set the tape position; if it fails */ + status = sim_tape_ioerr (uptr); /* then quit with I/O error status */ + break; + } + + bufcntr = bufcap; /* mark the buffer as invalid to force a read */ + + *bc = MTR_GAP; /* reset the marker */ + runaway_counter -= sizeof_gap / 2; /* and decrement the gap counter */ + } + + else { /* otherwise it's a record marker */ + if (bufcntr < bufcap /* if the position is within the buffer */ + && sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* then seek to the data area; if it fails */ + status = sim_tape_ioerr (uptr); /* then quit with I/O error status */ + break; + } + + sbc = MTR_L (*bc); /* extract the record length */ + uptr->pos = uptr->pos + sizeof (t_mtrlnt) /* position to the start */ + + (f == MTUF_F_STD ? (sbc + 1) & ~1 : sbc); /* of the record */ } } while (*bc == MTR_GAP && runaway_counter > 0); /* continue until data or runaway occurs */ - if (r == MTSE_OK && runaway_counter <= 0) /* if a tape runaway occurred */ - r = MTSE_RUNAWAY; /* then report it */ + if (runaway_counter <= 0) /* if a tape runaway occurred */ + status = MTSE_RUNAWAY; /* then report it */ break; /* otherwise the operation succeeded */ case MTUF_F_TPC: sim_fread (&tpcbc, sizeof (t_tpclnt), 1, uptr->fileref); *bc = tpcbc; /* save rec lnt */ + if (ferror (uptr->fileref)) { /* error? */ MT_SET_PNU (uptr); /* pos not upd */ - return sim_tape_ioerr (uptr); + status = sim_tape_ioerr (uptr); } - if (feof (uptr->fileref)) { /* eof? */ + else if (feof (uptr->fileref)) { /* eof? */ MT_SET_PNU (uptr); /* pos not upd */ - r = MTSE_EOM; - break; + status = MTSE_EOM; } - uptr->pos = uptr->pos + sizeof (t_tpclnt); /* spc over reclnt */ - if (tpcbc == TPC_TMK) /* tape mark? */ - r = MTSE_TMK; - uptr->pos = uptr->pos + ((tpcbc + 1) & ~1); /* spc over record */ + else { + uptr->pos = uptr->pos + sizeof (t_tpclnt); /* spc over reclnt */ + if (tpcbc == TPC_TMK) /* tape mark? */ + status = MTSE_TMK; + else + uptr->pos = uptr->pos + ((tpcbc + 1) & ~1); /* spc over record */ + } break; case MTUF_F_P7B: for (sbc = 0, all_eof = 1; ; sbc++) { /* loop thru record */ sim_fread (&c, sizeof (uint8), 1, uptr->fileref); + if (ferror (uptr->fileref)) { /* error? */ MT_SET_PNU (uptr); /* pos not upd */ - return sim_tape_ioerr (uptr); + status = sim_tape_ioerr (uptr); + break; } - if (feof (uptr->fileref)) { /* eof? */ + else if (feof (uptr->fileref)) { /* eof? */ if (sbc == 0) /* no data? eom */ - return MTSE_EOM; + status = MTSE_EOM; break; /* treat like eor */ } - if ((sbc != 0) && (c & P7B_SOR)) /* next record? */ + else if ((sbc != 0) && (c & P7B_SOR)) /* next record? */ break; - if ((c & P7B_DPAR) != P7B_EOF) + else if ((c & P7B_DPAR) != P7B_EOF) all_eof = 0; } - *bc = sbc; /* save rec lnt */ - sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* for read */ - uptr->pos = uptr->pos + sbc; /* spc over record */ - if (all_eof) /* tape mark? */ - r = MTSE_TMK; + + if (status == MTSE_OK) { + *bc = sbc; /* save rec lnt */ + sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* for read */ + uptr->pos = uptr->pos + sbc; /* spc over record */ + if (all_eof) /* tape mark? */ + status = MTSE_TMK; + } break; default: - return MTSE_FMT; - } -sim_debug (MTSE_DBG_STR, ctx->dptr, "rd_lnt: st: %d, lnt: %d, pos: %" T_ADDR_FMT "u\n", r, *bc, uptr->pos); -return r; + status = MTSE_FMT; + } + +return status; +} + +static t_stat sim_tape_rdrlfwd (UNIT *uptr, t_mtrlnt *bc) +{ +struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx; +t_stat status; + +if (ctx == NULL) /* if not properly attached? */ + return sim_messagef (SCPE_IERR, "Bad Attach\n"); /* that's a problem */ + +status = sim_tape_rdlntf (uptr, bc); /* read the record length */ + +sim_debug (MTSE_DBG_STR, ctx->dptr, "rd_lnt: st: %d, lnt: %d, pos: %" T_ADDR_FMT "u\n", status, *bc, uptr->pos); + +return status; } -/* Read record length reverse (internal routine) +/* Read record length reverse (internal routine). Inputs: uptr = pointer to tape unit bc = pointer to returned record length Outputs: @@ -897,43 +944,49 @@ direction. On return, status is MTSE_OK and the tape is positioned at the first data byte if a record was encountered, or status is an MTSE error code giving the reason that the operation did not succeed and the tape position is as indicated above. - See the notes at "sim_tape_rdlntf" and "sim_tape_wrgap" regarding tape - runaway and the erase gap implementation, respectively. + + Implementation notes: + + 1. The "sim_fread" call cannot return 0 in the absence of an error + condition. The preceding "sim_tape_bot" test ensures that "pos" >= 4, so + "sim_fseek" will back up at least that far, so "sim_fread" will read at + least one element. If the call returns zero, an error must have + occurred, so the "ferror" call must succeed. + + 2. See the notes at "sim_tape_rdlntf" and "tape_erase_fwd" regarding tape + runaway and the erase gap implementation, respectively. */ static t_stat sim_tape_rdlntr (UNIT *uptr, t_mtrlnt *bc) { -struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx; -uint8 c; -t_bool all_eof; -uint32 f = MT_GET_FMT (uptr); -t_addr ppos; +uint8 c; +t_bool all_eof; +uint32 f = MT_GET_FMT (uptr); +t_addr ppos; t_mtrlnt sbc; t_tpclnt tpcbc; t_mtrlnt buffer [256]; /* local tape buffer */ -uint32 bufcntr, bufcap; /* buffer counter and capacity */ -int32 runaway_counter, sizeof_gap; /* bytes remaining before runaway and bytes per gap */ -t_stat r = MTSE_OK; +uint32 bufcntr, bufcap; /* buffer counter and capacity */ +int32 runaway_counter, sizeof_gap; /* bytes remaining before runaway and bytes per gap */ +t_stat status = MTSE_OK; MT_CLR_PNU (uptr); /* clear the position-not-updated flag */ if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */ return MTSE_UNATT; /* then quit with an error */ -if (ctx == NULL) /* if not properly attached? */ - return sim_messagef (SCPE_IERR, "Bad Attach\n"); /* that's a problem */ if (sim_tape_bot (uptr)) /* if the unit is positioned at the BOT */ - return MTSE_BOT; /* then reading backward is not possible */ + status = MTSE_BOT; /* then reading backward is not possible */ -switch (f) { /* the read method depends on the tape format */ +else switch (f) { /* otherwise the read method depends on the tape format */ case MTUF_F_STD: case MTUF_F_E11: - runaway_counter = 25 * 12 * bpi [MT_DENS (uptr->dynflags)]; /* set largest legal gap size in bytes */ + runaway_counter = 25 * 12 * bpi [MT_DENS (uptr->dynflags)]; /* set the largest legal gap size in bytes */ if (runaway_counter == 0) { /* if tape density has not been not set */ sizeof_gap = 0; /* then disable runaway detection */ runaway_counter = INT_MAX; /* to allow gaps of any size */ } @@ -940,47 +993,52 @@ else /* otherwise */ sizeof_gap = sizeof (t_mtrlnt); /* set the size of the gap */ bufcntr = 0; /* force an initial read */ - bufcap = 1; /* but of just one metadata marker */ - - do { /* loop until a record, gap, or error seen */ - if (bufcntr == 0) { /* if the buffer is empty then refill it */ - if (sim_tape_bot (uptr)) { /* if the search has backed into the BOT */ - r = MTSE_BOT; /* then quit with an error */ - break; - } - - else if (uptr->pos < sizeof (buffer)) /* if less than a full buffer remains */ - bufcap = (uint32) uptr->pos /* then reduce the capacity accordingly */ - / sizeof (t_mtrlnt); - - sim_fseek (uptr->fileref, /* seek back to the location */ - uptr->pos - bufcap * sizeof (t_mtrlnt), /* corresponding to the start */ - SEEK_SET); /* of the buffer */ - - bufcntr = sim_fread (buffer, sizeof (t_mtrlnt), /* fill the buffer */ - bufcap, uptr->fileref); /* with tape metadata */ - - if (ferror (uptr->fileref)) { /* if a file I/O error occurred */ - MT_SET_PNU (uptr); /* then set position not updated */ - r = sim_tape_ioerr (uptr); /* report the error and quit */ - break; - } + bufcap = 0; /* but of just one metadata marker */ + + do { /* loop until a record, gap, or error is seen */ + if (bufcntr == 0) { /* if the buffer is empty then refill it */ + if (sim_tape_bot (uptr)) { /* if the search has backed into the BOT */ + status = MTSE_BOT; /* then quit with an error */ + break; + } + + else if (bufcap == 0) /* otherwise if this is the initial read */ + bufcap = 1; /* then start with just one marker */ + + else if (uptr->pos < sizeof (buffer)) /* otherwise if less than a full buffer remains */ + bufcap = (uint32) uptr->pos /* then reduce the capacity accordingly */ + / sizeof (t_mtrlnt); else /* otherwise reset the capacity */ bufcap = sizeof (buffer) /* to the full size of the buffer */ / sizeof (buffer [0]); + + if (sim_fseek (uptr->fileref, /* seek back to the location */ + uptr->pos - bufcap * sizeof (t_mtrlnt), /* corresponding to the start */ + SEEK_SET)) { /* of the buffer; if it fails */ + status = sim_tape_ioerr (uptr); /* and fail with I/O error status */ + break; + } + + bufcntr = sim_fread (buffer, sizeof (t_mtrlnt), /* fill the buffer */ + bufcap, uptr->fileref); /* with tape metadata */ + + if (ferror (uptr->fileref)) { /* if a file I/O error occurred */ + status = sim_tape_ioerr (uptr); /* then report the error and quit */ + break; + } } *bc = buffer [--bufcntr]; /* store the metadata marker value */ uptr->pos = uptr->pos - sizeof (t_mtrlnt); /* backspace over the marker */ if (*bc == MTR_TMK) { /* if the marker is a tape mark */ - r = MTSE_TMK; /* then quit with tape mark status */ + status = MTSE_TMK; /* then quit with tape mark status */ break; } else if (*bc == MTR_GAP) /* otherwise if the marker is a full gap */ runaway_counter -= sizeof_gap; /* then decrement the gap counter */ @@ -996,67 +1054,95 @@ else { /* otherwise it's a record marker */ sbc = MTR_L (*bc); /* extract the record length */ uptr->pos = uptr->pos - sizeof (t_mtrlnt) /* position to the start */ - (f == MTUF_F_STD ? (sbc + 1) & ~1 : sbc); /* of the record */ - sim_fseek (uptr->fileref, /* seek to the data area */ - uptr->pos + sizeof (t_mtrlnt), SEEK_SET); + + if (sim_fseek (uptr->fileref, /* seek to the start of the data area; if it fails */ + uptr->pos + sizeof (t_mtrlnt), /* then return with I/O error status */ + SEEK_SET)) { + status = sim_tape_ioerr (uptr); + break; + } } } while (*bc == MTR_GAP && runaway_counter > 0); /* continue until data or runaway occurs */ - if (r == MTSE_OK && runaway_counter <= 0) /* if a tape runaway occurred */ - r = MTSE_RUNAWAY; /* then report it */ + if (runaway_counter <= 0) /* if a tape runaway occurred */ + status = MTSE_RUNAWAY; /* then report it */ break; /* otherwise the operation succeeded */ case MTUF_F_TPC: ppos = sim_tape_tpc_fnd (uptr, (t_addr *) uptr->filebuf); /* find prev rec */ sim_fseek (uptr->fileref, ppos, SEEK_SET); /* position */ sim_fread (&tpcbc, sizeof (t_tpclnt), 1, uptr->fileref); *bc = tpcbc; /* save rec lnt */ + if (ferror (uptr->fileref)) /* error? */ - return sim_tape_ioerr (uptr); - if (feof (uptr->fileref)) { /* eof? */ - r = MTSE_EOM; - break; - } - uptr->pos = ppos; /* spc over record */ - if (*bc == MTR_TMK) { /* tape mark? */ - r = MTSE_TMK; - break; - } - sim_fseek (uptr->fileref, uptr->pos + sizeof (t_tpclnt), SEEK_SET); + status = sim_tape_ioerr (uptr); + else if (feof (uptr->fileref)) /* eof? */ + status = MTSE_EOM; + else { + uptr->pos = ppos; /* spc over record */ + if (*bc == MTR_TMK) /* tape mark? */ + status = MTSE_TMK; + else + sim_fseek (uptr->fileref, uptr->pos + sizeof (t_tpclnt), SEEK_SET); + } break; case MTUF_F_P7B: for (sbc = 1, all_eof = 1; (t_addr) sbc <= uptr->pos ; sbc++) { sim_fseek (uptr->fileref, uptr->pos - sbc, SEEK_SET); sim_fread (&c, sizeof (uint8), 1, uptr->fileref); - if (ferror (uptr->fileref)) /* error? */ - return sim_tape_ioerr (uptr); - if (feof (uptr->fileref)) { /* eof? */ - r = MTSE_EOM; - break; - } - if ((c & P7B_DPAR) != P7B_EOF) - all_eof = 0; - if (c & P7B_SOR) /* start of record? */ - break; - } - uptr->pos = uptr->pos - sbc; /* update position */ - *bc = sbc; /* save rec lnt */ - sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* for read */ - if (all_eof) /* tape mark? */ - r = MTSE_TMK; + + if (ferror (uptr->fileref)) { /* error? */ + status = sim_tape_ioerr (uptr); + break; + } + else if (feof (uptr->fileref)) { /* eof? */ + status = MTSE_EOM; + break; + } + else { + if ((c & P7B_DPAR) != P7B_EOF) + all_eof = 0; + if (c & P7B_SOR) /* start of record? */ + break; + } + } + + if (status == MTSE_OK) { + uptr->pos = uptr->pos - sbc; /* update position */ + *bc = sbc; /* save rec lnt */ + sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* for read */ + if (all_eof) /* tape mark? */ + status = MTSE_TMK; + } break; default: - return MTSE_FMT; + status = MTSE_FMT; } -sim_debug (MTSE_DBG_STR, ctx->dptr, "rd_lnt: st: %d, lnt: %d, pos: %" T_ADDR_FMT "u\n", r, *bc, uptr->pos); -return r; + +return status; +} + +static t_stat sim_tape_rdrlrev (UNIT *uptr, t_mtrlnt *bc) +{ +struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx; +t_stat status; + +if (ctx == NULL) /* if not properly attached? */ + return sim_messagef (SCPE_IERR, "Bad Attach\n"); /* that's a problem */ + +status = sim_tape_rdlntr (uptr, bc); /* read the record length */ + +sim_debug (MTSE_DBG_STR, ctx->dptr, "rd_lnt: st: %d, lnt: %d, pos: %" T_ADDR_FMT "u\n", status, *bc, uptr->pos); + +return status; } /* Read record forward Inputs: @@ -1089,19 +1175,20 @@ if (ctx == NULL) /* if not properly attached? */ return sim_messagef (SCPE_IERR, "Bad Attach\n"); /* that's a problem */ sim_debug (ctx->dbit, ctx->dptr, "sim_tape_rdrecf(unit=%d, buf=%p, max=%d)\n", (int)(uptr-ctx->dptr->units), buf, max); opos = uptr->pos; /* old position */ -if (MTSE_OK != (st = sim_tape_rdlntf (uptr, &tbc))) /* read rec lnt */ +st = sim_tape_rdrlfwd (uptr, &tbc); /* read rec lnt */ +if (st != MTSE_OK) return st; *bc = rbc = MTR_L (tbc); /* strip error flag */ if (rbc > max) { /* rec out of range? */ MT_SET_PNU (uptr); uptr->pos = opos; return MTSE_INVRL; } -i = (t_mtrlnt)sim_fread (buf, sizeof (uint8), rbc, uptr->fileref);/* read record */ +i = (t_mtrlnt) sim_fread (buf, sizeof (uint8), rbc, uptr->fileref); /* read record */ if (ferror (uptr->fileref)) { /* error? */ MT_SET_PNU (uptr); uptr->pos = opos; return sim_tape_ioerr (uptr); } @@ -1154,16 +1241,17 @@ if (ctx == NULL) /* if not properly attached? */ return sim_messagef (SCPE_IERR, "Bad Attach\n"); /* that's a problem */ sim_debug (ctx->dbit, ctx->dptr, "sim_tape_rdrecr(unit=%d, buf=%p, max=%d)\n", (int)(uptr-ctx->dptr->units), buf, max); -if (MTSE_OK != (st = sim_tape_rdlntr (uptr, &tbc))) /* read rec lnt */ +st = sim_tape_rdrlrev (uptr, &tbc); /* read rec lnt */ +if (st != MTSE_OK) return st; *bc = rbc = MTR_L (tbc); /* strip error flag */ if (rbc > max) /* rec out of range? */ return MTSE_INVRL; -i = (t_mtrlnt)sim_fread (buf, sizeof (uint8), rbc, uptr->fileref);/* read record */ +i = (t_mtrlnt) sim_fread (buf, sizeof (uint8), rbc, uptr->fileref); /* read record */ if (ferror (uptr->fileref)) /* error? */ return sim_tape_ioerr (uptr); for ( ; i < rbc; i++) /* fill with 0's */ buf[i] = 0; if (f == MTUF_F_P7B) /* p7b? strip SOR */ @@ -1361,52 +1449,46 @@ r = sim_tape_wreomrw (uptr); AIO_CALL(TOP_WEMR, NULL, NULL, NULL, 0, 0, 0, 0, NULL, callback); return r; } -/* Write erase gap - - Inputs: - uptr = pointer to tape unit - gaplen = length of gap in tenths of an inch - - Outputs: - status = operation status - - exit condition position - ------------------ ------------------ - unit unattached unchanged - unsupported format unchanged - write protected unchanged - read error unchanged, PNU set - write error unchanged, PNU set - gap written updated - + +/* Erase a gap in the forward direction (internal routine). + + An erase gap is written in the forward direction on the tape unit specified + by "uptr" for the number of bytes specified by "bc". The status of the + operation is returned, and the file position is altered as follows: + + Exit Condition File Position + ------------------ ------------------ + unit unattached unchanged + unsupported format unchanged + write protected unchanged + read error unchanged, PNU set + write error unchanged, PNU set + gap written updated + + If the requested byte count equals the metadatum size, then the routine + succeeds only if it can overlay a single metadatum (i.e., a tape mark, an + end-of-medium marker, or an existing erase gap marker); otherwise, the file + position is not altered, PNU is set, and MTSE_INVRL (invalid record length) + status is returned. An erase gap is represented in the tape image file by a special metadata - value. This value is chosen so that it is still recognizable even if it has - been "cut in half" by a subsequent data overwrite that does not end on a - metadatum-sized boundary. In addition, a range of metadata values are - reserved for detection in the reverse direction. Erase gaps are currently - supported only in SIMH (MTUF_F_STD) tape format. + value repeated throughout the gap. The value is chosen so that it is still + recognizable even if it has been "cut in half" by a subsequent data overwrite + that does not end on a metadatum-sized boundary. In addition, a range of + metadata values are reserved for detection in the reverse direction. This implementation supports erasing gaps in the middle of a populated tape image and will always produce a valid image. It also produces valid images when overwriting gaps with data records, with one exception: a data write that leaves only two bytes of gap remaining will produce an invalid tape. This limitation is deemed acceptable, as it is analogous to the existing limitation that data records cannot overwrite other data records without producing an invalid tape. - Because SIMH tape images do not carry physical parameters (e.g., recording - density), overwriting a tape image file containing gap metadata is - problematic if the density setting is not the same as that used during - recording. There is no way to establish a gap of a certain length - unequivocally in an image file, so this implementation establishes a gap of a - certain number of bytes that reflect the desired gap length at the tape - density in bits per inch used during writing. - To write an erase gap, the implementation uses one of two approaches, depending on whether or not the current tape position is at EOM. Erasing at EOM presents no special difficulties; gap metadata markers are written for the prescribed number of bytes. If the tape is not at EOM, then erasing must take into account the existing record structure to ensure that a valid tape @@ -1416,12 +1498,11 @@ increase that length, if necessary, to ensure that a partially overwritten data record at the end of the gap can be altered to maintain validity. Because the smallest legal tape record requires space for two metadata markers plus two data bytes, an erasure that would leave less than that is increased to consume the entire record. Otherwise, the final record is - truncated appropriately by rewriting the leading and trailing length words - appropriately. + truncated by rewriting the leading and trailing length words appropriately. When reading in either direction, gap metadata markers are ignored (skipped) until a record length header, EOF marker, EOM marker, or physical EOF is encountered. Thus, tape images containing gap metadata are transparent to the calling simulator (unless tape runaway support is enabled -- see the @@ -1450,80 +1531,83 @@ 0xFFFFFFFE - primary gap value 0xFFFEFFFF - reserved (indicates half-gap in forward reads) 0xFFFF0000:0xFFFF00FF - reserved (indicates half-gap in reverse reads) 0xFFFF8000:0xFFFF80FF - reserved (indicates half-gap in reverse reads) - If the tape density has been set via a previous sim_tape_set_dens call, and - the tape format is set to SIMH format, then this routine will write a gap of - the appropriate size. If the density has not been set, then no action will - be taken, and either MTSE_IOERR or MTSE_OK status will be returned, depending - on whether SIMH or another format is selected, respectively. A simulator - that calls this routine must set the density beforehand; failure to do so is - an error. However, calling while another format is enabled is OK and is - treated as a no-operation. This allows a device simulator that supports - writing erase gaps to use the same code without worrying about the tape - format currently selected by the user. + If the current tape format supports erase gaps, then this routine will write + a gap of the requested size. If the format does not, then no action will be + taken, and MTSE_OK status will be returned. This allows a device simulator + that supports writing erase gaps to use the same code without worrying about + the tape format currently selected by the user. A request for an erase gap + of zero length also succeeds with no action taken. + + + Implementation notes: + + 1. Erase gaps are currently supported only in SIMH (MTUF_F_STD) tape format. */ -t_stat sim_tape_wrgap (UNIT *uptr, uint32 gaplen) +static t_stat tape_erase_fwd (UNIT *uptr, t_mtrlnt gap_size) { -struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx; -t_stat st; +size_t xfer; +t_stat st; t_mtrlnt meta, sbc, new_len, rec_size; -t_addr gap_pos = uptr->pos; -uint32 file_size, marker_count, tape_density; -int32 gap_needed; -uint32 gap_alloc = 0; /* gap currently allocated from the tape */ -const uint32 format = MT_GET_FMT (uptr); /* tape format */ -const uint32 meta_size = sizeof (t_mtrlnt); /* bytes per metadatum */ -const uint32 min_rec_size = 2 + sizeof (t_mtrlnt) * 2; /* smallest data record size */ - -if (ctx == NULL) /* if not properly attached? */ - return sim_messagef (SCPE_IERR, "Bad Attach\n"); /* that's a problem */ -sim_debug (ctx->dbit, ctx->dptr, "sim_tape_wrgap(unit=%d, gaplen=%u)\n", (int)(uptr-ctx->dptr->units), gaplen); +uint32 file_size, marker_count; +int32 gap_needed = (int32) gap_size; /* the gap remaining to be allocated from the tape */ +uint32 gap_alloc = 0; /* the gap currently allocated from the tape */ +const t_addr gap_pos = uptr->pos; /* the file position where the gap will start */ +const uint32 format = MT_GET_FMT (uptr); /* the tape format */ +const uint32 meta_size = sizeof (t_mtrlnt); /* the number of bytes per metadatum */ +const uint32 min_rec_size = 2 + sizeof (t_mtrlnt) * 2; /* the smallest data record size */ MT_CLR_PNU (uptr); if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */ return MTSE_UNATT; /* then we cannot proceed */ else if (sim_tape_wrp (uptr)) /* otherwise if the unit is write protected */ return MTSE_WRP; /* then we cannot write */ -tape_density = bpi [MT_DENS (uptr->dynflags)]; /* get the density of the tape */ - -if (format != MTUF_F_STD) /* if erase gaps aren't supported by the format */ +else if (gap_size == 0 || format != MTUF_F_STD) /* otherwise if zero length or gaps aren't supported */ return MTSE_OK; /* then take no action */ -else if (tape_density == 0) /* otherwise if the density is not set */ - return MTSE_IOERR; /* then report an I/O error */ -else /* otherwise */ - gap_needed = (gaplen * tape_density) / 10; /* determine the gap size needed in bytes */ - -file_size = sim_fsize (uptr->fileref); /* get file size */ -sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* position tape */ - -/* Read tape records and allocate to gap until amount required is consumed. - - Read next metadatum from tape: + +file_size = sim_fsize (uptr->fileref); /* get the file size */ + +if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* position the tape; if it fails */ + MT_SET_PNU (uptr); /* then set position not updated */ + return sim_tape_ioerr (uptr); /* and quit with I/O error status */ + } + +/* Read tape records and allocate them to the gap until the amount required is + consumed. + + Read the next metadatum from tape: - EOF or EOM: allocate remainder of bytes needed. - TMK or GAP: allocate sizeof(metadatum) bytes. - Reverse GAP: allocate sizeof(metadatum) / 2 bytes. - Data record: see below. - Loop until bytes needed = 0. + Loop until the bytes needed = 0. */ do { - sim_fread (&meta, meta_size, 1, uptr->fileref); /* read metadatum */ + xfer = sim_fread (&meta, meta_size, 1, uptr->fileref); /* read a metadatum */ if (ferror (uptr->fileref)) { /* read error? */ uptr->pos = gap_pos; /* restore original position */ MT_SET_PNU (uptr); /* position not updated */ return sim_tape_ioerr (uptr); /* translate error */ } - else - uptr->pos = uptr->pos + meta_size; /* move tape over datum */ + + else if (xfer != 1 && feof (uptr->fileref) == 0) { /* otherwise if a partial metadatum was read */ + uptr->pos = gap_pos; /* then restore the original position */ + MT_SET_PNU (uptr); /* set the position-not-updated flag */ + return MTSE_INVRL; /* and return an invalid record length error */ + } + + else /* otherwise we had a good read */ + uptr->pos = uptr->pos + meta_size; /* so move the tape over the datum */ if (feof (uptr->fileref) || (meta == MTR_EOM)) { /* at eof or eom? */ gap_alloc = gap_alloc + gap_needed; /* allocate remainder */ gap_needed = 0; } @@ -1530,37 +1614,49 @@ else if ((meta == MTR_GAP) || (meta == MTR_TMK)) { /* gap or tape mark? */ gap_alloc = gap_alloc + meta_size; /* allocate marker space */ gap_needed = gap_needed - meta_size; /* reduce requirement */ } + + else if (gap_size == meta_size) { /* otherwise if the request is for a single metadatum */ + uptr->pos = gap_pos; /* then restore the original position */ + MT_SET_PNU (uptr); /* set the position-not-updated flag */ + return MTSE_INVRL; /* and return an invalid record length error */ + } else if (meta == MTR_FHGAP) { /* half gap? */ uptr->pos = uptr->pos - meta_size / 2; /* backup to resync */ - sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* position tape */ + + if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* position the tape; if it fails */ + return sim_tape_ioerr (uptr); /* then quit with I/O error status */ + gap_alloc = gap_alloc + meta_size / 2; /* allocate marker space */ gap_needed = gap_needed - meta_size / 2; /* reduce requirement */ } - else if (uptr->pos + - MTR_L (meta) + meta_size > file_size) { /* rec len out of range? */ - gap_alloc = gap_alloc + gap_needed; /* presume overwritten tape */ - gap_needed = 0; /* allocate remainder */ + else if (uptr->pos + MTR_L (meta) + meta_size > file_size) { /* rec len out of range? */ + gap_alloc = gap_alloc + gap_needed; /* presume overwritten tape */ + gap_needed = 0; /* allocate remainder */ } /* Allocate a data record: - Determine record size in bytes (including metadata) - If record size - bytes needed < smallest allowed record size, allocate entire record to gap, else allocate needed amount and truncate data record to reflect remainder. */ + else { /* data record */ sbc = MTR_L (meta); /* get record data length */ rec_size = ((sbc + 1) & ~1) + meta_size * 2; /* overall size in bytes */ if (rec_size < gap_needed + min_rec_size) { /* rec too small? */ uptr->pos = uptr->pos - meta_size + rec_size; /* position past record */ - sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* move tape */ + + if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* position the tape; if it fails */ + return sim_tape_ioerr (uptr); /* then quit with I/O error status */ + gap_alloc = gap_alloc + rec_size; /* allocate record */ gap_needed = gap_needed - rec_size; /* reduce requirement */ } else { /* record size OK */ @@ -1584,37 +1680,203 @@ gap_alloc = gap_alloc + gap_needed; /* allocate remainder */ gap_needed = 0; } } } -while (gap_needed > 0); +while (gap_needed > 0); /* loop until all of the gap has been allocated */ uptr->pos = gap_pos; /* reposition to gap start */ if (gap_alloc & (meta_size - 1)) { /* gap size "odd?" */ st = sim_tape_wrdata (uptr, MTR_FHGAP); /* write half gap marker */ + if (st != MTSE_OK) { /* write OK? */ uptr->pos = gap_pos; /* restore orig pos */ return st; /* PNU was set by wrdata */ } + uptr->pos = uptr->pos - meta_size / 2; /* realign position */ gap_alloc = gap_alloc - 2; /* decrease gap to write */ } marker_count = gap_alloc / meta_size; /* count of gap markers */ do { st = sim_tape_wrdata (uptr, MTR_GAP); /* write gap markers */ + if (st != MTSE_OK) { /* write OK? */ uptr->pos = gap_pos; /* restore orig pos */ return st; /* PNU was set by wrdata */ } } while (--marker_count > 0); return MTSE_OK; } + +/* Erase a gap in the reverse direction (internal routine). + + An erase gap is written in the reverse direction on the tape unit specified + by "uptr" for the number of bytes specified by "bc". The status of the + operation is returned, and the file position is altered as follows: + + Exit Condition File Position + ------------------ ------------------ + unit unattached unchanged + unsupported format unchanged + write protected unchanged + read error unchanged, PNU set + write error unchanged, PNU set + gap written updated + + If the requested byte count equals the metadatum size, then the routine + succeeds only if it can overlay a single metadatum (i.e., a tape mark or an + existing erase gap marker); otherwise, the file position is not altered, and + MTSE_INVRL (invalid record length) status is returned. + + + Implementation notes: + + 1. Erase gaps are currently supported only in SIMH (MTUF_F_STD) tape format. + + 2. Erasing a record in the reverse direction currently succeeds only if the + gap requested occupies the same space as the record located immediately + before the current file position. This limitation may be lifted in a + future update. + + 3. The "sim_fread" call cannot return 0 in the absence of an error + condition. The preceding "sim_tape_bot" test ensures that "pos" >= 4, so + "sim_fseek" will back up at least that far, so "sim_fread" will read at + least one element. If the call returns zero, an error must have + occurred, so the "ferror" call must succeed. +*/ + +static t_stat tape_erase_rev (UNIT *uptr, t_mtrlnt gap_size) +{ +const uint32 format = MT_GET_FMT (uptr); /* the tape format */ +const uint32 meta_size = sizeof (t_mtrlnt); /* the number of bytes per metadatum */ +t_stat status; +t_mtrlnt rec_size, metadatum; +t_addr gap_pos; +size_t xfer; + +MT_CLR_PNU (uptr); /* clear the position-not-updated flag */ + +if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */ + return MTSE_UNATT; /* then we cannot proceed */ + +else if (sim_tape_wrp (uptr)) /* otherwise if the unit is write protected */ + return MTSE_WRP; /* then we cannot write */ + +else if (gap_size == 0 || format != MTUF_F_STD) /* otherwise if the gap length is zero or unsupported */ + return MTSE_OK; /* then take no action */ + +gap_pos = uptr->pos; /* save the starting position */ + +if (gap_size == meta_size) { /* if the request is for a single metadatum */ + if (sim_tape_bot (uptr)) /* then if the unit is positioned at the BOT */ + return MTSE_BOT; /* then erasing backward is not possible */ + else /* otherwise */ + uptr->pos -= meta_size; /* back up the file pointer */ + + if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* position the tape; if it fails */ + return sim_tape_ioerr (uptr); /* then quit with I/O error status */ + + sim_fread (&metadatum, meta_size, 1, uptr->fileref); /* read a metadatum */ + + if (ferror (uptr->fileref)) /* if a file I/O error occurred */ + return sim_tape_ioerr (uptr); /* then report the error and quit */ + + else if (metadatum == MTR_TMK) /* otherwise if a tape mark is present */ + if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* then reposition the tape; if it fails */ + return sim_tape_ioerr (uptr); /* then quit with I/O error status */ + + else { /* otherwise */ + metadatum = MTR_GAP; /* replace it with an erase gap marker */ + + xfer = sim_fwrite (&metadatum, meta_size, /* write the gap marker */ + 1, uptr->fileref); + + if (ferror (uptr->fileref) || xfer == 0) /* if a file I/O error occurred */ + return sim_tape_ioerr (uptr); /* report the error and quit */ + else /* otherwise the write succeeded */ + status = MTSE_OK; /* so return success */ + } + + else if (metadatum == MTR_GAP) /* otherwise if a gap already exists */ + status = MTSE_OK; /* then take no additional action */ + + else { /* otherwise a data record is present */ + uptr->pos = gap_pos; /* so restore the starting position */ + return MTSE_INVRL; /* and fail with invalid record length status */ + } + } + +else { /* otherwise it's an erase record request */ + status = sim_tape_rdlntr (uptr, &rec_size); /* so get the length of the preceding record */ + + if (status == MTSE_OK /* if the read succeeded */ + && gap_size == rec_size + 2 * meta_size) { /* and the gap will exactly overlay the record */ + gap_pos = uptr->pos; /* then save the gap start position */ + + status = tape_erase_fwd (uptr, gap_size); /* erase the record */ + + if (status == MTSE_OK) /* if the gap write succeeded */ + uptr->pos = gap_pos; /* the reposition back to the start of the gap */ + } + + else { /* otherwise the read failed or is the wrong size */ + uptr->pos = gap_pos; /* so restore the starting position */ + + if (status != MTSE_OK) /* if the record was not found */ + return status; /* then return the failure reason */ + else /* otherwise the record is the wrong size */ + return MTSE_INVRL; /* so report an invalid record length */ + } + } + +return status; /* return the status of the erase operation */ +} + +/* Write an erase gap. + + An erase gap is written in on the tape unit specified by "uptr" for the + length specified by "gap_size" in tenths of an inch, and the status of the + operation is returned. The tape density must have been set via a previous + sim_tape_set_dens call; if it has not, then no action is taken, and + MTSE_IOERR is returned. + + If the requested gap length is zero, or the tape format currently selected + does not support erase gaps, the call succeeds with no action taken. This + allows a device simulator that supports writing erase gaps to use the same + code without worrying about the tape format currently selected by the user. + + Because SIMH tape images do not carry physical parameters (e.g., recording + density), overwriting a tape image file containing a gap is problematic if + the density setting is not the same as that used during recording. There is + no way to establish a gap of a certain length unequivocally in an image file, + so this implementation establishes a gap of a certain number of bytes that + reflect the desired gap length at the tape density in bits per inch used + during writing. +*/ + +t_stat sim_tape_wrgap (UNIT *uptr, uint32 gaplen) +{ +struct tape_context *ctx = (struct tape_context *)uptr->tape_ctx; +const uint32 density = bpi [MT_DENS (uptr->dynflags)]; /* the tape density in bits per inch */ +const uint32 byte_length = (gaplen * density) / 10; /* the size of the requested gap in bytes */ + +if (ctx == NULL) /* if not properly attached? */ + return sim_messagef (SCPE_IERR, "Bad Attach\n"); /* that's a problem */ + +sim_debug (ctx->dbit, ctx->dptr, "sim_tape_wrgap(unit=%d, gaplen=%u)\n", (int)(uptr-ctx->dptr->units), gaplen); + +if (density == 0) /* if the density has not been set */ + return MTSE_IOERR; /* then report an I/O error */ +else /* otherwise */ + return tape_erase_fwd (uptr, byte_length); /* erase the requested gap size in bytes */ +} t_stat sim_tape_wrgap_a (UNIT *uptr, uint32 gaplen, TAPE_PCALLBACK callback) { t_stat r = MTSE_OK; AIO_CALLSETUP @@ -1630,18 +1892,26 @@ bytes specified by "bc", and the status of the operation is returned. The resulting gap will occupy "bc" bytes plus the size of the record length metadata. This function may be used to erase a record of length "n" in place by requesting a gap of length "n". After erasure, the tape will be positioned at the end of the gap. + + If a length of 0 is specified, then the metadatum marker at the current tape + position will be erased. If the tape is not positioned at a metadatum + marker, the routine fails with MTSE_INVRL, and the tape position is + unchanged. */ t_stat sim_tape_errecf (UNIT *uptr, t_mtrlnt bc) { const t_mtrlnt meta_size = sizeof (t_mtrlnt); /* the number of bytes per metadatum */ const t_mtrlnt gap_size = bc + 2 * meta_size; /* the requested gap size in bytes */ -return MTSE_IOERR; /* stub return */ +if (bc == 0) /* if a zero-length erase is requested */ + return tape_erase_fwd (uptr, meta_size); /* then erase a metadatum marker */ +else /* otherwise */ + return tape_erase_fwd (uptr, gap_size); /* erase the requested gap */ } /* Erase a record reverse. An erase gap is written in the reverse direction on the tape unit specified @@ -1649,18 +1919,26 @@ bytes specified by "bc", and the status of the operation is returned. The resulting gap will occupy "bc" bytes plus the size of the record length metadata. This function may be used to erase a record of length "n" in place by requesting a gap of length "n". After erasure, the tape will be positioned at the start of the gap. + + If a length of 0 is specified, then the metadatum marker preceding the + current tape position will be erased. If the tape is not positioned after a + metadatum marker, the routine fails with MTSE_INVRL, and the tape position is + unchanged. */ t_stat sim_tape_errecr (UNIT *uptr, t_mtrlnt bc) { const t_mtrlnt meta_size = sizeof (t_mtrlnt); /* the number of bytes per metadatum */ const t_mtrlnt gap_size = bc + 2 * meta_size; /* the requested gap size in bytes */ -return MTSE_IOERR; /* stub return */ +if (bc == 0) /* if a zero-length erase is requested */ + return tape_erase_rev (uptr, meta_size); /* then erase a metadatum marker */ +else /* otherwise */ + return tape_erase_rev (uptr, gap_size); /* erase the requested gap */ } /* Space record forward Inputs: @@ -1686,11 +1964,11 @@ if (ctx == NULL) /* if not properly attached? */ return sim_messagef (SCPE_IERR, "Bad Attach\n"); /* that's a problem */ sim_debug (ctx->dbit, ctx->dptr, "sim_tape_sprecf(unit=%d)\n", (int)(uptr-ctx->dptr->units)); -st = sim_tape_rdlntf (uptr, bc); /* get record length */ +st = sim_tape_rdrlfwd (uptr, bc); /* get record length */ *bc = MTR_L (*bc); return st; } t_stat sim_tape_sprecf_a (UNIT *uptr, t_mtrlnt *bc, TAPE_PCALLBACK callback) @@ -1781,11 +2059,11 @@ if (MT_TST_PNU (uptr)) { MT_CLR_PNU (uptr); *bc = 0; return MTSE_OK; } -st = sim_tape_rdlntr (uptr, bc); /* get record length */ +st = sim_tape_rdrlrev (uptr, bc); /* get record length */ *bc = MTR_L (*bc); return st; } t_stat sim_tape_sprecr_a (UNIT *uptr, t_mtrlnt *bc, TAPE_PCALLBACK callback) @@ -1879,14 +2157,14 @@ sim_debug (ctx->dbit, ctx->dptr, "sim_tape_spfilebyrecf(unit=%d, count=%d, check_leot=%d)\n", (int)(uptr-ctx->dptr->units), count, check_leot); if (check_leot) { t_mtrlnt rbc; - st = sim_tape_rdlntr (uptr, &rbc); + st = sim_tape_rdrlrev (uptr, &rbc); last_tapemark = (MTSE_TMK == st); if ((st == MTSE_OK) || (st == MTSE_TMK)) - sim_tape_rdlntf (uptr, &rbc); + sim_tape_rdrlfwd (uptr, &rbc); } *skipped = 0; *recsskipped = 0; while (*skipped < count) { /* loopo */ while (1) { @@ -2394,11 +2672,11 @@ /* Set the tape density. Set the density of the specified tape unit either to the value supplied or to the value represented by the supplied character string. - + If "desc" is NULL, then "val" must be set to one of the MT_DENS_* constants in sim_tape.h other than MT_DENS_NONE; the supplied value is used as the tape density, and the character string is ignored. Otherwise, "desc" must point at an int32 value containing a set of allowed densities constructed as a bitwise OR of the appropriate MT_*_VALID values. In this case, the string Index: src/sim_timer.c ================================================================== --- src/sim_timer.c +++ src/sim_timer.c @@ -159,10 +159,11 @@ static uint32 sim_os_sleep_inc_ms = 0; static uint32 sim_os_clock_resoluton_ms = 0; static uint32 sim_os_tick_hz = 0; static uint32 sim_idle_stable = SIM_IDLE_STDFLT; static uint32 sim_idle_calib_pct = 0; +static double sim_timer_stop_time = 0; static uint32 sim_rom_delay = 0; static uint32 sim_throt_ms_start = 0; static uint32 sim_throt_ms_stop = 0; static uint32 sim_throt_type = 0; static uint32 sim_throt_val = 0; @@ -758,15 +759,17 @@ static uint32 sim_idle_cyc_ms = 0; /* Cycles per millisecond while not idling */ UNIT sim_timer_units[SIM_NTIMERS+1]; /* Clock assist units */ /* one for each timer and one for an internal */ /* clock if no clocks are registered. */ +UNIT sim_stop_unit; /* Stop unit */ UNIT sim_internal_timer_unit; /* Internal calibration timer */ UNIT sim_throttle_unit; /* one for throttle */ t_stat sim_throt_svc (UNIT *uptr); t_stat sim_timer_tick_svc (UNIT *uptr); +t_stat sim_timer_stop_svc (UNIT *uptr); #define DBG_IDL TIMER_DBG_IDLE /* idling */ #define DBG_QUE TIMER_DBG_QUEUE /* queue activities */ #define DBG_MUX TIMER_DBG_MUX /* tmxr queue activities */ #define DBG_TRC 0x008 /* tracing */ @@ -773,26 +776,29 @@ #define DBG_CAL 0x010 /* calibration activities */ #define DBG_TIM 0x020 /* timer thread activities */ #define DBG_THR 0x040 /* throttle activities */ #define DBG_ACK 0x080 /* interrupt acknowledgement activities */ #define DBG_CHK 0x100 /* check scheduled activation time*/ +#define DBG_INT 0x200 /* internal timer activities */ DEBTAB sim_timer_debug[] = { {"TRACE", DBG_TRC, "Trace routine calls"}, {"IDLE", DBG_IDL, "Idling activities"}, {"QUEUE", DBG_QUE, "Event queuing activities"}, {"IACK", DBG_ACK, "interrupt acknowledgement activities"}, {"CALIB", DBG_CAL, "Calibration activities"}, {"TIME", DBG_TIM, "Activation and scheduling activities"}, + {"INTER", DBG_INT, "Internal timer activities"}, {"THROT", DBG_THR, "Throttling activities"}, {"MUX", DBG_MUX, "Tmxr scheduling activities"}, {"CHECK", DBG_CHK, "Check scheduled activation time"}, {0} }; /* Forward device declarations */ extern DEVICE sim_timer_dev; extern DEVICE sim_throttle_dev; +extern DEVICE sim_stop_dev; void sim_rtcn_init_all (void) { int32 tmr; @@ -898,22 +904,15 @@ rtc_ticks[tmr] = rtc_ticks[tmr] + 1; /* count ticks */ if (rtc_ticks[tmr] < ticksper) /* 1 sec yet? */ return rtc_currd[tmr]; rtc_ticks[tmr] = 0; /* reset ticks */ rtc_elapsed[tmr] = rtc_elapsed[tmr] + 1; /* count sec */ -if (sim_throt_type != SIM_THROT_NONE) { - rtc_gtime[tmr] = sim_gtime(); /* save instruction time */ - rtc_currd[tmr] = (int32)(sim_throt_cps / ticksper); /* use throttle calibration */ - ++rtc_calibrations[tmr]; /* count calibrations */ - sim_debug (DBG_CAL, &sim_timer_dev, "using throttle calibrated value - result: %d\n", rtc_currd[tmr]); - return rtc_currd[tmr]; - } if (!rtc_avail) /* no timer? */ return rtc_currd[tmr]; if (sim_calb_tmr != tmr) { rtc_currd[tmr] = (int32)(sim_timer_inst_per_sec()/ticksper); - sim_debug (DBG_CAL, &sim_timer_dev, "calibrated calibrated tmr=%d against system tmr=%d, tickper=%d (result: %d)\n", tmr, sim_calb_tmr, ticksper, rtc_currd[tmr]); + sim_debug (DBG_CAL, &sim_timer_dev, "calibrated calibrated tmr=%d against internal system tmr=%d, tickper=%d (result: %d)\n", tmr, sim_calb_tmr, ticksper, rtc_currd[tmr]); return rtc_currd[tmr]; } new_rtime = sim_os_msec (); /* wall time */ ++rtc_calibrations[tmr]; /* count calibrations */ sim_debug (DBG_TRC, &sim_timer_dev, "sim_rtcn_calb(ticksper=%d, tmr=%d)\n", ticksper, tmr); @@ -1027,10 +1026,11 @@ for (tmr=0; tmr<=SIM_NTIMERS; tmr++) { sim_timer_units[tmr].action = &sim_timer_tick_svc; sim_timer_units[tmr].flags = UNIT_DIS | UNIT_IDLE; sim_clock_cosched_queue[tmr] = QUEUE_LIST_END; } +sim_stop_unit.action = &sim_timer_stop_svc; SIM_INTERNAL_UNIT.flags = UNIT_IDLE; sim_register_internal_device (&sim_timer_dev); /* Register Clock Assist device */ sim_throttle_unit.action = &sim_throt_svc; sim_register_clock_unit_tmr (&SIM_INTERNAL_UNIT, SIM_INTERNAL_CLK); sim_idle_enab = FALSE; /* init idle off */ @@ -1301,10 +1301,30 @@ if (newpct == 0) return SCPE_ARG; sim_idle_calib_pct = (uint32)newpct; return SCPE_OK; } + +/* Set stop time */ + +t_stat sim_timer_set_stop (int32 flag, CONST char *cptr) +{ +t_stat r; +t_value stop_time; + +if (cptr == NULL) + return SCPE_ARG; +stop_time = get_uint (cptr, 10, T_VALUE_MAX, &r); +if (r != SCPE_OK) + return r; +if (stop_time <= (t_value)sim_gtime()) + return SCPE_ARG; +sim_register_internal_device (&sim_stop_dev); /* Register Stop Device */ +sim_timer_stop_time = (double)stop_time; +sim_activate_abs (&sim_stop_unit, (int32)(sim_timer_stop_time - sim_gtime())); +return SCPE_OK; +} /* Set/Clear asynch */ t_stat sim_timer_set_async (int32 flag, CONST char *cptr) { @@ -1329,10 +1349,11 @@ { "NOASYNCH", &sim_timer_set_async, 0 }, #endif { "CATCHUP", &sim_timer_set_catchup, 1 }, { "NOCATCHUP", &sim_timer_set_catchup, 0 }, { "CALIB", &sim_timer_set_idle_pct, 0 }, + { "STOP", &sim_timer_set_stop, 0 }, { NULL, NULL, 0 } }; MTAB sim_timer_mod[] = { { 0 }, @@ -1347,10 +1368,15 @@ static const char *sim_int_timer_description (DEVICE *dptr) { return "Internal Timer"; } + +static const char *sim_int_stop_description (DEVICE *dptr) +{ +return "Stop facility"; +} static const char *sim_throttle_description (DEVICE *dptr) { return "Throttle facility"; } @@ -1357,21 +1383,32 @@ DEVICE sim_timer_dev = { "INT-CLOCK", sim_timer_units, sim_timer_reg, sim_timer_mod, SIM_NTIMERS+1, 0, 0, 0, 0, 0, NULL, NULL, &sim_timer_clock_reset, NULL, NULL, NULL, - NULL, DEV_DEBUG | DEV_NOSAVE, 0, sim_timer_debug}; + NULL, DEV_DEBUG | DEV_NOSAVE, 0, + sim_timer_debug}; DEVICE sim_int_timer_dev = { "INT-TIMER", &sim_internal_timer_unit, NULL, NULL, 1, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, DEV_NOSAVE}; +DEVICE sim_stop_dev = { + "INT-STOP", &sim_stop_unit, NULL, NULL, + 1, 0, 0, 0, 0, 0, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, DEV_NOSAVE, 0, + NULL, NULL, NULL, NULL, NULL, NULL, + sim_int_stop_description}; + DEVICE sim_throttle_dev = { - "INT-THROTTLE", &sim_throttle_unit, sim_throttle_reg, NULL, 1}; - + "INT-THROTTLE", &sim_throttle_unit, sim_throttle_reg, NULL, + 1, 0, 0, 0, 0, 0, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, DEV_NOSAVE}; /* SET CLOCK command */ t_stat sim_set_timers (int32 arg, CONST char *cptr) { @@ -1600,14 +1637,19 @@ else { sim_throt_sleep_time = sim_idle_rate_ms; sim_throt_val = (uint32) (val * (1 + (sim_idle_rate_ms / val2))); } } + sim_throt_state = SIM_THROT_STATE_THROTTLE; /* force state */ + sim_throt_wait = sim_throt_val; } } sim_register_internal_device (&sim_throttle_dev); /* Register Throttle Device */ -sim_throt_cps = SIM_INITIAL_IPS; /* Initial value while correct one is determined */ +if (sim_throt_type == SIM_THROT_SPC) /* Set initial value while correct one is determined */ + sim_throt_cps = (int32)((1000.0 * sim_throt_val) / (double)sim_throt_sleep_time); +else + sim_throt_cps = SIM_INITIAL_IPS; return SCPE_OK; } t_stat sim_show_throt (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, CONST char *cptr) { @@ -1633,11 +1675,12 @@ if (sim_throt_wait) fprintf (st, "Throttling by sleeping for: %d ms every %d cycles\n", sim_throt_sleep_time, sim_throt_wait); break; case SIM_THROT_SPC: - fprintf (st, "Throttle: sleep %d ms every %d cycles\n", sim_throt_sleep_time, sim_throt_val); + fprintf (st, "Throttle: %d/%d\n", sim_throt_val, sim_throt_sleep_time); + fprintf (st, "Throttling by sleeping for: %d ms every %d cycles\n", sim_throt_sleep_time, sim_throt_val); break; default: fprintf (st, "Throttling: Disabled\n"); break; @@ -1675,22 +1718,25 @@ { int32 tmr; uint32 delta_ms; double a_cps, d_cps; -if (sim_throt_type == SIM_THROT_SPC) { /* Non dynamic? */ - sim_throt_state = SIM_THROT_STATE_THROTTLE; /* force state */ - sim_throt_wait = sim_throt_val; - } switch (sim_throt_state) { case SIM_THROT_STATE_INIT: /* take initial reading */ - sim_idle_ms_sleep (sim_idle_rate_ms); /* start on a tick boundart to calibrate */ + sim_idle_ms_sleep (sim_idle_rate_ms); /* start on a tick boundary to calibrate */ sim_throt_ms_start = sim_os_msec (); sim_throt_inst_start = sim_gtime(); - sim_throt_wait = SIM_THROT_WST; - sim_throt_state = SIM_THROT_STATE_TIME; /* next state */ + if (sim_throt_type != SIM_THROT_SPC) { /* dynamic? */ + sim_throt_wait = SIM_THROT_WST; + sim_throt_state = SIM_THROT_STATE_TIME; /* next state */ + } + else { /* Non dynamic? */ + sim_throt_wait = sim_throt_val; + sim_throt_state = SIM_THROT_STATE_THROTTLE; /* force state */ + sim_throt_cps = (int32)((1000.0 * sim_throt_val) / (double)sim_throt_sleep_time); + } sim_debug (DBG_THR, &sim_timer_dev, "sim_throt_svc(INIT) Starting. Values wait = %d\n", sim_throt_wait); break; /* reschedule */ case SIM_THROT_STATE_TIME: /* take final reading */ sim_throt_ms_stop = sim_os_msec (); @@ -1748,14 +1794,15 @@ break; case SIM_THROT_STATE_THROTTLE: /* throttling */ sim_idle_ms_sleep (sim_throt_sleep_time); delta_ms = sim_os_msec () - sim_throt_ms_start; - if (sim_throt_type != SIM_THROT_SPC) { /* when not dynamic throttling */ - if (delta_ms >= 10000) { /* recompute every 10 sec */ - double delta_insts = sim_gtime() - sim_throt_inst_start; - a_cps = (delta_insts * 1000.0) / (double) delta_ms; + if (delta_ms >= 10000) { /* recompute every 10 sec */ + double delta_insts = sim_gtime() - sim_throt_inst_start; + + a_cps = (delta_insts * 1000.0) / (double) delta_ms; + if (sim_throt_type != SIM_THROT_SPC) { /* when not dynamic throttling */ if (sim_throt_type == SIM_THROT_MCYC) /* calc desired cps */ d_cps = (double) sim_throt_val * 1000000.0; else if (sim_throt_type == SIM_THROT_KCYC) d_cps = (double) sim_throt_val * 1000.0; else d_cps = (a_cps * ((double) sim_throt_val)) / 100.0; @@ -1763,16 +1810,19 @@ sim_throt_wait = sim_throt_val; sim_throt_state = SIM_THROT_STATE_TIME;/* next state to recalibrate */ sim_debug (DBG_THR, &sim_timer_dev, "sim_throt_svc() Recalibrating throttle based on values a_cps = %f, d_cps = %f\n", a_cps, d_cps); } - sim_throt_ms_start = sim_os_msec (); sim_throt_inst_start = sim_gtime(); } + else { /* record instruction rate */ + sim_throt_cps = (int32)a_cps; + sim_debug (DBG_THR, &sim_timer_dev, "sim_throt_svc() Recalibrating Special %d/%u Cycles Per Second of %f\n", + sim_throt_wait, sim_throt_sleep_time, sim_throt_cps); + } + sim_throt_ms_start = sim_os_msec (); } - else /* record instruction rate */ - sim_throt_cps = (int32)((1000.0 * sim_throt_val) / (double)delta_ms); break; } sim_activate (uptr, sim_throt_wait); /* reschedule */ return SCPE_OK; @@ -1802,10 +1852,13 @@ if (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END) sim_clock_cosched_queue[tmr]->time = sim_cosched_interval[tmr]; if ((stat == SCPE_OK) && (sim_cosched_interval[tmr] <= 0) && (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END)) { + UNIT *sptr = sim_clock_cosched_queue[tmr]; + UNIT *cptr = QUEUE_LIST_END; + if (rtc_clock_catchup_eligible[tmr]) { /* calibration started? */ struct timespec now; double skew; clock_gettime(CLOCK_REALTIME, &now); @@ -1812,37 +1865,48 @@ skew = (_timespec_to_double(&now) - (rtc_calib_tick_time[tmr]+rtc_clock_catchup_base_time[tmr])); if (fabs(skew) > fabs(rtc_clock_skew_max[tmr])) rtc_clock_skew_max[tmr] = skew; } + /* First gather the queued events that are scheduled for now */ do { - UNIT *cptr = sim_clock_cosched_queue[tmr]; + cptr = sim_clock_cosched_queue[tmr]; sim_clock_cosched_queue[tmr] = cptr->next; - cptr->next = NULL; - cptr->cancel = NULL; - cptr->time = 0; if (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END) { sim_clock_cosched_queue[tmr]->time += sim_cosched_interval[tmr]; sim_cosched_interval[tmr] = sim_clock_cosched_queue[tmr]->time; } else sim_cosched_interval[tmr] = 0; + } while ((sim_cosched_interval[tmr] <= 0) && + (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END)); + if (cptr != QUEUE_LIST_END) + cptr->next = QUEUE_LIST_END; + /* Now dispatch that list (in order). */ + while (sptr != QUEUE_LIST_END) { + cptr = sptr; + sptr = sptr->next; + cptr->next = NULL; + cptr->cancel = NULL; cptr->time = 0; - sim_debug (DBG_QUE, &sim_timer_dev, "sim_timer_tick_svc(tmr=%d) - coactivating %s", tmr, sim_uname (cptr)); if (cptr->usecs_remaining) { - sim_debug (DBG_QUE, &sim_timer_dev, " remnant: %.0f - next %s after cosched interval: %d ticks\n", cptr->usecs_remaining, (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END) ? sim_uname (sim_clock_cosched_queue[tmr]) : "", sim_cosched_interval[tmr]); + sim_debug (DBG_QUE, &sim_timer_dev, " remnant: %.0f - next %s after cosched interval: %d ticks\n", cptr->usecs_remaining, (sptr != QUEUE_LIST_END) ? sim_uname (sptr) : "", sim_cosched_interval[tmr]); sim_timer_activate_after (cptr, cptr->usecs_remaining); } else { - sim_debug (DBG_QUE, &sim_timer_dev, " - next %s after cosched interval: %d ticks\n", (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END) ? sim_uname (sim_clock_cosched_queue[tmr]) : "", sim_cosched_interval[tmr]); + sim_debug (DBG_QUE, &sim_timer_dev, " - next %s after cosched interval: %d ticks\n", (sptr != QUEUE_LIST_END) ? sim_uname (sptr) : "", sim_cosched_interval[tmr]); _sim_activate (cptr, 0); } - } while ((sim_cosched_interval[tmr] <= 0) && - (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END)); + } } return stat; } + +t_stat sim_timer_stop_svc (UNIT *uptr) +{ +return SCPE_STOP; +} void sim_rtcn_get_time (struct timespec *now, int tmr) { sim_debug (DBG_CAL, &sim_timer_dev, "sim_rtcn_get_time(tmr=%d)\n", tmr); clock_gettime (CLOCK_REALTIME, now); @@ -2069,10 +2133,11 @@ #define CLK_INIT (SIM_INITIAL_IPS/CLK_TPS) static int32 sim_int_clk_tps; static t_stat sim_timer_clock_tick_svc (UNIT *uptr) { +sim_debug(DBG_INT, &sim_timer_dev, "sim_timer_clock_tick_svc()\n"); sim_rtcn_calb (sim_int_clk_tps, SIM_INTERNAL_CLK); sim_activate_after (uptr, 1000000/sim_int_clk_tps); /* reactivate unit */ return SCPE_OK; } @@ -2122,11 +2187,11 @@ sim_cancel (sim_clock_unit[sim_calb_tmr]); sim_cancel (&sim_timer_units[sim_calb_tmr]); } /* Start the internal timer */ sim_calb_tmr = SIM_NTIMERS; - sim_debug (DBG_CAL, &sim_timer_dev, "_rtcn_configure_calibrated_clock(newtmr=%d) - Starting Internal Calibrated Timer at %dHz\n", newtmr, sim_int_clk_tps); + sim_debug (DBG_CAL|DBG_INT, &sim_timer_dev, "_rtcn_configure_calibrated_clock(newtmr=%d) - Starting Internal Calibrated Timer at %dHz\n", newtmr, sim_int_clk_tps); SIM_INTERNAL_UNIT.action = &sim_timer_clock_tick_svc; SIM_INTERNAL_UNIT.flags = UNIT_IDLE; sim_register_internal_device (&sim_int_timer_dev); /* Register Internal timer device */ sim_rtcn_init_unit (&SIM_INTERNAL_UNIT, (CLK_INIT*CLK_TPS)/sim_int_clk_tps, SIM_INTERNAL_CLK); SIM_INTERNAL_UNIT.action (&SIM_INTERNAL_UNIT); /* Force tick to activate timer */ @@ -2135,11 +2200,11 @@ } if ((tmr == newtmr) && (sim_calb_tmr == newtmr)) /* already set? */ return; if (sim_calb_tmr == SIM_NTIMERS) { /* was old the internal timer? */ - sim_debug (DBG_CAL, &sim_timer_dev, "_rtcn_configure_calibrated_clock(newtmr=%d) - Stopping Internal Calibrated Timer, New Timer = %d (%dHz)\n", newtmr, tmr, rtc_hz[tmr]); + sim_debug (DBG_CAL|DBG_INT, &sim_timer_dev, "_rtcn_configure_calibrated_clock(newtmr=%d) - Stopping Internal Calibrated Timer, New Timer = %d (%dHz)\n", newtmr, tmr, rtc_hz[tmr]); rtc_initd[SIM_NTIMERS] = 0; rtc_hz[SIM_NTIMERS] = 0; sim_register_clock_unit_tmr (NULL, SIM_INTERNAL_CLK); sim_cancel (&SIM_INTERNAL_UNIT); sim_cancel (&sim_timer_units[SIM_NTIMERS]); @@ -2161,11 +2226,11 @@ _sim_activate (uptr, 1); uptr->usecs_remaining = usecs_remaining; } rtc_hz[sim_calb_tmr] = 0; /* back to 0 */ } - sim_debug (DBG_CAL, &sim_timer_dev, "_rtcn_configure_calibrated_clock(newtmr=%d) - Changing Calibrated Timer from %d (%dHz) to %d (%dHz)\n", newtmr, sim_calb_tmr, rtc_hz[sim_calb_tmr], tmr, rtc_hz[tmr]); + sim_debug (DBG_CAL|DBG_INT, &sim_timer_dev, "_rtcn_configure_calibrated_clock(newtmr=%d) - Changing Calibrated Timer from %d (%dHz) to %d (%dHz)\n", newtmr, sim_calb_tmr, rtc_hz[sim_calb_tmr], tmr, rtc_hz[tmr]); sim_calb_tmr = tmr; } sim_calb_tmr = tmr; } @@ -2174,10 +2239,11 @@ sim_debug (DBG_TRC, &sim_timer_dev, "sim_timer_clock_reset()\n"); _rtcn_configure_calibrated_clock (sim_calb_tmr); sim_timer_dev.description = &sim_timer_description; sim_throttle_dev.description = &sim_throttle_description; sim_int_timer_dev.description = &sim_int_timer_description; +sim_stop_dev.description = &sim_int_stop_description; if (sim_switches & SWMASK ('P')) { sim_cancel (&SIM_INTERNAL_UNIT); sim_calb_tmr = -1; } return SCPE_OK; @@ -2185,10 +2251,12 @@ void sim_start_timer_services (void) { sim_debug (DBG_TRC, &sim_timer_dev, "sim_start_timer_services()\n"); _rtcn_configure_calibrated_clock (sim_calb_tmr); +if (sim_timer_stop_time > sim_gtime()) + sim_activate_abs (&sim_stop_unit, (int32)(sim_timer_stop_time - sim_gtime())); #if defined(SIM_ASYNCH_CLOCKS) pthread_mutex_lock (&sim_timer_lock); if (sim_asynch_timer) { pthread_attr_t attr; @@ -2219,12 +2287,12 @@ int32 clock_time = _sim_activate_time (&sim_timer_units[tmr]); if (clock_time < 0) clock_time = 0; /* Stop clock assist unit and make sure the clock unit has a tick queued */ - sim_cancel (&sim_timer_units[tmr]); - if (rtc_hz[tmr]) { + if (sim_is_active (&sim_timer_units[tmr])) { + sim_cancel (&sim_timer_units[tmr]); sim_debug (DBG_QUE, &sim_timer_dev, "sim_stop_timer_services() - tmr=%d scheduling %s after %d\n", tmr, sim_uname (sim_clock_unit[tmr]), clock_time); _sim_activate (sim_clock_unit[tmr], clock_time); } /* Move coscheduled units to the standard event queue */ accum = 0; @@ -2355,11 +2423,11 @@ int32 ticks_til_calib = rtc_hz[sim_calb_tmr] - rtc_ticks[sim_calb_tmr]; int32 inst_til_calib = inst_til_tick + ((ticks_til_calib - 1) * rtc_currd[sim_calb_tmr]); uint32 usecs_til_calib = (uint32)ceil(inst_til_calib / inst_per_usec); if (uptr != &sim_timer_units[sim_calb_tmr]) { /* Not scheduling calibrated timer? */ - if (inst_delay_d >= (double)inst_til_calib) { /* long wait? */ + if (inst_delay_d > (double)inst_til_calib) { /* long wait? */ stat = sim_clock_coschedule_tmr (uptr, sim_calb_tmr, ticks_til_calib - 1); uptr->usecs_remaining = (stat == SCPE_OK) ? usec_delay - usecs_til_calib : 0.0; sim_debug (DBG_TIM, &sim_timer_dev, "sim_timer_activate_after(%s, %.0f usecs) - coscheduling with with calibrated timer(%d), ticks=%d, usecs_remaining=%.0f usecs, inst_til_tick=%d\n", sim_uname(uptr), usec_delay, sim_calb_tmr, ticks_til_calib, uptr->usecs_remaining, inst_til_tick); sim_debug (DBG_CHK, &sim_timer_dev, "sim_timer_activate_after(%s, %.0f usecs) - result = %.0f usecs, %.0f usecs\n", Index: src/sim_tmxr.c ================================================================== --- src/sim_tmxr.c +++ src/sim_tmxr.c @@ -462,11 +462,11 @@ lp->xmte = 1; /* enable transmit */ lp->dstb = 0; /* default bin mode */ lp->rxbpr = lp->rxbpi = lp->rxcnt = lp->rxpcnt = 0; /* init receive indexes */ if (!lp->txbfd || lp->notelnet) /* if not buffered telnet */ lp->txbpr = lp->txbpi = lp->txcnt = lp->txpcnt = 0; /* init transmit indexes */ -lp->txdrp = 0; +lp->txdrp = lp->txstall = 0; tmxr_set_get_modem_bits (lp, 0, 0, NULL); if ((!lp->mp->buffered) && (!lp->txbfd)) { lp->txbfd = 0; lp->txbsz = TMXR_MAXBUF; lp->txb = (char *)realloc (lp->txb, lp->txbsz); @@ -530,11 +530,11 @@ } if (!mp->buffered) { lp->txbpi = 0; /* init buf pointers */ lp->txbpr = (int32)(lp->txbsz - strlen (msgbuf)); - lp->rxcnt = lp->txcnt = lp->txdrp = 0; /* init counters */ + lp->rxcnt = lp->txcnt = lp->txdrp = lp->txstall = 0;/* init counters */ lp->rxpcnt = lp->txpcnt = 0; } else if (lp->txcnt > lp->txbsz) lp->txbpr = (lp->txbpi + 1) % lp->txbsz; @@ -541,15 +541,24 @@ else lp->txbpr = (int32)(lp->txbsz - strlen (msgbuf)); psave = lp->txbpi; /* save insertion pointer */ lp->txbpi = lp->txbpr; /* insert connection message */ +if ((lp->serport) && (!sim_is_running)) { + sim_os_ms_sleep (TMXR_DTR_DROP_TIME); /* Wait for DTR to be noticed */ + lp->ser_connect_pending = FALSE; /* Mark line as ready for action */ + lp->conn = TRUE; + } tmxr_linemsg (lp, msgbuf); /* beginning of buffer */ lp->txbpi = psave; /* restore insertion pointer */ unwritten = tmxr_send_buffered_data (lp); /* send the message */ +if ((lp->serport) && (!sim_is_running)) { + lp->ser_connect_pending = TRUE; /* Mark line as not yet ready for action */ + lp->conn = FALSE; + } if (unwritten == 0) /* buffer now empty? */ lp->xmte = 1; /* reenable transmission if paused */ lp->txcnt -= (int32)strlen (msgbuf); /* adjust statistics */ return; @@ -695,15 +704,15 @@ if (lp->loopback) return loop_write (lp, &(lp->txb[i]), length); if (lp->serport) { /* serial port connection? */ - if (sim_gtime () < lp->txnexttime) + if ((sim_gtime () < lp->txnexttime) && (sim_is_running)) return 0; written = sim_write_serial (lp->serport, &(lp->txb[i]), length); - if (written > 0) - lp->txnexttime = floor (sim_gtime () + (lp->txdelta * sim_timer_inst_per_sec ())); + if ((written > 0) && (sim_is_running)) + lp->txnexttime = floor (sim_gtime () + (written * lp->txdelta * sim_timer_inst_per_sec ())); return written; } else { /* Telnet connection */ written = sim_write_sock (lp->sock, &(lp->txb[i]), length); @@ -2054,13 +2063,18 @@ sim_oline = NULL; /* save output socket */ fputc (chr, lp->txlog); /* log to actual file */ sim_oline = save_oline; /* resture output socket */ } sim_exp_check (&lp->expect, chr); /* process expect rules as needed */ + if ((sim_interval > 0) && /* not called within sim_process_event? */ + (lp->txbps) && (lp->txdelta > 1000)) { /* and rate limiting output slower than 1000 cps */ + tmxr_send_buffered_data (lp); /* put data on wire */ + sim_os_ms_sleep((lp->txdelta - 1000) / 1000); /* wait an approximate character delay */ + } return SCPE_OK; /* char sent */ } -++lp->txdrp; lp->xmte = 0; /* no room, dsbl line */ +++lp->txstall; lp->xmte = 0; /* no room, dsbl line */ return SCPE_STALL; /* char not sent */ } /* Store packet in line buffer @@ -2387,11 +2401,10 @@ SERHANDLE serport; CONST char *tptr = cptr; t_bool nolog, notelnet, listennotelnet, modem_control, loopback, datagram, packet; TMLN *lp; t_stat r = SCPE_OK; -t_bool not_quiet = (!sim_quiet) && (0 == (sim_switches & SWMASK ('Q'))); if (*tptr == '\0') return SCPE_ARG; for (i = 0; i < mp->lines; i++) { /* initialize lines */ lp = mp->ldsc + i; @@ -2441,11 +2454,11 @@ break; } if (0 == MATCH_CMD (gbuf, "LOG")) { if ((NULL == cptr) || ('\0' == *cptr)) return sim_messagef (SCPE_2FARG, "Missing Log Specifier\n"); - strncpy(logfiletmpl, cptr, sizeof(logfiletmpl)-1); + strlcpy(logfiletmpl, cptr, sizeof(logfiletmpl)); continue; } if (0 == MATCH_CMD (gbuf, "LOOPBACK")) { if ((NULL != cptr) && ('\0' != *cptr)) return sim_messagef (SCPE_2MARG, "Unexpected Loopback Specifier: %s\n", cptr); @@ -2507,18 +2520,18 @@ continue; } if (0 == MATCH_CMD (gbuf, "CONNECT")) { if ((NULL == cptr) || ('\0' == *cptr)) return sim_messagef (SCPE_2FARG, "Missing Connect Specifier\n"); - strncpy (destination, cptr, sizeof(destination)-1); + strlcpy (destination, cptr, sizeof(destination)); continue; } if (0 == MATCH_CMD (gbuf, "SPEED")) { if ((NULL == cptr) || ('\0' == *cptr) || (_tmln_speed_delta (cptr) < 0)) return sim_messagef (SCPE_ARG, "Invalid Speed Specifier: %s\n", (cptr ? cptr : "")); - strncpy (speed, cptr, sizeof(speed)-1); + strlcpy (speed, cptr, sizeof(speed)); continue; } cptr = get_glyph (gbuf, port, ';'); if (sim_parse_addr (port, NULL, 0, NULL, NULL, 0, NULL, NULL)) return sim_messagef (SCPE_ARG, "Invalid Port Specifier: %s\n", port); @@ -2565,11 +2578,11 @@ } else { char *eptr; memset (hostport, '\0', sizeof(hostport)); - strncpy (hostport, destination, sizeof(hostport)-1); + strlcpy (hostport, destination, sizeof(hostport)); if ((eptr = strchr (hostport, ';'))) *(eptr++) = '\0'; if (eptr) { get_glyph (eptr, eptr, 0); /* upcase this string */ if (0 == MATCH_CMD (eptr, "NOTELNET")) @@ -2592,20 +2605,21 @@ } if (line == -1) { if (modem_control != mp->modem_control) return SCPE_ARG; if (logfiletmpl[0]) { - strncpy(mp->logfiletmpl, logfiletmpl, sizeof(mp->logfiletmpl)-1); + strlcpy(mp->logfiletmpl, logfiletmpl, sizeof(mp->logfiletmpl)); for (i = 0; i < mp->lines; i++) { lp = mp->ldsc + i; sim_close_logfile (&lp->txlogref); lp->txlog = NULL; lp->txlogname = (char *)realloc(lp->txlogname, CBUFSIZE); + lp->txlogname[CBUFSIZE-1] = '\0'; if (mp->lines > 1) - sprintf(lp->txlogname, "%s_%d", mp->logfiletmpl, i); + snprintf(lp->txlogname, CBUFSIZE-1, "%s_%d", mp->logfiletmpl, i); else - strcpy (lp->txlogname, mp->logfiletmpl); + strlcpy (lp->txlogname, mp->logfiletmpl, CBUFSIZE); r = sim_open_logfile (lp->txlogname, TRUE, &lp->txlog, &lp->txlogref); if (r != SCPE_OK) { free (lp->txlogname); lp->txlogname = NULL; break; @@ -2652,12 +2666,11 @@ sim_close_sock (mp->master); mp->master = 0; free (mp->port); mp->port = NULL; } - if (not_quiet) - sim_printf ("Listening on port %s\n", listen); + sim_messagef (SCPE_OK, "Listening on port %s\n", listen); mp->port = (char *)realloc (mp->port, 1 + strlen (listen)); strcpy (mp->port, listen); /* save port */ mp->master = sock; /* save master socket */ mp->ring_sock = INVALID_SOCKET; free (mp->ring_ipad); @@ -2686,12 +2699,11 @@ } } if (loopback) { if (mp->lines > 1) return sim_messagef (SCPE_ARG, "Ambiguous Loopback specification\n"); - if (not_quiet) - sim_printf ("Operating in loopback mode\n"); + sim_messagef (SCPE_OK, "Operating in loopback mode\n"); for (i = 0; i < mp->lines; i++) { lp = mp->ldsc + i; tmxr_set_line_loopback (lp, loopback); if (speed[0]) tmxr_set_line_speed (lp, speed); @@ -2718,14 +2730,12 @@ lp->notelnet = TRUE; tmxr_init_line (lp); /* init the line state */ if (!lp->mp->modem_control) /* raise DTR and RTS for non modem control lines */ sim_control_serial (lp->serport, TMXR_MDM_DTR|TMXR_MDM_RTS, 0, NULL); lp->cnms = sim_os_msec (); /* record time of connection */ - if (sim_switches & SWMASK ('V')) { /* -V flag reports connection on port */ - sim_os_ms_sleep (TMXR_DTR_DROP_TIME); + if (sim_switches & SWMASK ('V')) /* -V flag reports connection on port */ tmxr_report_connection (mp, lp); /* report the connection to the line */ - } } else { lp->datagram = datagram; if (datagram) { if (listen[0]) { @@ -2805,12 +2815,11 @@ if (r) return sim_messagef (SCPE_ARG, "Invalid Listen Specification: %s\n", listen); if (sock == INVALID_SOCKET) /* open error */ return sim_messagef (SCPE_OPENERR, "Can't listen on port: %s\n", listen); _mux_detach_line (lp, TRUE, FALSE); - if (not_quiet) - sim_printf ("Line %d Listening on port %s\n", line, listen); + sim_messagef (SCPE_OK, "Line %d Listening on port %s\n", line, listen); lp->port = (char *)realloc (lp->port, 1 + strlen (listen)); strcpy (lp->port, listen); /* save port */ lp->master = sock; /* save master socket */ if (listennotelnet != mp->notelnet) lp->notelnet = listennotelnet; @@ -2828,14 +2837,12 @@ lp->notelnet = TRUE; tmxr_init_line (lp); /* init the line state */ if (!lp->mp->modem_control) /* raise DTR and RTS for non modem control lines */ sim_control_serial (lp->serport, TMXR_MDM_DTR|TMXR_MDM_RTS, 0, NULL); lp->cnms = sim_os_msec (); /* record time of connection */ - if (sim_switches & SWMASK ('V')) { /* -V flag reports connection on port */ - sim_os_ms_sleep (TMXR_DTR_DROP_TIME); + if (sim_switches & SWMASK ('V')) /* -V flag reports connection on port */ tmxr_report_connection (mp, lp); /* report the connection to the line */ - } } else { lp->datagram = datagram; if (datagram) { if (listen[0]) { @@ -2864,12 +2871,11 @@ return sim_messagef (SCPE_ARG, "Can't open %s socket on %s%s%s\n", datagram ? "Datagram" : "Stream", datagram ? listen : "", datagram ? "<->" : "", hostport); } } if (loopback) { tmxr_set_line_loopback (lp, loopback); - if (not_quiet) - sim_printf ("Line %d operating in loopback mode\n", line); + sim_messagef (SCPE_OK, "Line %d operating in loopback mode\n", line); } lp->modem_control = modem_control; if (speed[0] && (!datagram) && (!lp->serport)) tmxr_set_line_speed (lp, speed); r = SCPE_OK; @@ -3535,12 +3541,11 @@ } if (!found) { tmxr_open_devices = (TMXR **)realloc(tmxr_open_devices, (tmxr_open_device_count+1)*sizeof(*tmxr_open_devices)); tmxr_open_devices[tmxr_open_device_count++] = mux; for (i=0; ilines; i++) - if (0 == mux->ldsc[i].send.delay) - mux->ldsc[i].send.delay = SEND_DEFAULT_DELAY; + mux->ldsc[i].send.after = mux->ldsc[i].send.delay = 0; } #if defined(SIM_ASYNCH_MUX) pthread_mutex_unlock (&sim_tmxr_poll_lock); if ((tmxr_open_device_count == 1) && (sim_asynch_enabled)) tmxr_start_poll (); @@ -3604,10 +3609,45 @@ t_stat tmxr_locate_line_expect (const char *cptr, EXPECT **exp) { return _tmxr_locate_line_send_expect (cptr, NULL, exp); } + +static const char *_tmxr_send_expect_line_name (const SEND *snd, const EXPECT *exp) +{ +static char line_name[CBUFSIZE]; +int i, j; + +strcpy (line_name, ""); +for (i=0; ilines; ++j) + if ((snd == &tmxr_open_devices[i]->ldsc[j].send) || + (exp == &tmxr_open_devices[i]->ldsc[j].expect)) { + if (tmxr_open_devices[i]->lines > 1) + snprintf (line_name, sizeof (line_name), "%s:%d", tmxr_open_devices[i]->ldsc[j].send.dptr->name, j); + else + strlcpy (line_name, tmxr_open_devices[i]->ldsc[j].send.dptr->name, sizeof (line_name)); + break; + } +return line_name; +} + +const char *tmxr_send_line_name (const SEND *snd) +{ +if (snd == sim_cons_get_send ()) + return "CONSOLE"; +else + return _tmxr_send_expect_line_name (snd, NULL); +} + +const char *tmxr_expect_line_name (const EXPECT *exp) +{ +if (exp == sim_cons_get_expect ()) + return "CONSOLE"; +else + return _tmxr_send_expect_line_name (NULL, exp); +} t_stat tmxr_change_async (void) { #if defined(SIM_ASYNCH_IO) if (sim_asynch_enabled) @@ -3624,10 +3664,13 @@ t_stat tmxr_attach_ex (TMXR *mp, UNIT *uptr, CONST char *cptr, t_bool async) { t_stat r; int32 i; +if (mp->dptr == NULL) /* has device been set? */ + mp->dptr = find_dev_from_unit (uptr); /* no, so set device now */ + r = tmxr_open_master (mp, cptr); /* open master socket */ if (r != SCPE_OK) /* error? */ return r; mp->uptr = uptr; /* save unit for polling */ uptr->filename = tmxr_mux_attach_string (uptr->filename, mp);/* save */ @@ -3644,13 +3687,10 @@ uptr->dynflags |= TMUF_NOASYNCH; /* tag as no asynch */ #else uptr->dynflags |= TMUF_NOASYNCH; /* tag as no asynch */ #endif -if (mp->dptr == NULL) /* has device been set? */ - mp->dptr = find_dev_from_unit (uptr); /* no, so set device now */ - if (mp->dptr) { for (i=0; ilines; i++) { mp->ldsc[i].expect.dptr = mp->dptr; mp->ldsc[i].expect.dbit = TMXR_DBG_EXP; mp->ldsc[i].send.dptr = mp->dptr; @@ -4363,10 +4403,12 @@ if (lp->txcnt || lp->txbpi) fprintf (st, " bytes in buffer = %d\n", ((lp->txcnt > 0) && (lp->txcnt > lp->txbsz)) ? lp->txbsz : lp->txbpi); if (lp->txdrp) fprintf (st, " dropped = %d\n", lp->txdrp); +if (lp->txstall) + fprintf (st, " stalled = %d\n", lp->txstall); return; } /* Disconnect a line. @@ -4412,11 +4454,13 @@ return status; /* report it */ if ((lp->sock) || (lp->serport)) { /* connection active? */ if (!lp->notelnet) tmxr_linemsg (lp, "\r\nOperator disconnected line\r\n\n");/* report closure */ - tmxr_reset_ln_ex (lp, (sim_switches & SWMASK ('C'))); /* drop the line */ + if (lp->serport && (sim_switches & SWMASK ('C'))) + return tmxr_detach_ln (lp); + return tmxr_reset_ln_ex (lp, FALSE); /* drop the line */ } return SCPE_OK; } @@ -4436,11 +4480,11 @@ if (lp->txlog) /* close existing log */ tmxr_set_nolog (NULL, val, NULL, desc); lp->txlogname = (char *) calloc (CBUFSIZE, sizeof (char)); /* alloc namebuf */ if (lp->txlogname == NULL) /* can't? */ return SCPE_MEM; -strncpy (lp->txlogname, cptr, CBUFSIZE); /* save file name */ +strlcpy (lp->txlogname, cptr, CBUFSIZE); /* save file name */ sim_open_logfile (cptr, TRUE, &lp->txlog, &lp->txlogref);/* open log */ if (lp->txlog == NULL) { /* error? */ free (lp->txlogname); /* free buffer */ return SCPE_OPENERR; } Index: src/sim_tmxr.h ================================================================== --- src/sim_tmxr.h +++ src/sim_tmxr.h @@ -151,10 +151,11 @@ int32 txbpr; /* xmt buf remove */ int32 txbpi; /* xmt buf insert */ int32 txcnt; /* xmt count */ int32 txpcnt; /* xmt packet count */ int32 txdrp; /* xmt drop count */ + int32 txstall; /* xmt stall count */ int32 txbsz; /* xmt buffer size */ int32 txbfd; /* xmt buffered flag */ t_bool modem_control; /* line supports modem control behaviors */ int32 modembits; /* modem bits which are currently set */ FILE *txlog; /* xmt log file */ @@ -285,10 +286,12 @@ t_stat tmxr_clock_coschedule_tmr (UNIT *uptr, int32 tmr, int32 ticks); t_stat tmxr_clock_coschedule_tmr_abs (UNIT *uptr, int32 tmr, int32 ticks); t_stat tmxr_change_async (void); t_stat tmxr_locate_line_send (const char *dev_line, SEND **snd); t_stat tmxr_locate_line_expect (const char *dev_line, EXPECT **exp); +const char *tmxr_send_line_name (const SEND *snd); +const char *tmxr_expect_line_name (const EXPECT *exp); t_stat tmxr_startup (void); t_stat tmxr_shutdown (void); t_stat tmxr_start_poll (void); t_stat tmxr_stop_poll (void); void _tmxr_debug (uint32 dbits, TMLN *lp, const char *msg, char *buf, int bufsize); Index: src/sim_video.c ================================================================== --- src/sim_video.c +++ src/sim_video.c @@ -29,12 +29,10 @@ #include "sim_video.h" #include "scp.h" t_bool vid_active = FALSE; -int32 vid_mouse_xrel; -int32 vid_mouse_yrel; int32 vid_cursor_x; int32 vid_cursor_y; t_bool vid_mouse_b1 = FALSE; t_bool vid_mouse_b2 = FALSE; t_bool vid_mouse_b3 = FALSE; @@ -94,11 +92,11 @@ * * Returns 0 success or -1 on failure, the error message is then retrievable * via SDL_GetError(). */ #define SDL_SavePNG(surface, file) \ - SDL_SavePNG_RW(surface, SDL_RWFromFile(file, "wb"), 1) + SDL_SavePNG_RW(surface, SDL_RWFromFile(file, "wb"), 1) /* * SDL_SavePNG -- libpng-based SDL_Surface writer. * * This code is free software, available under zlib/libpng license. @@ -521,12 +519,10 @@ vid_active = TRUE; vid_width = width; vid_height = height; vid_mouse_captured = FALSE; vid_cursor_visible = (vid_flags & SIM_VID_INPUTCAPTURED); - vid_mouse_xrel = 0; - vid_mouse_yrel = 0; vid_key_events.head = 0; vid_key_events.tail = 0; vid_key_events.count = 0; vid_key_events.sem = SDL_CreateSemaphore (1); @@ -714,10 +710,13 @@ void vid_set_cursor_position (int32 x, int32 y) { int32 x_delta = vid_cursor_x - x; int32 y_delta = vid_cursor_y - y; + +if (vid_flags & SIM_VID_INPUTCAPTURED) + return; if ((x_delta) || (y_delta)) { sim_debug (SIM_VID_DBG_CURSOR, vid_dev, "vid_set_cursor_position(%d, %d) - Cursor position changed\n", x, y); /* Any queued mouse motion events need to have their relative positions adjusted since they were queued based on different info. */ @@ -726,16 +725,12 @@ SIM_MOUSE_EVENT *ev; for (i=0; ix_rel, ev->y_rel, ev->x_rel + x_delta, ev->y_rel + y_delta); - vid_mouse_xrel -= ev->x_rel; /* remove previously accumulated relative position */ - vid_mouse_yrel -= ev->y_rel; ev->x_rel += x_delta; ev->y_rel += y_delta; - vid_mouse_xrel += ev->x_rel; /* update cumulative x & y rel */ - vid_mouse_yrel += ev->y_rel; } if (SDL_SemPost (vid_mouse_events.sem)) sim_printf ("%s: vid_set_cursor_position(): SDL_SemPost error: %s\n", vid_dev ? sim_dname(vid_dev) : "Video Device", SDL_GetError()); } else { @@ -1248,17 +1243,15 @@ if (SDL_SemWait (vid_mouse_events.sem) == 0) { if (!vid_mouse_captured) { event->xrel = (event->x - vid_cursor_x); event->yrel = (event->y - vid_cursor_y); } - vid_mouse_xrel += event->xrel; /* update cumulative x rel */ - vid_mouse_yrel += event->yrel; /* update cumulative y rel */ vid_mouse_b1 = (event->state & SDL_BUTTON(SDL_BUTTON_LEFT)) ? TRUE : FALSE; vid_mouse_b2 = (event->state & SDL_BUTTON(SDL_BUTTON_MIDDLE)) ? TRUE : FALSE; vid_mouse_b3 = (event->state & SDL_BUTTON(SDL_BUTTON_RIGHT)) ? TRUE : FALSE; - sim_debug (SIM_VID_DBG_MOUSE, vid_dev, "Mouse Move Event: pos:(%d,%d) rel:(%d,%d) buttons:(%d,%d,%d) - Count: %d vid_mouse_rel:(%d,%d), vid_cursor:(%d,%d)\n", - event->x, event->y, event->xrel, event->yrel, (event->state & SDL_BUTTON(SDL_BUTTON_LEFT)) ? 1 : 0, (event->state & SDL_BUTTON(SDL_BUTTON_MIDDLE)) ? 1 : 0, (event->state & SDL_BUTTON(SDL_BUTTON_RIGHT)) ? 1 : 0, vid_mouse_events.count, vid_mouse_xrel, vid_mouse_yrel, vid_cursor_x, vid_cursor_y); + sim_debug (SIM_VID_DBG_MOUSE, vid_dev, "Mouse Move Event: pos:(%d,%d) rel:(%d,%d) buttons:(%d,%d,%d) - Count: %d vid_cursor:(%d,%d)\n", + event->x, event->y, event->xrel, event->yrel, (event->state & SDL_BUTTON(SDL_BUTTON_LEFT)) ? 1 : 0, (event->state & SDL_BUTTON(SDL_BUTTON_MIDDLE)) ? 1 : 0, (event->state & SDL_BUTTON(SDL_BUTTON_RIGHT)) ? 1 : 0, vid_mouse_events.count, vid_cursor_x, vid_cursor_y); if (vid_mouse_events.count < MAX_EVENTS) { SIM_MOUSE_EVENT *tail = &vid_mouse_events.events[(vid_mouse_events.tail+MAX_EVENTS-1)%MAX_EVENTS]; ev.x_rel = event->xrel; ev.y_rel = event->yrel; @@ -1273,12 +1266,12 @@ (ev.b3_state == tail->b3_state)) { /* Merge the motion */ tail->x_rel += ev.x_rel; tail->y_rel += ev.y_rel; tail->x_pos = ev.x_pos; tail->y_pos = ev.y_pos; - sim_debug (SIM_VID_DBG_MOUSE, vid_dev, "Mouse Move Event: Coalesced into pending event: (%d,%d) vid_mouse_rel:(%d,%d)\n", - tail->x_rel, tail->y_rel, vid_mouse_xrel, vid_mouse_yrel); + sim_debug (SIM_VID_DBG_MOUSE, vid_dev, "Mouse Move Event: Coalesced into pending event: (%d,%d)\n", + tail->x_rel, tail->y_rel); } else { /* Add a new event */ vid_mouse_events.events[vid_mouse_events.tail++] = ev; vid_mouse_events.count++; if (vid_mouse_events.tail == MAX_EVENTS) @@ -1702,10 +1695,13 @@ sim_debug (SIM_VID_DBG_VIDEO|SIM_VID_DBG_KEY|SIM_VID_DBG_MOUSE|SIM_VID_DBG_CURSOR, vid_dev, "vid_thread() - Window Event: %d - %s\n", event.window.event, windoweventtypes[event.window.event]); switch (event.window.event) { case SDL_WINDOWEVENT_ENTER: if (vid_flags & SIM_VID_INPUTCAPTURED) SDL_WarpMouseInWindow (NULL, vid_width/2, vid_height/2); /* center position */ + break; + case SDL_WINDOWEVENT_EXPOSED: + vid_update (); break; } } break; #endif Index: src/sim_video.h ================================================================== --- src/sim_video.h +++ src/sim_video.h @@ -195,15 +195,10 @@ t_stat vid_show (FILE* st, DEVICE *dptr, UNIT* uptr, int32 val, CONST char* desc); t_stat vid_screenshot (const char *filename); extern t_bool vid_active; extern uint32 vid_mono_palette[2]; -extern int32 vid_mouse_xrel; /* mouse cumulative x rel */ -extern int32 vid_mouse_yrel; /* mouse cumulative y rel */ -extern t_bool vid_mouse_b1; /* mouse button 1 state */ -extern t_bool vid_mouse_b2; /* mouse button 2 state */ -extern t_bool vid_mouse_b3; /* mouse button 3 state */ void vid_set_cursor_position (int32 x, int32 y); /* cursor position (set by calling code) */ #define SIM_VID_DBG_MOUSE 0x01000000 #define SIM_VID_DBG_CURSOR 0x02000000 #define SIM_VID_DBG_KEY 0x04000000 Index: tools/bosi ================================================================== --- tools/bosi +++ tools/bosi @@ -163,10 +163,16 @@ sudo rm -f /junk encpass=$(openssl passwd -1 edsonDeCastro1968) sudo usermod -p $encpass pidp8i sudo passwd -e pidp8i + + cl=/boot/cmdline.txt + if ! grep -Fq ' init=' $cl + then + sudo sed -i -e 's#$# init=/usr/lib/raspi-config/init_resize.sh#' $cl + fi sudo poweroff } ADDED tools/cc8-tu56-update Index: tools/cc8-tu56-update ================================================================== --- /dev/null +++ tools/cc8-tu56-update @@ -0,0 +1,189 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +######################################################################## +# cc8-tu56-update - Rebuilds cc8.tu56 from source code. +# +# It is intended to be be called manually whenever a file in src/cc8/os8 +# changes. It is not called automatically from the top-level Makefile +# because not all end user systems will be able to run it, since our +# dependencies (host-side cc8 and SABR) are not always present. +# +# Copyright © 2017 by Warren Young. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +# Bring in just the basics so we can bring in our local modules +import os +import sys +sys.path.insert (0, os.path.dirname (__file__) + '/../lib') +sys.path.insert (0, os.getcwd () + '/lib') + +# Our local modules +from pidp8i import * +from simh import * + +# Other global Python modules +import glob +import subprocess + + +#### GLOBALS AND CONSTANTS ############################################# + +# Path to the cross-compiler version of cc8. +cc8_cross = os.path.join (dirs.build, 'bin', 'cc8') + +# Path to the CC8 sources +cc8_src = os.path.join (dirs.src, 'src', 'cc8') + + +#### cross_compile ##################################################### +# Cross-compile a *.c file on the local machine to a *.s file we can +# send into the OS/8 instance. The parameter should have no extension. + +def cross_compile (module): + inf = module + '.c' + outf = module + '.s' + + if os.path.exists (outf) and \ + os.path.getmtime (inf) <= os.path.getmtime (outf): + print outf + " up-to-date." + else: + print "Building " + outf + "..." + rc = subprocess.call ([cc8_cross, inf]) + if rc == 0: + print inf + " -> " + outf + else: + print "Failed to cross-compile the " + module.upper() + " module!" + exit (1) + + +#### main ############################################################## + +def main (): + # We have to do much of the following from within the CC8 source dir + # since the same source code is used for both versions of CC8, so the + # compiler assumes a flat filesystem, hence no "include path", hence + # all headers and such must be symlinked or copied into the same + # directory as our primary inputs. + os.chdir (os.path.join (cc8_src, 'os8')) + + # Cross-compile the OS/8 version of CC8's C sources to SABR. These + # files are listed in dependency order, in case that later matters. + modules = [ 'libc', 'c8', 'n8', 'p8' ] + try: + for m in modules: cross_compile (m) + except OSError as e: + print "Cross-compilation steps failed: " + e.strerror + '!' + exit (1) + + # Create the SIMH child instance and tell it where to send log output + try: + s = simh (dirs.build, True) + except (RuntimeError) as e: + print "Could not start simulator: " + e.message + '!' + exit (1) + s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0)) + + # Zero core before beginning. SIMH's PDP-8 simulator doesn't do it, + # on purpose, because the actual hardware did not do that. SIMH does + # not attempt to simulate the persistence of core memory by saving it + # to disk between runs, but the developers are right: you cannot trust + # the prior state of core before initializing it yourself. Rather + # than do that in some OS/8 specific way, we tell SIMH to do it. + s.send_cmd ('de all 0') + + # Attach an empty DECtape to the simulator to hold our output. + tape = os.path.join (dirs.os8mi, 'subsys', 'cc8.tu56') + if os.path.exists (tape): os.remove (tape) + s.send_cmd ("att dt0 " + tape) + + # Find and boot the bootable OS/8 disk. Use the "patched" version + # because that is what "make run" uses; we use that command to + # inspect this script's work. + rk = os.path.join (dirs.os8mo, 'os8v3d-patched.rk05') + if not os.path.isfile (rk): + print "Could not find " + rk + "; OS/8 media not yet built?" + exit (1) + print "Booting " + rk + "..." + s.send_cmd ("att rk0 " + rk) + s.send_cmd ("boot rk0") + + # Get rid of any C program sources that may happen to be laying around + # on the RK05 disk image we start with so we don't copy any working + # files created by hand to the cc8.tu56 tape with the wildcard below. + s.os8_send_cmd ('\\.', 'DEL *.C') + + # Copy the SABR files produced above by the host-side cross-compiler + # to the OS/8 environment's RK05 disk. + for m in modules: + s.os8_send_file (m + '.sb') + + # Copy example programs in as well. + # + # We have to filter these files' content for the reasons given at the + # top of cc8-to-os8. + # + # We don't let os8_send_file build the destination file name because + # the source is a random-named temp file. + tool = os.path.join (dirs.bin, 'cc8-to-os8') + for src in glob.glob ('../examples/*.c'): + if 'basic.c' not in src: + # else: leave this one out. The OS/8 compiler can't cope with it. + ctf = tempfile.NamedTemporaryFile () + ctn = ctf.name + os.system (tool + ' < ' + src + ' > ' + ctn) + s.os8_send_file (ctn, os.path.basename (src).upper ()) + + # Copy the remaining static OS/8 CC8 text files in. We purposely do + # not send init.h or libc.h because OS/8 CC8 doesn't process #include, + # so it has those bits hard-coded into the compiler at the moment. + for src in [ 'bldcc8.bi', 'cc.bi', 'header.sb' ]: + s.os8_send_file (src) + + # Build the OS/8 version of CC8 using the batch file we copied in. + s.os8_send_cmd ('\\.', 'EXE BLDCC8.BI') + + # Get rid of intermediary files on DSK: so the subsequent COPY + # commands don't add them to the DECtape image. + s.os8_send_cmd ('\\.', 'DEL ?8.RL,?8.SB,LIBC.SB') + + # Save all the remaining involved files to the cc8.tu56 tape, giving + # DIR DTA0: output approximately like this: + # + # CC .SV 27 CC1 .SV 41 CC2 .SV 41 + # FIB .C 1 CALC .C 5 BASIC .C 11 + # PS .C 1 CC .BI 1 HEADER.SB 6 + # LIBC .RL 22 + s.os8_send_cmd ('\\.', 'ZERO DTA0:') + s.os8_send_cmd ('\\.', 'COPY DTA0: + + Compares two lz4-compressed files, reporting differences in hex form. + +USAGE + exit 99 +fi ADDED tools/diffstat-since-release Index: tools/diffstat-since-release ================================================================== --- /dev/null +++ tools/diffstat-since-release @@ -0,0 +1,3 @@ +#!/bin/bash +files=$(ls | grep -v -e autosetup -e test | tr '\n' ' ') +fossil diff --internal --from release $files | diffstat ADDED tools/mkadrules Index: tools/mkadrules ================================================================== --- /dev/null +++ tools/mkadrules @@ -0,0 +1,123 @@ +#!/usr/bin/env perl +######################################################################## +# mkadrules - Write .c.o Makefile rules that also do autodependency +# generation to $adf for each source subdirectory named on the command +# line to avoid the need to manually maintain these near-identical +# Makefile rule sets. +# +# Each *.o file gets a *.d file generated by the compiler listing the +# object file's dependencies as discovered by the compiler at build +# time, using the same build options used to generate the *.o file. +# These automatically generated dependency rule sets are therefore +# inherently accurate and comprehensive, which hand-written rules +# virtually never are, particularly for software that builds on +# multiple platforms, where out-of-tree dependencies (e.g. the +# location of stdio.h) vary from one platform to another. +# +# This mechanism depends on the -M feature of GCC and Clang, which +# means our build system is probably not as widely portable as it +# might otherwise be. +# +# Based on http://scottmcpeak.com/autodepend/autodepend.html +# +# Copyright © 2017 Warren Young +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +use strict; +use warnings; + +use Cwd; + +# The basic rule set we generate, which is repeatedly modified and +# written to the output file. +my $template = <<'TEMPLATE'; +obj/%.o: SRCDIR/src/%.c + $(CC) -c $(CFLAGS) SRCDIR/src/$*.c -o obj/$*.o + $(CC) -MM $(CFLAGS) SRCDIR/src/$*.c > obj/$*.d + @mv -f obj/$*.d obj/$*.d.tmp + @sed -e 's|.*:|obj/$*.o:|' < obj/$*.d.tmp > obj/$*.d + @sed -e 's/.*://' -e 's/\\$$//' < obj/$*.d.tmp | fmt -1 | \ + sed -e 's/^ *//' -e 's/$$/:/' >> obj/$*.d + @rm -f obj/$*.d.tmp +TEMPLATE + +# Parse command line +die "usage: $0 \n\n" unless @ARGV >= 2; +my $srcdir = shift @ARGV; +die "Source tree $srcdir does not exist!\n" unless -d $srcdir; +my @dirs = @ARGV; +for (@dirs) { + die "Source subdir $_ does not exist!\n" unless -d "$srcdir/$_"; +} + +# Create the output file +my $adf = 'adrules.mk'; +unlink $adf; +open my $ad, '>', $adf, or die "Could not write to $adf: $!\n"; +print $ad "## DO NOT MODIFY. GENERATED BY tools/mkadrules! ##\n"; +print $ad "## -------------------------------------------- ##\n\n"; + +# If we're building in-tree, we can simplify $srcdir. +my $intree = $srcdir eq getcwd; +if ($intree) { + $srcdir = '.'; +} + +# Write rule set for each dir passed +for my $sdir (@dirs) { + my $rule = $template; + + my $odir = 'obj/' . $sdir; + $odir =~ s{/src}{}; + + my $cflags = uc $sdir; + $cflags =~ s{SRC}{}; + $cflags =~ s{^/}{}; + $cflags =~ s{/}{_}g; + $cflags = $cflags ? "${cflags}_CFLAGS" : 'TOP_CFLAGS'; + + $rule =~ s{obj/}{$odir/}g; + $rule =~ s{src/}{$sdir/}g; + $rule =~ s{CFLAGS}{$cflags}g; + $rule =~ s{SRCDIR}{$srcdir}g; + $rule =~ s{ \./}{ }g; + print $ad $rule, "\n"; +} + +# If we're building out-of-tree, some of the *.c files were written +# by autosetup from $srcdir/*.c.in, so we need an additional copy of +# the top src dir rule set especially for them. +unless ($intree) { + my $rule = $template; + $rule =~ s{SRCDIR/}{}g; + print $ad $rule, "\n"; +} + +# Finish up +close $ad; +chmod 0440, $adf; +print "Generated $adf for ", join(', ', @dirs), ".\n"; DELETED tools/mkos8.in Index: tools/mkos8.in ================================================================== --- tools/mkos8.in +++ /dev/null @@ -1,493 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -######################################################################## -# mkos8 - Build bin/os8v3d-*.rk05 from media/*/*.tu56 by scripting -# commands to SIMH and OS/8. -# -# Copyright © 2017 by Jonathan Trites, William Cattey, and Warren Young. -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, -# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT -# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Except as contained in this notice, the names of the authors above -# shall not be used in advertising or otherwise to promote the sale, -# use or other dealings in this Software without prior written -# authorization from those authors. -######################################################################## - -import argparse -import os -import sys -import subprocess -import pexpect -import time -from shutil import copyfile - - -#### globals and constants ############################################# - -child = None - -# Flag set when -v is *not* given. Causes make_*() and the functions -# called thereby to print progress messages to the console since SIMH -# and OS/8 output is not being sent there to clue the user into the -# script's progress. -progmsg = True - -# kludgy flag to add more verbose debug output. -debug = False - -_media_dir = "@abs_top_srcdir@/media/os8/" -_bin_dir = "@builddir@/bin/" -_bin_rk05 = "os8v3d-bin.rk05" -_src_rk05 = "os8v3d-src.rk05" - - -#### simh_send ######################################################### -# Wait for a SIMH command prompt and then send the given line - -def simh_send (send_line): - global child - - child.expect("sim> $") - child.sendline(send_line) - - -#### os8_kbd_delay ##################################################### -# Artificially delay the media generation process to account for the -# fact that OS/8 lacks a modern multi-character keyboard input buffer. -# It is unsafe to send text faster than a contemporary terminal could. -# -# The constant is expanded to show that it comes from the 2400 bps of -# the VT05s used by some well-heeled PDP-8 users divided by 7-bit ASCII -# plus necessary start, stop, and parity bits. The constant is inverted -# to get seconds per character instead of characters per second. OS/8 -# must be at least this fast, being contemporary with the VT05. -# -# You might think to try and speed this up by increasing the 2400 bps -# value below. For one thing, the 9600 bps VT52 was probably used with -# some later OS/8 systems. Also, the higher IPS rate of our simulated -# PDP-8 must help here. However, if you actually time doing so, it -# doesn't help, presumably because other I/O overheads (e.g. the TU56 -# reading time) swamp any improvement you can get here. - -_kbd_delay = 1 / (2400 / (7 + 1 + 1 + 1)) -def os8_kbd_delay(): - time.sleep(_kbd_delay) - - -#### os8_pmt_send ###################################################### -# Wait for an OS/8 command prompt running within SIMH, then send the -# given line. -# -# The prompt string is passed in because OS/8 has several different -# prompt types. - -def os8_pmt_send (prompt, send_line): - global child - - child.expect("\n%s$" % prompt) - for i in xrange(0, len(send_line)): - child.send(send_line[i]) - os8_kbd_delay() - child.send("\r") - - -#### back_to_simh ###################################################### -# Go back to SIMH when the passed prompt string is encountered - -def back_to_simh (prompt): - global child - - child.expect("\n%s$" % prompt) - os8_kbd_delay() - child.sendcontrol('e') - - -#### check_exists ###################################################### -# Check existence of all files needed - -def check_exists (image_copyins): - global child - - # Confirm necessary media images exist. - for copyin in image_copyins: - image = copyin[1] - image_path = _media_dir + image - if (not os.path.isfile(image_path)): - print "Required file: " + image_path + " not found." - simh_send("q") - child.expect(pexpect.EOF) - exit (-1) - # else: print "Found " + image_path - - -#### copyin_pair ####################################################### -# Copy two images into two destinations with two messages -# -# A copyin is a 3 element array: [destination, source, message] -# Assumes our context is "in simh". -# Assumes dt0 and dt1 are free. -# Assumes rk0 is the boot device -# Detaches dt0 and dt1 after using them. -# copyin0 mounts on dt0. copyin1 mounts on dt1. -# Either copyin or both can be None - -def copyin_pair (copyin0, copyin1): - if debug: - print "Copying: " + copyin0[1] + " to: " + copyin0[0] + "from dt0" - if copyin1: - print "Copying: " + copyin1[1] + " to: " + copyin1[0] + "from dt1" - else: print "copyin1 is empty." - - if not copyin0 and not copyin1: return # Nothing to do. - if copyin0: simh_send("attach -r dt0 " + _media_dir + copyin0[1]) - if copyin1: simh_send("attach -r dt1 " + _media_dir + copyin1[1]) - simh_send("boot rk0") - if copyin0: - if progmsg: print copyin0[2] - os8_pmt_send("\.", "COPY " + copyin0[0] + "', $ofile or die "Cannot write $ofile: $!\n"; + + while (<$if>) { + # Strip all the CRs out. We only need the LFs. + s{\r}{}gs; + my $original = $_; # save it post-strip + + # Strip variable parts of SIMH line following each Ctrl-E: + # + # "Simulation stopped, PC: 01210 (JMP 1207)" + s{ + (Simulation\ stopped) # bit to preserve + ,\ PC:\ \d+\ \([A-Z]+(\ \d+)?\) # variable bit + }{$1.}x; + + # Rewrite SIMH ATTACH commands to remove pointless differences + # in absolute paths between machines or Fossil checkouts. + s{ + (attach\ ) # SIMH command + (-r\ )? # optional flag + ([a-z0-9]+\ ) # SIMH device name + (.*test/tmp/[0-9]+/) # noise parts of image file name thru PID + }{$1$3.../}x; + s{ + (attach\ ) + (-r\ )? + ([a-z0-9]+\ ) + (.*/(media/os8)/) # also squish this noise + }{$1$3.../$5/}x; + + # Add cleaned line to ofile + print $of $_; + } + + close $if; + close $of; + + return; +} + + +#### construct_test #################################################### +# Assembles a test record for a single permutation + +sub construct_test +{ + my @opts = @_; + + # Distill this option set to a hash value after which we will + # name the output files. We don't want to name files with a + # leading hyphen or with long variable-length names, potentially + # multiple lines long. + my $optstr = join ' ', @opts; + my $hash = sha256_hex($optstr); + my $hdir = "test/$hash"; # test hash dir relative to our CWD + my $rhdir = "../../$hash"; # $hdir relative to builddir test/tmp/$PID + my $test = { + hash => $hash, + hdir => $rhdir, + log => "$rhdir/last.log", + name => '{' . substr ($hash, 0, 12) . '}', + optstr => $optstr, + rklz => "$rhdir/last.rklz", + }; + + # Skip this one if it already exists and we're in -g mode. + if ($generate_only) { + if (-d $hdir) { + if (-f "$hdir/last.log") { + if (-f "$hdir/last.rklz") { + print "Skipping $test->{name}: already done.\n"; + return; + } + elsif ($verbose) { + print "Must re-gen $hash despite -g: rklz missing!\n"; + } + } + elsif ($verbose) { + print "Must re-gen $hash despite -g: log missing!\n"; + } + } + elsif ($verbose) { + print "Will generate $hash.\n"; + } + } + elsif (-d $hdir) { + print "Will test $optstr against $hash.\n" if $verbose; + } + elsif ($verbose) { + print "Must generate missing test set $hash.\n"; + } + + push @tests, $test; + + return; +} + + +#### compare_rklz ###################################################### +# Compare two lz4-compressed RK05 disk images, returning true if they +# are the same. If they are different, also outputs a binary difference +# report. +# +# We call a separate shell script instead of use inline shell code here +# because the helper code uses a Bash feature, and /bin/sh might not be +# bash, as on a Raspbian box. + +sub compare_rklz +{ + my ($r1, $r2) = @_; + return system("$cmplz '$r1' '$r2'") == 0; +} + + +#### do_test ########################################################### +# Test a single permutation + +sub do_test +{ + # Set up working directory + my $test = $_; + print "Configuring test $test->{name}, PID $PID...\n"; + return if $dry_run; + chdir "test/tmp" or die "Could not CD to tmp dir: $!\n"; + mkdir $PID; + chdir $PID; + die "Could not mkdir $test->{hdir}: $!\n" + unless -d $test->{hdir} || mkdir($test->{hdir}); + + # Are we building the -patched disk or the -bin disk? + my $patched = $test->{optstr} !~ m{--disable-os8-patches}x; + my $typestr = $patched ? 'patched' : 'bin'; + my $currdsk = "bin/os8v3d-$typestr.rk05"; + my $currdlz = substr ($currdsk, 0, length ($currdsk) - 2) . 'lz'; + my $target = 'os8-' . $typestr; + + # Configure the test disk image + system "../../../configure $test->{optstr} > cfg.log 2>&1" + and die "Failed to configure $test->{name}!\n"; + link "../../../bin/pidp8i-sim", "bin/pidp8i-sim"; # no sense rebuilding it + open my $itf, '>', 'media/os8/init.tx' # avoid a pointless diff + or die "Cannot overwrite init.tx with neutral version: $!\n"; + print $itf "TEST-MKOS8 BUILT THIS DISK IMAGE.\n\n"; + close $itf; + + # Build the test disk image + print "Building $currdsk for test $test->{name} (PID $PID)...\n"; + system "make $target > make.log 2>&1" + and die "Failed to build $currdsk!\n"; + + # Quickly compress the test disk: we don't want to store all the + # "air" in an RK05 in our test corpus. + system("lz4 -q $currdsk > $currdlz"); + + if (not -f $test->{log} or not -f $test->{rklz}) { + # This test hasn't run here yet, so save it as our exemplar for + # this optstr, to be compared to future builds. + sanitize_log ($currlog, $test->{log}); + move ($currdlz, $test->{rklz}); + + # Log the mapping between the hash and the options it + # represents, so the user can reverse it. + print $tests_mf "$test->{hash} $test->{optstr}\n"; + $generated{$test->{hash}} = 1; + } + elsif (compare_rklz ($currdlz, $test->{rklz})) { + # We had this test examplar here already and on re-doing it we + # got the same result. + print colored(['green'], "mkos8 $test->{name} test passed."), "\n"; + $tested{$test->{hash}} = 1; + } + else { + # This build resulted in a difference, so yell and save the + # results for manual comparison. + my $fdiff = "$test->{hdir}/fail.diff"; + my $faillog = "$test->{hdir}/fail.log"; + sanitize_log ($currlog, $faillog); + move ($currdlz, $test->{hdir} . '/fail.rklz'); + system "diff -wu $test->{log} $faillog > $fdiff"; + print colored(['bold red'], 'RESULT DIFFERS! See test/', + substr($fdiff, 6)), "\n"; + $tested{$test->{hash}} = 0; + } + + system("cd .. ; rm -fr $PID"); # -f because there are read-only files + + return; +} + + +#### remove_missing #################################################### +# Implements -e: given a list of mkos8 options, returns only those for +# which we have a valid test set. + +sub remove_missing +{ + my (@tests) = @_; + my $all = @tests; + + # First read in the set of prebuilt tests, filtering out those that + # refer to output files that do not exist here. (This happens when + # copying over the manifest file but only a subset of the actual + # test output files.) + my %existing; + my $genned = 0; + my $mff = 'test/tests-manifest.txt'; + open my $mf, '<', $mff or die "Could not read from $mff: $!\n"; + while (<$mf>) { + chomp; + my ($hash, @opts) = split ' '; + my $dir = 'test/' . $hash; + if (-d $dir and -f "$dir/last.rklz" and -f "$dir/last.log") { + $existing{join ' ', @opts} = $hash; + } + ++$genned; + } + close $mf; + + # Now filter the test set to remove those that do not exist + my @filtered = grep { $existing{$_->{optstr}} } @tests; + print "Filtered $genned of $all tests down to ", scalar(@filtered), + " for -e.\n"; + return @filtered; +} + + +#### report* ########################################################### +# Print on-exit status report. + +sub report_part +{ + my ($partref, $kind) = @_; + + return unless keys %$partref; + + my ($successes, $tries) = (0, 0); + for my $s (values %$partref) { + ++$tries; + ++$successes if $s; + } + + my $extra = $successes == $tries ? '' : " of $tries"; + print colored ([ + $successes == 0 ? 'bold red' : + $successes != $tries ? + 'bold yellow' : + 'green' + ], + "Successfully $kind $successes$extra tests.\n"); +} + +sub report +{ + print "\n", '=' x 79, "\n"; + report_part (\%generated, 'generated'); + report_part (\%tested, 'built'); + print "\n"; + + return; +} + + +#### main ############################################################## + +# Parse command line +my %clopts; +getopts('egnsv1', \%clopts) or die "Failed to parse command line!\n"; +$dry_run = $clopts{n}; +$existing_only = $clopts{e}; +$generate_only = $clopts{g}; +$shuffle = $clopts{s}; +$verbose = $clopts{v}; +$single_core = $clopts{1}; + +# Init global resources +mkdir 'test'; +system("rm -rf test/tmp"); +mkdir 'test/tmp'; +open $tests_mf, '>>', 'test/tests-manifest.txt' + or die "Cannot append to test manifest: $!\n"; +$SIG{INT} = $SIG{TERM} = sub { report; exit 1 }; +$SIG{PIPE} = 'IGNORE'; + +# Get all current --*-os8-* options, filtering out those we know should +# not be tried for this: +# +# * No --os8-minimal because that just turns on all --disable-os8-* +# options, so it's already covered. +# * No --disable-os8-src because we don't test the src disk; it's +# always generated the same way. +# * No --disable-os8-focal because it disables the other two FOCAL +# options, which we're already going to test singly and together. +my @cmd = ( + "./configure --help", + "grep -- -os8-", + "sed -Ee 's/^ +//'", + "cut -f1 -d' '", + "grep -v -e 'os8-minimal' -e 'os8-src' -e 'os8-focal\$'" +); +open my $ocmd, '-|', join('|', @cmd) or die "Failed to get os8 option set: $!\n"; +my @cfgOpts = <$ocmd>; +close $ocmd; +chomp @cfgOpts; + +# Generate all possible permutations of those options. Shuffle them if +# requested. +subsets \&construct_test, @cfgOpts; +@tests = remove_missing(@tests) if $existing_only; +@tests = shuffle @tests if $shuffle; + +# Run the tests +my $tdir = abs_path(dirname($0)); +my $cores = $single_core ? 1 : int(`$tdir/corecount`); +my $pl = Parallel::Loops->new($cores); +$pl->share (\%generated, \%tested); +$pl->foreach ( \@tests, \&do_test); + +report; ADDED tools/test-os8-send-file Index: tools/test-os8-send-file ================================================================== --- /dev/null +++ tools/test-os8-send-file @@ -0,0 +1,127 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +######################################################################## +# test-os8-send-file - Repeatedly sends random files through class simh +# method os8_send_file() and pulls it back through os8_get_file(), +# then checks that the file is unchanged. +# +# Copyright © 2017 by Warren Young. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +# Bring in just the basics so we can bring in our local modules +import os +import sys +sys.path.insert (0, os.path.dirname (__file__) + '/../lib') +sys.path.insert (0, os.getcwd () + '/lib') + +# Our local modules +from pidp8i import * +from simh import * + +# Other core modules we need +import filecmp +import os.path +import random +import tempfile + + +#### gen_file ########################################################## +# Generate a random text file. In order that the process be lossless +# through the txt2ptp/ptp2txt filters and the SIMH + OS/8 terminal +# handling, we use only printable ASCII plus CR+LF characters. Returns +# the name of the generated file. + +def gen_file (): + f = tempfile.NamedTemporaryFile (delete = False, suffix = '.tmp') + for i in range (0, random.randint (10, 4000)): + if random.randint (0, 10) != 0: + # Normal case: write some number of printable ASCII characters + # on this line. + for j in range (0, random.randint (1, 79)): + f.write (chr (random.randint (32, 126))) + # else: Every now and then, just write a blank line + + f.write ('\r\n') + + f.close () + return f.name + + +#### main ############################################################## + +def main (): + # Create the SIMH child instance and tell it where to send log output + try: + s = simh (dirs.build) + except (RuntimeError) as e: + print "Could not start simulator: " + e.message + '!' + exit (1) + s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0)) + + # Find and boot the built OS/8 bin disk + rk = os.path.join (dirs.os8mo, 'os8v3d-bin.rk05') + if not os.path.isfile (rk): + print "Could not find " + rk + "; OS/8 media not yet built?" + exit (1) + print "Booting " + rk + "..." + s.send_cmd ("att rk0 " + rk) + s.send_cmd ("boot rk0") + + # Setup + random.seed () + + # Transfer several random files through. Beware increasing the range + # too far: max is 99999 due to the file name length limit of OS/8 due + # to the temporary file naming scheme we use in the loop. + for i in range (0, 1000): + # Build another temp file + ifn = gen_file () + of = tempfile.NamedTemporaryFile (suffix = '.out', delete = False) + of.close () + + # Send it + ofn = of.name + tfn = 'T%05d.TX' % i + s.os8_send_file (ifn, tfn) + s.os8_get_file (tfn, ofn) + + # Did it change? + if filecmp.cmp (ifn, ofn): + print ifn + ' transferred successfully.' + s.os8_send_cmd ('\\.', 'DEL ' + tfn) + os.remove (ifn) + os.remove (ofn) + elif os.path.getsize (ofn) == 0: + print "\nDifferences found: output is empty!\n" + else: + print "\nDifferences found:\n--------------------------------" + os.system ('diff -wu "' + ifn + '" "' + ofn + '"') + print 'Left ' + tfn + ' inside OS/8.' + + +if __name__ == "__main__": + main() Index: tools/version ================================================================== --- tools/version +++ tools/version @@ -30,12 +30,22 @@ ######################################################################## use strict; use warnings; -use Cwd 'abs_path'; +use Cwd qw(abs_path getcwd); use File::Basename; + +# Give dummy result when run via test-mkos8. A proper version string +# isn't helpful, and fossil isn't intended to be run in parallel +# multiple times in the same directory. We could retry several times +# until we succeed, but again, it doesn't really help us to bother. +my $cwd = getcwd; +if ($cwd =~ m{/test/tmp/}x) { + print "test:id[00000000]\n"; + exit 0; +} my $topdir = dirname($0) . '/..'; if (-e "$topdir/.fslckout") { # Get version info from Fossil my ($branch, $checkout, $version, $comment);