mirror of
https://github.com/pkimpel/retro-220.git
synced 2026-02-11 02:31:01 +00:00
1. Implement paper-tape/TTY versions of compiler INPUTMEDIA and OUTPUTMEDIA routines, and the run-time REED and RITE routines. 2. Create Generator callout deck to make a compiler tape containing paper-tape/TTY support. 3. Create a compiler tape containing paper-tape/TTY support. 4. Create a paper-tape compiler callout bootstrap program. 5. Correct address for NUMB in Generator source to match the address in the compiler's Overlay module. 6. Create corrected Generator tape (must be used to create compilers with paper-tape/TTY support). 7. Add option to produce Generator INPUTMEDIA/OUTPUTMEDIA object card decks to BAC-Assembler and GEN-Assembler. 8. Create Xlate-Card-PT.wsf utility to convert card-image files to retro-220 paper-tape image files. 9. Create paper-tape versions of example BALGOL programs.
3528 lines
134 KiB
HTML
3528 lines
134 KiB
HTML
<!DOCTYPE html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
<title>GEN-220 Assembler</title>
|
|
<!--
|
|
/***********************************************************************
|
|
* 220/software/tools GEN-Assembler.html
|
|
************************************************************************
|
|
* Copyright (c) 2017, Paul Kimpel.
|
|
* Licensed under the MIT License, see
|
|
* http://www.opensource.org/licenses/mit-license.php
|
|
************************************************************************
|
|
* Cross-Assembler for the Burroughs 220 Algebraic Compiler (BALGOL)
|
|
* Generator utility.
|
|
*
|
|
* Assembles source for the 220 machine language generator utility for the
|
|
* BALGOL compiler. Source is read from an emulated card deck, a text
|
|
* file with the following format:
|
|
*
|
|
* col 1 Cardatron format band selection digit. Can be anything,
|
|
* but is typically 1.
|
|
* col 5... symbolic label or point label (numeric): may extend past
|
|
* op code field with remainder of instruction on next card.
|
|
* col 16 sign digit (or -) or start of constant value.
|
|
* col 17-24 symbolic op code (standard 220 mnemonics).
|
|
* col 25... operands (terminated by first free space).
|
|
* col 73-80 sequence and identification.
|
|
*
|
|
* All other columns are ignored by this assembler.
|
|
*
|
|
* To maintain the same addresses for literal pool entries, a JSON file
|
|
* containing a "pool set" of one or more arrays of literal word values
|
|
* and their starting memory location may be pre-loaded into the assembler
|
|
* before loading the source code.
|
|
*
|
|
* Output is a simulated line printer listing in a text panel of the web
|
|
* page from which the assembler is run. The output of Pass 2 is a
|
|
* traditional assembler listing, with words of generated object code
|
|
* and data. It is designed to match the listing from which the compiler
|
|
* was transcribed, so that it may be compared for proofing purposes.
|
|
*
|
|
* Output of the generated code and data can be obtained in three forms:
|
|
*
|
|
* - Punched cards in a self-loading band-6 format (default).
|
|
*
|
|
* - Punched cards in BALGOL Machine Language format. This is also a
|
|
* band-6 format, but requires a separate loader program. See Appendix
|
|
* F in the BAC-220 reference manual.
|
|
*
|
|
* - Magnetic tape image recorded in 100-word blocks. The code is
|
|
* written to lane 0 of the tape image.
|
|
*
|
|
* The form of output is selected from a pull-down list on the browser
|
|
* page. The output is displayed in a sub-window that opens at the end of
|
|
* the assembly. From there is can be saved or copied to the local file
|
|
* system.
|
|
*
|
|
************************************************************************
|
|
* 2017-12-02 P.Kimpel
|
|
* Original version, cloned from retro-220 BAC-Assembler.html.
|
|
***********************************************************************/
|
|
-->
|
|
<meta name="Author" content="Paul Kimpel">
|
|
<meta http-equiv="Content-Script-Type" content="text/javascript">
|
|
<meta http-equiv="Content-Style-Type" content="text/css">
|
|
|
|
<style>
|
|
HTML {
|
|
height: 100%}
|
|
|
|
BODY {
|
|
position: relative;
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
height: 100%;
|
|
margin: 1ex}
|
|
|
|
DIV.heading {
|
|
margin-top: 12px;
|
|
margin-bottom: 6px;
|
|
font-weight: bold}
|
|
|
|
LABEL {
|
|
font-size: smaller}
|
|
|
|
#CardReaderPanel {
|
|
position: relative;
|
|
color: white;
|
|
background-color: #666;
|
|
width: 640px;
|
|
border: 1px solid black;
|
|
border-radius: 8px;
|
|
font-size: smaller;
|
|
padding: 8px}
|
|
|
|
#CardReaderTable {
|
|
border-spacing: 0;
|
|
border-collapse: collapse;
|
|
table-layout: fixed;
|
|
width: 100%}
|
|
#CardReaderCol1 {
|
|
width: 18ex}
|
|
#CardReaderCol2 {
|
|
}
|
|
#CardReaderTable TD {
|
|
text-align: left;
|
|
vertical-align: top;
|
|
padding-top: 1px;
|
|
padding-bottom: 1px;
|
|
padding-left: 2px;
|
|
padding-right: 2px}
|
|
|
|
#CRFileSelector {
|
|
width: 100%;
|
|
border: 1px solid white}
|
|
|
|
#CRPoolSelector {
|
|
width: 100%;
|
|
border: 1px solid white}
|
|
|
|
#OptionsTable {
|
|
position: relative;
|
|
margin-top: 4px;
|
|
margin-bottom: 4px;
|
|
width: 640px}
|
|
|
|
#TextDiv {
|
|
position: relative;
|
|
height: 75%;
|
|
width: 640px}
|
|
|
|
#TextPanel {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
bottom: 8px;
|
|
width: 100%;
|
|
overflow: scroll;
|
|
padding: 4px;
|
|
border: 1px solid black;
|
|
color: black;
|
|
background-color: white;
|
|
font-family: DejaVu Sans Mono, Consolas, Courier, monospace;
|
|
font-size: 8pt;
|
|
font-weight: normal}
|
|
|
|
#Spinner {
|
|
position: absolute;
|
|
top: 200px;
|
|
left: 200px;
|
|
z-index: 10}
|
|
|
|
.center {
|
|
text-align: center}
|
|
.rj {
|
|
text-align: right}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class=heading>
|
|
Assembler for the Burroughs 220 BAC Generator
|
|
</div>
|
|
|
|
<div id=CardReaderPanel>
|
|
<table id=CardReaderTable>
|
|
<colgroup><col id=CardReaderCol1><col id=CardReaderCol2></colgroup>
|
|
<tr><td>Pre-load Pool
|
|
<td><input id=CRPoolSelector type=file size=90>
|
|
<tr><td>Load Source & Go
|
|
<td><input id=CRFileSelector type=file size=90>
|
|
</table>
|
|
</div>
|
|
|
|
<table id=OptionsTable>
|
|
<thead>
|
|
<tr>
|
|
<th><label for=Pass1ListCheck>List Pass 1</label>
|
|
<th><label for=Pass2ListCheck>List Pass 2</label>
|
|
<th><label for=ChecksumCheck>Write Checksum</label>
|
|
<th><label for=OutputModeSelect>Output Mode</label>
|
|
<th>
|
|
<tbody>
|
|
<tr>
|
|
<td class=center>
|
|
<input id=Pass1ListCheck type=checkbox value=1>
|
|
<td class=center>
|
|
<input id=Pass2ListCheck type=checkbox value=1 CHECKED>
|
|
<td class=center>
|
|
<input id=ChecksumCheck type=checkbox value=1>
|
|
<td class=center>
|
|
<select id=OutputModeSelect>
|
|
<option value="" >No Object
|
|
<option value=L SELECTED>Loadable Deck
|
|
<option value=M >BALGOL ML Deck
|
|
<option value=P >Gen MEDIA Deck
|
|
<option value=T >Object Tape
|
|
</select>
|
|
<td class=rj>
|
|
<button id=SelectListing type=button>Select Listing</button>
|
|
</table>
|
|
|
|
<div id=TextDiv><pre id=TextPanel></pre></div> <!-- Don't add any whitespace! -->
|
|
|
|
|
|
<script>
|
|
"use strict";
|
|
|
|
window.addEventListener("load", function() {
|
|
|
|
// Card reader properties
|
|
var buffer = "";
|
|
var bufferLength = 0;
|
|
var bufferOffset = 0;
|
|
var sourceName = "?";
|
|
|
|
var eolRex = /([^\n\r\f]*)((:?\r[\n\f]?)|\n|\f)?/g;
|
|
var isNumericRex = /^[0-9]/;
|
|
var isAlphaRex = /^[A-Z]/;
|
|
var isLabelRex = /^[A-Z.0-9]/;
|
|
var labelRex = /^([0-9]+|[A-Z][0-9.A-Z]*)(\-[0-9]+)? +/;
|
|
var rTrimRex = /\s*$/;
|
|
var spoStringRex = /^[ILRT]*'/;
|
|
|
|
// Card 0-relative column offsets
|
|
var labelOffset = 4;
|
|
var signOffset = labelOffset + 11;
|
|
var opCodeOffset = labelOffset + 12;
|
|
var operandOffset = labelOffset + 20;
|
|
var sequenceOffset = 72;
|
|
var operandLength = 48;
|
|
|
|
// Card data structure
|
|
var cardData = {
|
|
atEOF: false, // buffer exhausted
|
|
fileOffset: 0, // bufferOffset at start of file
|
|
fileSerial: 0, // card serial number at start of file
|
|
offset: 0, // current card image bufferOffset
|
|
length: 0, // length of card image, including delimiters
|
|
serial: 0, // serial number within file
|
|
text: ""} // padded card image
|
|
|
|
// Output listing column offsets
|
|
var serialOffset = 0;
|
|
var wordOffset = 5;
|
|
var flagOffset = 34;
|
|
var cardOffset = 39;
|
|
|
|
var printFlag = "";
|
|
var pass1List = false;
|
|
var pass2List = true;
|
|
var listPass = false;
|
|
var outputChecksum = false;
|
|
|
|
var panel = $$("TextPanel");
|
|
var cardReaderUnit = 1; // Cardatron input unit number for object card decks
|
|
|
|
// Token.type values
|
|
var tokEmpty = 0; // empty primary
|
|
var tokLocation = 1; // $, the current location counter
|
|
var tokInteger = 2; // integer literal
|
|
var tokLabel = 3; // regular label
|
|
var tokBackwardPoint = 4; // backward point label (without the leading * or the trailing -)
|
|
var tokForwardPoint = 5; // forward point label (ditto, without the trailing +)
|
|
var tokLiteral = 6; // pool literal, 1-11 digit number or string)
|
|
var tokString = 7; // '-delimited string literal
|
|
|
|
// token structure for operand parsing
|
|
var token = {
|
|
type: tokEmpty, // token type
|
|
offset: 0, // offset into card source image
|
|
newOffset: 0, // next offset after token scanned
|
|
text: "", // token text
|
|
word: 0, // token 220 word value
|
|
value: 0}; // token arithmetic value
|
|
|
|
// Assembled code data structure
|
|
var assembly = {
|
|
label: "", // any label for this instruction
|
|
labelOffset: 0, // words to offset label location from current location
|
|
location: 0, // assembled memory address
|
|
opCode: NaN, // assembled opCode value from opTab[][0]
|
|
placeLoc: 0, // offset to physical assembled location
|
|
count: 0, // number of words assembled in "words"
|
|
words: // assembled words for the command
|
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]};
|
|
|
|
// Assembly storage
|
|
var asmCode = []; // buffer for words of assembled code
|
|
var djList = [0]; // sL values for DJ field definitions
|
|
var errorCount = 0; // assembler error count
|
|
var errorTank = []; // holding area for errors on current line
|
|
var lastPoolLength = 0; // size of literal pool table in prior pass
|
|
var lastPoolUnresolved = 0; // count of unresolved pool literals from prior pass
|
|
var lastSymUnresolved = 0; // count of unresolved labels
|
|
var location = 0; // current instruction address
|
|
var locationAdjustment = 0; // amount by which to adjust label locations due to change in pool size
|
|
var nextLocation = 0; // next instruction address
|
|
var placeOffset = 0; // PLACE address offset
|
|
var pointTab = {}; // point-label table: holds the current sequence number for each point label
|
|
var poolData = []; // literal pool data table: holds words for literals
|
|
var poolDumped = false; // true if literal pool has been output in current pass
|
|
var poolLocation = NaN; // address of the current literal pool
|
|
var poolSetName = ""; // name of optional pool set data file
|
|
var poolUnresolved = 0; // count of unresolved pool literal values
|
|
var programUnitNr = 0; // ordinal number of the program unit being assembled
|
|
var startAddress = -1; // starting execution address, from END card
|
|
var symTab = {}; // symbol table: holds the address value for each label, initially NaN
|
|
|
|
var poolSets = [ // skeleton table of pre-loaded pools for default program unit #0
|
|
{poolLoc: NaN, poolData: []}];
|
|
|
|
var p10 = [ 1, // powers of 10 table
|
|
10,
|
|
100,
|
|
1000,
|
|
10000,
|
|
100000,
|
|
1000000,
|
|
10000000,
|
|
100000000,
|
|
1000000000,
|
|
10000000000,
|
|
100000000000,
|
|
1000000000000,
|
|
10000000000000,
|
|
100000000000000,
|
|
1000000000000000,
|
|
10000000000000000];
|
|
|
|
var xlateANSI220 = [ // translate ANSI to 220 internal character codes
|
|
// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-0F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10-1F
|
|
0, 0, 0, 33, 13, 24, 10, 34, 24, 4, 14, 10, 23, 20, 3, 21, // 20-2F
|
|
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 0, 13, 4, 33, 0, 0, // 30-3F
|
|
34, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, // 40-4F
|
|
57, 58, 59, 62, 63, 64, 65, 66, 67, 68, 69, 0, 0, 0, 0, 0, // 50-5F
|
|
0, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, // 60-6F
|
|
57, 58, 59, 62, 63, 64, 65, 66, 67, 68, 69, 0, 0, 0, 0, 0]; // 70-7F
|
|
|
|
var xlate220ANSI = [ // translate internal B220 code to ANSI (Algol glyphs)
|
|
// 00 01 02 03 04 05 06 07 08 09
|
|
" ", "?", "9", ".", ")", "?", "?", "?", "?", "?", // 00-09
|
|
"+", "?", "?", "$", "*", "*", "$", "?", "?", "?", // 10-19
|
|
"-", "/", "?", ",", "(", "?", ",", "?", "?", "?", // 20-29
|
|
"?", "?", "?", "=", "@", "\\", "?", "?", "?", "?", // 30-39
|
|
"?", "A", "B", "C", "D", "E", "F", "G", "H", "I", // 40-49
|
|
"?", "J", "K", "L", "M", "N", "O", "P", "Q", "R", // 50-59
|
|
"?", "?", "S", "T", "U", "V", "W", "X", "Y", "Z", // 60-69
|
|
"?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 70-79
|
|
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", // 80-89
|
|
"?", "?", "?", "?", "?", "?", "?", "?", "?", "?"]; // 90-99
|
|
|
|
var signValues = { // numeric values of sign column
|
|
" ": 0,
|
|
"+": 0,
|
|
"0": 0,
|
|
"-": 1,
|
|
"1": 1,
|
|
"2": 2,
|
|
"3": 3,
|
|
"4": 4,
|
|
"5": 5,
|
|
"6": 6,
|
|
"7": 7,
|
|
"8": 8,
|
|
"9": 9};
|
|
|
|
/*******************************************************************
|
|
* Opcode table:
|
|
* Each 220 assembler op code is defined by an array with a variable number
|
|
* of elements. The first element is the operation code, including any variant
|
|
* digits in (45) and is always required. The variant digit may be overlaid by
|
|
* other fields in the instruction. If the code is negative, it indicates a
|
|
* pseudo-operator or other special handling.
|
|
*
|
|
* The remainder of the array consists of number pairs, one for each comma-
|
|
* delimited operand field that may be present. The first number is a code
|
|
* indicating the type of field, the second is the default value for that
|
|
* field if it is not specified (or is empty) in the operand area of the
|
|
* instruction. If the default value is negative, that operand is not optional
|
|
* and must be specified.
|
|
*
|
|
* Any additional parameters in the operand field after those defined for
|
|
* an op code must have a "(sL)" suffix to designate the partial field in
|
|
* the instruction word to which that parameter will be applied.
|
|
*
|
|
* Type codes:
|
|
* 1 = address in /04, no /sL allowed
|
|
* 2 = address in /04, /sL in /22 required
|
|
* 3 = address in/04, /sL in /22 optional, if specified sets /31=1
|
|
* 4 = unit or count digit inserted in /11
|
|
* 5 = variant digit inserted in /41
|
|
* 6 = value in /32
|
|
* 7 = value in /42
|
|
* 8 = control digit in /11, if specified sets /41=1
|
|
* 9 = value in /44
|
|
* 10 = mag tape unit/lane as LLU, inserted as ULL in /33
|
|
* 11 = count in /21
|
|
* 12 = format band digit added to /41: (B-1)*2
|
|
* 13 = SPO operand: special string or address,count pair
|
|
* 19 = resolved address only
|
|
*******************************************************************/
|
|
|
|
// Pseudo-instruction codes
|
|
var pseudoREM = -1;
|
|
var pseudoEND = -2;
|
|
var pseudoWord = -3;
|
|
var pseudoIS = -4;
|
|
var pseudoORIGIN = -5;
|
|
var pseudoPLACE = -6;
|
|
var pseudoPLACED = -7;
|
|
var pseudoFORGET = -8;
|
|
var pseudoFORGETNAMES =
|
|
-9;
|
|
var pseudoFORMAT = -10;
|
|
var pseudoFILL = -11;
|
|
var pseudoPOOL = -12;
|
|
var pseudoDO = -13;
|
|
var pseudoDJ = -14;
|
|
var pseudoJ = -15;
|
|
|
|
var opTab = {
|
|
"HLT ": [ 0, 1, 0],
|
|
"NOP ": [ 1, 1, 0],
|
|
"PNC ": [ 3, 1,-1, 4,-1, 6, 0],
|
|
"PRD ": [ 103, 1,-1, 4,-1, 6, 0],
|
|
"PRB ": [ 4, 1,-1, 4,-1, 5, 0],
|
|
"PRI ": [ 5, 1,-1, 4,-1, 6, 0, 5, 0],
|
|
"PWR ": [ 6, 1,-1, 4,-1, 6, 0],
|
|
"PWI ": [ 7, 1, 0],
|
|
"KAD ": [ 8, 1, 0],
|
|
"SPO ": [ 9, 13,-1],
|
|
"CAD ": [ 10, 1, 0],
|
|
"CAA ": [ 110, 1, 0],
|
|
"CSU ": [ 11, 1, 0],
|
|
"CSA ": [ 111, 1, 0],
|
|
"ADD ": [ 12, 1, 0],
|
|
"ADA ": [ 112, 1, 0],
|
|
"SUB ": [ 13, 1, 0],
|
|
"SUA ": [ 113, 1, 0],
|
|
"MUL ": [ 14, 1, 0],
|
|
"DIV ": [ 15, 1, 0],
|
|
"RND ": [ 16, 1, 0],
|
|
"EXT ": [ 17, 1, 0],
|
|
"CFA ": [ 18, 3, 0],
|
|
"CFR ": [ 118, 3, 0],
|
|
"ADL ": [ 19, 1, 0],
|
|
"IBB ": [ 20, 1,-1, 9, 0],
|
|
"DBB ": [ 21, 1,-1, 9, 0],
|
|
"FAD ": [ 22, 1,-1, 4, 0],
|
|
"FAA ": [ 122, 1,-1, 4, 0],
|
|
"FSU ": [ 23, 1,-1, 4, 0],
|
|
"FSA ": [ 123, 1,-1, 4, 0],
|
|
"FMU ": [ 24, 1, 0],
|
|
"FDV ": [ 25, 1, 0],
|
|
"IFL ": [ 26, 2,-1, 7, 0],
|
|
"DFL ": [ 27, 2,-1, 7, 0],
|
|
"DLB ": [ 28, 2,-1, 7, 0],
|
|
"RTF ": [ 29, 1,-1, 6, 0],
|
|
"BUN ": [ 30, 1, 0],
|
|
"BOF ": [ 31, 1, 0],
|
|
"BRP ": [ 32, 1, 0],
|
|
"BSA ": [ 33, 1,-1, 5,-1],
|
|
"BPA ": [ 33, 1, 0],
|
|
"BMA ": [ 133, 1, 0],
|
|
"BCH ": [ 34, 1, 0],
|
|
"BCL ": [ 134, 1, 0],
|
|
"BCE ": [ 35, 1, 0],
|
|
"BCU ": [ 135, 1, 0],
|
|
"BFA ": [ 36, 2,-1, 7,-1],
|
|
"BZA ": [ 36, 1, 0],
|
|
"BFR ": [ 37, 2,-1, 7,-1],
|
|
"BZR ": [ 37, 1,-1],
|
|
"BCS ": [ 38, 1,-1, 4,-1],
|
|
"SOR ": [ 39, 1, 0],
|
|
"SOH ": [ 139, 1, 0],
|
|
"IOM ": [ 239, 1, 0],
|
|
"STA ": [ 40, 3, 0],
|
|
"STR ": [ 140, 3, 0],
|
|
"STB ": [ 240, 3, 0],
|
|
"LDR ": [ 41, 1, 0],
|
|
"LDB ": [ 42, 1, 0],
|
|
"LBC ": [ 142, 1, 0],
|
|
"LSA ": [ 43, 5,-1],
|
|
"STP ": [ 44, 1, 0],
|
|
"CLA ": [ 145, 1, 0],
|
|
"CLR ": [ 245, 1, 0],
|
|
"CAR ": [ 345, 1, 0],
|
|
"CLB ": [ 445, 1, 0],
|
|
"CAB ": [ 545, 1, 0],
|
|
"CRB ": [ 645, 1, 0],
|
|
"CLT ": [ 745, 1, 0],
|
|
"CLL ": [ 46, 1, 0],
|
|
"SRA ": [ 48, 1, 0],
|
|
"SRT ": [ 148, 1, 0],
|
|
"SRS ": [ 248, 1, 0],
|
|
"SLA ": [ 49, 1, 0],
|
|
"SLT ": [ 149, 1, 0],
|
|
"SLS ": [ 249, 1, 0],
|
|
"MTS ": [ 50, 1, 0, 10,-1],
|
|
"MFS ": [4000050, 1, 0, 10,-1],
|
|
"MLS ": [ 450, 10,-1],
|
|
"MRW ": [ 850, 10,-1],
|
|
"MDA ": [ 950, 10,-1],
|
|
"MTC ": [ 51, 1, 0, 10,-1],
|
|
"MFC ": [4000051, 1, 0, 10,-1],
|
|
"MRD ": [ 52, 1,-1, 4,-1, 11, 0, 5, 0],
|
|
"MNC ": [ 152, 1,-1, 4,-1, 11, 0, 5, 1],
|
|
"MRR ": [ 53, 1,-1, 4,-1, 11, 0, 5, 0],
|
|
"MIW ": [ 54, 1,-1, 4,-1, 11, 0, 7, 0],
|
|
"MIR ": [ 55, 1,-1, 4,-1, 11, 0],
|
|
"MOW ": [ 56, 1,-1, 4,-1, 11, 0, 7, 0],
|
|
"MOR ": [ 57, 1,-1, 4,-1, 11, 0],
|
|
"MPF ": [ 58, 4,-1, 11, 0],
|
|
"MPB ": [ 158, 4,-1, 11, 0],
|
|
"MPE ": [ 258, 4,-1],
|
|
"MIB ": [ 59, 1,-1, 4,-1],
|
|
"MIE ": [ 159, 1,-1, 4,-1],
|
|
"CRD ": [ 60, 1,-1, 4,-1, 5, 0, 6, 0],
|
|
"CNC ": [ 1060, 1,-1, 4,-1],
|
|
"CNCL ": [ 1160, 1,-1, 4,-1],
|
|
"CWR ": [ 161, 1,-1, 4,-1, 12,-1, 6, 0],
|
|
"CRF ": [ 62, 1,-1, 4,-1, 12,-1],
|
|
"CRFL ": [ 162, 1,-1, 4,-1, 12,-1],
|
|
"CWF ": [ 63, 1,-1, 4,-1, 12,-1],
|
|
"CRI ": [ 64, 1,-1, 4,-1],
|
|
"CWI ": [ 65, 1,-1, 4,-1],
|
|
"HPW ": [ 66, 1,-1, 6,-1],
|
|
"HPI ": [ 67, 1, 0],
|
|
|
|
// Pseudo-ops
|
|
"REM ": // remark line
|
|
[pseudoREM],
|
|
"IS ": // define label location
|
|
[pseudoIS, 19,-1],
|
|
"ORIGIN ": // set location counter
|
|
[pseudoORIGIN, 19,-1],
|
|
"FORMAT ": // assemble Cardatron format band
|
|
[pseudoFORMAT],
|
|
"PLACE ": // define address where code will be loaded
|
|
[pseudoPLACE, 19,-1],
|
|
"PLACED ": // end physical load offset
|
|
[pseudoPLACED],
|
|
"FORGET ": // discard internal tables (e.g., NAMES)
|
|
[pseudoFORGET],
|
|
"FILL ": // fill words with same value
|
|
[pseudoFILL],
|
|
"POOL ": // define literal pool location
|
|
[pseudoPOOL],
|
|
"DO ": // STP/BUN subroutine call
|
|
[pseudoDO],
|
|
"DJ ": // define "join" fields for a word
|
|
[pseudoDJ],
|
|
"J ": // build a word from "join" fields
|
|
[pseudoJ],
|
|
"END ": // finish assembly, output literal pool
|
|
[pseudoEND, 1, 0]
|
|
};
|
|
|
|
|
|
/*******************************************************************
|
|
* Miscellaneous Utilities *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function $$(id) {
|
|
return document.getElementById(id);
|
|
}
|
|
|
|
/**************************************/
|
|
function padTo(s, len) {
|
|
/* Pads the string "s" on the right with spaces or truncates it as
|
|
necessary to a length of "len" */
|
|
var result = s;
|
|
|
|
if (result.length > len) {
|
|
result = result.substring(0, len);
|
|
} else {
|
|
while (result.length-len > 8) {
|
|
result += " ";
|
|
}
|
|
|
|
while (result.length < len) {
|
|
result += " ";
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**************************************/
|
|
function padLeft(s, len, fill) {
|
|
/* Pads the string "s" on the left to length "len" with the filler character
|
|
"fill". If fill is empty or missing, space is used. If the initial string is
|
|
longer than "len", it is truncated on the left to that length */
|
|
var pad = (fill || " ").charAt(0);
|
|
var result = s.toString();
|
|
var rLen = result.length;
|
|
|
|
if (rLen > len) {
|
|
result = result.substring(rLen-len);
|
|
} else {
|
|
while (rLen < len) {
|
|
result = pad + result;
|
|
++rLen;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**************************************/
|
|
function padRight(s, len, fill) {
|
|
/* Pads the string "s" on the right to length "len" with the filler character
|
|
"fill". If fill is empty or missing, space is used. If the initial string is
|
|
longer than "len", it is truncated on the right to that length */
|
|
var pad = (fill || " ").charAt(0);
|
|
var result = s.toString();
|
|
var rLen = s.length;
|
|
|
|
if (rLen > len) {
|
|
result = result.substring(0, len);
|
|
} else {
|
|
while (rLen < len) {
|
|
result = result + pad;
|
|
++rLen;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**************************************/
|
|
function rTrim(s) {
|
|
/* Returns the string "s" stripped of any trailing whitespace */
|
|
var x = s.search(rTrimRex);
|
|
|
|
if (x < 0 ) {
|
|
return s;
|
|
} else if (x < 1) {
|
|
return "";
|
|
} else {
|
|
return s.substring(0, x);
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function tensComp(value) {
|
|
/* If "value" is algebraically negative, returns its 11-digit tens
|
|
complement. Otherwise returns the 11-digit value */
|
|
|
|
if (value < 0) {
|
|
return p10[11] + value%p10[11];
|
|
} else {
|
|
return value%p10[11];
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function applySign(word, sign) {
|
|
/* Applies an unsigned "sign" digit to a 220 "word" value. If the word
|
|
value is algebraically negative, it is first converted to a 10-digit
|
|
number with a 220 sign in the 11-th high-order digit. The low-order bit
|
|
of "sign" and the low-order bit of the word's sign digit are XOR-ed so
|
|
that each of those bits designates negation. Returns the new value as an
|
|
11-digit unsigned 220 word in binary */
|
|
var s = 0;
|
|
var value = 0;
|
|
|
|
if (word < 0) {
|
|
value = (-word)%p10[10];
|
|
s = 1;
|
|
} else {
|
|
value = word%p10[10];
|
|
s = (word%p10[11] - value)/p10[10];
|
|
}
|
|
|
|
return (sign%10 ^ s)*p10[10] + value;
|
|
}
|
|
|
|
/**************************************/
|
|
function getField(word, sL) {
|
|
/* Extracts an n-digit value from an 11-digit "word" and returns it.
|
|
"sL" is the same as for putField(). The word is a Javascript Number
|
|
object, but is treated as if it represents an 11-digit decimal integer */
|
|
var L = sL%10;
|
|
var s = (sL%100 - L)/10;
|
|
var result = applySign(word, 0);
|
|
|
|
s = (s == 0 ? 0 : 10-s);
|
|
L = (L == 0 ? 10 : L);
|
|
|
|
if (sL < 0 || sL > 99 || s+L > 11) {
|
|
result = -1;
|
|
} else {
|
|
result = (result%p10[s+L] - result%p10[s])/p10[s];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**************************************/
|
|
function putField(word, value, sL) {
|
|
/* Inserts an n-digit "value" into designated digits of an 11-digit
|
|
"word". "sL" is the partial-word field in standard 220 start-Length
|
|
notation. Note that Javascript flags literal integers of the form "0n"
|
|
as the old C-style octal literal notation is deprecated. This routine
|
|
uses only the two low-order digits of "sL", however, so you can pass sL
|
|
literal values like 104 (or even 57321604) for /04 without ill effect.
|
|
The "value" and "word" are Javascript Number objects, but are treated as
|
|
if they represent 11-digit decimal integers. If value is negative, it is
|
|
converted to its 10s-complement value before insertion into word.
|
|
Returns a new word with the inserted field */
|
|
var L = sL%10;
|
|
var s = (sL%100 - L)/10;
|
|
var upperPart = 0;
|
|
var lowerPart = 0;
|
|
var result = applySign(word, 0);
|
|
|
|
s = (s == 0 ? 0 : 10-s);
|
|
L = (L == 0 ? 10 : L);
|
|
|
|
if (sL < 0 || s+L > 11) {
|
|
printError("INVALID /SL VALUE: ", sL);
|
|
} else {
|
|
upperPart = result%p10[11] - result%p10[s+L];
|
|
if (s > 0) {
|
|
lowerPart = result%p10[s];
|
|
}
|
|
|
|
result = (tensComp(value)%p10[L])*p10[s] + upperPart + lowerPart;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Card Reader Input Module *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function readACard() {
|
|
/* Reads one card image from the buffer, pads or trims the image as
|
|
necessary to 80 columns, and fills in the global "cardData" structure
|
|
with the text and its location in the reader's buffer */
|
|
var bx = bufferOffset; // current buffer offset
|
|
var card; // card image, padded/truncated to 80 columns
|
|
var cardLength; // original delimited card image length
|
|
var match; // regular expression match result
|
|
|
|
cardData.offset = bx;
|
|
if (bx >= bufferLength) {
|
|
cardData.atEOF = true;
|
|
cardData.length = 0;
|
|
cardData.text = "7 <<EOF>>";
|
|
} else {
|
|
eolRex.lastIndex = bx;
|
|
match = eolRex.exec(buffer);
|
|
if (!match) {
|
|
card = "";
|
|
} else {
|
|
bx += match[0].length;
|
|
card = match[1];
|
|
}
|
|
|
|
cardLength = card.length;
|
|
if (cardLength > 80) {
|
|
card = card.substring(0, 80);
|
|
} else {
|
|
while (card.length <= 70) {
|
|
card += " ";
|
|
}
|
|
while (card.length < 80) {
|
|
card += " ";
|
|
}
|
|
}
|
|
|
|
cardData.length = bx - bufferOffset;
|
|
++cardData.serial;
|
|
cardData.text = card;
|
|
bufferOffset = bx;
|
|
}
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Output Listing Interface *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function clearPanel() {
|
|
/* Clears the text panel */
|
|
var kid;
|
|
|
|
while (kid = panel.firstChild) {
|
|
panel.removeChild(kid);
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function xlate220Word(word) {
|
|
/* Translate 10 digits in a 220 word to five ANSI characters */
|
|
var chars = ""; // translated characters
|
|
var code = 0; // current 220 character code
|
|
var count = 5; // chars/word
|
|
|
|
while (count > 0) {
|
|
code = word%100;
|
|
chars = xlate220ANSI[code] + chars;
|
|
word = (word - code)/100;
|
|
--count;
|
|
}
|
|
|
|
return chars;
|
|
}
|
|
|
|
/**************************************/
|
|
function printLine(text) {
|
|
/* Appends "text"+NL as a new text node to the panel DOM element */
|
|
var e = document.createTextNode(rTrim(text) + "\n");
|
|
|
|
panel.appendChild(e);
|
|
panel.scrollTop += 30
|
|
}
|
|
|
|
/**************************************/
|
|
function printWord(location, placeLoc, word) {
|
|
/* Formats the printer columns for one assembled word and returns the
|
|
resulting string */
|
|
var text = "";
|
|
|
|
if (placeLoc != location) {
|
|
text += padLeft(location, 4, "0");
|
|
} else {
|
|
text += " ";
|
|
}
|
|
|
|
text += " " + padLeft(placeLoc, 4, "0");
|
|
if (isNaN(word)) {
|
|
text += " - ---- -- ----";
|
|
} else {
|
|
text += padLeft((word - word%p10[10])/p10[10], 3) + " " +
|
|
padLeft((word%p10[10] - word%p10[ 6])/p10[ 6], 4, "0") + " " +
|
|
padLeft((word%p10[ 6] - word%p10[ 4])/p10[ 4], 2, "0") + " " +
|
|
padLeft((word%p10[ 4]), 4, "0");
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
/**************************************/
|
|
function printAssembly(card, assembly, flag) {
|
|
/* Prints the card image, assembly data, and any flag field to the panel */
|
|
var text = "";
|
|
var x = 0;
|
|
|
|
if (card) {
|
|
text = padTo(text, serialOffset) + padLeft(card.serial, 4);
|
|
}
|
|
|
|
if (assembly) {
|
|
text = padTo(text, wordOffset) +
|
|
printWord(assembly.location, assembly.placeLoc, assembly.words[0]);
|
|
}
|
|
|
|
if (flag) {
|
|
text = padTo(text, flagOffset) + flag;
|
|
} else if (printFlag) {
|
|
text = padTo(text, flagOffset) + printFlag;
|
|
printFlag = "";
|
|
}
|
|
|
|
if (card) {
|
|
text = padTo(text, cardOffset) + card.text.substring(4);
|
|
}
|
|
|
|
printLine(text);
|
|
if (assembly) {
|
|
for (x=1; x<assembly.count; ++x) {
|
|
text = padTo("", wordOffset) +
|
|
printWord(assembly.location+x, assembly.placeLoc+x, assembly.words[x]);
|
|
printLine(text);
|
|
} // for x
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function printError(msg) {
|
|
/* Prints an error message to the text panel and bumps the error count */
|
|
|
|
++errorCount;
|
|
errorTank.push("******** " + msg);
|
|
}
|
|
|
|
/**************************************/
|
|
function dumpErrorTank() {
|
|
/* Dumps the tank of error messages to the text panel */
|
|
var x;
|
|
|
|
if (errorTank.length > 0) {
|
|
if (!listPass) {
|
|
printAssembly(cardData, null, null);
|
|
}
|
|
|
|
for (x=0; x<errorTank.length; ++x) {
|
|
printLine(errorTank[x]);
|
|
}
|
|
|
|
printLine("");
|
|
errorTank = [];
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function dumpSymbolTable() {
|
|
/* Dumps the contents of the symbol table to the text panel */
|
|
var addr = 0; // label address
|
|
var keys = null; // sorted symbol table labels
|
|
var offset = 0; // print line offset
|
|
var text = ""; // print line
|
|
var x; // scratch index
|
|
|
|
printLine("");
|
|
printLine("SYMBOL TABLE");
|
|
printLine("");
|
|
|
|
keys = Object.keys(symTab).sort();
|
|
for (x=0; x<keys.length; ++x) {
|
|
if (offset + keys[x].length + 5 > 100) {
|
|
printLine(text);
|
|
text = "";
|
|
offset = 0;
|
|
}
|
|
|
|
addr = symTab[keys[x]];
|
|
if (isNaN(addr)) {
|
|
text += padLeft("****", offset-text.length+5);
|
|
} else {
|
|
text += padLeft(addr, offset-text.length+5);
|
|
}
|
|
|
|
text += " " + keys[x];
|
|
offset = Math.floor((text.length+19)/20)*20;
|
|
}
|
|
|
|
printLine(text);
|
|
printLine("");
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Address and Symbol Primitives *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function setLocation(pass2) {
|
|
/* Establishes the current assembly location from "nextLocation". If the
|
|
command being assembled has a label, assign the current location to that
|
|
label. If the label already has an assigned location, adjust it as
|
|
necessary to account for any change in literal pool size. Otherwise,
|
|
check whether the label location has changed and flag it as "HMM.." if
|
|
it has. Astonishingly, this is not considered to be an error, but rather
|
|
a sneaky way to do ORIGIN */
|
|
var labelLoc = 0;
|
|
var newLoc = 0;
|
|
|
|
if (assembly.label.length > 0) {
|
|
newLoc = nextLocation - assembly.labelOffset;
|
|
labelLoc = fetchLabel(token.text);
|
|
if (isNaN(labelLoc)) {
|
|
declareLabel(assembly.label, newLoc);
|
|
} else {
|
|
labelLoc += locationAdjustment;
|
|
if (labelLoc != newLoc) {
|
|
printFlag = "HMM..";
|
|
//if (pass2) {
|
|
// printError("LABEL LOCATION CHANGED: PASS1=" + labelLoc +
|
|
// ", PASS2=" + newLoc);
|
|
//} else { // define the label location
|
|
// printError("DUPLICATE LABEL DEFINITION: " + token.text +
|
|
// ", OLD=" + pass1Loc + ", NEW=" + token.value);
|
|
//}
|
|
|
|
nextLocation = labelLoc + assembly.labelOffset;
|
|
}
|
|
}
|
|
}
|
|
|
|
location = nextLocation;
|
|
assembly.location = location;
|
|
assembly.placeLoc = location + placeOffset;
|
|
assembly.count = 0;
|
|
assembly.words[0] = 0;
|
|
}
|
|
|
|
/**************************************/
|
|
function declareLabel(label, location) {
|
|
/* Specifies the the location of a symbolic label, creating it first
|
|
if necessary */
|
|
|
|
symTab[label] = location;
|
|
}
|
|
|
|
/**************************************/
|
|
function fetchLabel(label, errorMsg) {
|
|
/* Fetches the value of a symbolic label. If the label does not exist,
|
|
creates it first and sets its value to NaN */
|
|
|
|
if (label in symTab) {
|
|
return symTab[label];
|
|
} else {
|
|
if (errorMsg) {
|
|
printError(errorMsg + ": " + label);
|
|
}
|
|
return (symTab[label] = NaN); // yes, it's an assignment expression
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function buildPointLabelID(label, pointNr) {
|
|
/* Constructs the internal symbol table ID for a point label instance */
|
|
|
|
return label + "." + pointNr;
|
|
}
|
|
|
|
/**************************************/
|
|
function declarePointLabel(label, location) {
|
|
/* For each instance of a "point" label in the label field (i.e., a
|
|
numeric label that is referenced locally as nF or nB), creates a
|
|
pseudo-label in the symbol table with the next number for that point */
|
|
var labelID = ""; // unique label for the point label instance
|
|
var oldLoc = 0; // prior label location, if any
|
|
var pointNr = 0; // current point number
|
|
|
|
if (label in pointTab) {
|
|
pointNr = ++pointTab[label];
|
|
} else {
|
|
pointNr = pointTab[label] = 1;
|
|
}
|
|
|
|
labelID = buildPointLabelID(label, pointNr);
|
|
if (!(labelID in symTab)) {
|
|
declareLabel(labelID, location);
|
|
} else {
|
|
oldLoc = fetchLabel(labelID);
|
|
if (isNaN(oldLoc)) {
|
|
declareLabel(labelID, location);
|
|
} else if (!isNaN(location) && oldLoc != location) {
|
|
printError("DUPLICATE POINT LABEL -- IMPOSSIBLE: " + labelID);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function advancePointLabel(label) {
|
|
/* Advances to the next instance of a specified "point" label. Returns
|
|
the current label location */
|
|
var labelID = ""; // unique label for the point label instance
|
|
var pointNr = 0; // current point number
|
|
|
|
if (label in pointTab) {
|
|
pointNr = ++pointTab[label];
|
|
} else {
|
|
pointNr = pointTab[label] = 1;
|
|
}
|
|
|
|
labelID = buildPointLabelID(label, pointNr);
|
|
if (labelID in symTab) {
|
|
return symTab[labelID];
|
|
} else {
|
|
printError("LOST POINT LABEL: " + labelID);
|
|
return NaN;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function fetchPointLabel(label, forward) {
|
|
/* Accesses the location of the specified point label in the forward or
|
|
backward direction. Returns the location of that label, or NaN if the
|
|
label is currently undefined */
|
|
var addr = 0; // referenced label address
|
|
var labelID = ""; // unique label for the point label instance
|
|
var pointNr = 0; // current point number
|
|
|
|
if (label in pointTab) {
|
|
pointNr = pointTab[label];
|
|
} else {
|
|
pointNr = pointTab[label] = 0;
|
|
if (!forward) {
|
|
printError("BACKWARD REFERENCE TO UNDECLARED POINT LABEL: " + label);
|
|
}
|
|
}
|
|
|
|
if (forward) {
|
|
++pointNr;
|
|
}
|
|
|
|
// If this point label refers to the current location, use the prior one.
|
|
do {
|
|
labelID = buildPointLabelID(label, pointNr);
|
|
if (labelID in symTab) {
|
|
addr = symTab[labelID];
|
|
if (addr == location) {
|
|
--pointNr;
|
|
}
|
|
} else {
|
|
addr = NaN;
|
|
}
|
|
} while (addr == location);
|
|
|
|
return addr;
|
|
}
|
|
|
|
/**************************************/
|
|
function insertPool(values) {
|
|
/* Searches "poolData" for a sequence of words that matches the words in
|
|
"values". If a match is found, returns the index of the first (or only)
|
|
word in the sequence. If no match is found, appends "values" to
|
|
"poolData". Returns the index of the new or existing entry */
|
|
var done = true; // optimistic loop control
|
|
var index = 0; // current poolData[] index
|
|
var length = values.length; // length of new entry
|
|
var x = 0; // scratch index
|
|
|
|
do {
|
|
done = true;
|
|
if (poolData.length - index < length) {
|
|
index = -1;
|
|
} else {
|
|
index = poolData.indexOf(values[0], index);
|
|
}
|
|
|
|
if (index < 0) {
|
|
// No matching entry found, append the new entry.
|
|
index = poolData.length;
|
|
for (x=0; x<length; ++x) {
|
|
poolData.push(values[x]);
|
|
}
|
|
} else if (length > 1) {
|
|
// Check this entry for the remaining words in values[].
|
|
for (x=1; x<length; ++x) {
|
|
if (poolData[index+x] !== values[x]) {
|
|
done = false;
|
|
++index;
|
|
break; // out of for loop to continue while loop
|
|
}
|
|
}
|
|
}
|
|
} while (!done);
|
|
|
|
return index;
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Parsing Primitives *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function parseLabel(token, pass2) {
|
|
/* Parses any label on the current card image. If the initial column is
|
|
blank, there is no label, and sets token.type to tokEmpty. If the label
|
|
begins with a letter, sets token.type to tokLabel. If the label begins
|
|
with a number, sets token.type to tokForwardPoint. Anything else
|
|
generates an error. Fills in token.text with the label and token.value
|
|
with the assigned location */
|
|
var c = cardData.text.substring(labelOffset, signOffset).trim();
|
|
var match = null;
|
|
var offset = 0;
|
|
var pass1Loc = 0;
|
|
|
|
token.offset = labelOffset;
|
|
token.text = "";
|
|
token.value = 0;
|
|
|
|
if (c.length < 1) { // only spaces in the label field
|
|
token.type = tokEmpty;
|
|
} else { // something is in the label field
|
|
match = labelRex.exec(cardData.text.substring(labelOffset));
|
|
if (!match) { // whatever it is, it's not a label
|
|
printError("INVALID LABEL: " + c);
|
|
} else { // have a valid label
|
|
token.text = match[1];
|
|
token.newOffset = token.offset + match[0].length;
|
|
if (match[2]) { // label has offset value
|
|
offset = parseInt(match[2], 10);
|
|
token.value = offset;
|
|
}
|
|
|
|
if (!isNumericRex.test(token.text)) { // a regular label
|
|
token.type = tokLabel;
|
|
} else { // a "point" label
|
|
token.type = tokForwardPoint;
|
|
if (!pass2) {
|
|
declarePointLabel(token.text, NaN);
|
|
} else {
|
|
pass1Loc = advancePointLabel(token.text);
|
|
if (pass1Loc != nextLocation) {
|
|
printError("POINT LOCATION MISMATCH: PASS1=" + pass1Loc +
|
|
", PASS2=" + nextLocation);
|
|
}
|
|
}
|
|
|
|
// Reassign the token to be the actual label
|
|
token.text = buildPointLabelID(token.text, pointTab[token.text]);
|
|
}
|
|
|
|
if (token.newOffset < signOffset) {
|
|
printError("INVALID TEXT IN LABEL FIELD: " +
|
|
cardData.text.substring(token.newOffset, signOffset));
|
|
} else if (token.newOffset > opCodeOffset) { // overflowed to next card
|
|
if (listPass) {
|
|
printAssembly(cardData, null, null);
|
|
}
|
|
|
|
readACard();
|
|
token.newOffset = signOffset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function parseInteger(text, token) {
|
|
/* Parses a decimal number up to 11 digits in length starting at the
|
|
current offset in "text". Returns the 11-digit decimal string in
|
|
token.text and the arithmetic value as the function result */
|
|
var c = ""; // current parse character
|
|
var length = text.length; // length of operand string
|
|
var raw = ""; // raw parsed token text
|
|
var result = 0; // arithmetic result (binary)
|
|
var x = token.offset; // current offset into operand string
|
|
|
|
token.type = tokInteger;
|
|
while (x < length) {
|
|
c = text.charAt(x);
|
|
if (isNumericRex.test(c)) {
|
|
++x;
|
|
raw += c;
|
|
} else {
|
|
break; // out of while loop
|
|
}
|
|
}
|
|
|
|
token.newOffset = x;
|
|
if (raw.length > 11) {
|
|
printError("NUMERIC LITERAL LONGER THAN 11 DIGITS: " + raw);
|
|
raw = raw.substring(raw.length-11);
|
|
}
|
|
|
|
token.word = result = parseInt(raw, 10);
|
|
token.text = padLeft(result, 11, "0");
|
|
token.value = result;
|
|
return result;
|
|
}
|
|
|
|
/**************************************/
|
|
function parseString(text, token, singleWord) {
|
|
/* Parses a '-delimited string starting at the current offset in "text".
|
|
Returns the text of the string in token.text and an array of binary words
|
|
with the string translated to 220 character code as the function result.
|
|
The binary words will all have sign digits set to 2.
|
|
Complete strings less than multiple of five characters are padded to a
|
|
multiple of five with spaces (binary zeroes). If "singleWord" is true,
|
|
the string must be five or fewer characters in length */
|
|
var c; // current parse character
|
|
var code; // current 220 char code
|
|
var count = 0; // chars in current word
|
|
var length = text.length; // length of operand text
|
|
var raw = ""; // raw (ANSI) parsed string text
|
|
var values = []; // words of 220 char codes
|
|
var word = 2; // current 220 word with sign of 2
|
|
var x = token.offset; // current parsing offset
|
|
|
|
function appendCode(code) {
|
|
if (count >= 5) { // push out the full word
|
|
values.push(word);
|
|
word = 2;
|
|
count = 0;
|
|
}
|
|
|
|
word = word*100 + code;
|
|
++count;
|
|
}
|
|
|
|
++x; // bypass the initial "'"
|
|
|
|
while (x < length) {
|
|
c = text.charAt(x);
|
|
if (c == "'") {
|
|
break;
|
|
} else {
|
|
++x;
|
|
raw += c;
|
|
code = c.charCodeAt(0);
|
|
appendCode((code < xlateANSI220.length ? xlateANSI220[code]
|
|
: (code == 0xA4 ? 4 : 0))); // the lozenge
|
|
}
|
|
}
|
|
|
|
if (x >= length) {
|
|
printError("'-STRING NOT TERMINATED");
|
|
} else {
|
|
token.type = tokString; // string is complete, so...
|
|
++x; // bypass the terminating "'"
|
|
while (count < 5) { // pad out final word with spaces
|
|
appendCode(0);
|
|
}
|
|
|
|
values.push(word); // push out final word
|
|
if (singleWord && raw.length > 5) {
|
|
printError("STRING OCCUPIES MORE THAN ONE WORD");
|
|
}
|
|
}
|
|
|
|
token.newOffset = x;
|
|
token.text = raw;
|
|
token.word = word; // save any partial word for continuation
|
|
token.value = 0;
|
|
return values;
|
|
}
|
|
|
|
/**************************************/
|
|
function parsePrimaryToken(token, pass2) {
|
|
/* Parses the next primary token from the card starting at "token.offset",
|
|
returning the parsed item in "token.text" and the updated parse offset
|
|
in "token.newOffset". "token.type" indicates the type */
|
|
var c; // current parse character
|
|
var raw = ""; // raw text of parsed token
|
|
var text = cardData.text; // card image
|
|
var val = 0; // temp used with numeric pool literals
|
|
var values = null; // array reference for pool data
|
|
var x = token.offset; // current offset into operand string
|
|
|
|
if (x >= sequenceOffset) { // empty primary
|
|
token.type = tokEmpty;
|
|
token.newOffset = sequenceOffset;
|
|
token.text = "";
|
|
} else {
|
|
c = text.charAt(x);
|
|
switch (true) {
|
|
case (isAlphaRex.test(c)): // parse regular label
|
|
token.type = tokLabel;
|
|
raw = c;
|
|
while (++x < sequenceOffset) {
|
|
c = text.charAt(x);
|
|
if (isLabelRex.test(c)) {
|
|
raw += c;
|
|
} else {
|
|
break; // out of while loop
|
|
}
|
|
}
|
|
|
|
token.text = raw;
|
|
token.newOffset = x;
|
|
break;
|
|
|
|
case (isNumericRex.test(c)): // parse integer literal or point label
|
|
token.type = tokInteger;
|
|
token.value = parseInteger(text, token);
|
|
if (token.newOffset < sequenceOffset) {
|
|
c = text.charAt(token.newOffset);
|
|
if (c == "F") {
|
|
token.type = tokForwardPoint;
|
|
token.text = text.substring(x, token.newOffset);
|
|
++token.newOffset;
|
|
} else if (c == "B") {
|
|
token.type = tokBackwardPoint;
|
|
token.text = text.substring(x, token.newOffset);
|
|
++token.newOffset;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case (c == "-"): // parse signed integer literal
|
|
token.type = tokInteger;
|
|
++token.offset;
|
|
token.value = -parseInteger(text, token);
|
|
break;
|
|
|
|
case (c == "$"): // parse current location counter
|
|
token.type = tokLocation;
|
|
token.text = c;
|
|
token.value = location;
|
|
token.newOffset = x+1;
|
|
break;
|
|
|
|
case (c == "="): // parse pool numeric literal
|
|
raw = c;
|
|
++x;
|
|
if (x >= sequenceOffset) {
|
|
printError("INVALID POOL LITERAL SYNTAX");
|
|
token.newOffset = x;
|
|
} else {
|
|
token.offset = x;
|
|
val = parseExpression(token, pass2, false);
|
|
if (token.newOffset < sequenceOffset && text.charAt(token.newOffset) == "=") {
|
|
++token.newOffset;
|
|
} else {
|
|
printError("NUMERIC LITERAL NOT TERMINATED BY \"=\"");
|
|
}
|
|
}
|
|
|
|
values = [val];
|
|
token.type = tokLiteral;
|
|
token.text = "=" + padLeft(applySign(val, 0), 11, "0") + "=";
|
|
break;
|
|
|
|
case (c == "'"): // parse pool string literal
|
|
values = parseString(text, token, true);
|
|
token.type = tokLiteral; // parseString sets type to tokString
|
|
token.text = "'" + token.text + "'";
|
|
break;
|
|
|
|
case (c == " "): // empty primary
|
|
token.type = tokEmpty;
|
|
token.newOffset = x;
|
|
token.text = "";
|
|
break;
|
|
|
|
default: // not valid
|
|
printError("PRIMARY CANNOT START WITH \"" + c + "\"");
|
|
token.type = tokEmpty;
|
|
token.newOffset = x;
|
|
token.text = "";
|
|
break;
|
|
} // switch true
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
/**************************************/
|
|
function parsePrimary(token, pass2) {
|
|
/* Evaluates the current offset in the card image as an expression
|
|
primary and returns its binary value. If "pass2" is true, the address
|
|
must resolve to a known address, otherwise an error is issued. If the
|
|
address cannot be resolved, returns NaN */
|
|
var sL = 0; // field start-length
|
|
var val = NaN; // value of primary
|
|
var values = null; // literal pool words, if any
|
|
|
|
if (token.offset >= sequenceOffset) {
|
|
val = token.value = 0;
|
|
token.text = "";
|
|
token.type = tokEmpty;
|
|
} else if (cardData.text.charAt(token.offset) == " ") {
|
|
val = token.value = 0;
|
|
token.text = "";
|
|
token.type = tokEmpty;
|
|
} else if (cardData.text.charAt(token.offset) == "(") {
|
|
++token.offset;
|
|
val = parseExpression(token, pass2, false);
|
|
if (token.newOffset < sequenceOffset && cardData.text.charAt(token.newOffset) == ")") {
|
|
++token.newOffset;
|
|
} else {
|
|
printError("NESTED EXPRESSION MUST END WITH \")\"");
|
|
}
|
|
} else {
|
|
values = parsePrimaryToken(token, pass2);
|
|
switch (token.type) {
|
|
case tokLocation: // $: current location counter
|
|
val = token.value;
|
|
break;
|
|
|
|
case tokInteger: // integer literal
|
|
val = token.value;
|
|
break;
|
|
|
|
case tokLabel: // regular label
|
|
val = fetchLabel(token.text);
|
|
break;
|
|
|
|
case tokBackwardPoint: // backward point label
|
|
case tokForwardPoint: // forward point label
|
|
val = fetchPointLabel(token.text, token.type == tokForwardPoint);
|
|
break;
|
|
|
|
case tokLiteral: // pool literal
|
|
val = values[0];
|
|
if (isNaN(val)) {
|
|
++poolUnresolved;
|
|
} else {
|
|
val = insertPool(values) + poolLocation;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
val = NaN;
|
|
printError("INVALID ADDRESS TOKEN TYPE CODE: " + token.type);
|
|
break;
|
|
} // switch token.type
|
|
|
|
if (pass2 && isNaN(val)) {
|
|
printError("MUST BE A RESOLVED ADDRESS: " + token.text);
|
|
}
|
|
}
|
|
|
|
token.offset = token.newOffset;
|
|
if (token.offset < sequenceOffset && cardData.text.charAt(token.offset) == "(") {
|
|
++token.offset;
|
|
sL = parseInteger(cardData.text, token);
|
|
if (token.newOffset < sequenceOffset && cardData.text.charAt(token.newOffset) == ")") {
|
|
++token.newOffset;
|
|
val = putField(0, val, sL);
|
|
} else {
|
|
printError("INVALID PRIMARY (SL) SUFFIX SYNTAX");
|
|
}
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
/**************************************/
|
|
function parseExpression(token, pass2, inhibitDivision) {
|
|
/* Parses a literal or address expresssion from "token" and returns its
|
|
binary value. If "inhibitDivision" is true, a single "/" is treated as
|
|
end of expression, to allow for partial-word (/sL) notation in first-
|
|
level address expressions */
|
|
var done = false; // loop control
|
|
var value = 0; // expression value
|
|
var value2 = NaN; // RHS value for dyadic operator
|
|
|
|
value = parsePrimary(token, pass2);
|
|
while (token.newOffset < sequenceOffset && !done) {
|
|
token.offset = token.newOffset;
|
|
switch (cardData.text.charAt(token.offset)) {
|
|
case "+":
|
|
++token.offset;
|
|
value += parsePrimary(token, pass2);
|
|
break;
|
|
case "-":
|
|
++token.offset;
|
|
value -= parsePrimary(token, pass2);
|
|
break;
|
|
case "*":
|
|
++token.offset;
|
|
if (token.offset < sequenceOffset && cardData.text.charAt(token.offset) == "*") {
|
|
++token.offset;
|
|
value *= parsePrimary(token, pass2);
|
|
} else {
|
|
done = true;
|
|
token.newOffset = token.offset+1;
|
|
printError("INVALID PRIMARY * OPERATOR: " + cardData.text.charAt(token.offset));
|
|
}
|
|
break;
|
|
case "/":
|
|
if (token.offset+1 < sequenceOffset && cardData.text.charAt(token.offset+1) == "/") {
|
|
token.offset += 2;
|
|
value %= parsePrimary(token, pass2);
|
|
} else if (inhibitDivision) {
|
|
done = true;
|
|
} else {
|
|
++token.offset;
|
|
value2 = parsePrimary(token, pass2);
|
|
value = (value - value%value2)/value2; // integer division with truncation
|
|
}
|
|
break;
|
|
default:
|
|
done = true;
|
|
break;
|
|
} // switch
|
|
} // while token.offset
|
|
|
|
return value;
|
|
}
|
|
|
|
/**************************************/
|
|
function parseSPOOperand(token, pass2, opCode) {
|
|
/* Parses the special SPO operand, which can be an address,count pair or
|
|
a multi-part string with interspersed letter codes for carriage-control
|
|
characters. The string version can be continued across multiple cards by
|
|
placing " ..." after the last phrase on the line. Returns the updated
|
|
op code with address and count inserted. Leaves the v and d digits for
|
|
decimal-point insertion undisturbed */
|
|
var addr = 0; // text buffer address
|
|
var c = ""; // current parse character
|
|
var count = 0; // chars in current word
|
|
var done = false; // loop control
|
|
var raw = ""; // raw (ANSI) parsed string text
|
|
var subValues = null; // words of 220 char codes from a sub-string
|
|
var values = []; // words of 220 char codes
|
|
var word = 2; // current 220 word with sign of 2
|
|
var x = token.offset; // current parsing offset
|
|
|
|
var subToken = {
|
|
type: tokEmpty,
|
|
offset: 0,
|
|
newOffset: 0,
|
|
text: "",
|
|
word: 0,
|
|
value: 0};
|
|
|
|
|
|
function appendCode(code) {
|
|
if (count >= 5) { // push out the full word
|
|
values.push(word);
|
|
word = 2;
|
|
count = 0;
|
|
}
|
|
|
|
word = word*100 + code;
|
|
++count;
|
|
}
|
|
|
|
function appendSubstring(values, count) {
|
|
var code = 0;
|
|
var subCount = 0;
|
|
var subWord = values[0];
|
|
var x = 0;
|
|
|
|
while (count > 0) {
|
|
--count;
|
|
code = (subWord%p10[10] - subWord%p10[8])/p10[8];
|
|
appendCode(code);
|
|
if (++subCount < 5) {
|
|
subWord = (subWord%p10[8])*100;
|
|
} else {
|
|
subCount = 0;
|
|
subWord = subValues[++x];
|
|
}
|
|
} // while count
|
|
}
|
|
|
|
if (!spoStringRex.test(cardData.text.substring(token.offset))) {
|
|
// Not a SPO string -- parse the address,count pair.
|
|
addr = parseExpression(token, pass2, false);
|
|
if (token.newOffset >= sequenceOffset || cardData.text.charAt(token.newOffset) != ",") {
|
|
printError("COUNT REQUIRED FOR SPO ADDRESS OPERAND");
|
|
} else {
|
|
token.offset = token.newOffset+1;
|
|
count = parseExpression(token, true, false);
|
|
}
|
|
} else {
|
|
// Special SPO multi-part string parameter that can continue accross cards.
|
|
while (!done) {
|
|
c = cardData.text.charAt(x);
|
|
switch (c) {
|
|
case "I": // non-printing space
|
|
raw += "9";
|
|
appendCode( 2);
|
|
++x;
|
|
break;
|
|
case "L": // form feed
|
|
raw += "*";
|
|
appendCode(15);
|
|
++x;
|
|
break;
|
|
case "R": // carriage-return/line-feed
|
|
raw += "$";
|
|
appendCode(16);
|
|
++x;
|
|
break;
|
|
case "T": // horizontal tab
|
|
raw += ",";
|
|
appendCode(26);
|
|
++x;
|
|
break;
|
|
case "'":
|
|
subToken.offset = x;
|
|
subValues = parseString(cardData.text, subToken, false);
|
|
raw += subToken.text;
|
|
token.offset = x = subToken.newOffset;
|
|
appendSubstring(subValues, subToken.text.length);
|
|
break;
|
|
case " ":
|
|
if (x >= sequenceOffset-4 || cardData.text.indexOf(" ...", x) != x) {
|
|
done = true;
|
|
} else {
|
|
if (listPass) {
|
|
printAssembly(cardData, null, null);
|
|
}
|
|
|
|
readACard();
|
|
token.offset = x = operandOffset;
|
|
}
|
|
break;
|
|
default:
|
|
printError("INVALID SPO STRING CODE: \"" + c + "\"");
|
|
done = true;
|
|
break;
|
|
} // switch c
|
|
}
|
|
|
|
while (count < 5) { // pad out final word with spaces
|
|
appendCode(0);
|
|
}
|
|
|
|
values.push(word); // push out final word
|
|
addr = insertPool(values) + poolLocation;
|
|
count = values.length; // set length for the SPO op code
|
|
token.value = addr; // literal pool address
|
|
token.text = raw;
|
|
token.type = tokLiteral;
|
|
token.newOffset = x;
|
|
}
|
|
|
|
token.word = 0;
|
|
word = putField(opCode, addr, 4);
|
|
word = putField(word, count, 32);
|
|
return word;
|
|
|
|
}
|
|
|
|
/**************************************/
|
|
function parsePartialWordDesignator(token) {
|
|
/* Parses an optional partial-word designator in /sL format. If the
|
|
designatort is present, returns sL as a non-negative integer. If not,
|
|
returns -1 */
|
|
var sL = -1;
|
|
var x = token.offset;
|
|
|
|
if (x < sequenceOffset-3 && cardData.text.charAt(x) == "/") {
|
|
++x;
|
|
if (isNumericRex.test(cardData.text.charAt(x))) {
|
|
if (isNumericRex.test(cardData.text.charAt(x+1))) {
|
|
sL = parseInt(cardData.text.substring(x, x+2), 10);
|
|
token.newOffset = x+2;
|
|
}
|
|
}
|
|
}
|
|
|
|
return sL;
|
|
}
|
|
|
|
/**************************************/
|
|
function parseOperandField(token, word, pass2, operandType, defaultValue) {
|
|
/* Parses one comma-delimited field from the operand string. "word" is
|
|
the word assembled thus far; "pass2" is true if this is the second
|
|
assembly pass; "operandType" is the type code from opTable;
|
|
"defaultValue" is the default operand value if it is not specified. If
|
|
"operandType" is null, then this is an additional field and must be
|
|
suffixed with the field position as "(sL)". Returns the word updated
|
|
by the new field value */
|
|
var result = word; // result word modified by this field
|
|
var sL = 0; // partial-word designator
|
|
var val = 0; // operand field value
|
|
|
|
switch (operandType) {
|
|
case 13: // special SPO address or string paramter
|
|
val = NaN; // parsed specially below
|
|
break;
|
|
default:
|
|
if (token.offset >= sequenceOffset) {
|
|
val = defaultValue;
|
|
} else {
|
|
switch (cardData.text.charAt(token.offset)) {
|
|
case " ":
|
|
val = defaultValue;
|
|
break;
|
|
case ",":
|
|
token.newOffset = token.offset+1;
|
|
val = defaultValue;
|
|
break;
|
|
default:
|
|
val = parseExpression(token, pass2, true);
|
|
break;
|
|
} // switch token char
|
|
}
|
|
break;
|
|
} // switch operandType 1
|
|
|
|
switch (operandType) {
|
|
case 1: // address in /04, no /sL allowed
|
|
result = putField(result, val, 4);
|
|
break;
|
|
|
|
case 2: // address in /04, /sL in /22 required
|
|
result = putField(result, val, 4);
|
|
sL = parsePartialWordDesignator(token);
|
|
if (sL < 0) {
|
|
printError("/SL REQUIRED AFTER ADDRESS");
|
|
} else {
|
|
result = putField(result, sL, 22);
|
|
}
|
|
break;
|
|
|
|
case 3: // address in/04, /sL in /22 optional, if specified sets /31=1
|
|
result = putField(result, val, 4);
|
|
sL = parsePartialWordDesignator(token);
|
|
if (sL >= 0) {
|
|
result = putField(result, sL, 22);
|
|
result = putField(result, 1, 31);
|
|
}
|
|
break;
|
|
|
|
case 4: // unit or count digit inserted in /11
|
|
result = putField(result, val, 11);
|
|
break;
|
|
|
|
case 5: // variant digit inserted in /41
|
|
result = putField(result, val, 41);
|
|
break;
|
|
|
|
case 6: // value in /32
|
|
result = putField(result, val, 32);
|
|
break;
|
|
|
|
case 7: // value in /42
|
|
result = putField(result, val, 42);
|
|
break;
|
|
|
|
case 8: // control digit in /11, if specified sets /41=1
|
|
if (val >= 0 && val < 10) {
|
|
result = putField(result, val, 11);
|
|
result = putField(result, 1, 41);
|
|
}
|
|
break;
|
|
|
|
case 9: // value in /44
|
|
result = putField(result, val, 44);
|
|
break;
|
|
|
|
case 10: // mag tape unit/lane as LLU, inserted as ULL in /33
|
|
val = (val%10)*100 + (val%1000 - val%10)/10; // convert from LLU to ULL
|
|
result = putField(result, val, 33);
|
|
break;
|
|
|
|
case 11: // count in /21
|
|
result = putField(result, val, 21);
|
|
break;
|
|
|
|
case 12: // format band digit added to /41: (B-1)*2
|
|
val = getField(result, 41)%2 + (val-1)*2;
|
|
result = putField(result, val, 41);
|
|
break;
|
|
|
|
case 13: // special SPO address or string parameter
|
|
result = parseSPOOperand(token, pass2, result);
|
|
break;
|
|
|
|
case 19: // resolved address only
|
|
if (isNaN(val)) {
|
|
printError("MUST HAVE FULLY-RESOLVED ADDRESS");
|
|
} else {
|
|
result = putField(result, val, 4);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
printError("INVALID OPERAND TYPE: " + operandType);
|
|
break;
|
|
} // switch operandType 2
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Pseudo-operators *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function generateFormatBand(token) {
|
|
/* Generates the 29 words of a Cardatron format band from the operand
|
|
text and returns an array of 29 words with band data. Examples:
|
|
|
|
FBGR INPUT,16(P5A),P10Z
|
|
FBGR PRINT,49B,T5A,T1A1B2A4Z,T10N,T8Z1A,XB6Z2A,48B MONITOR
|
|
FBGR PRINT,32B,11(T5A),33B ERROR MESSAGE FORMAT BAND
|
|
FBGR INPUT,T2Z1B4A,15(T5A)
|
|
FBGR INPUT,16(P5A),P10Z
|
|
FBGR PRINT,49B,TZZZZZZNNNN,BBB,SBNNNNBNNBNNNN,BT5A,44B
|
|
FBGR PRINT,49B,TZZZZZZNNNN,BBB,SBNNNNBNNBZZZZ,5BT5A,44B
|
|
FBGR PRINT,49B,TZZZZZZNNNN,BBB,T6Z10BNNNN,50B
|
|
FBGR PRINT,7(T5A),85B
|
|
FBGR PRINT,TZZNNNNZZZZ,4B,16(T5A),32B
|
|
|
|
The first parameter can be "INPUT", "PUNCH", or "PRINT". The first two
|
|
values generate bands for 80 card columns; the third generates a band
|
|
for 120 print columns. The remaining comma-delimited parameters are
|
|
format phrases, one per 220 word. Each phrase may be prefixed by a
|
|
numeric repeat count. Allowable format codes are:
|
|
|
|
A: copy two zone/numeric digits (one character)
|
|
B: input: ignore two digits in memory
|
|
output: supply two zero zone/numeric digits on output
|
|
N: copy one digit to/from memory and a card column, normally
|
|
supplying a zero for the zone
|
|
P: input: store a zero for the sign digit
|
|
output: ignore sign digit in memory
|
|
S: input: copy one numeric digit, must be in sign-digit position
|
|
output: copy zone digit as a separate column
|
|
T: input: like P, but store a 2 for the sign instead of zero
|
|
output: same as P
|
|
X: input: store zone digit in memory
|
|
output: copy a zone digit as an overpunch for next code
|
|
Z: input: store zero digit in memory
|
|
output: supply a zero zone/numeric digit to the device
|
|
(: start of repeating group
|
|
): end of repeating group
|
|
|
|
This generator works by expanding all repeat counts and groups to a linear
|
|
string of format codes, then walking that string backwards to generate the
|
|
Cardatron format band digits. If this is an input format, the result is
|
|
padded with 11 zero codes to push the final word into the 220's D register.
|
|
For both input and output bands, the result is padded with 3 (delete) codes
|
|
out to a total of 29 words. Note that the band words are returned in
|
|
reverse order, to match the right-to-left processing of card columns by the
|
|
Cardatron and the order in which the 220 fetched words from memory when
|
|
loading format bands. If all that sounds very confusing, it's why we have
|
|
format band generators */
|
|
var band = []; // generated format band words
|
|
var bandType; // band type: INPUT, PRINT, PUNCH
|
|
var codes = ""; // expanded format band phrases
|
|
var cx = 0; // current operand char offset
|
|
var dx = 0; // band word digit index
|
|
var oLen; // length of phrase text in operand
|
|
var operand = cardData.text.substring(token.offset, sequenceOffset);
|
|
var word = 0; // generated band word
|
|
var wx = 0; // band word digit index (power of 10)
|
|
var x; // scratch index
|
|
|
|
//--------------------------------------
|
|
function emitBandDigits(digits, count) {
|
|
/* Moves digits into format band words starting at the low-order
|
|
digit of the word. Handles overflow to the next word */
|
|
var digit; // current output digit
|
|
|
|
while (count > 0) {
|
|
--count;
|
|
digit = digits%10;
|
|
digits = (digits-digit)/10;
|
|
if (wx < 11) {
|
|
word += digit*p10[wx];
|
|
++wx;
|
|
} else {
|
|
band.unshift(word);
|
|
word = digit;
|
|
wx = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------
|
|
function flushBandWord(digit) {
|
|
/* If a partial word exists, fill the remainder with "digit" values,
|
|
then push it to the band array */
|
|
|
|
while (wx < 11) {
|
|
emitBandDigits(digit, 1);
|
|
}
|
|
|
|
band.unshift(word);
|
|
wx = 0;
|
|
word = 0;
|
|
}
|
|
|
|
//--------------------------------------
|
|
function expandPhrase(cx, level) {
|
|
/* Expands repeat counts and groups to a string of individual phrase
|
|
codes in "codes"; adjusts phrase codes for X-code zone-digit behavior */
|
|
var c; // current phrase character
|
|
var count = 0; // repeat count
|
|
var done = false; // loop control
|
|
var overpunch = false; // X-code zone punch flag
|
|
var px = cx; // current phrease char offset
|
|
var tx; // temp offset for repeating groups
|
|
|
|
do {
|
|
if (px >= oLen) {
|
|
done = true;
|
|
} else {
|
|
c = operand.charAt(px);
|
|
++px;
|
|
switch (c) {
|
|
case "0":
|
|
case "1":
|
|
case "2":
|
|
case "3":
|
|
case "4":
|
|
case "5":
|
|
case "6":
|
|
case "7":
|
|
case "8":
|
|
case "9":
|
|
count = count*10 + (c.charCodeAt(0) - ("0").charCodeAt(0));
|
|
break;
|
|
case "A":
|
|
case "P":
|
|
case "S":
|
|
case "T":
|
|
case "Z":
|
|
do {
|
|
--count;
|
|
codes += c;
|
|
} while (count > 0);
|
|
count = 0;
|
|
break;
|
|
case "B":
|
|
do {
|
|
--count;
|
|
if (overpunch) {
|
|
codes += "Y"; // half column: standalone sign
|
|
overpunch = false;
|
|
} else {
|
|
codes += c;
|
|
}
|
|
} while (count > 0);
|
|
count = 0;
|
|
break;
|
|
case "N":
|
|
do {
|
|
--count;
|
|
if (overpunch) {
|
|
codes += "D"; // half column: overpunched sign
|
|
overpunch = false;
|
|
} else {
|
|
codes += c;
|
|
}
|
|
} while (count > 0);
|
|
count = 0;
|
|
break;
|
|
case "X":
|
|
overpunch = true;
|
|
do {
|
|
--count;
|
|
codes += c; // half-column zone digit
|
|
} while (count > 0);
|
|
count = 0;
|
|
break;
|
|
case "(":
|
|
do {
|
|
--count;
|
|
tx = expandPhrase(px, level+1);
|
|
} while (count > 0);
|
|
count = 0;
|
|
px = tx;
|
|
break;
|
|
case ")":
|
|
if (level < 1) {
|
|
printError("EXCESS RIGHT PARENTHESIS");
|
|
}
|
|
done = true;
|
|
break;
|
|
case ",":
|
|
if (level > 0) {
|
|
printError("COMMA IN NESTED PHRASE");
|
|
}
|
|
codes += c;
|
|
count = 0;
|
|
break;
|
|
default:
|
|
printError("INVALID FORMAT BAND CHARACTER: " + c);
|
|
break;
|
|
} // switch c
|
|
}
|
|
} while (!done);
|
|
|
|
return px;
|
|
}
|
|
|
|
//--------------------------------------
|
|
function buildInputBand(cols) {
|
|
/* Builds an input band from the expanded phrase code string
|
|
in "codes", placing the result in the "band" array */
|
|
var c; // current phrase code
|
|
var colCount = 0; // number of card columns
|
|
var cx; // current offset into codes
|
|
var dx = 0; // current memory word digit index
|
|
|
|
for (cx=codes.length-1; cx>=0; --cx) {
|
|
c = codes.charAt(cx);
|
|
switch (c) {
|
|
case "A": // copy alphanumeric column
|
|
if (dx & 1) {
|
|
printError("A CODE ON ODD DIGIT: " + cx);
|
|
} else if (dx > 9) {
|
|
printError("A CODE ON SIGN DIGIT: " + cx);
|
|
} else {
|
|
emitBandDigits(11, 2);
|
|
}
|
|
colCount += 1;
|
|
dx += 2;
|
|
break;
|
|
case "B": // ignore column
|
|
emitBandDigits(33, 2);
|
|
colCount += 1;
|
|
break;
|
|
case "D": // copy numeric half-column for overpunched sign
|
|
emitBandDigits(1, 1);
|
|
colCount +=1;
|
|
dx += 1;
|
|
break;
|
|
case "N": // copy numeric column
|
|
emitBandDigits(31, 2);
|
|
colCount += 1;
|
|
dx += 1;
|
|
break;
|
|
case "P": // insert zero in memory word for sign
|
|
if (dx < 10) {
|
|
printError("P CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(0, 1);
|
|
dx += 1;
|
|
break;
|
|
case "S": // copy numeric column for sign
|
|
if (dx < 10) {
|
|
printError("S CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(31, 2);
|
|
colCount += 1;
|
|
dx += 1;
|
|
break;
|
|
case "T": // insert 2 in memory word sign
|
|
if (dx < 10) {
|
|
printError("T CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(2, 1);
|
|
dx += 1;
|
|
break;
|
|
case "X": // copy zone half-column for sign
|
|
emitBandDigits(1, 1);
|
|
dx += 1;
|
|
break;
|
|
case "Y": // delete half-column (usually for overpunched sign)
|
|
emitBandDigits(3, 1);
|
|
colCount += 1;
|
|
break;
|
|
case "Z": // insert zero in memory word
|
|
emitBandDigits(0, 1);
|
|
dx += 1;
|
|
break;
|
|
case ",": // check that phrase/word boundaries match
|
|
if (dx > 0) {
|
|
printError("WRONG NUMBER OF DIGITS IN WORD: " + dx);
|
|
}
|
|
break;
|
|
default:
|
|
printError("INVALID INPUT BAND CODE: " + c);
|
|
break;
|
|
} // switch c
|
|
|
|
if (dx > 10) {
|
|
if (dx > 11) {
|
|
printError("EXCESS NUMBER OF DIGITS IN WORD: " + dx);
|
|
}
|
|
dx = 0;
|
|
}
|
|
} // for cx
|
|
|
|
if (dx > 0) {
|
|
printError("FINAL BAND WORD INCOMPLETE: " + dx);
|
|
}
|
|
|
|
if (colCount != cols) {
|
|
printError("WRONG NUMBER OF COLUMNS: " + colCount);
|
|
}
|
|
|
|
emitBandDigits(0, 11); // push out last word from Cardatron
|
|
flushBandWord(3); // pad out the last word with 3s
|
|
}
|
|
|
|
//--------------------------------------
|
|
function buildOutputBand(cols) {
|
|
/* Builds an output band from the expanded phrase code string
|
|
in "codes", placing the result in the "band" array */
|
|
var c; // current phrase code
|
|
var colCount = 0; // number of columns on card/line
|
|
var cx; // current offset into codes
|
|
var dx = 0; // current memory word digit index
|
|
|
|
for (cx=codes.length-1; cx>=0; --cx) {
|
|
c = codes.charAt(cx);
|
|
switch (c) {
|
|
case "A": // copy alphanumeric column
|
|
if (dx & 1) {
|
|
printError("A CODE ON ODD DIGIT: " + cx);
|
|
} else if (dx > 9) {
|
|
printError("A CODE ON SIGN DIGIT: " + cx);
|
|
} else {
|
|
emitBandDigits(11, 2);
|
|
}
|
|
colCount += 1;
|
|
dx += 2;
|
|
break;
|
|
case "B": // supply zeroes for blank column
|
|
emitBandDigits(0, 2);
|
|
colCount += 1;
|
|
break;
|
|
case "D": // copy numeric half-column for overpunched sign
|
|
emitBandDigits(1, 1);
|
|
colCount +=1;
|
|
dx += 1;
|
|
break;
|
|
case "N": // copy numeric column
|
|
emitBandDigits(2, 1);
|
|
colCount += 1;
|
|
dx += 1;
|
|
break;
|
|
case "P": // ignore sign digit in word
|
|
case "T":
|
|
if (dx < 10) {
|
|
printError(c + " CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(3, 1);
|
|
dx += 1;
|
|
break;
|
|
case "S": // copy numeric column for standalone sign
|
|
if (dx < 10) {
|
|
printError("S CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(2, 1);
|
|
colCount += 1;
|
|
dx += 1;
|
|
break;
|
|
case "X": // copy zone digit for sign
|
|
emitBandDigits(1, 1);
|
|
dx += 1;
|
|
break;
|
|
case "Y": // supply numeric half-column for standalone sign
|
|
emitBandDigits(0, 1);
|
|
colCount += 1;
|
|
break;
|
|
case "Z": // ignore digit in memory word
|
|
emitBandDigits(3, 1);
|
|
dx += 1;
|
|
break;
|
|
case ",": // check that phrase/word boundaries match
|
|
if (dx > 0) {
|
|
printError("WRONG NUMBER OF DIGITS IN WORD: " + dx);
|
|
}
|
|
break;
|
|
default:
|
|
printError("INVALID OUTPUT BAND CODE: " + c);
|
|
break;
|
|
} // switch c
|
|
|
|
if (dx > 10) {
|
|
if (dx > 11) {
|
|
printError("EXCESS NUMBER OF DIGITS IN WORD: " + dx);
|
|
}
|
|
dx = 0;
|
|
}
|
|
} // for cx
|
|
|
|
if (dx > 0) {
|
|
printError("FINAL BAND WORD INCOMPLETE: " + dx);
|
|
}
|
|
|
|
if (colCount != cols) {
|
|
printError("WRONG NUMBER OF COLUMNS: " + colCount);
|
|
}
|
|
|
|
flushBandWord(3); // pad out the last word with 3s
|
|
}
|
|
|
|
//-------------------------------------- start of generateFormatBand()
|
|
x = operand.indexOf(" ");
|
|
oLen = (x < 0 ? operand.length : x);
|
|
|
|
x = operand.indexOf(",");
|
|
bandType = (x < 0 ? "" : operand.substring(0,x));
|
|
|
|
cx = x+1;
|
|
while (cx < oLen) {
|
|
cx = expandPhrase(cx, 0);
|
|
}
|
|
|
|
//printLine(codes); // DEBUG
|
|
|
|
switch (bandType) {
|
|
case "INPUT":
|
|
buildInputBand(80);
|
|
break;
|
|
case "PUNCH":
|
|
buildOutputBand(80);
|
|
break;
|
|
case "PRINT":
|
|
buildOutputBand(120);
|
|
break;
|
|
default:
|
|
printError("INVALID FORMAT BAND TYPE: " + bandType);
|
|
break;
|
|
}
|
|
|
|
while (band.length < 29) {
|
|
band.unshift(33333333333);
|
|
}
|
|
|
|
return band;
|
|
}
|
|
|
|
/**************************************/
|
|
function buildPool(pass2) {
|
|
/* Awright -- everybody outta tha pool */
|
|
var flag = ""; // ANSI version of pool word
|
|
var text = ""; // scratch string
|
|
var word = 0; // current pool word
|
|
var x = 0; // scratch index
|
|
|
|
if (poolData.length > 0 && !poolDumped) {
|
|
setLocation(pass2);
|
|
nextLocation = location + poolData.length;
|
|
if (isNaN(poolLocation)) {
|
|
poolLocation = location;
|
|
} else if (location != poolLocation) {
|
|
printError("POOL LOCATION CHANGED: WAS=" + poolLocation + ", NOW=" + location);
|
|
}
|
|
|
|
// Relocate all labels declared after the pool to adjust for any new length
|
|
locationAdjustment = poolData.length - lastPoolLength;
|
|
if (locationAdjustment != 0) {
|
|
lastPoolLength = poolData.length;
|
|
if (!pass2) {
|
|
++poolUnresolved; // force another pass to re-resolve the adjusted labels
|
|
}
|
|
}
|
|
|
|
// Emit the literal pool words
|
|
for (x=0; x<poolData.length; ++x) {
|
|
word = poolData[x];
|
|
assembly.words[assembly.count++] = word;
|
|
if (listPass) {
|
|
if (word - word%p10[10] == 20000000000) {
|
|
flag = xlate220Word(word);
|
|
} else {
|
|
flag = "";
|
|
}
|
|
|
|
if (x == 0) {
|
|
printAssembly(cardData, assembly, flag);
|
|
} else {
|
|
text = padTo(text, wordOffset) +
|
|
printWord(assembly.location+x, assembly.placeLoc+x, word);
|
|
if (flag) {
|
|
text = padTo(text, flagOffset) + flag;
|
|
}
|
|
printLine(text);
|
|
}
|
|
}
|
|
} // for x
|
|
|
|
// Dump the pool words to the output medium
|
|
poolDumped = true;
|
|
if (pass2) {
|
|
emitAssembly();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Assembly Drivers *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function emitAssembly() {
|
|
/* Outputs the current "assembly" code structure to the object code buffer */
|
|
var addr = assembly.placeLoc; // address of assembled code
|
|
var x = 0; // assembly.words index
|
|
|
|
while (asmCode.length < addr) {
|
|
asmCode.push(0);
|
|
}
|
|
|
|
for (x=0; x<assembly.count; ++x) {
|
|
asmCode[addr+x] = assembly.words[x];
|
|
} // for x
|
|
}
|
|
|
|
/**************************************/
|
|
function assembleLiteralList(token, numericSign, pass2) {
|
|
/* Parses the text for a comma-delimited list of literals and returns an
|
|
array of binary values in assembly.words with the values found */
|
|
var c = ""; // current parse character
|
|
var text = cardData.text; // operand text
|
|
var values = null; // temp array of result words from parsing strings
|
|
var x = 0; // scratch index
|
|
|
|
setLocation(pass2);
|
|
while (token.offset < sequenceOffset) { // parse comma-delimited values
|
|
c = text.charAt(token.offset);
|
|
if (c == "'") {
|
|
values = parseString(text, token, false);
|
|
for (x=0; x<values.length; ++x) {
|
|
assembly.words[assembly.count++] = values[x];
|
|
}
|
|
} else if (c == ",") {
|
|
assembly.words[assembly.count++] = 0;
|
|
} else {
|
|
assembly.words[assembly.count++] =
|
|
applySign(parseExpression(token, pass2, false), numericSign);
|
|
numericSign = 0; // only apply the sign column to first word in the list
|
|
}
|
|
|
|
if (token.newOffset < sequenceOffset) {
|
|
c = text.charAt(token.newOffset);
|
|
if (c == ",") {
|
|
++token.newOffset; // continue with next value
|
|
} else if (c == " ") {
|
|
break; // out of while loop
|
|
} else {
|
|
printError("INVALID CONSTANT LIST: " + c);
|
|
break; // out of while loop
|
|
}
|
|
}
|
|
|
|
token.offset = token.newOffset;
|
|
} // while x
|
|
|
|
//for (x=0; x<values.length; ++x) {
|
|
// printLine(" DEBUG: " + padLeft(values[x], 11));
|
|
//}
|
|
|
|
if (listPass) {
|
|
printAssembly(cardData, assembly, null);
|
|
}
|
|
|
|
if (pass2) {
|
|
emitAssembly();
|
|
}
|
|
|
|
nextLocation = location + assembly.count;
|
|
}
|
|
|
|
/**************************************/
|
|
function assemblePseudoOp(token, opDesc, sign, pass2) {
|
|
/* Processes a pseudo-operation and emits code as required. Enters with
|
|
any label for the command in token.text */
|
|
var printed = !listPass; // true if has been or shouldn't be printed
|
|
var match = null; // regex.exec() result
|
|
var word = 0; // result word from op
|
|
var x = 0; // scratch
|
|
|
|
assembly.opCode = opDesc[0];
|
|
token.word = 0;
|
|
|
|
switch (assembly.opCode) {
|
|
case pseudoEND: // end of assembly
|
|
token.value = tensComp(parseExpression(token, pass2, false));
|
|
startAddress = token.value%10000;
|
|
break;
|
|
|
|
case pseudoIS: // equate label to address
|
|
if (assembly.label.length < 1) {
|
|
printError("VALUE MUST BE EQUATED TO A LABEL");
|
|
} else {
|
|
x = fetchLabel(assembly.label);
|
|
word = parseExpression(token, pass2, false) - assembly.labelOffset;
|
|
if (isNaN(x)) {
|
|
declareLabel(assembly.label, word);
|
|
} else if (x != word) {
|
|
printFlag = "HMM..";
|
|
printError("LABEL DEFINED TO A DIFFERENT VALUE: OLD=" + x + ", NEW=" + word);
|
|
declareLabel(assembly.label, word);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case pseudoORIGIN: // set current assembly location
|
|
locationAdjustment = 0;
|
|
x = tensComp(parseExpression(token, true, false))%10000;
|
|
if (!isNaN(x)) {
|
|
token.value = nextLocation = x;
|
|
if (assembly.label.length > 0) {
|
|
declareLabel(assembly.label, nextLocation);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case pseudoPLACE: // set current address for assembled words in memory
|
|
x = tensComp(parseExpression(token, true, false))%10000;
|
|
if (!isNaN(x)) {
|
|
token.value = placeOffset = x-nextLocation;
|
|
}
|
|
break;
|
|
|
|
case pseudoPLACED: // reset place address to current location
|
|
placeOffset = 0;
|
|
break;
|
|
|
|
case pseudoFORGET: // reset assembler tables (acts like END for pass control)
|
|
token.text = cardData.text.substring(operandOffset, operandOffset+5);
|
|
if (token.text != "NAMES") {
|
|
printError("UNSUPPORTED FORGET OPTION: " + token.text);
|
|
} else {
|
|
assembly.opCode = pseudoFORGETNAMES;
|
|
}
|
|
break;
|
|
|
|
case pseudoFORMAT: // generate Cardatron format band
|
|
setLocation(pass2);
|
|
assembly.words = generateFormatBand(token);
|
|
assembly.count = assembly.words.length;
|
|
if (listPass) {
|
|
printAssembly(cardData, assembly, null);
|
|
printed = true;
|
|
}
|
|
|
|
if (pass2) {
|
|
emitAssembly();
|
|
}
|
|
|
|
nextLocation = location + assembly.count;
|
|
break;
|
|
|
|
case pseudoFILL: // smear copies of a word in memory
|
|
setLocation(pass2);
|
|
word = applySign(parseExpression(token, pass2, false), sign);
|
|
x = token.offset = token.newOffset;
|
|
if (!(x < sequenceOffset && cardData.text.charAt(x) == ",")) {
|
|
printError("FILL COUNT REQUIRED");
|
|
} else {
|
|
++token.offset;
|
|
x = parseExpression(token, true, false);
|
|
if (isNaN(x) || x < 1) {
|
|
printError("FILL COUNT MUST BE NUMBER GREATER THAN ZERO: " + x);
|
|
} else {
|
|
while (x > 0) {
|
|
assembly.words[assembly.count++] = word;
|
|
--x;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (listPass) {
|
|
x = assembly.count; // only print the first few words
|
|
assembly.count = 1;
|
|
printAssembly(cardData, assembly, null);
|
|
assembly.count = x;
|
|
printed = true;
|
|
}
|
|
|
|
if (pass2) {
|
|
emitAssembly();
|
|
}
|
|
|
|
nextLocation = location + assembly.count;
|
|
break;
|
|
|
|
case pseudoPOOL: // dump literal pool at this location
|
|
buildPool(pass2);
|
|
printed = true;
|
|
break;
|
|
|
|
case pseudoDO: // subroutine call macro
|
|
// DO XXXXX => enter at XXXXX.1, return at XXXXX
|
|
// DO XXXXX.n => enter at XXXXX.n, return at XXXXX
|
|
setLocation(pass2);
|
|
word = parsePrimary(token, pass2);
|
|
if (token.type != tokLabel) {
|
|
printError("LABEL REQUIRED");
|
|
} else {
|
|
// Make x the entry location and word the return location
|
|
x = token.text.lastIndexOf(".");
|
|
if (x < 0) {
|
|
x = fetchLabel(token.text + ".1");
|
|
} else {
|
|
match = token.text.substring(0, x);
|
|
x = word;
|
|
word = fetchLabel(match);
|
|
}
|
|
|
|
assembly.words[assembly.count++] = putField(word, 44, 67); // STP XXXXX
|
|
assembly.words[assembly.count++] = putField( x, 30, 67); // BUN XXXXX.n
|
|
}
|
|
|
|
if (listPass) {
|
|
printAssembly(cardData, assembly, null);
|
|
printed = true;
|
|
}
|
|
|
|
if (pass2) {
|
|
emitAssembly();
|
|
}
|
|
|
|
nextLocation = location + assembly.count;
|
|
break;
|
|
|
|
case pseudoDJ: // define format of a multi-field word (sL codes in reverse order)
|
|
match = labelRex.exec(cardData.text.substring(token.offset, sequenceOffset));
|
|
if (!match || !isNumericRex.test(match[1])) {
|
|
printError("FIELD LIST INTEGER REQUIRED");
|
|
} else {
|
|
djList = [];
|
|
x = match[1].length;
|
|
while (x > 0) {
|
|
if (x > 1) {
|
|
djList.push(parseInt(match[1].substring(x-2, x), 10));
|
|
} else {
|
|
djList.push(parseInt(match[1].substring(x-1, x), 10));
|
|
}
|
|
x -= 2;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case pseudoJ: // construct a multi-field word per DJ specs
|
|
setLocation(pass2);
|
|
for (x=0; x<djList.length; ++x) {
|
|
word = putField(word, parseExpression(token, pass2, false), djList[x]);
|
|
token.offset = token.newOffset;
|
|
if (token.offset < sequenceOffset && cardData.text.charAt(token.offset) == ",") {
|
|
++token.offset;
|
|
}
|
|
} // for x
|
|
|
|
assembly.words[assembly.count++] = applySign(word, sign);
|
|
if (listPass) {
|
|
printAssembly(cardData, assembly, null);
|
|
printed = true;
|
|
}
|
|
|
|
if (pass2) {
|
|
emitAssembly();
|
|
}
|
|
|
|
nextLocation = location+1;
|
|
if (token.newOffset < sequenceOffset && cardData.text.charAt(token.newOffset) != " ") {
|
|
printError("EXTRANEOUS TEXT AFTER J-FIELD LIST");
|
|
}
|
|
break;
|
|
|
|
case pseudoWord: // assemble a literal word
|
|
printError("OOPS... WORD LITERAL SHOULD HAVE BEEN HANDLED ALREADY");
|
|
break;
|
|
|
|
default:
|
|
printError("INVALID PSEUDO-OP CODE: " + assembly.opCode);
|
|
break;
|
|
} // switch opCode
|
|
|
|
if (listPass && !printed) {
|
|
printAssembly(cardData, null, null);
|
|
}
|
|
|
|
if (pass2) {
|
|
emitAssembly();
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function assembleOpCode(token, opDesc, sign, pass2) {
|
|
/* Assembles a 220 instruction. "opDesc" is the opTab entry for the
|
|
symbolic opCode. "sign" is the numeric sign. "pass2" is true if this
|
|
is the second assembly pass */
|
|
var c = ""; // current character
|
|
var field = 0; // value of current operand field
|
|
var word = opDesc[0]*p10[4]; // initial instruction word
|
|
var x = 0; // operand index
|
|
|
|
setLocation(pass2);
|
|
word = applySign(word, sign);
|
|
assembly.opCode = opDesc[0];
|
|
token.newOffset = token.offset;
|
|
token.word = token.value = 0;
|
|
|
|
// Parse the operands
|
|
for (x=1; x<opDesc.length; x+=2) {
|
|
token.offset = token.newOffset;
|
|
word = parseOperandField(token, word, pass2, opDesc[x], opDesc[x+1]);
|
|
if (token.newOffset < sequenceOffset) {
|
|
c = cardData.text.charAt(token.newOffset);
|
|
if (c == ",") {
|
|
++token.newOffset;
|
|
} else if (c != " ") {
|
|
printError("COMMA EXPECTED: " + c);
|
|
}
|
|
}
|
|
} // for x
|
|
|
|
// The values of any additional fields not defined by opDesc are simply
|
|
// added to the instruction word. Normally these will be partial-word
|
|
// expressions positioned into the unused digits of the instruction,
|
|
// which are by default zero.
|
|
|
|
while (token.newOffset < sequenceOffset) {
|
|
token.offset = token.newOffset;
|
|
if (cardData.text.charAt(token.offset) == " ") {
|
|
break; // out of while loop
|
|
} else {
|
|
word += parseExpression(token, pass2, false);
|
|
if (token.newOffset < sequenceOffset) {
|
|
c = cardData.text.charAt(token.newOffset);
|
|
if (c == " ") {
|
|
break; // out of while loop
|
|
} else if (c == ",") {
|
|
++token.newOffset;
|
|
} else {
|
|
printError("COMMA EXPECTED: " + c);
|
|
break; // out of while loop
|
|
}
|
|
}
|
|
}
|
|
} // while remaining fields
|
|
|
|
nextLocation = location+1;
|
|
assembly.words[0] = word;
|
|
assembly.count = 1;
|
|
if (listPass) {
|
|
printAssembly(cardData, assembly, null);
|
|
}
|
|
|
|
if (pass2) {
|
|
emitAssembly();
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function assembleCommand(pass2) {
|
|
/* Assembles one instruction or pseudo operation. Enter with the next
|
|
memory location to be used in "nextLocation" and card image in the
|
|
"cardData" structure. Fills in the "assembly" structure */
|
|
var card = ""; // current card to be assembled
|
|
var opCode = ""; // op code field from the card
|
|
var opDesc = null; // array of op code description words from opTab{}
|
|
var sign = ""; // sign-override field from the card
|
|
var numericSign = 0; // numeric sign value
|
|
|
|
parseLabel(token, pass2);
|
|
assembly.label = token.text;
|
|
assembly.labelOffset = token.value;
|
|
assembly.opCode = pseudoWord; // literal word by default
|
|
card = cardData.text;
|
|
sign = card.substring(signOffset, signOffset+1);
|
|
opCode = card.substring(opCodeOffset, opCodeOffset+7);
|
|
|
|
numericSign = signValues[sign];
|
|
opDesc = opTab[opCode];
|
|
if (sign == " " && opDesc && opDesc[0] == pseudoREM) {
|
|
assembly.opCode = pseudoREM;
|
|
if (listPass) {
|
|
cardData.text = padRight(cardData.text.substring(0, opCodeOffset), operandOffset) +
|
|
cardData.text.substring(operandOffset); // blank out the op code field
|
|
printAssembly(cardData, null, null);
|
|
}
|
|
} else if (!(sign in signValues)) {
|
|
token.offset = signOffset;
|
|
assembleLiteralList(token, 0, pass2);
|
|
} else if (sign == " " && isNumericRex.test(opCode)) {
|
|
token.offset = opCodeOffset;
|
|
assembleLiteralList(token, 0, pass2);
|
|
} else if (isNumericRex.test(opCode)) {
|
|
token.offset = signOffset;
|
|
assembleLiteralList(token, 0, pass2);
|
|
} else if (opCode.charAt(0) == "(") {
|
|
token.offset = opCodeOffset;
|
|
assembleLiteralList(token, numericSign, pass2);
|
|
} else {
|
|
if (!opDesc) {
|
|
token.offset = opCodeOffset;
|
|
assembleLiteralList(token, numericSign, pass2);
|
|
} else if (opDesc[0] < 0) {
|
|
token.offset = operandOffset;
|
|
assemblePseudoOp(token, opDesc, numericSign, pass2);
|
|
} else {
|
|
token.offset = operandOffset;
|
|
assembleOpCode(token, opDesc, numericSign, pass2);
|
|
}
|
|
}
|
|
|
|
dumpErrorTank();
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Pass One *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function startPass1() {
|
|
/* Initiates Pass 1 of the assembler */
|
|
|
|
listPass = pass1List;
|
|
printLine("");
|
|
printLine("START PASS 1");
|
|
printLine("");
|
|
initializePass();
|
|
poolUnresolved = 0;
|
|
initializeLiteralPool(programUnitNr);
|
|
assemblePass1();
|
|
}
|
|
|
|
/**************************************/
|
|
function assemblePass1() {
|
|
/* Processes card images for Pass 1 of the assembler. Enters with the
|
|
first card in "cardData". Reschedules itself for subsequent cards */
|
|
var label = "";
|
|
var symUnresolved = 0;
|
|
|
|
assembleCommand(false);
|
|
switch (assembly.opCode) {
|
|
case pseudoEND:
|
|
case pseudoFORGETNAMES:
|
|
// Wrap up Pass 1 and check for undefined labels.
|
|
cardData.text = "";
|
|
for (label in symTab) {
|
|
if (isNaN(symTab[label])) {
|
|
++symUnresolved;
|
|
}
|
|
}
|
|
|
|
buildPool(false);
|
|
dumpErrorTank();
|
|
printLine("UNRESOLVED LABEL REFERENCES = " + symUnresolved);
|
|
printLine("UNRESOLVED POOL LITERAL EXPRESSIONS = " + poolUnresolved);
|
|
printLine("LITERAL POOL LENGTH = " + poolData.length + ", LAST = " + lastPoolLength);
|
|
if (pass1List && !pass2List) {
|
|
dumpSymbolTable();
|
|
}
|
|
|
|
printLine("END PASS 1, ERRORS = " + errorCount);
|
|
if (errorCount > 0) {
|
|
finishAssembly();
|
|
} else if (poolUnresolved == 0) {
|
|
setTimeout(startPass2, 100); // advance to Pass 2
|
|
if (pass2List) {
|
|
printLine("\f"); // output a form-feed
|
|
}
|
|
} else if (poolUnresolved == lastPoolUnresolved ||
|
|
(symUnresolved != 0 && symUnresolved == lastSymUnresolved)) {
|
|
printError("UNRESOLVABLE LABELS OR LITERAL EXPRESSIONS");
|
|
finishAssembly();
|
|
} else {
|
|
printLine("RESTARTING PASS 1 TO RE-RESOLVE SYMBOLS");
|
|
setTimeout(startPass1, 100);
|
|
if (pass1List) {
|
|
printLine("\f"); // output a form-feed
|
|
}
|
|
}
|
|
|
|
lastSymUnresolved = symUnresolved;
|
|
lastPoolUnresolved = poolUnresolved;
|
|
break;
|
|
|
|
default:
|
|
readACard();
|
|
if (cardData.atEOF) {
|
|
printError("EOF encountered before END in Pass 1");
|
|
} else {
|
|
Promise.resolve(false).then(assemblePass1);
|
|
}
|
|
break;
|
|
} // switch opCode
|
|
|
|
dumpErrorTank();
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Pass Two *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function startPass2() {
|
|
/* Initiates Pass 2 of the assembler */
|
|
|
|
listPass = pass2List;
|
|
printLine("");
|
|
printLine("START PASS 2");
|
|
printLine("");
|
|
initializePass();
|
|
assemblePass2();
|
|
}
|
|
|
|
/**************************************/
|
|
function assemblePass2() {
|
|
/* Processes card images for Pass 2 of the assembler. Enters with the
|
|
first card in "cardData". Reschedules itself for subsequent cards */
|
|
var label = "";
|
|
var symUnresolved = 0;
|
|
|
|
assembleCommand(true);
|
|
switch (assembly.opCode) {
|
|
case pseudoEND:
|
|
case pseudoFORGETNAMES:
|
|
// Wrap up Pass 2 and check for undefined labels.
|
|
cardData.text = "";
|
|
for (label in symTab) {
|
|
if (isNaN(symTab[label])) {
|
|
++symUnresolved;
|
|
printError("LABEL NOT DEFINED: " + label);
|
|
}
|
|
}
|
|
|
|
buildPool(true);
|
|
dumpErrorTank();
|
|
printLine("END OF PASS 2, ERRORS = " + errorCount);
|
|
printLine("UNRESOLVED LABEL REFERENCES = " + symUnresolved);
|
|
printLine("UNRESOLVED POOL LITERAL EXPRESSIONS = " + poolUnresolved);
|
|
printLine("LITERAL POOL LENGTH = " + poolData.length + ", LAST = " + lastPoolLength);
|
|
if (pass2List) {
|
|
dumpSymbolTable();
|
|
}
|
|
|
|
if (symUnresolved > 0 || poolUnresolved > 0) {
|
|
printError("UNRESOLVED LABEL OR POOL LITERAL REFERENCES IN PASS 2 - FATAL");
|
|
}
|
|
|
|
finishAssembly();
|
|
break;
|
|
|
|
default:
|
|
readACard();
|
|
if (cardData.atEOF) {
|
|
printError("EOF encountered before END in Pass 2");
|
|
} else {
|
|
Promise.resolve(false).then(assemblePass2);
|
|
}
|
|
break;
|
|
} // switch opCode
|
|
|
|
dumpErrorTank();
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Object Code Output *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function generateFormat6LoadableDeck(asmCode, outputChecksum, startAddress) {
|
|
/* Formats the assembled object code as a standard Cardatron format-6
|
|
loadable deck in a temporary window. From there it can be save, copied,
|
|
etc. */
|
|
var crdSkeleton = ""; // skeleton card-read instruction
|
|
var doc = null; // temp window document object
|
|
var deck = null; // temp window text area
|
|
var pval = p10[10]; // modulus for a word's absolute value
|
|
var psign = pval*2; // modulus for a word's sign bit
|
|
var title = "220 GEN-Assembler Band-6 Loadable Deck";
|
|
var win = window.open("../../webUI/B220FramePaper.html", "GEN-Asm-Object",
|
|
"scrollbars,resizable,width=600,height=500");
|
|
|
|
function checksum(cksum, word) {
|
|
var sw = word%psign; // will be the signed algebraic value of word
|
|
|
|
if (sw >= pval) { // check if word has sign bit=1
|
|
sw = pval - sw; // if so, make it algebraically negative
|
|
}
|
|
|
|
return (cksum+sw)%pval; // compute the algebraic checksum
|
|
}
|
|
|
|
function writeCard(card, addr) {
|
|
|
|
deck.appendChild(doc.createTextNode("666" +
|
|
padLeft(crdSkeleton + padLeft(addr, 4, "0") + card, 77, " ") + "\n"));
|
|
}
|
|
|
|
function generateDeck(ev) {
|
|
var addr = 0; // assembled code address
|
|
var card = ""; // text line for an 80-column card image
|
|
var cksum = 0; // algebraic value of the checksum word
|
|
var count = 0; // number of words on card
|
|
var gapCount = 0; // number of consecutive undefined words
|
|
var priorAddr = 0; // CRD address for prior card image
|
|
var priorCard = ""; // prior image, awaiting terminating CRD instruction
|
|
var word = undefined; // object code word
|
|
var x = 0; // scratch index
|
|
|
|
win.removeEventListener("load", generateDeck, false);
|
|
doc = win.document;
|
|
doc.title = title;
|
|
deck = doc.getElementById("Paper");
|
|
|
|
while (addr < asmCode.length && asmCode[addr] === undefined) {
|
|
++addr;
|
|
}
|
|
|
|
while (addr < asmCode.length) {
|
|
word = asmCode[addr];
|
|
if (word === undefined) {
|
|
++gapCount;
|
|
} else {
|
|
if (count > 5 || gapCount > 0) {
|
|
writeCard(priorCard, priorAddr);
|
|
priorCard = card;
|
|
card = "";
|
|
gapCount = 0;
|
|
count = 0;
|
|
}
|
|
|
|
priorAddr = addr;
|
|
card += padLeft(word, 11, "0");
|
|
++count;
|
|
if (outputChecksum) {
|
|
cksum = checksum(cksum, word);
|
|
}
|
|
}
|
|
|
|
++addr;
|
|
} // while addr
|
|
|
|
writeCard(priorCard, priorAddr);
|
|
if (outputChecksum) {
|
|
// Compute and output a card with the 220 negative checksum at the next address
|
|
writeCard(card, priorAddr+1);
|
|
card = crdSkeleton + "9999";
|
|
if (cksum < 0) {
|
|
card += padLeft(-cksum, 11, "0");
|
|
} else {
|
|
card += padLeft(cksum+pval, 11, "0");
|
|
}
|
|
|
|
card = "***CHECKSUM FOR " + sourceName + " " + card;
|
|
}
|
|
|
|
writeCard(card, 9999);
|
|
if (startAddress >= 0) {
|
|
writeCard("6000030" + padLeft(startAddress, 4, "0"), 9999); // BUN to start
|
|
} else {
|
|
writeCard("69999009999", 9999); // HLT 9999
|
|
}
|
|
}
|
|
|
|
crdSkeleton = "6" + padLeft(cardReaderUnit, 1, "0") + "00060";
|
|
win.addEventListener("load", generateDeck, false);
|
|
win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
|
|
}
|
|
|
|
/**************************************/
|
|
function generateMachineLanguageDeck(asmCode, outputChecksum, startAddress) {
|
|
/* Formats the assembled object code as a BALGOL Machine-Language deck
|
|
in a temporary window. From there it can be save, copied, etc. */
|
|
var doc = null; // temp window document object
|
|
var deck = null; // temp window text area
|
|
var pval = p10[10]; // modulus for a word's absolute value
|
|
var psign = pval*2; // modulus for a word's sign bit
|
|
var title = "220 GEN-Assembler Machine-Language Deck";
|
|
var win = window.open("../../webUI/B220FramePaper.html", "GEN-Asm-Object",
|
|
"scrollbars,resizable,width=600,height=500");
|
|
|
|
function checksum(cksum, word) {
|
|
var sw = word%psign; // will be the signed algebraic value of word
|
|
|
|
if (sw >= pval) { // check if word has sign bit=1
|
|
sw = pval - sw; // if so, make it algebraically negative
|
|
}
|
|
|
|
return (cksum+sw)%pval; // compute the algebraic checksum
|
|
}
|
|
|
|
function writeCard(card, count, addr, seq) {
|
|
|
|
deck.appendChild(doc.createTextNode(
|
|
"60" + padLeft(count, 1, "0") + padLeft(seq, 7, "0") +
|
|
padLeft(addr, 4, "0") + card + "\n"));
|
|
}
|
|
|
|
function generateDeck(ev) {
|
|
var addr = 0; // assembled code address
|
|
var card = ""; // text line for an 80-column card image
|
|
var cardAddr = 0; // first address of words on card
|
|
var cksum = 0; // algebraic value of the checksum word
|
|
var count = 0; // number of words on card
|
|
var seq = 10; // card sequence number
|
|
var word = undefined; // object code word
|
|
var x = 0; // scratch index
|
|
|
|
win.removeEventListener("load", generateDeck, false);
|
|
doc = win.document;
|
|
doc.title = title;
|
|
deck = doc.getElementById("Paper");
|
|
|
|
while (addr < asmCode.length) {
|
|
word = asmCode[addr] || 0;
|
|
if (count > 5) {
|
|
writeCard(card, count, cardAddr, seq);
|
|
card = "";
|
|
cardAddr = addr;
|
|
count = 0;
|
|
seq += 10;
|
|
}
|
|
|
|
card += padLeft(word, 11, "0");
|
|
++count;
|
|
if (outputChecksum) {
|
|
cksum = checksum(cksum, word);
|
|
}
|
|
|
|
++addr;
|
|
} // while addr
|
|
|
|
writeCard(card, count, cardAddr, seq);
|
|
if (outputChecksum) {
|
|
// Compute and output a card with the 220 negative checksum at the next address
|
|
if (cksum < 0) {
|
|
card = padLeft(-cksum, 11, "0");
|
|
} else {
|
|
card = padLeft(cksum+pval, 11, "0");
|
|
}
|
|
|
|
writeCard(card + " ***CHECKSUM FOR " + sourceName, 1, addr, 9999990);
|
|
}
|
|
}
|
|
|
|
win.addEventListener("load", generateDeck, false);
|
|
win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
|
|
}
|
|
|
|
/**************************************/
|
|
function generateGeneratorMediaDeck(asmCode, outputChecksum, startAddress) {
|
|
/* Formats the assembled object code as a Generator INPUTMEDIA/OUTPUTMEDIA
|
|
deck in a temporary window. From there it can be save, copied, etc. */
|
|
var doc = null; // temp window document object
|
|
var deck = null; // temp window text area
|
|
var pval = p10[10]; // modulus for a word's absolute value
|
|
var psign = pval*2; // modulus for a word's sign bit
|
|
var title = "220 GEN-Assembler Generator MEDIA Deck";
|
|
var win = window.open("../../webUI/B220FramePaper.html", "GEN-Asm-MEDIA",
|
|
"scrollbars,resizable,width=600,height=500");
|
|
|
|
function writeCard(word, addr, seq) {
|
|
|
|
deck.appendChild(doc.createTextNode(
|
|
"600" + padLeft(seq, 7, "0") + padLeft(addr, 4, "0") +
|
|
padRight(" ", 22, " ") + padLeft(word, 11, "0") + "\n"));
|
|
}
|
|
|
|
function generateDeck(ev) {
|
|
var addr = 0; // assembled code address
|
|
var seq = 10; // card sequence number
|
|
var word = undefined; // object code word
|
|
|
|
win.removeEventListener("load", generateDeck, false);
|
|
doc = win.document;
|
|
doc.title = title;
|
|
deck = doc.getElementById("Paper");
|
|
|
|
while (addr < asmCode.length) {
|
|
word = asmCode[addr] || 0;
|
|
writeCard(word, addr, seq);
|
|
seq += 10;
|
|
++addr;
|
|
} // while addr
|
|
}
|
|
|
|
win.addEventListener("load", generateDeck, false);
|
|
win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
|
|
}
|
|
|
|
/**************************************/
|
|
function generateObjectTape(asmCode, outputChecksum, startAddress) {
|
|
/* Formats the assembled object code as a 220 tape image in a temporary
|
|
window. From there it can be save, copied, etc. */
|
|
var doc = null; // temp window document object
|
|
var pval = p10[10]; // modulus for a word's absolute value
|
|
var psign = pval*2; // modulus for a word's sign bit
|
|
var tape = null; // temp window text area
|
|
var title = "220 GEN-Assembler Object Tape";
|
|
var win = window.open("../../webUI/B220FramePaper.html", "GEN-Asm-Object",
|
|
"scrollbars,resizable,width=500,height=500");
|
|
|
|
function checksum(cksum, word) {
|
|
var sw = word%psign; // will be the signed algebraic value of word
|
|
|
|
if (sw >= pval) { // check if word has sign bit=1
|
|
sw = pval - sw; // if so, make it algebraically negative
|
|
}
|
|
|
|
return (cksum+sw)%pval; // compute the algebraic checksum
|
|
}
|
|
|
|
function generateTape(ev) {
|
|
var addr = 0; // assembled code address
|
|
var block = ""; // text line for a 100-word tape block
|
|
var blockPrefix = "0,100"; // lane and prefix word count for block
|
|
var count = 0; // count of words in block
|
|
var cksum = 0; // algebraic value of the checksum word
|
|
var length = asmCode.length;// number of words to output
|
|
var word = 0; // object code word
|
|
|
|
win.removeEventListener("load", generateTape, false);
|
|
doc = win.document;
|
|
doc.title = title;
|
|
tape = doc.getElementById("Paper");
|
|
block = blockPrefix;
|
|
|
|
while (addr < length) {
|
|
if (count >= 100) {
|
|
tape.appendChild(doc.createTextNode(block + "\n"));
|
|
count = 0;
|
|
block = blockPrefix;
|
|
}
|
|
|
|
word = asmCode[addr] || 0;
|
|
block += "," + word.toString();
|
|
++count;
|
|
++addr;
|
|
if (outputChecksum) {
|
|
cksum = checksum(cksum, word);
|
|
}
|
|
} // while addr
|
|
|
|
if (outputChecksum) {
|
|
// Compute and output a block with the 220 negative checksum at the next address
|
|
if (count >= 100) {
|
|
tape.appendChild(doc.createTextNode(block + "\n"));
|
|
count = 0;
|
|
block = blockPrefix;
|
|
}
|
|
|
|
if (cksum < 0) {
|
|
block += "," + padLeft(-cksum, 11, "0");
|
|
} else {
|
|
block += "," + padLeft(cksum+pval, 11, "0");
|
|
}
|
|
}
|
|
|
|
tape.appendChild(doc.createTextNode(block + "\n"));
|
|
count = Math.floor((addr+99)/100); // number of blocks in program
|
|
count = Math.floor(3480*208.333/106) - count; // blocks left on tape
|
|
tape.appendChild(doc.createTextNode(count.toString() + "*0,100\n"));
|
|
|
|
tape.appendChild(doc.createTextNode("0,1,1\n")); // EOT control word for lane 0
|
|
tape.appendChild(doc.createTextNode("6839*0,100\n1,1,1\n")); // zero blocks & EOT for lane 1
|
|
}
|
|
|
|
win.addEventListener("load", generateTape, false);
|
|
win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Initialization and Termination *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function initializeLiteralPool(unitNr) {
|
|
/* Initializes the literal pool prior to an assembly pass. If data for
|
|
a pool has been pre-loaded for this program "unitNr" then initialize
|
|
pool with that location and data; otherwise initialize it to the default
|
|
in poolSets[0] */
|
|
var thisSet = poolSets[unitNr] || poolSets[0];
|
|
var x = 0; // scratch index
|
|
|
|
poolLocation = thisSet.poolLoc;
|
|
poolData = new Array(thisSet.poolData.length);
|
|
for (x=thisSet.poolData.length-1; x>=0; --x) {
|
|
poolData[x] = thisSet.poolData[x];
|
|
} // for x
|
|
}
|
|
|
|
/**************************************/
|
|
function initializePass() {
|
|
/* Initializes assembler storage for the start of a pass */
|
|
var e; // point label ID
|
|
|
|
location = 0;
|
|
locationAdjustment = 0;
|
|
nextLocation = 0;
|
|
placeOffset = 0;
|
|
poolDumped = false;
|
|
printFlag = "";
|
|
assembly.location = 0;
|
|
assembly.placeLoc = 0;
|
|
assembly.opCode = pseudoEND;
|
|
assembly.count = 0;
|
|
djList = [0]; // reset the DJ fields to /00
|
|
for (e in pointTab) { // reset the point label table
|
|
pointTab[e] = 0;
|
|
}
|
|
|
|
// Read (or re-read) the first card image for the program unit
|
|
bufferOffset = cardData.fileOffset;
|
|
cardData.serial = cardData.fileSerial;
|
|
readACard();
|
|
}
|
|
|
|
/**************************************/
|
|
function initializeAssembly() {
|
|
/* Initializes the assembler storage for a new program unit */
|
|
|
|
errorCount = 0;
|
|
errorTank = [];
|
|
lastPoolLength = 0;
|
|
lastPoolUnresolved = 0;
|
|
lastSymUnresolved = 0;
|
|
pointTab = {};
|
|
symTab = {};
|
|
|
|
++programUnitNr;
|
|
printLine("");
|
|
printLine("ASSEMBLE PROGRAM UNIT #" + programUnitNr + ":");
|
|
|
|
setTimeout(startPass1, 100);
|
|
}
|
|
|
|
/**************************************/
|
|
function finishAssembly() {
|
|
/* Finishes the assembly of a program unit, and if appropriate, starts
|
|
assembly of the next one */
|
|
var oms = $$("OutputModeSelect");
|
|
var x = oms.selectedIndex;
|
|
|
|
cardData.fileOffset = bufferOffset;
|
|
cardData.fileSerial = cardData.serial;
|
|
do {
|
|
readACard();
|
|
} while (!cardData.atEOF && cardData.text.search(/\S/) < 0);
|
|
|
|
if (!cardData.atEOF) { // assemble the next program unit
|
|
setTimeout(initializeAssembly, 100);
|
|
if (pass1List) {
|
|
printLine("\f");
|
|
}
|
|
} else { // We're done, just output code and exit
|
|
if (errorCount == 0 && x >= 0) {
|
|
switch(oms.options[x].value) {
|
|
case "L":
|
|
generateFormat6LoadableDeck(asmCode, outputChecksum, startAddress);
|
|
break;
|
|
case "M":
|
|
generateMachineLanguageDeck(asmCode, outputChecksum, startAddress);
|
|
break;
|
|
case "P":
|
|
generateGeneratorMediaDeck(asmCode, outputChecksum, startAddress);
|
|
break;
|
|
case "T":
|
|
generateObjectTape(asmCode, outputChecksum, startAddress);
|
|
break;
|
|
}// switch oms.options
|
|
}
|
|
|
|
$$("CRFileSelector").value = null; // reset the <input> elements
|
|
$$("CRPoolSelector").value = null; // so they can be reused
|
|
poolSets.length = 1; // discard any poolSets that were loaded
|
|
$$("TextDiv").removeChild($$("Spinner")); // remove the spinner image
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function startAssembly() {
|
|
/* Initializes or reinitializes the assembler for a new source file
|
|
and starts the assembly */
|
|
var done = false; // loop control
|
|
var opCode = 0; // op code field of current card
|
|
var stamp = new Date(); // timestamp for heading
|
|
|
|
asmCode = []; // clear the assembled code buffer
|
|
clearPanel();
|
|
printLine("Assembler for the Burroughs 220 BAC Generator -- " +
|
|
stamp.getFullYear().toString() + "-" +
|
|
padLeft(stamp.getMonth()+1, 2, "0") + "-" +
|
|
padLeft(stamp.getDate(), 2, "0") + " " +
|
|
padLeft(stamp.getHours(), 2, "0") + ":" +
|
|
padLeft(stamp.getMinutes(), 2, "0"));
|
|
printLine("");
|
|
printLine("Source File: " + sourceName);
|
|
if (poolSets.length > 1) {
|
|
printLine("Pool Data: " + poolSetName);
|
|
}
|
|
|
|
printLine("");
|
|
do {
|
|
readACard();
|
|
if (cardData.atEOF) {
|
|
done = true;
|
|
printError("EOF ENCOUNTERED DURING ASSEMBLER PRAGMAS");
|
|
} else {
|
|
opCode = rTrim(cardData.text.substring(opCodeOffset, operandOffset));
|
|
switch (opCode) {
|
|
case "LOAD":
|
|
case "ON":
|
|
printAssembly(cardData, null, null);
|
|
break;
|
|
default:
|
|
done = true;
|
|
cardData.fileOffset = cardData.offset; // save the offset of the first card image
|
|
cardData.fileSerial = cardData.serial-1;
|
|
setTimeout(initializeAssembly, 100);
|
|
break;
|
|
}
|
|
}
|
|
} while (!done);
|
|
}
|
|
|
|
/**************************************/
|
|
function loadPoolsets(ev) {
|
|
/* Handle the <input type=file> onchange event when a file for a
|
|
pre-loaded literal pool set is selected */
|
|
var f = ev.target.files[0];
|
|
var reader = new FileReader();
|
|
|
|
function poolLoader_onLoad(ev) {
|
|
/* Handles the onload event for a readAsText FileReader */
|
|
var sets = null;
|
|
var thisSet = null;
|
|
var unitNr = 0;
|
|
var x = 0;
|
|
|
|
poolSets.length = 1; // remove any old entries
|
|
try {
|
|
sets = JSON.parse(ev.target.result);
|
|
if (!(typeof sets == "object" && "poolSet" in sets)) {
|
|
printError("POOL DATA MUST BE AN OBJECT AND CONTAIN A \"POOLSET\" PROPERTY");
|
|
} else if (!(sets.poolSet instanceof Array)) {
|
|
printError("POOL DATA \"POOLSET\" PROPERTY MUST BE AN ARRAY");
|
|
} else {
|
|
sets = sets.poolSet;
|
|
for (x=0; x<sets.length; ++x) {
|
|
thisSet = sets[x];
|
|
unitNr = x+1;
|
|
if (thisSet === null) {
|
|
poolSets[unitNr] = null; // allow an empty poolSet entry
|
|
} else if (typeof thisSet != "object") {
|
|
printError("POOLSET FOR PROGRAM UNIT #" + unitNr + " MUST BE AN OBJECT");
|
|
} else if (!("poolLoc" in thisSet && typeof thisSet["poolLoc"] == "number")) {
|
|
printError("POOLSET FOR PROGRAM UNIT #" + unitNr + " MUST HAVE NUMERIC \"POOLLOC\" PROPERTY");
|
|
} else if (!("poolData" in thisSet && thisSet["poolData"] instanceof Array)) {
|
|
printError("POOLSET FOR PROGRAM UNIT #" + unitNr + " MUST HAVE ARRAY \"POOLDATA\" PROPERTY");
|
|
} else {
|
|
poolSets[unitNr] = thisSet;
|
|
}
|
|
} // for x
|
|
}
|
|
} catch (e) {
|
|
printError("ERROR PARSING PRE-LOADED POOL DATA JSON:");
|
|
printError(e.toString());
|
|
}
|
|
|
|
if (errorCount > 0) {
|
|
dumpErrorTank();
|
|
errorCount = 0;
|
|
$$("CRPoolSelector").value = null;
|
|
}
|
|
}
|
|
|
|
poolSetName = f.name;
|
|
/********************
|
|
alert("Pool Data file selected: " + f.name +
|
|
"\nModified " + f.lastModifiedDate +
|
|
"\nType=" + f.type + ", Size=" + f.size + " octets");
|
|
********************/
|
|
|
|
reader.onload = poolLoader_onLoad;
|
|
reader.readAsText(f);
|
|
}
|
|
|
|
/**************************************/
|
|
function loadSourceFile(ev) {
|
|
/* Handle the <input type=file> onchange event when a source file is
|
|
selected */
|
|
var e; // spinner image DOM element
|
|
var f = ev.target.files[0];
|
|
var reader = new FileReader();
|
|
|
|
function fileLoader_onLoad(ev) {
|
|
/* Handles the onload event for a readAsText FileReader */
|
|
|
|
buffer = ev.target.result;
|
|
bufferOffset = 0;
|
|
bufferLength = buffer.length;
|
|
cardData.atEOF = false;
|
|
cardData.fileOffset = 0;
|
|
cardData.offset = 0;
|
|
cardData.serial = 0;
|
|
setTimeout(startAssembly, 100);
|
|
}
|
|
|
|
sourceName = f.name;
|
|
/********************
|
|
alert("Source file selected: " + f.name +
|
|
"\nModified " + f.lastModifiedDate +
|
|
"\nType=" + f.type + ", Size=" + f.size + " octets");
|
|
********************/
|
|
|
|
// initiate the spinner to run while compiling
|
|
e = document.createElement("img");
|
|
e.src = "../../webUI/resources/ajax-spinner.gif";
|
|
e.id = "Spinner";
|
|
$$("TextDiv").appendChild(e);
|
|
|
|
reader.onload = fileLoader_onLoad;
|
|
reader.readAsText(f);
|
|
}
|
|
|
|
/**************************************/
|
|
function checkBrowser() {
|
|
/* Checks whether this browser can support the necessary stuff */
|
|
var missing = "";
|
|
|
|
if (!window.File ) {missing += ", File"}
|
|
if (!window.FileReader ) {missing += ", FileReader"}
|
|
if (!window.FileList ) {missing += ", FileList"}
|
|
if (!window.DOMTokenList) {missing += ", DOMTokenList"}
|
|
if (!window.ArrayBuffer ) {missing += ", ArrayBuffer"}
|
|
if (!window.DataView ) {missing += ", DataView"}
|
|
if (!window.Promise ) {missing += ", Promise"}
|
|
if (!window.JSON ) {missing += ", JSON"}
|
|
|
|
if (missing.length == 0) {
|
|
return false;
|
|
} else {
|
|
alert("No can do... your browser does not\n" +
|
|
"support the following features:\n" + missing.substring(2));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/******************** Start of window.onload() ********************/
|
|
if (checkBrowser()) {
|
|
return;
|
|
}
|
|
|
|
$$("CRPoolSelector").value = null; // clear any prior pool selection
|
|
$$("CRPoolSelector").addEventListener("change", loadPoolsets, false);
|
|
$$("CRFileSelector").value = null; // clear any prior file selection
|
|
$$("CRFileSelector").addEventListener("change", loadSourceFile, false);
|
|
|
|
pass1List = $$("Pass1ListCheck").checked;
|
|
$$("Pass1ListCheck").addEventListener("click", function(ev) {
|
|
pass1List = ev.target.checked;
|
|
});
|
|
|
|
pass2List = $$("Pass2ListCheck").checked;
|
|
$$("Pass2ListCheck").addEventListener("click", function(ev) {
|
|
pass2List = ev.target.checked;
|
|
});
|
|
|
|
outputChecksum = $$("ChecksumCheck").checked;
|
|
$$("ChecksumCheck").addEventListener("click", function(ev) {
|
|
outputChecksum = ev.target.checked;
|
|
});
|
|
|
|
$$("SelectListing").addEventListener("click", function(ev) {
|
|
window.getSelection().selectAllChildren($$("TextPanel"));
|
|
});
|
|
}, false);
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|