1
0
mirror of https://github.com/pkimpel/retro-220.git synced 2026-02-17 13:07:47 +00:00
Files
pkimpel.retro-220/software/tools/BAC-Assembler.html
Paul Kimpel db25dabd6f Commit BALGOL-Overlay transcription WIP as of 2016-12-29.
Commit further corrections to Pass 1 of BAC-Assembler.html.
2016-12-29 10:19:49 -08:00

1415 lines
50 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
************************************************************************
* Assembler for the Burroughs 220 Algebraic Compiler (BALGOL)
*
* ...
*
************************************************************************
* 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}
BUTTON.greenButton {
background-color: #060;
color: white;
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
font-size: 9pt;
font-weight: bold;
width: 60px;
height: 40px;
border: 1px solid #DDD;
border-radius: 4px}
BUTTON.whiteButton {
background-color: #999;
color: black;
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
font-size: 9pt;
font-weight: bold;
width: 60px;
height: 40px;
border: 1px solid #DDD;
border-radius: 4px}
BUTTON.redButton {
background-color: #900;
color: white;
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
font-size: 9pt;
font-weight: bold;
width: 60px;
height: 40px;
border: 1px solid #DDD;
border-radius: 4px}
BUTTON.greenLit {
background-color: green}
BUTTON.whiteLit {
background-color: white}
BUTTON.redLit {
background-color: #F00}
DIV.heading {
margin-top: 12px;
margin-bottom: 6px;
font-weight: bold}
#CardReaderPanel {
position: relative;
color: white;
background-color: #666;
width: 600px;
height: 150px;
border: 1px solid black;
border-radius: 8px;
padding: 0;
vertical-align: top}
#CRNotReadyLight {
position: absolute;
top: 8px;
left: 8px}
#CREOFBtn {
position: absolute;
top: 8px;
left: 76px}
#CRStopBtn {
position: absolute;
top: 8px;
left: 144px}
#CRStartBtn {
position: absolute;
top: 8px;
left: 212px;}
#CRFileSelector {
position: absolute;
top: 56px;
left: 8px;
width: 580px;
border: 1px solid white}
#CRProgressBar {
position: absolute;
top: 84px;
left: 8px;
width: 580px;
border: 1px solid white}
#CROutHopperFrame {
position: absolute;
top: 106px;
left: 8px;
width: 580px;
height: 3em;
margin-top: 1px;
border: 1px solid white;
color: black;
background-color: white;
font-family: DejaVu Sans Mono, Consolas, Courier, monospace;
font-size: 9pt;
font-weight: normal}
#TextPanel {
position: absolute;
top: 180px;
left: 0;
bottom: 8px;
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}
</style>
</head>
<body>
<div class=heading>
Burroughs 220 BALGOL Assembler
</div>
<div id=CardReaderPanel>
<button id=CRNotReadyLight class="whiteButton whiteLit">NOT READY</button>
<button id=CRStartBtn class="greenButton">START</button>
<button id=CREOFBtn class="redButton">EOF</button>
<button id=CRStopBtn class="redButton">STOP</button>
<input id=CRFileSelector type=file size=90>
<meter id=CRProgressBar min=0 max=100 value=0 title="Click to clear input hopper"></meter>
<iframe id=CROutHopperFrame scrolling=auto></iframe>
</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 cardHandler = startAssembly;
var cardsPerMinute = 8000;
var eofArmed = 0;
var eolRex = /([^\n\r\f]*)((:?\r[\n\f]?)|\n|\f)?/g;
var lastReaderStamp = 0;
var millisPerCard = 60000/cardsPerMinute;
var outHopper;
var outHopperFrame = $$("CROutHopperFrame");
var panel = $$("TextPanel");
var readerState = 0;
// Input field 0-relative column locations
var labelIndex = 4;
var opCodeIndex = labelIndex + 6;
var operandIndex = labelIndex + 12;
// Card reader ready state
var readerNotReady = 0;
var readerReady = 1;
// Card data structure
var cardData = {
offset: 0,
length: 0,
serial: 0,
text: ""}
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, 0, 24, 4, 14, 0, 23, 20, 3, 21, // 20-2F
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 0, 13, 0, 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
// Output control
var printLine = printPass1;
/***************************************
* 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 overlayed by
* other fields in the instruction. If this value 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 (01)
* 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 = count or value inserted in (32)
* 9 = count or value inserted in (42)
* 10 = count or value inserted in (21)
* 11 = 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 = {
"CAD": [0x010, 1, -1, 2, 0],
"CAA": [0x110, 1, -1, 2, 0],
"ADD": [0x012, 1, -1, 2, 0],
"ADA": [0x112, 1, -1, 2, 0],
"ADL": [0x010, 1, -1, 3, 0],
"CSU": [0x011, 1, -1, 2, 0],
"CSA": [0x111, 1, -1, 2, 0],
"SUB": [0x013, 1, -1, 2, 0],
"SUA": [0x113, 1, -1, 2, 0],
"MUL": [0x014, 1, -1, 3, 0],
"DIV": [0x015, 1, -1, 3, 0],
"RND": [0x016, 1, 0, 3, 0],
"FAD": [0x022, 1, -1, 2, 0, 4, -1],
"FAA": [0x122, 1, -1, 2, 0, 4, -1],
"FSU": [0x023, 1, -1, 2, 0, 4, -1],
"FSA": [0x123, 1, -1, 2, 0, 4, -1],
"FMU": [0x024, 1, -1, 3, 0],
"FDV": [0x025, 1, -1, 3, 0],
"SRA": [0x048, 1, -1, 2, 0],
"SRT": [0x148, 1, -1, 2, 0],
"SRS": [0x248, 1, -1, 2, 0],
"SLA": [0x049, 1, -1, 2, 0],
"SLT": [0x149, 1, -1, 2, 0],
"SLS": [0x249, 1, -1, 2, 0],
"LDR": [0x041, 1, -1, 3, 0],
"LDB": [0x042, 1, -1, 2, 0],
"LBC": [0x142, 1, -1, 2, 0],
"LSA": [0x043, 1, 0, 5, -1, 2, 0],
"STA": [0x040, 1, -1, 6, 0],
"STR": [0x140, 1, -1, 6, 0],
"STB": [0x240, 1, -1, 6, 0],
"STP": [0x044, 1, -1, 3, 0],
"RTF": [0x029, 1, -1, 8, -1],
"CLA": [0x145, 1, 0, 2, 0],
"CLR": [0x245, 1, 0, 2, 0],
"CAR": [0x345, 1, 0, 2, 0],
"CLB": [0x445, 1, 0, 2, 0],
"CAB": [0x545, 1, 0, 2, 0],
"CRB": [0x645, 1, 0, 2, 0],
"CLT": [0x745, 1, 0, 2, 0],
"CLL": [0x046, 1, -1, 3, 0],
"EXT": [0x017, 1, -1, 3, 0],
"CFA": [0x018, 1, -1, 6, 0],
"CFR": [0x118, 1, -1, 2, 0],
"BUN": [0x030, 1, -1, 3, 0],
"BOF": [0x031, 1, -1, 3, 0],
"BRP": [0x032, 1, -1, 3, 0],
"BSA": [0x033, 1, -1, 5, -1, 2, 0],
"BPA": [0x033, 1, -1, 5, 0, 2, 0],
"BMA": [0x033, 1, -1, 5, 1, 2, 0],
"BCH": [0x034, 1, -1, 2, 0],
"BCL": [0x134, 1, -1, 2, 0],
"BCE": [0x035, 1, -1, 2, 0],
"BCU": [0x135, 1, -1, 2, 0],
"BFA": [0x036, 1, -1, 7, -1, 9, -1],
"BZA": [0x036, 1, -1, 7, 0, 9, 0],
"BFR": [0x037, 1, -1, 7, -1, 9, -1],
"BZR": [0x037, 1, -1, 7, 0, 9, 0],
"BCS": [0x038, 1, -1, 4, 0],
"SOR": [0x039, 1, 0, 2, 0],
"SOH": [0x139, 1, 0, 2, 0],
"IOM": [0x239, 1, -1, 2, 0],
"HLT": [0x000, 1, 0, 3, 0],
"NOP": [0x001, 1, 0, 3, 0],
"IBB": [0x020, 1, -1, 3, -1],
"DBB": [0x021, 1, -1, 3, -1],
"IFL": [0x026, 1, -1, 7, -1, 9, -1],
"DFL": [0x027, 1, -1, 7, -1, 9, -1],
"DLB": [0x028, 1, -1, 7, -1, 9, -1],
"MTS": [0x050, 1, -1, 4, -1, 8, -1],
"MFS": [0x4000050,
1, -1, 4, -1, 8, -1],
"MTC": [0x051, 1, -1, 4, -1, 8, -1, 5, -1],
"MFC": [0x4000051,
1, -1, 4, -1, 8, -1, 5, -1],
"MRD": [0x052, 1, -1, 4, -1, 10, -1, 5, 0],
"MNC": [0x052, 1, -1, 4, -1, 8, -1, 5, 1],
"MRR": [0x053, 1, -1, 4, -1, 10, -1, 5, 0],
"MIW": [0x054, 1, -1, 4, -1, 10, -1, 9, -1],
"MIR": [0x055, 1, -1, 4, -1, 10, -1, 9, 0],
"MOW": [0x056, 1, -1, 4, -1, 10, -1, 9, -1],
"MIR": [0x057, 1, -1, 4, -1, 10, -1, 9, 0],
"MPF": [0x058, 4, -1, 10, -1, 1, 0],
"MPB": [0x158, 4, -1, 10, -1, 1, 0],
"MPE": [0x258, 4, -1, 1, 0],
"MLS": [0x450, 4, -1, 8, -1, 1, 0],
"MRW": [0x850, 4, -1, 8, -1, 1, 0],
"MDA": [0x950, 4, -1, 8, -1, 1, 0],
"MIB": [0x059, 1, -1, 4, -1, 8, 0],
"MIE": [0x159, 1, -1, 4, -1, 8, 0],
"PRD": [0x003, 1, -1, 4, -1, 8, -1, 5, 0],
"PRB": [0x004, 1, -1, 4, -1, 5, 0, 8, 0],
"PRI": [0x005, 1, -1, 4, -1, 8, -1, 5, 0],
"PWR": [0x006, 1, -1, 4, -1, 8, -1],
"PWI": [0x007, 1, -1, 4, -1],
"CRD": [0x060, 1, -1, 4, -1, 5, 0],
"CWR": [0x061, 1, -1, 4, -1, 5, -1, 8, 0],
"CRF": [0x062, 1, -1, 4, -1, 5, -1, 8, 0],
"CWF": [0x063, 1, -1, 4, -1, 5, -1, 8, 0],
"CRI": [0x064, 1, -1, 4, -1],
"CWI": [0x065, 1, -1, 4, -1],
"KAD": [0x008, 1, 0, 3, 0],
"SPO": [0x009, 1, -1, 8, -1],
// Pseudo-ops
"DEFN": [pseudoDEFN, // define symbol
11, -1],
"LOCN": [pseudoLOCN, // set location counter
11, -1],
"CNST": [pseudoCNST], // assemble list of constant values
"F244": [pseudoF244, // assemble word from 22-64-04 fields
1, 0, 1, 0, 1, 0],
"F424": [pseudoF424, // assemble word from 44-62-04 fields
1, 0, 1, 0, 1, 0],
"FBGR": [pseudoFBGR], // assemble Cardatron format band
"FINI": [pseudoFINI, // finish assembly, output constant pool
1, 0]
};
// Tank entry types
var tankFini = 0;
var tankError = 1;
var tankComment = 2;
var tankInstruction = 3;
var tankPseudo = 4;
// Assembly storage
var errorCount = 0; // assembler error count
var location = 0; // current instruction address
var tank = []; // assembled line structures {serial, type, location, word, offset, length}
// Symbol table: holds the address value for each label
var symTab = {};
// Point-label table: holds the current sequence number for each point label
var pointTab = {};
// Pool constant table: holds address of the constant word
var poolTab = {};
/**************************************/
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 clearPanel() {
/* Clears the text panel */
var kid;
while (kid = panel.firstChild) {
panel.removeChild(kid);
}
}
/**************************************/
function setReaderReady(ready) {
/* Controls the ready-state of the card reader */
$$("CRFileSelector").disabled = ready;
if (ready) {
readerState = readerReady;
$$("CRStartBtn").classList.add("greenLit");
$$("CRNotReadyLight").classList.remove("whiteLit");
} else {
readerState = readerNotReady;
$$("CRStartBtn").classList.remove("greenLit");
$$("CRNotReadyLight").classList.add("whiteLit");
}
}
/**************************************/
function armEOF(armed) {
/* Controls the arming/disarming of the EOF signal when starting with
an empty input hopper */
if (armed) {
eofArmed = 1;
$$("CREOFBtn").classList.add("redLit");
} else {
eofArmed = 0;
$$("CREOFBtn").classList.remove("redLit");
}
}
/**************************************/
function readACard(successor, cardData) {
/* Reads one card image from the buffer, pads or trims the image as
necessary to 80 columns, and calls the "successor" function with it
and its location in the buffer.
If the reader is not ready, nothing happens */
var bx = bufferOffset;
var card;
var cardLength;
var line;
var match;
var stamp = performance.now();
var delta = millisPerCard - stamp + lastReaderStamp;
lastReaderStamp = stamp;
cardData.offset = bx;
if (readerState != readerReady) {
cardData.length = -1; // just exit
} else if (bx >= bufferLength) {
cardData.length = 0;
setReaderReady(false);
$$("CRProgressBar").value = 0;
} 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) {
line = card = card.substring(0, 80);
} else {
line = card;
while (card.length <= 70) {
card += " ";
}
while (card.length < 80) {
card += " ";
}
}
cardData.length = bx - bufferOffset;
++cardData.serial;
cardData.text = card;
bufferOffset = bx;
$$("CRProgressBar").value = bufferLength-bx;
while (outHopper.childNodes.length > 1) {
outHopper.removeChild(outHopper.firstChild);
}
outHopper.appendChild(document.createTextNode("\n"));
outHopper.appendChild(document.createTextNode(line));
if (delta < 2) {
successor(cardData);
} else {
setTimeout(successor, delta, cardData);
}
}
}
/**************************************/
function CRStartBtn_onclick(ev) {
/* Handle the click event for the START button */
if (readerState != readerReady) {
if (bufferOffset >= bufferLength) {
//alert("Empty hopper.");
if (eofArmed) {
printLine("\\\\\\\\\\ [EOF] /////");
armEOF(false);
}
} else {
setReaderReady(true);
setTimeout(startReader, 500); // delay until the reader can come up to speed...
}
}
}
/**************************************/
function CRStopBtn_onclick(ev) {
/* Handle the click event for the STOP button */
$$("CRFileSelector").value = null; // reset the control so the same file can be reloaded
if (readerState == readerNotReady) {
armEOF(false);
} else if (readerState == readerReady) {
setReaderReady(false);
}
}
/**************************************/
function CREOFBtn_onclick(ev) {
/* Handle the click event for the EOF button */
armEOF(!eofArmed);
}
/**************************************/
function CRProgressBar_onclick(ev) {
/* Handle the click event for the "input hopper" progress bar */
if (bufferOffset < bufferLength && readerState == readerNotReady) {
if (confirm("Do you want to clear the reader input hopper?")) {
buffer = "";
bufferLength = 0;
bufferOffset = 0;
$$("CRProgressBar").value = 0;
}
}
}
/**************************************/
function fileLoader_onLoad(ev) {
/* Handle the onload event for a Text FileReader */
if (bufferOffset < bufferLength) {
buffer = buffer.substring(bufferOffset);
} else {
clearPanel();
buffer = "";
}
buffer += ev.target.result;
bufferOffset = 0;
bufferLength = buffer.length;
$$("CRProgressBar").value = buffer.length;
$$("CRProgressBar").max = buffer.length;
}
/**************************************/
function fileSelector_onChange(ev) {
/* Handle the <input type=file> onchange event when a file is selected */
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");
********************/
reader.onload = fileLoader_onLoad;
reader.readAsText(f);
}
/**************************************/
function startReader() {
/* Reads a deck of cards and displays them until the reader goes empty */
readACard(cardHandler, cardData);
}
/**************************************/
function printPass1(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 printError(msg) {
++errorCount;
printLine("******** " + msg);
printLine("");
}
/**************************************/
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;
var pointNr;
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 LABEL IN POINT TABLE -- IMPOSSIBLE")
} 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;
var pointNr;
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. Returns the decimal string
in token.text and the binary value as the function result */
var c;
var length = text.length;
var raw = "";
var x = token.offset;
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 CONSTANT LONGER THAN 11 DIGITS: " + raw);
raw = raw.substring(raw.length-11);
} else if (raw.length < 11) {
raw = sign.toString() + padLeft(raw, 10, "0");
}
token.newOffset = x;
token.text = raw;
return parseInt(raw, 10);
}
/**************************************/
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.
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;
var code;
var count = 0;
var doubleDollar = false;
var length = text.length
var raw = "";
var values = [];
var word = 2;
var x = token.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 (++x < length) { // bypass the initial "$"
c = text.charAt(x);
if (c == "$") {
++x; // bypass the second "$"
doubleDollar = true; // don't know what this means yet
appendCode(16); // carriage return
}
}
while (x < length) {
c = text.charAt(x);
if (c == "$") {
break;
} else {
raw += c;
code = c.charCodeAt(0);
appendCode((code < asciiFilter.length ? asciiFilter[code]
: (code == 0xA4 ? 04 : 0))); // the lozenge
++x;
}
}
if (x >= length) {
printError("$-STRING NOT TERMINATED");
} else {
++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); // 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 && values.length > 1) {
printError("STRING OCCUPIES MORE THAN ONE WORD");
}
token.newOffset = x;
token.text = raw;
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:
0: empty primary
1: *, the current location counter
2: integer constant
3: regular label
4: backward point label (without the leading * or the trailing -)
5: forward point label (ditto, without the trailing +)
6: pool constant, 1-11 digit number with a leading + or -)
*/
var c;
var length = text.length;
var raw = "";
var val = 0;
var x = token.offset;
if (x >= length) { // empty primary
token.type = 0;
token.newOffset = length;
token.text = "";
} else {
c = text.charAt(x);
switch (true) {
case (c == "*"): // parse current location counter
token.type = 1;
token.text = c;
token.newOffset = x+1;
break;
case (c >= "0" && c <= "9"): // parse integer constant
val = parseNumber(text, token, 0);
token.type = 2;
if (val > 9999) {
printError("INTEGER CONSTANT 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 = 3; // label is at the end, can't be a point label
} else if (c != "+" && c != "-") {
token.type = 3; // not followed by +/-, not a point label
} else {
token.type = (c == "-" ? 4 : 5);
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 = 3; // not a point label
}
}
}
token.text = raw;
token.newOffset = x;
break;
case (c == "+"): // parse pool numeric constant
case (c == "-"):
raw = c;
if (++x >= length) {
printError("INVALID POOL CONSTANT 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 CONSTANT LONGER THAN 10 DIGITS: " + token.text);
}
token.text = raw + padLeft(token.text, 10, "0");
} else if (c == " " || c == ",") {
printError("EMPTY POOL CONSTANT");
} else {
val = evaluateAddress(text, token, resolved);
token.text = raw + padLeft(val, 10, "0");
}
}
token.type = 6;
break;
case (c == "$"): // parse pool string constant
parseString(text, token, true);
token.text = "$" + token.text.substring(0, 5);
token.type = 6;
break;
case (c == " "): // empty primary
token.type = 0;
token.newOffset = x;
token.text = "";
break;
default: // not valid
token.type = 0;
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;
var length = text.length; // length of operand text
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 0: // empty text
val = 0;
break;
case 1: // *, current location counter
val = location;
break;
case 2: // integer constant
val = parseInt(token.text, 10);
break;
case 3: // regular label
if (token.text in symTab) {
val = symTab[token.text];
} else {
symTab[token.text] = val = -1;
unresolvable = true;
}
break;
case 4: // backward point label
case 5: // forward point label
label = "*" + token.text;
val = fetchPointLabel(label, token.type == 5);
if (val < 0) {
val = -1;
unresolvable = true;
}
break;
case 6: // pool constant
if (token.text in poolTab) {
val = poolTab[token.text];
} 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;
var length = text.length;
var values = [];
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) {
/* Parses the operand text for the CNST pseudo-operator and returns an
array of binary words with the values found */
var c;
var length = text.length;
var val2;
var values = [];
var x;
while (token.offset < length) { // parse comma-delimited values
c = text.charAt(token.offset);
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));
} else if (c == "$") {
val2 = parseString(text, token, false);
for (x=0; x<val2.length; ++x) {
values.push(val2[x]);
}
}
if (token.newOffset < length) {
c = text.charAt(token.newOffset);
if (c == ",") {
token.offset = ++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
}
}
} // while x
//for (x=0; x<values.length; ++x) {
// printLine(" DEBUG: " + padLeft(values[x], 11));
//}
return values;
}
/**************************************/
function startAssembly(cardData) {
/* Callback function for the card reader. Reads the control cards and
then passes control to pass 1 of the assembler */
var card = cardData.text;
var opCode = card.substring(opCodeIndex, opCodeIndex+5);
switch (opCode) {
case "ASMBL":
readACard(startAssembly, cardData);
break;
case "REORD":
readACard(startAssembly, cardData);
break;
default:
cardHandler = assemblePass1;
assemblePass1(cardData);
break;
}
}
/**************************************/
function assemblePass1(cardData) {
/* Callback function for the card reader. Processes the card image for
pass 1 of the assembler */
var card = cardData.text;
var entry;
var finito = false;
var label = card.substring(labelIndex, labelIndex+5).trim();
var opCode = card.substring(opCodeIndex, opCodeIndex+4).trim();
var opDesc;
var operand = card.substring(operandIndex, operandIndex+56);
var sign = card.substring(opCodeIndex+4, opCodeIndex+5);
var tankType = tankComment;
var text;
var thisLoc = location;
var values;
var x;
var token = { // token structure for operand parsing
type: 0,
offset: 0,
text: "",
newOffset: -1};
if (opCode.length <= 0) {
text = " " + card.substring(labelIndex, 72);
printLine(text);
} else {
text = card.substring(72, 80) + " " + padLeft(location, 4, "0") + " " + card.substring(labelIndex, 72);
printLine(text);
if (!(opCode in opTab)) {
tankType = tankError;
printError("INVALID OP CODE");
} else {
opDesc = opTab[opCode];
if (opDesc[0] >= 0) { // normal instruction
tankType = tankInstruction;
values = evaluateOperand(operand, token, false); // discard result, take only side effects
++location; // normal instructions bump the location counter
} else {
// Parse the pseudo-op for size and location
tankType = tankPseudo;
switch (opDesc[0]) {
case pseudoDEFN:
values = evaluateOperand(operand, token, false);
if (values.length > 0) {
thisLoc = values[0];
} else {
printError("OPERAND ADDRESS REQUIRED");
}
break;
case pseudoLOCN:
values = evaluateOperand(operand, token, true);
if (values.length < 1) {
printError("OPERAND ADDRESS REQUIRED");
} else if (values[0] >= 0) {
location = values[0];
}
break;
case pseudoCNST:
values = parseConstantList(operand, token);
location += values.length;
break;
case pseudoF244:
case pseudoF424:
values = evaluateOperand(operand, token, false); // discard result, take only side effects
++location; // word-builders merely bump the location counter
break;
case pseudoFBGR:
location += 29; // all format bands are 29 words long
break;
case pseudoFINI:
finito = true;
tankType = tankFini;
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;
}
}
}
entry = {
serial: cardData.serial,
type: tankType,
location: thisLoc,
word: 0,
offset: cardData.offset,
length: cardData.length}
tank.push(entry);
if (!finito) {
readACard(assemblePass1, cardData);
} else {
// Build the constant pool
for (text in poolTab) {
symTab[text] = location;
printLine(" " + padLeft(location, 4, "0") + " " + text);
++location;
}
// Wrap up Pass 1
for (text in symTab) {
if (symTab[text] < 0) {
printError("SYMBOL NOT DEFINED: " + text);
}
}
printLine("");
printLine("END OF PASS 1, ERRORS = " + errorCount);
printLine("");
printLine("");
printLine("SYMBOL TABLE");
text = "";
thisLoc = 0;
values = Object.keys(symTab).sort();
for (x=0; x<values.length; ++x) {
if (thisLoc > 80) {
printLine(text);
text = "";
thisLoc = 0;
}
text += padLeft(symTab[values[x]], thisLoc-text.length+5) + " " + values[x];
thisLoc += 20;
}
printLine(text);
}
}
/**************************************/
function processCard(card) {
/* Callback function for the card reader. Processes the card image */
var count = 0;
var entry;
var match;
var opCode;
var operand;
var operandRex = /^(\S+)/;
var x;
// Accumulate statistics
// printLine(card);
opCode = card.substring(26, 30).trim();
if (opCode.length > 0) {
operandRex.lastIndex = 0;
match = operandRex.exec(card.substring(32));
if (match) {
operand = match[1];
count = 1;
if (operand.charAt(0) != "$") {
x = -1;
do {
x = operand.indexOf(",", x+1);
if (x >= 0) {
++count;
}
} while (x >= 0);
}
}
if (opCode in opTab) {
entry = opTab[opCode];
} else {
opTab[opCode] = entry = [0];
}
while (entry.length <= count) {
entry.push(0);
}
++entry[count];
}
if (bufferOffset < bufferLength && opCode != "FINI") {
readACard(processCard);
} else {
// Report statistics
setReaderReady(false);
printLine("");
printLine("___________________________");
printLine("");
for (opCode in opTab) {
entry = opTab[opCode];
operand = padRight(opCode, 5);
for (x=0; x<entry.length; ++x) {
operand += padLeft(entry[x].toString(), 5);
}
printLine(operand);
}
}
}
/**************************************/
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.performance && "now" in performance)) {missing += ", performance.now"}
if (missing.length == 0) {
return false;
} else {
alert("No can do... your browser does not support the following features:\n" + missing.substring(2));
return true;
}
}
/******************** Start of window.onload() ********************/
if (checkBrowser()) {
return;
}
armEOF(false);
setReaderReady(false);
$$("CRFileSelector").addEventListener("change", fileSelector_onChange, false);
$$("CRStartBtn").addEventListener("click", CRStartBtn_onclick, false);
$$("CRStopBtn").addEventListener("click", CRStopBtn_onclick, false);
$$("CREOFBtn").addEventListener("click", CREOFBtn_onclick, false);
$$("CRProgressBar").addEventListener("click", CRProgressBar_onclick, false);
outHopperFrame.contentDocument.head.innerHTML += "<style>" +
"BODY {background-color: #F0DCB0; margin: 0; padding: 0} " +
"PRE {margin: 0; font-size: 9pt; font-family: DejaVu Sans Mono, Consolas, Courier, monospace}" +
"</style>";
outHopper = document.createElement("pre");
outHopperFrame.contentDocument.body.appendChild(outHopper);
}, false);
</script>
</body>
</html>