1
0
mirror of https://github.com/pkimpel/retro-220.git synced 2026-01-13 15:18:24 +00:00
pkimpel.retro-220/software/tools/BAC-Assembler.html

2294 lines
93 KiB
HTML

<!DOCTYPE html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>BAC-220 Assembler</title>
<!--
/***********************************************************************
* 220/software/tools BAC-Assembler.html
************************************************************************
* Copyright (c) 2016, 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).
*
* Assembles source for 220 machine language programs as used in 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-9 symbolic label or point label (*label).
* col 11-14 symbolic op code (standard 220 mnemonics).
* col 15 override sign (0-9, +, -), blank => 0.
* col 17... operands (terminated by first free space).
* col 73-80 sequence and identification.
*
* All other columns are ignored by this assembler.
*
* Output is a simulated line printer listing in the <iframe> of the web
* page from which the assembler is run. The output of Pass 1 is designed
* to match the listing from which the compiler was transcribed, so that
* it may be compared for proofing purposes.
*
* Output of Pass 2 is a traditional assembler listing, with words of
* generated object code and data.
*
* Output of the generated code and data is... *TBD*
*
************************************************************************
* 2016-12-09 P.Kimpel
* Original version, cloned from retro-b5500 B5500CardReaderPrototype.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;
height: 100%;
margin: 1ex}
DIV.heading {
margin-top: 12px;
margin-bottom: 6px;
font-weight: bold}
#CardReaderPanel {
position: relative;
color: white;
background-color: #666;
width: 600px;
height: 40px;
border: 1px solid black;
border-radius: 8px;
padding: 0;
vertical-align: top}
#OptionsPanel {
position: relative;
margin-top: 4px;
margin-bottom: 4px}
#CRFileSelector {
position: absolute;
top: 8px;
left: 8px;
width: 580px;
border: 1px solid white}
#TextPanel {
position: absolute;
top: 90px;
left: 0;
bottom: 200px;
width: 640px;
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}
</style>
</head>
<body>
<div class=heading>
Assembler for the Burroughs 220 BALGOL Compiler &amp; Intrinsics
</div>
<div id=CardReaderPanel>
<input id=CRFileSelector type=file size=90>
</div>
<div id=OptionsPanel>
<input id=Pass1ListCheck type=checkbox value=1>
<label for=Pass1ListCheck>Pass 1 Listing</label>
&nbsp;&nbsp;
<input id=Pass2ListCheck type=checkbox value=1 CHECKED>
<label for=Pass2ListCheck>Pass 2 Listing</label>
</div>
<div id=TextDiv>
<pre id=TextPanel></pre>
</div>
<script>
"use strict";
window.addEventListener("load", function() {
// Card reader properties
var buffer = "";
var bufferLength = 0;
var bufferOffset = 0;
var eolRex = /([^\n\r\f]*)((:?\r[\n\f]?)|\n|\f)?/g;
var pass1List = false;
var pass2List = true;
var panel = $$("TextPanel");
var rTrimRex = /\s*$/;
var sprintLimit = 100; // cards processed before yielding control
// Input field 0-relative column locations
var labelIndex = 4;
var opCodeIndex = labelIndex + 6;
var operandIndex = labelIndex + 12;
var operandLength = 55;
// Card data structure
var cardData = {
atEOF: false,
offset: 0,
length: 0,
serial: 0,
text: ""}
// 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 with a leading + or -)
var tokIncompleteString = 7; // unterminated $-delimited string literal (probably continued on next card)
var tokString = 8; // $-delimited string literal
// token structure for operand parsing
var token = {
type: tokEmpty,
offset: 0,
text: "",
word: 0,
newOffset: -1};
// Assembly storage
var autoSymTab = {}; // auto-declared (undefined) symbol table
var errorCount = 0; // assembler error count
var errorTank = []; // holding area for errors on current line
var location = 0; // current instruction address
var pointTab = {}; // Point-label table: holds the current sequence number for each point label
var poolTab = {}; // Pool literal table: holds address of the literal word
var symTab = {}; // Symbol table: holds the address value for each label
var p10 = [ 1, // powers of 10 table
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
10000000000,
100000000000,
1000000000000];
var asciiFilter = [ // translate ASCII to 220 internal character codes
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
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, 0, 23, 20, 3, 21, // 20-2F
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 0, 13, 4, 0, 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 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
* digit in (41) 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.
*
* Type codes:
* 1 = address field, inserted in (04)
* 2 = secondary value inserted in (33)
* 3 = secondary value inserted in (44)
* 4 = unit or count digit inserted in (11)
* 5 = variant digit inserted in (41)
* 6 = sL field designator inserted in (22); if specified, insert 1 in (31)
* 7 = sL field designator inserted in (22); (31) is undisturbed
* 8 = value inserted in (32)
* 9 = value inserted in (42)
* 10 = value inserted in (21)
* 11 = value inserted in (62)
* 12 = value inserted in (64)
* 13 = BU pair for CRF/CWF: (B-1)*2 in (41) U in (11)
* 14 = reload-lockout value added to (41)
* 15 = digit inserted in (11); if specified, insert 1 in (41)
* 16 = format band digit in (41): (B-1)*2
* 19 = resolved address only
***************************************/
// Pseudo-instruction codes
var pseudoDEFN = -1;
var pseudoLOCN = -2;
var pseudoCNST = -3;
var pseudoF244 = -4;
var pseudoF424 = -5;
var pseudoFBGR = -6;
var pseudoFINI = -9;
var opTab = {
"HLT": [ 0, 1, 0, 3, 0],
"NOP": [ 1, 1, 0, 3, 0],
"PRD": [ 3, 1, -1, 4, -1, 8, -1, 5, 0],
"PRB": [ 4, 1, -1, 4, -1, 5, 0, 8, 0],
"PRI": [ 5, 1, -1, 4, -1, 8, -1, 5, 0],
"PWR": [ 6, 1, -1, 4, -1, 8, -1],
"PWI": [ 7, 1, -1, 4, -1],
"KAD": [ 8, 1, 0, 3, 0],
"SPO": [ 9, 1, -1, 8, -1, 15, 0],
"CAD": [ 10, 1, -1, 2, 0],
"CAA": [110, 1, -1, 2, 0],
"CSU": [ 11, 1, -1, 2, 0],
"CSA": [111, 1, -1, 2, 0],
"ADD": [ 12, 1, -1, 2, 0],
"ADA": [112, 1, -1, 2, 0],
"SUB": [ 13, 1, -1, 2, 0],
"SUA": [113, 1, -1, 2, 0],
"MUL": [ 14, 1, -1, 3, 0],
"DIV": [ 15, 1, -1, 3, 0],
"RND": [ 16, 1, 0, 3, 0],
"EXT": [ 17, 1, -1, 3, 0],
"CFA": [ 18, 1, -1, 6, 0],
"CFR": [118, 1, -1, 2, 0],
"ADL": [ 19, 1, -1, 3, 0],
"IBB": [ 20, 1, -1, 3, -1],
"DBB": [ 21, 1, -1, 3, -1],
"FAD": [ 22, 1, -1, 2, 0, 4, 0],
"FAA": [122, 1, -1, 2, 0, 4, 0],
"FSU": [ 23, 1, -1, 2, 0, 4, 0],
"FSA": [123, 1, -1, 2, 0, 4, 0],
"FMU": [ 24, 1, -1, 3, 0],
"FDV": [ 25, 1, -1, 3, 0],
"IFL": [ 26, 1, -1, 7, -1, 9, -1],
"DFL": [ 27, 1, -1, 7, -1, 9, -1],
"DLB": [ 28, 1, -1, 7, -1, 9, -1],
"RTF": [ 29, 1, -1, 8, -1],
"BUN": [ 30, 1, -1, 3, 0],
"BOF": [ 31, 1, -1, 3, 0],
"BRP": [ 32, 1, -1, 3, 0],
"BSA": [ 33, 1, -1, 5, -1, 2, 0],
"BPA": [ 33, 1, -1, 5, 0, 2, 0],
"BMA": [ 33, 1, -1, 5, 1, 2, 0],
"BCH": [ 34, 1, -1, 2, 0],
"BCL": [134, 1, -1, 2, 0],
"BCE": [ 35, 1, -1, 2, 0],
"BCU": [135, 1, -1, 2, 0],
"BFA": [ 36, 1, -1, 7, -1, 9, -1],
"BZA": [ 36, 1, -1, 7, 0, 9, 0],
"BFR": [ 37, 1, -1, 7, -1, 9, -1],
"BZR": [ 37, 1, -1, 7, 0, 9, 0],
"BCS": [ 38, 1, -1, 4, 0],
"SOR": [ 39, 1, 0, 2, 0],
"SOH": [139, 1, 0, 2, 0],
"IOM": [239, 1, -1, 2, 0],
"STA": [ 40, 1, -1, 6, 0],
"STR": [140, 1, -1, 6, 0],
"STB": [240, 1, -1, 6, 0],
"LDR": [ 41, 1, -1, 3, 0],
"LDB": [ 42, 1, -1, 2, 0],
"LBC": [142, 1, -1, 2, 0],
"LSA": [ 43, 5, -1, 1, 0, 2, 0],
"STP": [ 44, 1, -1, 3, 0],
"CLA": [145, 1, 0, 2, 0],
"CLR": [245, 1, 0, 2, 0],
"CAR": [345, 1, 0, 2, 0],
"CLB": [445, 1, 0, 2, 0],
"CAB": [545, 1, 0, 2, 0],
"CRB": [645, 1, 0, 2, 0],
"CLT": [745, 1, 0, 2, 0],
"CLL": [ 46, 1, -1, 3, 0],
"SRA": [ 48, 1, -1, 2, 0],
"SRT": [148, 1, -1, 2, 0],
"SRS": [248, 1, -1, 2, 0],
"SLA": [ 49, 1, -1, 2, 0],
"SLT": [149, 1, -1, 2, 0],
"SLS": [249, 1, -1, 2, 0],
"MTS": [ 50, 1, -1, 4, -1, 8, 0],
"MFS": [4000050,
1, -1, 4, -1, 8, 0],
"MLS": [450, 4, -1, 8, 0, 1, 0],
"MRW": [850, 4, -1, 8, 0, 1, 0],
"MDA": [950, 4, -1, 8, -1, 1, 0],
"MTC": [ 51, 1, -1, 4, -1, 8, -1, 5, -1],
"MFC": [4000051,
1, -1, 4, -1, 8, -1, 5, -1],
"MRD": [ 52, 1, -1, 4, -1, 10, -1, 5, 0],
"MNC": [ 52, 1, -1, 4, -1, 10, -1, 5, 1],
"MRR": [ 53, 1, -1, 4, -1, 10, -1, 5, 0],
"MIW": [ 54, 1, -1, 4, -1, 10, -1, 9, 0],
"MIR": [ 55, 1, -1, 4, -1, 10, -1, 9, 0],
"MOW": [ 56, 1, -1, 4, -1, 10, -1, 9, 0],
"MOR": [ 57, 1, -1, 4, -1, 10, -1, 9, 0],
"MPF": [ 58, 4, -1, 10, -1, 1, 0],
"MPB": [158, 4, -1, 10, -1, 1, 0],
"MPE": [258, 4, -1, 1, 0],
"MIB": [ 59, 1, -1, 4, -1, 8, 0],
"MIE": [159, 1, -1, 4, -1, 8, 0],
"CRD": [ 60, 1, -1, 4, -1, 5, 0, 8, 0],
"CWR": [ 61, 1, -1, 4, -1, 16, -1, 8, 0],
"CRF": [ 62, 1, -1, 13, -1, 14, 0],
"CWF": [ 63, 1, -1, 13, -1, 14, 0],
"CRI": [ 64, 1, -1, 4, -1],
"CWI": [ 65, 1, -1, 4, -1],
"HPW": [ 66, 1, -1, 8, -1],
"HPI": [ 67, 1, 0, 3, 0],
// Pseudo-ops
"DEFN": [pseudoDEFN, // define symbol
19, -1],
"LOCN": [pseudoLOCN, // set location counter
19, -1],
"CNST": [pseudoCNST], // assemble list of literal values
"F244": [pseudoF244, // assemble word from 22-64-04 fields
7, 0, 12, 0, 1, 0],
"F424": [pseudoF424, // assemble word from 44-62-04 fields
3, 0, 11, 0, 1, 0],
"FBGR": [pseudoFBGR], // assemble Cardatron format band
"FINI": [pseudoFINI, // finish assembly, output literal pool
1, 0]
};
/**************************************/
function $$(id) {
return document.getElementById(id);
}
/**************************************/
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 clearPanel() {
/* Clears the text panel */
var kid;
while (kid = panel.firstChild) {
panel.removeChild(kid);
}
}
/**************************************/
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 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;
}
}
/**************************************/
function fileLoader_onLoad(ev) {
/* Handles the onload event for a readAsText FileReader */
buffer = ev.target.result;
bufferOffset = 0;
bufferLength = buffer.length;
$$("CRFileSelector").value = null;
assembleFile();
}
/**************************************/
function fileSelector_onChange(ev) {
/* Handle the <input type=file> onchange event when a file is selected */
var e; // spinner image DOM element
var f = ev.target.files[0];
var reader = new FileReader();
/********************
alert("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 printLine(text) {
/* Appends "text"+NL as a new text node to the panel DOM element */
var e = document.createTextNode(text + "\n");
panel.appendChild(e);
panel.scrollTop += 30
}
/**************************************/
function dumpErrorTank() {
/* Dumps the tank of error messages to the text panel */
var x;
for (x=0; x<errorTank.length; ++x) {
printLine(errorTank[x]);
}
printLine("");
errorTank = [];
}
/**************************************/
function printError(msg) {
/* Prints an error message to the text panel and bumps the error count */
++errorCount;
errorTank.push("******** " + msg);
}
/**************************************/
function declarePointLabel(label, location) {
/* For each instance of a point label in the label field, creates a
pseudo-label in the symbol table with the next number for that point */
var labelID; // unique symbol for the point label instance
var pointNr; // current point number
if (label in pointTab) {
pointNr = ++pointTab[label];
} else {
pointNr = pointTab[label] = 1;
}
labelID = label + padLeft(pointNr, 5-label.length, ".");
if (labelID in symTab) {
printError("DUPLICATE POINT LABEL -- IMPOSSIBLE: " + labelID);
} else {
symTab[labelID] = location;
}
}
/**************************************/
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 -1 if the
label is currently undefined */
var labelID; // unique symbol for the point label instance
var pointNr; // 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;
}
labelID = label + padLeft(pointNr, 5-label.length, ".");
if (labelID in symTab) {
return symTab[labelID];
} else {
return -1;
}
}
/**************************************/
function parseNumber(text, token, sign) {
/* Parses a decimal number up to 11 digits in length starting at the
current offset in "text". If the number is less than 11 digits long,
the sign is applied as the high-order digit; otherwise the sign is
ignored. Returns the decimal string in token.text and the binary 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 x = token.offset; // current offset into operand string
while (x < length) {
c = text.charAt(x);
if (c >= "0" && c <= "9") {
++x;
raw += c;
} else {
break; // out of while loop
}
}
if (raw.length > 11) {
printError("NUMERIC LITERAL LONGER THAN 11 DIGITS: " + raw);
raw = raw.substring(raw.length-11);
} else if (raw.length < 11) {
raw = sign.toString() + padLeft(raw, 10, "0");
}
token.type = tokInteger;
token.newOffset = x;
token.text = raw;
return parseInt(raw, 10);
}
/**************************************/
function parseString(text, token, singleWord, continued) {
/* 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.
Strings can be continued across card boundaries. The caller will indicate
this is a continuation of an existing string by setting "continued" to
true, and placing data for any prior partial word in token.text and
token.word */
var c; // current parse character
var code; // current 220 char code
var count = 0; // chars in current word
var doubleDollar = false; // $$-delimited string
var length = text.length; // length of operand text
var raw = ""; // raw (ASCII) 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;
}
//--------------------------------------
if (continued) {
raw = token.text;
count = raw.length;
word = token.word;
} else {
if (++x < length) { // bypass the initial "$"
c = text.charAt(x);
if (c == "$") {
++x; // bypass the second "$"
doubleDollar = true;
appendCode(16); // carriage return prints as $ on Cardatron
}
}
}
while (x < length) {
c = text.charAt(x);
if (c == "$") {
break;
} else {
++x;
raw += c;
code = c.charCodeAt(0);
appendCode((code < asciiFilter.length ? asciiFilter[code]
: (code == 0xA4 ? 4 : 0))); // the lozenge
}
}
if (x >= length) {
token.type = tokIncompleteString;
// printError("$-STRING NOT TERMINATED");
} else {
token.type = tokString; // string is complete, so...
++x; // bypass the terminating "$"
if (doubleDollar) {
if (x >= length) {
printError("$$-STRING NOT TERMINATED");
} else if (text.charAt(x) == "$") {
++x; // bypass the second terminating "$"
appendCode(16); // and append another carriage return
} else {
printError("$$-STRING NOT TERMINATED");
}
}
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
return values;
}
/**************************************/
function parseAddressPrimary(text, token, resolved) {
/* Parses the next address primary from "text" 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 length = text.length; // length of operand text
var raw = ""; // raw text of parsed token
var val = 0; // temp used with numeric pool literals
var x = token.offset; // current offset into operand string
if (x >= length) { // empty primary
token.type = tokEmpty;
token.newOffset = length;
token.text = "";
} else {
c = text.charAt(x);
switch (true) {
case (c == "*"): // parse current location counter
token.type = tokLocation;
token.text = c;
token.newOffset = x+1;
break;
case (c >= "0" && c <= "9"): // parse integer literal
val = parseNumber(text, token, 0);
token.type = tokInteger;
if (val > 9999) {
printError("INTEGER LITERAL LONGER THAN 4 DIGITS: " + token.text);
}
break;
case (c >= "A" && c <= "Z"): // regular or point label
raw = c;
while (++x < length) {
c = text.charAt(x);
if (c >= "A" && c <= "Z") {
raw += c;
} else if (c >= "0" && c <= "9") {
raw += c;
} else {
break; // out of while loop
}
}
if (raw.length > 5) {
printError("LABEL LONGER THAN 5 CHARACTERS: " + raw);
}
// Check if it's a point label (could be followed by an adding operator)
if (x >= length) {
token.type = tokLabel; // label is at the end, can't be a point label
} else if (c != "+" && c != "-") {
token.type = tokLabel; // not followed by +/-, not a point label
} else {
token.type = (c == "-" ? tokBackwardPoint : tokForwardPoint);
if (x+1 >= length) {
++x; // +/- is at the end, a point label
} else {
c = text.charAt(x+1);
if (c == " ") {
++x; // +/- followed by a space, a point label
} else if (c == ",") {
++x; // +/- followed by a comma, a point label
} else if (c == "+" || c == "-") {
++x; // +/- followed by a +/-, a point label
} else {
token.type = tokLabel; // not a point label
}
}
}
token.text = raw;
token.newOffset = x;
break;
case (c == "+"): // parse pool numeric literal
case (c == "-"):
raw = c;
if (++x >= length) {
printError("INVALID POOL LITERAL SYNTAX");
token.newOffset = x;
} else {
token.offset = x;
c = text.charAt(x);
if (c >= "0" && c <= "9") {
parseNumber(text, token, "");
if (token.text.length > 10) {
printError("POOL LITERAL LONGER THAN 10 DIGITS: " + token.text);
}
token.text = raw + padLeft(token.text, 10, "0");
} else if (c == " " || c == ",") {
printError("EMPTY POOL LITERAL");
} else {
val = evaluateAddress(text, token, resolved);
if (val >= 0) { // a resolved address
token.text = raw + padLeft(val, 10, "0");
} else { // unresolved, save address expression for end of pass
token.text = "." + raw + text.substring(x, token.newOffset);
}
}
}
token.type = tokLiteral;
break;
case (c == "$"): // parse pool string literal
parseString(text, token, true, false);
token.text = "$" + token.text.substring(0, 5);
token.type = tokLiteral;
break;
case (c == " "): // empty primary
token.type = tokEmpty;
token.newOffset = x;
token.text = "";
break;
default: // not valid
token.type = tokEmpty;
token.newOffset = x;
token.text = "";
printError("ADDRESS PRIMARY CANNOT START WITH \"" + c + "\"");
break;
} // switch true
}
}
/**************************************/
function evaluateAddress(text, token, resolved) {
/* Evaluates the current offset in "text" as an address expression and
returns its binary value. If "resolved" is true, the address must resolve
to a known address, otherwise an error is issued. If the address cannot
be resolved, returns -1.
Address expressions consist of a list of address primaries delimited
by + or - operators. See parseAddressPrimary() for a definition */
var address = 0; // result address
var label; // constructed point label ID
var length = text.length; // length of operand string
var adding = 1; // +/- operator
var unresolvable = false; // true if address cannot be resolved
var val; // value of primary
do {
parseAddressPrimary(text, token, resolved);
switch (token.type) {
case tokEmpty: // empty text
val = 0;
break;
case tokLocation: // *, current location counter
val = location;
break;
case tokInteger: // integer literal
val = parseInt(token.text, 10);
break;
case tokLabel: // regular label
if (token.text in symTab) {
val = symTab[token.text];
if (val < 0) {
unresolvable = true;
}
} else {
symTab[token.text] = val = -1;
unresolvable = true;
}
break;
case tokBackwardPoint: // backward point label
case tokForwardPoint: // forward point label
label = "*" + token.text;
val = fetchPointLabel(label, token.type == tokForwardPoint);
if (val < 0) {
val = -1;
unresolvable = true;
}
break;
case tokLiteral: // pool literal
if (token.text in poolTab) {
val = poolTab[token.text];
if (val < 0) {
unresolvable = true;
}
} else {
poolTab[token.text] = val = -1;
unresolvable = true;
}
break;
default:
printError("INVALID ADDRESS TOKEN TYPE CODE: " + token.type);
break;
} // switch token.type
if (resolved && unresolvable) {
printError("MUST BE A RESOLVED ADDRESS: " + token.text);
}
address += val*adding;
if (token.newOffset < length) {
switch (text.charAt(token.newOffset)) {
case "+":
adding = 1;
token.offset = ++token.newOffset;
break;
case "-":
adding = -1;
token.offset = ++token.newOffset;
break;
default:
length = -1; // exit do loop
break;
}
}
} while (token.newOffset < length);
return (unresolvable ? -1 : address);
}
/**************************************/
function evaluateOperand(text, token, resolved) {
/* Parses and evaluates the fields of a standard operand. An operand
consists of a comma-delimited list of address values. Each address value
is a sequence of address primaries delimited by + or - operators. The
operand is terminated by the first space character. If "resolved" is
true, all address values must resolve to defined addresses, otherwise
an error is produced and the resulting address value will be -1.
Returns an array of address values */
var c; // current parse character
var length = text.length; // length of operand string
var values = []; // array of operand field address values
if (text.charAt(token.offset) != " ") { // check for empty operand
while (token.offset < length) {
values.push(evaluateAddress(text, token, resolved));
if (token.newOffset < length) {
c = text.charAt(token.newOffset);
if (c == ",") {
token.offset = ++token.newOffset; // continue with next field
} else if (c == " ") {
break; // out of while loop
} else {
++token.newOffset;
printError("INVALID OPERAND LIST: " + c);
break;
}
}
}
}
//for (var x=0; x<values.length; ++x) {
// printLine(" DEBUG: " + padLeft(values[x], 11));
//}
return values;
}
/**************************************/
function parseConstantList(text, token, continued) {
/* Parses the operand text for the CNST pseudo-operator and returns an
array of binary words with the values found. This pseudo can continue
across multiple cards if the continuation cards have a blank opCode field.
The only time this is an issue is for continued strings, so the caller
must indicate this by setting "continued" to true. Only fully-parsed words
are returned in the result array. Any partial word of string text for an
unterminated string is left for the continuation call */
var c; // current parse character
var length = text.length; // length of operand string
var val2; // temp array of result words from parsing strings
var values = []; // result array of completely parsed words
var x; // scratch index
while (token.offset < length) { // parse comma-delimited values
c = text.charAt(token.offset);
if (continued || c == "$") {
val2 = parseString(text, token, false, continued);
for (x=0; x<val2.length; ++x) {
values.push(val2[x]);
}
} else if (c == ",") {
values.push(0);
} else if (c == "+") {
++token.offset;
values.push(parseNumber(text, token, 0));
} else if (c == "-") {
++token.offset;
values.push(parseNumber(text, token, 1));
} else if (c >= "0" && c <= "9") {
values.push(parseNumber(text, token, 0));
}
if (token.newOffset < length) {
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));
//}
return values;
}
/**************************************/
function emitWord(location, word) {
/* Outputs one word of assembled code to the object program */
//*** STUB FOR NOW ***//
return;
}
/**************************************/
function generateInstructionWord(opDesc, sign, values) {
/* Generates the word value for an executable instruction.
"opDesc" is the array of values for the op code from "opTab".
"values" is the list of address values parsed from the operand text.
Returns the binary value of the word */
var f; // current field value
var nsign = 0; // numeric sign value
var opTop = opDesc.length; // length of op code description array
var ox = 0; // opDesc[] index
var w1; // word field 1
var w2; // word field 2
var word = 0; // resultant binary word value
var vTop = values.length; // length of operand address values array
var vx = 0; // values[] index
if (!(sign in signValues)) {
printError("INVALID SIGN VALUE: " + sign);
} else {
nsign = signValues[sign];
}
if (opDesc[0] > 0) {
word = opDesc[0]*p10[4];
}
for (ox=1; ox<opDesc.length; ox+=2) {
if (vx < vTop) {
f = values[vx];
} else if (opDesc[ox+1] < 0) {
f = 0;
printError("OPERAND FIELD #" + (vx+1).toFixed(0) + " MISSING");
} else {
f = opDesc[ox+1];
}
if (f < 0) {
f = p10[11] - f; // compute a 10-digit 10s-complement
}
switch (opDesc[ox]) {
case 1: // address field, inserted in (04)
w2 = word % p10[4];
word += f%p10[4] - w2;
break;
case 2: // secondary value inserted in (33)
w1 = word % p10[10];
w2 = w1 - w1 % p10[7];
word += (f%p10[3])*p10[7] - w2;
break;
case 3: // secondary value inserted in (44)
w1 = word % p10[10];
w2 = w1 - w1 % p10[6];
word += (f%p10[4])*p10[6] - w2;
break;
case 4: // unit or count digit inserted in (11)
w1 = word % p10[10];
w2 = w1 - w1 % p10[9];
word += (f%10)*p10[9] - w2;
break;
case 5: // variant digit inserted in (41)
w1 = word % p10[7];
w2 = w1 - w1 % p10[6];
word += (f%10)*p10[6] - w2;
break;
case 6: // sL field designator inserted in (22); if specified, insert 1 in (31)
w1 = word % p10[10];
w2 = w1 - w1 % p10[8];
word += (f%100)*p10[8] - w2;
if (vx < vTop) {
w1 = word % p10[8];
w2 = w1 - w1 % p10[7];
word += p10[7] - w2;
}
break;
case 7: // sL field designator inserted in (22); (31) is undisturbed
w1 = word % p10[10];
w2 = w1 - w1 % p10[8];
word += (f%100)*p10[8] - w2;
break;
case 8: // value inserted in (32)
w1 = word % p10[9];
w2 = w1 - w1 % p10[7];
word += (f%100)*p10[7] - w2;
break;
case 9: // value inserted in (42)
w1 = word % p10[8];
w2 = w1 - w1 % p10[6];
word += (f%100)*p10[6] - w2;
break;
case 10: // value inserted in (21)
w1 = word % p10[9];
w2 = w1 - w1 % p10[8];
word += (f%10)*p10[8] - w2;
break;
case 11: // value inserted in (62)
w1 = word % p10[6];
w2 = w1 - w1 % p10[4];
word += (f%100)*p10[4] - w2;
break;
case 12: // value inserted in (64)
w1 = word % p10[8];
w2 = w1 - w1 % p10[4];
word += (f%p10[4])*p10[4] - w2;
break;
case 13: // BU pair for CRF/CWF: (B-1)*2 in (41) U in (11)
w1 = word % p10[7]; // band in (41)
w2 = w1 - w1 % p10[6];
word += (((f%100-f%10)/10)-1)*2*p10[6] - w2;
w1 = word % p10[10]; // unit in (11)
w2 = w1 - w1 % p10[9];
word += (f%10)*p10[9] - w2;
break;
case 14: // reload-lockout value added to (41)
w1 = word % p10[7]; // band in (41)
w2 = w1 + ((f%10)*p10[6])%p10[7];
word += w2 - w1;
break;
case 15: // digit inserted in (11); if specified, insert 1 in (41)
w1 = word % p10[10];
w2 = w1 - w1 % p10[9];
word += (f%100)*p10[9] - w2;
if (vx < vTop) {
w1 = word % p10[7];
w2 = w1 - w1 % p10[6];
word += p10[6] - w2;
}
break;
case 16: // format band digit in (41): (B-1)*2
w1 = word % p10[7]; // band in (41)
w2 = w1 - w1 % p10[6];
word += (f%10-1)*2*p10[6] - w2;
break;
case 19: // resolved address only
w2 = word % p10[4];
word += f%p10[4] - w2;
break;
default:
printError("INVALID OPDESC INDEX: " + opDesc[ox]);
break;
} // switch opDesc[ox]
++vx;
} // for ox
if (vx < vTop) {
printError("EXTRANEOUS OPERAND FIELDS IGNORED: " + values[vx]);
}
if (sign != " ") {
word = nsign*p10[10] + word % p10[10];
}
return word;
}
/**************************************/
function generateFormatBand(operand) {
/* 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 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 "T":
case "Z":
do {
--count;
codes += c;
} while (count > 0);
count = 0;
break;
case "B":
do {
--count;
if (overpunch) {
codes += "S"; // 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 "S":
codes += "XS"; // acts like XB
count = 0;
break;
case "X":
overpunch = true;
codes += c; // half-column: zone digit
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 for standalone sign
if (dx < 10) {
printError("P CODE NOT FOR SIGN DIGIT: " + cx);
}
emitBandDigits(3, 1);
colCount += 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 "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("P,T CODE NOT FOR SIGN DIGIT: " + cx);
}
emitBandDigits(3, 1);
dx += 1;
break;
case "S": // supply numeric half-column for standalone sign
if (dx < 10) {
printError("P,T CODE NOT FOR SIGN DIGIT: " + cx);
}
emitBandDigits(0, 1);
colCount += 1;
break;
case "X": // copy zone digit for sign
emitBandDigits(1, 1);
dx += 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 dumpSymbolTable() {
/* Dumps the contents of the symbol table to the text panel */
var keys; // sorted symbol table keys
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 > 80) {
printLine(text);
text = "";
offset = 0;
}
text += padLeft(symTab[keys[x]], offset-text.length+5) + " " + keys[x];
offset += 20;
}
printLine(text);
}
/**************************************/
function assembleFile() {
/* Initializes or reinitializes the assembler for a new file */
errorCount = 0;
errorTank = [];
location = 0;
cardData.atEOF = false;
cardData.serial = 0;
autoSymTab = {};
pointTab = {};
poolTab = {};
symTab = {};
symTab["RLO"] = 1; // kludge for CWR reload-lockout field
symTab["BMOD"] = 1; // kludge for MRD B-modify address field
clearPanel();
startPass1();
}
/**************************************/
function startPass1() {
/* Reads the control cards and then passes control to Pass 1 of the assembler */
var done = false; // loop control
var opCode; // op code field of current card
do {
readACard();
if (cardData.atEOF) {
done = true;
printError("EOF ENCOUNTERED BEFORE PASS 1");
} else {
opCode = cardData.text.substring(opCodeIndex, opCodeIndex+5).trim();
switch (opCode) {
case "ASMBL":
printLine(padRight("", 8+5+4+3+8+4+6) +
cardData.text.substring(opCodeIndex, operandIndex+operandLength).trim());
break;
case "REORD":
printLine(padRight("", 8+5+4+3+8+4+6) +
cardData.text.substring(opCodeIndex, operandIndex+operandLength).trim());
break;
default:
done = true;
Promise.resolve().then(assemblePass1);
break;
}
}
} while (!done);
}
/**************************************/
function printPass1(seq, location, label, opCode, sign, operand) {
/* Prints a line of output from Pass 1 of the assembly to the text panel */
var text; // formatted line image
if (pass1List) {
if (opCode === null) {
text = padRight(" ", 8+5+4+3, " ");
} else {
text = padLeft(seq, 8, " ") + " " + padLeft(location, 4, "0") + " ";
}
text += padRight(label, 6) + padRight(opCode || " ", 4) +
padRight(sign, 2, " ") + rTrim(operand);
printLine(text);
if (errorTank.length > 0) {
dumpErrorTank();
}
}
}
/**************************************/
function buildPoolPass1() {
/* Builds the initial literal pool from symbols encountered in Pass 1 */
var label;
var text;
for (label in poolTab) {
if (label.charAt(0) != ".") {
text = label;
} else {
token.offset = 1;
poolTab[label] = location;
parseAddressPrimary(label, token, true);
text = token.text;
delete poolTab[label];
}
poolTab[text] = location;
symTab[text] = location;
printPass1("", location, "", "", "", text);
++location;
}
}
/**************************************/
function assemblePass1() {
/* Processes card images for Pass 1 of the assembler. Enters with the
first card in "cardData. Every "sprintCount" cards, this routine reschedules
itself after a short delay and then exits. This allows the text panel to be
updated in the browser window */
var card; // current card to be assembled
var continuedString = false; // true if processing a string split across cards
var done = false; // loop control
var finito = false; // true if the FINI pseudo-op has been sensed
var label; // label symbol from the card
var opCode; // op code field from the card
var opDesc; // array of op code description words from opTab{}
var operand; // operand field from the card
var origLoc; // original location for this line
var seq; // sequence field from the card
var sign; // sign-override field from the card
var sprintCount = 0; // counter for periodic refreshing of the text panel
var text; // scratch string
var thisLoc; // current location for this line (changed by DEFN)
var values; // array of address or word values from parsing operands
var x; // scratch index
do {
card = cardData.text;
label = card.substring(labelIndex, labelIndex+5).trim();
opCode = card.substring(opCodeIndex, opCodeIndex+4).trim();
operand = card.substring(operandIndex, operandIndex+operandLength); // no trim
seq = card.substring(72, 80);
sign = card.substring(opCodeIndex+4, opCodeIndex+5);
origLoc = thisLoc = location;
if (opCode == "REM") {
printPass1(seq, location, label, null, sign, operand);
readACard();
} else {
token.offset = 0;
if (opCode.length <= 0) { // treat like a CNST pseudo-op
opDesc = opTab["CNST"];
} else if (!(opCode in opTab)) {// treat like a NOP
printError("INVALID OP CODE");
opDesc = opTab["NOP"];
} else {
opDesc = opTab[opCode];
}
if (opDesc[0] >= 0) { // normal instruction
values = evaluateOperand(operand, token, false); // discard result, take only side effects
++location; // normal instructions bump the location counter
readACard();
} else {
// Parse the pseudo-op for size and location
switch (opDesc[0]) {
case pseudoDEFN:
values = evaluateOperand(operand, token, false);
if (values.length > 0) {
thisLoc = values[0];
} else {
printError("OPERAND ADDRESS REQUIRED");
}
readACard();
break;
case pseudoLOCN:
values = evaluateOperand(operand, token, true);
if (values.length < 1) {
printError("OPERAND ADDRESS REQUIRED");
} else if (values[0] >= 0) {
location = values[0];
}
readACard();
break;
case pseudoCNST:
values = parseConstantList(operand, token, continuedString);
location += values.length;
continuedString = (token.type == tokIncompleteString);
readACard();
if (continuedString) {
if (cardData.atEOF) {
printError("$-STRING NOT TERMINATED AT EOF");
} else {
text = cardData.text.substring(opCodeIndex, opCodeIndex+4).trim();
if (text.length > 0) {
printError("$-STRING NOT TERMINATED");
} else { // extract the partial word of the incomplete string
x = token.text.length % 5;
token.text = token.text.substring(token.text.length - x);
}
}
}
break;
case pseudoF244:
case pseudoF424:
values = evaluateOperand(operand, token, false); // discard result, take only side effects
++location; // word-builders merely bump the location counter
readACard();
break;
case pseudoFBGR:
location += 29; // all format bands are 29 words long
readACard();
break;
case pseudoFINI:
done = finito = true;
values = evaluateOperand(operand, token, true);
break;
default:
printError("INVALID PSEUDO INSTRUCTION CODE: " + opDesc[0]);
} // switch
}
if (label.length > 0) { // enter the label into the symbol table
if (label.charAt(0) == "*") {
declarePointLabel(label, thisLoc);
} else if (!(label in symTab)) {
symTab[label] = thisLoc;
} else if (symTab[label] >= 0) {
printError("DUPLICATE LABEL DEFINITION");
} else {
symTab[label] = thisLoc;
}
}
printPass1(seq, origLoc, label, opCode, sign, operand);
}
if (cardData.atEOF) {
done = true;
if (!finito) {
finito = true;
printError("EOF encountered before FINI in Pass 1");
}
} else if (++sprintCount > sprintLimit && !continuedString) {
done = true;
}
} while (!done);
if (!finito) {
Promise.resolve().then(assemblePass1);
} else {
// Wrap up Pass 1 and check for undefined symbols.
// Oddly, undefined symbols appear to have been implicitly defined at the end,
// so build a table of them and assign locations.
for (label in symTab) {
if (symTab[label] < 0) {
autoSymTab[label] = location;
symTab[label] = location;
printPass1("", location, label, "", "", "");
++location;
}
}
buildPoolPass1();
dumpErrorTank();
printLine("END OF PASS 1, ERRORS = " + errorCount);
if (pass1List || pass2List) {
dumpSymbolTable();
}
if (errorCount == 0) { // advance to Pass 2
Promise.resolve().then(initializePass2);
} else { // we're done -- remove the spinner image
$$("TextDiv").removeChild($$("Spinner"));
}
}
}
/**************************************/
function initializePass2() {
/* Reinitializes the assembler to reread the input file for Pass 2 */
var e; // point label ID
errorCount = 0;
errorTank = [];
location = 0;
bufferOffset = 0; // reset the card buffer position
cardData.atEOF = false;
cardData.serial = 0;
for (e in pointTab) { // reset the point label table
pointTab[e] = 0;
}
printLine("");
startPass2();
}
/**************************************/
function startPass2() {
/* Sets up for Pass 2 of the assembly and initiates it */
var done = false; // loop control
var opCode; // op code parsed from card
do {
readACard();
if (cardData.atEOF) {
done = true;
printError("EOF encountered before Pass 2");
} else {
opCode = cardData.text.substring(opCodeIndex, opCodeIndex+5).trim();
switch (opCode) {
case "ASMBL":
break;
case "REORD":
break;
default:
done = true;
Promise.resolve().then(assemblePass2);
break;
}
}
} while (!done);
}
/**************************************/
function printPass2(seq, serial, location, word, label, opCode, sign, operand) {
/* Prints a line of output from Pass 2 of the assembly to the text panel */
var addr; // address (04) field of instruction word
var op; // op code (62) field of instruction word
var text; // scratch string
var variant; // variant (44) field of instruction word
var w; // scratch variable for word field partitioning
var wordText; // instruction word as text "9 9999 99 9999"
if (pass2List) {
if (opCode === null) {
text = padRight(" ", 8+6+6+4+16+3, " ");
} else {
if (word === null) {
wordText = padRight(" ", 16, " ");
} else {
w = word;
addr = w % 10000;
w = (w-addr)/10000;
op = w % 100;
w = (w-op)/100;
variant = w % 10000;
w = (w-variant)/10000; // should be just the sign digit left
wordText = padLeft(w, 3, " ") + " " + padLeft(variant, 4, "0") + " " +
padLeft(op, 2, "0") + " " + padLeft(addr, 4, "0");
}
text = padLeft(seq, 8, " ") + padLeft(serial || " ", 6, " ") +
" " + padLeft(location, 4, "0") + wordText + " ";
}
text += padRight(label, 6) + padRight(opCode || " ", 4) +
padRight(sign, 2, " ") + rTrim(operand);
printLine(text);
if (errorTank.length > 0) {
dumpErrorTank();
}
}
}
/**************************************/
function buildPoolPass2() {
/* Builds the final literal pool */
var keys; // addresses for pool words
var label; // pool entry ID
var text; // scratch string
var values; // parseConstantList() result
var x; // scratch index
var xref = {}; // hash used to build address keys for sorting
// First, extract all the viable pool entries for sorting
for (label in poolTab) {
if (!(label in symTab)) {
printError("POOL LITERAL NOT IN SYMBOL TABLE: " + label);
} else {
location = symTab[label];
if (location < 0) {
printError("POOL LITERAL LOCATION NOT ASSIGNED: " + label);
} else {
text = padLeft(location, 4, "0");
xref[text] = label;
}
}
} // for label
// Now extract the address keys and sort them.
keys = Object.keys(xref).sort();
// Finally, build the pool in address sequence from the sorted keys
for (x=0; x<keys.length; ++x) {
label = xref[keys[x]];
location = symTab[label];
if (label.charAt(0) == "$") {
text = label + "$"
} else {
text = label;
}
token.offset = 0;
values = parseConstantList(text, token, false);
printPass2("", null, location, values[0], "", "", "", label);
emitWord(location, values[0]);
} // for x
}
/**************************************/
function assemblePass2() {
/* Processes card images for Pass 2 of the assembler. Enters with the
first card in "cardData. Every "sprintCount" cards, this routine reschedules
itself after a short delay and then exits. This allows the text panel to be
updated in the browser window */
var card; // current card to be assembled
var continuedString = false; // true if processing a string split across cards
var done = false; // loop control
var finito = false; // true if the FINI pseudo-op has been sensed
var label; // label symbol from the card
var opCode; // op code field from the card
var opDesc; // array of op code description words from opTab{}
var operand; // operand field from the card
var origLoc; // original location for this line
var seq; // sequence field from the card
var serial; // card serial number for printPass2()
var sign; // sign-override field from the card
var sprintCount = 0; // counter for periodic refreshing of the text panel
var text; // scratch string
var thisLoc; // current location for this line (changed by DEFN)
var values; // array of address or word values from parsing operands
var word; // assembled instruction word
var x; // scratch index
do {
card = cardData.text;
label = card.substring(labelIndex, labelIndex+5).trim();
opCode = card.substring(opCodeIndex, opCodeIndex+4).trim();
operand = card.substring(operandIndex, operandIndex+operandLength);
seq = card.substring(72, 80);
serial = cardData.serial;
sign = card.substring(opCodeIndex+4, opCodeIndex+5);
origLoc = thisLoc = location;
if (opCode == "REM") {
printPass2(seq, serial, location, word, label, null, sign, operand);
readACard();
} else {
token.offset = 0;
if (opCode.length <= 0) { // treat line a CNST pseudo-op
opDesc = opTab["CNST"];
} else if (!(opCode in opTab)) {// treat like a NOP
printError("INVALID OP CODE");
opDesc = opTab["NOP"];
} else {
opDesc = opTab[opCode];
}
if (opDesc[0] >= 0) { // normal instruction
values = evaluateOperand(operand, token, true);
word = generateInstructionWord(opDesc, sign, values);
printPass2(seq, serial, origLoc, word, label, opCode, sign, operand);
emitWord(location, word);
++location; // normal instructions bump the location counter
readACard();
} else {
// Parse the pseudo-op
switch (opDesc[0]) {
case pseudoDEFN:
values = evaluateOperand(operand, token, true);
if (values.length > 0) {
thisLoc = values[0];
} else {
printError("OPERAND ADDRESS REQUIRED");
}
printPass2(seq, serial, origLoc, null, label, opCode, sign, operand);
readACard();
break;
case pseudoLOCN:
values = evaluateOperand(operand, token, true);
if (values.length < 1) {
printError("OPERAND ADDRESS REQUIRED");
} else if (values[0] >= 0) {
location = values[0];
}
printPass2(seq, serial, origLoc, null, label, opCode, sign, operand);
readACard();
break;
case pseudoCNST:
values = parseConstantList(operand, token, continuedString);
printPass2(seq, serial, origLoc, values[0], label, opCode, sign, operand);
emitWord(location, values[0]);
++location;
for (x=1; x<values.length; ++x) {
printPass2("", null, origLoc+x, values[x], "", "", "", "", "");
emitWord(location, values[x]);
++location;
}
continuedString = (token.type == tokIncompleteString);
readACard();
if (continuedString) {
if (cardData.atEOF) {
printError("$-STRING NOT TERMINATED AT EOF");
} else {
text = cardData.text.substring(opCodeIndex, opCodeIndex+4).trim();
if (text.length > 0) {
printError("$-STRING NOT TERMINATED");
} else { // extract the partial word of the incomplete string
x = token.text.length % 5;
token.text = token.text.substring(token.text.length - x);
}
}
}
break;
case pseudoF244:
case pseudoF424:
values = evaluateOperand(operand, token, true);
word = generateInstructionWord(opDesc, sign, values);
printPass2(seq, serial, origLoc, word, label, opCode, sign, operand);
emitWord(location, word);
++location;
readACard();
break;
case pseudoFBGR:
values = generateFormatBand(operand);
printPass2(seq, serial, origLoc, values[0], label, opCode, sign, operand);
emitWord(location, values[0]);
++location;
for (x=1; x<values.length; ++x) {
printPass2("", null, origLoc+x, values[x], "", "", "", "");
emitWord(location, values[x]);
++location;
}
readACard();
break;
case pseudoFINI:
done = finito = true;
values = evaluateOperand(operand, token, true);
printPass2(seq, serial, origLoc, null, label, opCode, sign, operand);
break;
default:
printError("INVALID PSEUDO INSTRUCTION CODE: " + opDesc[0]);
readACard();
} // switch
}
if (label.length > 0) { // increment any point label counter
if (label.charAt(0) == "*") {
if (label in pointTab) {
++pointTab[label];
} else {
pointTab[label] = 1;
}
}
}
}
if (cardData.atEOF) {
done = true;
if (!finito) {
finito = true;
printError("EOF encountered before FINI in Pass 2");
}
} else if (++sprintCount > sprintLimit && !continuedString) {
done = true;
}
} while (!done);
if (!finito) {
Promise.resolve().then(assemblePass2);
} else {
// dump the auto-defined symbols
for (label in autoSymTab) {
printPass2("", null, autoSymTab[label], 0, label, "", "", "");
}
buildPoolPass2();
// Wrap up Pass 2, check again for undefined symbols (shouldn't be any)
for (text in symTab) {
if (symTab[text] < 0) {
printError("SYMBOL NOT DEFINED: " + text);
}
}
buffer = ""; // release the card buffer area
dumpErrorTank();
printLine("");
printLine("END OF PASS 2, ERRORS = " + errorCount);
$$("TextDiv").removeChild($$("Spinner")); // remove the spinner image
}
}
/**************************************/
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 (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;
}
$$("CRFileSelector").value = null; // clear any prior file selection
$$("CRFileSelector").addEventListener("change", fileSelector_onChange, 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});
}, false);
</script>
</body>
</html>