1
0
mirror of https://github.com/pkimpel/retro-220.git synced 2026-02-10 18:20:44 +00:00
Files
pkimpel.retro-220/software/tools/GEN-Assembler.html
Paul Kimpel 1ef4bc9d14 Commit updates to BALGOL DIRICHLET example.
1. Changes to DIRICHLET program that allow it to compile and run correctly.
2. Cosmetic changes to BALGOL-Main, BAC-Assembler, GEN-Assembler.
3. Commit software/tools/BALGOL-DumpAnalyzer.html script that does a parial
analysis of a memory dump from a BALGOL compiler run.
4. Commit compiler tape image and generator deck configured to run on a
system with 10000 words of memory.
2018-06-10 18:55:55 -07:00

3489 lines
132 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 &amp; 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>&nbsp;
<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=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 assembleFormatBand(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: same as P
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": // delete numeric half-column, copy zone for standalone sign
if (dx < 10) {
printError("S CODE NOT FOR SIGN DIGIT: " + cx);
}
emitBandDigits(13, 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 = assembleFormatBand(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 gapCount = 0; // number of consecutive undefined words
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];
if (word === undefined) {
++gapCount;
} else {
if (count > 5 || gapCount > 0) {
writeCard(card, count, cardAddr, seq);
card = "";
cardAddr = addr;
gapCount = 0;
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 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 "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>