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

2959 lines
118 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.
*
* To maintain the same addresses for literal pool entries, a JSON file
* containing a "pool set" of one or more arrays of literal word values
* and their starting memory location may be pre-loaded into the assembler
* before loading the source code.
*
* Output is a simulated line printer listing in a text panel of the web
* page from which the assembler is run. The output of Pass 1 is designed
* to match the listing from which the compiler was transcribed, so that
* it may be compared for proofing purposes. This listing is suppressed by
* default.
*
* Output of Pass 2 is a traditional assembler listing, with words of
* generated object code and data. This listing is created by default.
*
* Output of the generated code and data can be obtained in three forms:
*
* - Punched cards in a self-loading band-6 format (default).
*
* - Punched cards in BALGOL Machine Language format. This is also a
* band-6 format, but requires a separate loader program. See Appendix
* F in the BAC-220 reference manual.
*
* - Magnetic tape image recorded in 100-word blocks. The code is
* written to lane 0 of the tape image.
*
* The form of output is selected from a pull-down list on the browser
* page. The output is displayed in a sub-window that opens at the end of
* the assembly. From there is can be saved or copied to the local file
* system.
*
************************************************************************
* 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;
font-family: Arial, Helvetica, sans-serif;
height: 100%;
margin: 1ex}
DIV.heading {
margin-top: 12px;
margin-bottom: 6px;
font-weight: bold}
LABEL {
font-size: smaller}
#CardReaderPanel {
position: relative;
color: white;
background-color: #666;
width: 640px;
border: 1px solid black;
border-radius: 8px;
font-size: smaller;
padding: 8px}
#CardReaderTable {
border-spacing: 0;
border-collapse: collapse;
table-layout: fixed;
width: 100%}
#CardReaderCol1 {
width: 18ex}
#CardReaderCol2 {
}
#CardReaderTable TD {
text-align: left;
vertical-align: top;
padding-top: 1px;
padding-bottom: 1px;
padding-left: 2px;
padding-right: 2px}
#CRFileSelector {
width: 100%;
border: 1px solid white}
#CRPoolSelector {
width: 100%;
border: 1px solid white}
#OptionsTable {
position: relative;
margin-top: 4px;
margin-bottom: 4px;
width: 640px}
#TextDiv {
position: relative;
height: 75%;
width: 640px}
#TextPanel {
position: absolute;
left: 0;
top: 0;
bottom: 8px;
width: 100%;
overflow: scroll;
padding: 4px;
border: 1px solid black;
color: black;
background-color: white;
font-family: DejaVu Sans Mono, Consolas, Courier, monospace;
font-size: 8pt;
font-weight: normal}
#Spinner {
position: absolute;
top: 200px;
left: 200px;
z-index: 10}
.center {
text-align: center}
.rj {
text-align: right}
</style>
</head>
<body>
<div class=heading>
Assembler for the Burroughs 220 BALGOL Compiler &amp; Library
</div>
<div id=CardReaderPanel>
<table id=CardReaderTable>
<colgroup><col id=CardReaderCol1><col id=CardReaderCol2></colgroup>
<tr><td>Pre-load Pool
<td><input id=CRPoolSelector type=file size=90>
<tr><td>Load Source &amp; Go
<td><input id=CRFileSelector type=file size=90>
</table>
</div>
<table id=OptionsTable>
<thead>
<tr>
<th><label for=Pass1ListCheck>List Pass 1</label>
<th><label for=Pass2ListCheck>List Pass 2</label>
<th><label for=ChecksumCheck>Write Checksum</label>
<th><label for=OutputModeSelect>Output Mode</label>
<th>&nbsp;
<tbody>
<tr>
<td class=center>
<input id=Pass1ListCheck type=checkbox value=1>
<td class=center>
<input id=Pass2ListCheck type=checkbox value=1 CHECKED>
<td class=center>
<input id=ChecksumCheck type=checkbox value=1>
<td class=center>
<select id=OutputModeSelect>
<option value="" >No Object
<option value=L SELECTED>Loadable Deck
<option value=M >BALGOL ML Deck
<option value=T >Object Tape
</select>
<td class=rj>
<button id=SelectListing type=button>Select Listing</button>
</table>
<div id=TextDiv><pre id=TextPanel></pre></div> <!-- Don't add any whitespace! -->
<script>
"use strict";
window.addEventListener("load", function() {
// Card reader properties
var buffer = "";
var bufferLength = 0;
var bufferOffset = 0;
var sourceName = "?";
var eolRex = /([^\n\r\f]*)((:?\r[\n\f]?)|\n|\f)?/g;
var rTrimRex = /\s*$/;
var sprintLimit = 100; // cards processed before yielding control
// Card 0-relative column offsets
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: ""}
var pass1List = false;
var pass2List = true;
var outputChecksum = false;
var panel = $$("TextPanel");
var cardReaderUnit = 1; // Cardatron input unit number for object card decks
// Token.type values
var tokEmpty = 0; // empty primary
var tokLocation = 1; // *, the current location counter
var tokInteger = 2; // integer literal
var tokLabel = 3; // regular label
var tokBackwardPoint = 4; // backward point label (without the leading * or the trailing -)
var tokForwardPoint = 5; // forward point label (ditto, without the trailing +)
var tokLiteral = 6; // pool literal, 1-11 digit number 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 asmCode = []; // buffer for assembled words of code
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 poolLocation = NaN; // address of the current literal pool
var poolSetName = ""; // name of optional pool set data file
var poolTab = {}; // Pool literal table: holds address of the literal word
var startAddress = -1; // starting execution address, from FINI card
var symTab = {}; // Symbol table: holds the address value for each label
var poolSets = [ // skeleton table of pre-loaded pools for default program unit #0
{poolLoc: NaN, poolData: []}];
var p10 = [ 1, // powers of 10 table
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
10000000000,
100000000000,
1000000000000,
10000000000000,
100000000000000,
1000000000000000,
10000000000000000];
var xlateANSI220 = [ // translate ANSI to 220 internal character codes
// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-0F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10-1F
0, 0, 0, 33, 13, 24, 10, 34, 24, 4, 14, 10, 23, 20, 3, 21, // 20-2F
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 0, 13, 4, 33, 0, 0, // 30-3F
34, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, // 40-4F
57, 58, 59, 62, 63, 64, 65, 66, 67, 68, 69, 0, 0, 0, 0, 0, // 50-5F
0, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, // 60-6F
57, 58, 59, 62, 63, 64, 65, 66, 67, 68, 69, 0, 0, 0, 0, 0]; // 70-7F
var xlate220ANSI = [ // translate internal B220 code to ANSI (Algol glyphs)
// 00 01 02 03 04 05 06 07 08 09
" ", "?", "9", ".", ")", "?", "?", "?", "?", "?", // 00-09
"+", "?", "?", "$", "*", "*", "$", "?", "?", "?", // 10-19
"-", "/", "?", ",", "(", "?", ",", "?", "?", "?", // 20-29
"?", "?", "?", "=", "@", "\\", "?", "?", "?", "?", // 30-39
"?", "A", "B", "C", "D", "E", "F", "G", "H", "I", // 40-49
"?", "J", "K", "L", "M", "N", "O", "P", "Q", "R", // 50-59
"?", "?", "S", "T", "U", "V", "W", "X", "Y", "Z", // 60-69
"?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 70-79
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", // 80-89
"?", "?", "?", "?", "?", "?", "?", "?", "?", "?"]; // 90-99
var signValues = { // numeric values of sign column
" ": 0,
"+": 0,
"0": 0,
"-": 1,
"1": 1,
"2": 2,
"3": 3,
"4": 4,
"5": 5,
"6": 6,
"7": 7,
"8": 8,
"9": 9};
/***************************************
* Opcode table:
* Each 220 assembler op code is defined by an array with a variable number
* of elements. The first element is the operation code, including any variant
* 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 added to (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 added to (41): (B-1)*2
* 17 = variant value added to (41)
* 18 = mag tape unit/lane as LLU, inserted as ULL in (33)
* 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],
"CAA": [ 110, 1, -1],
"CSU": [ 11, 1, -1],
"CSA": [ 111, 1, -1],
"ADD": [ 12, 1, -1],
"ADA": [ 112, 1, -1],
"SUB": [ 13, 1, -1],
"SUA": [ 113, 1, -1],
"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, 6, 0],
"ADL": [ 19, 1, -1, 3, 0],
"IBB": [ 20, 1, -1, 3, -1],
"DBB": [ 21, 1, -1, 3, -1],
"FAD": [ 22, 1, -1, 4, 0],
"FAA": [ 122, 1, -1, 4, 0],
"FSU": [ 23, 1, -1, 4, 0],
"FSA": [ 123, 1, -1, 4, 0],
"FMU": [ 24, 1, -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, 18, -1],
"MFS": [4000050, 1, -1, 18, -1],
"MLS": [ 450, 18, -1, 1, 0],
"MRW": [ 850, 18, -1, 1, 0],
"MDA": [ 950, 18, -1, 1, 0],
"MTC": [ 51, 1, -1, 18, -1, 5, -1],
"MFC": [4000051, 1, -1, 18, -1, 5, -1],
"MRD": [ 52, 1, -1, 4, -1, 10, -1, 17, 0],
"MNC": [ 152, 1, -1, 4, -1, 10, -1, 17, 0],
"MRR": [ 53, 1, -1, 4, -1, 10, -1, 17, 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],
"CNC": [ 1060, 1, -1, 4, -1, 5, 0],
"CWR": [ 61, 1, -1, 13, -1, 16, 0, 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]
};
/*******************************************************************
* Miscellaneous Utility Functions *
*******************************************************************/
/**************************************/
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 tensComp(value) {
/* If "value" is algebraically negative, returns its 11-digit tens
complement. Otherwise returns the 11-digit value */
if (value < 0) {
return p10[11] + value%p10[11];
} else {
return value%p10[11];
}
}
/**************************************/
function applySign(word, sign) {
/* Applies an unsigned "sign" digit to a 220 "word" value. If the word
value is algebraically negative, it is first converted to a 10-digit
number with a 220 sign in the 11-th high-order digit. The low-order bit
of "sign" and the low-order bit of the word's sign digit are XOR-ed so
that each of those bits designates negation. Returns the new value as an
11-digit unsigned 220 word in binary */
var s = 0;
var value = 0;
if (word < 0) {
value = (-word)%p10[10];
s = 1;
} else {
value = word%p10[10];
s = (word%p10[11] - value)/p10[10];
}
return (sign%10 ^ s)*p10[10] + value;
}
/**************************************/
function getField(word, sL) {
/* Extracts an n-digit value from an 11-digit "word" and returns it.
"sL" is the same as for putField(). The word is a Javascript Number
object, but is treated as if it represents an 11-digit decimal integer */
var L = sL%10;
var s = (sL%100 - L)/10;
var result = applySign(word, 0);
s = (s == 0 ? 0 : 10-s);
L = (L == 0 ? 10 : L);
if (sL < 0 || sL > 99 || s+L > 11) {
result = -1;
} else {
result = (result%p10[s+L] - result%p10[s])/p10[s];
}
return result;
}
/**************************************/
function putField(word, value, sL) {
/* Inserts an n-digit "value" into designated digits of an 11-digit
"word". "sL" is the partial-word field in standard 220 start-Length
notation. Note that Javascript flags literal integers of the form "0n"
as the old C-style octal literal notation is deprecated. This routine
uses only the two low-order digits of "sL", however, so you can pass sL
literal values like 104 (or even 57321604) for /04 without ill effect.
The "value" and "word" are Javascript Number objects, but are treated as
if they represent 11-digit decimal integers. If value is negative, it is
converted to its 10s-complement value before insertion into word.
Returns a new word with the inserted field */
var L = sL%10;
var s = (sL%100 - L)/10;
var upperPart = 0;
var lowerPart = 0;
var result = applySign(word, 0);
s = (s == 0 ? 0 : 10-s);
L = (L == 0 ? 10 : L);
if (sL < 0 || s+L > 11) {
printError("INVALID /SL VALUE: ", sL);
} else {
upperPart = result%p10[11] - result%p10[s+L];
if (s > 0) {
lowerPart = result%p10[s];
}
result = (tensComp(value)%p10[L])*p10[s] + upperPart + lowerPart;
}
return result;
}
/*******************************************************************
* Listing Utilities *
*******************************************************************/
/**************************************/
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 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 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);
}
/*******************************************************************
* Assembler Primitives *
*******************************************************************/
/**************************************/
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 < xlateANSI220.length ? xlateANSI220[code]
: (code == 0xA4 ? 4 : 0))); // the lozenge
}
}
if (x >= length) {
if (singleWord) {
printError("$-STRING NOT TERMINATED");
} else {
token.type = tokIncompleteString;
}
} 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));
token.offset = token.newOffset;
if (token.offset < length) {
c = text.charAt(token.offset);
if (c == ",") {
++token.offset; // continue with next field
} else if (c == " ") {
break; // out of while loop
} else {
++token.offset;
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 == "+") {
++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 == "," || c == " ") {
values.push(0);
token.newOffset = token.offset;
}
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 */
asmCode[location] = word;
}
/**************************************/
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];
}
switch (opDesc[ox]) {
case 1: // address field, inserted in (04)
word = putField(word, f, 4);
break;
case 2: // secondary value inserted in (33)
word = putField(word, f, 33);
break;
case 3: // secondary value inserted in (44)
word = putField(word, f, 44);
break;
case 4: // unit or count digit inserted in (11)
word = putField(word, f, 11);
break;
case 5: // variant digit inserted in (41)
word = putField(word, f, 41);
break;
case 6: // sL field designator inserted in (22); if specified, insert 1 in (31)
word = putField(word, f, 22);
if (vx < vTop) {
word = putField(word, 1, 31);
}
break;
case 7: // sL field designator inserted in (22); (31) is undisturbed
word = putField(word, f, 22);
break;
case 8: // value inserted in (32)
word = putField(word, f, 32);
break;
case 9: // value inserted in (42)
word = putField(word, f, 42);
break;
case 10: // value inserted in (21)
word = putField(word, f, 21);
break;
case 11: // value inserted in (62)
word = putField(word, f, 62);
break;
case 12: // value inserted in (64)
word = putField(word, f, 64);
break;
case 13: // BU pair for CRF/CWF: (B-1)*2 added to (41), U in (11)
w1 = f%10; // unit value for (11)
w2 = (f%100-w1)/10; // band value for (41)
word = putField(word, w1, 11);
if (w2 > 0) {
word = putField(word, getField(word, 41) + (w2-1)*2, 41);
}
break;
case 14: // reload-lockout value added to (41)
word = putField(word, getField(word, 41) + f%10, 41);
break;
case 15: // digit inserted in (11); if specified, insert 1 in (41)
word = putField(word, f, 11);
if (vx < vTop) {
word = putField(word, 1, 41);
}
break;
case 16: // format band digit added to (41): (B-1)*2
w1 = f%10;
if (w1 > 0) {
word = putField(word, getField(word, 41) + (w1-1)*2, 41);
}
break;
case 17: // BMOD value added to (41)
word = putField(word, getField(word, 41) + f%10, 41);
break;
case 18: // mag tape unit/lane as LLU, inserted as ULL in (33)
f = (f%10)*100 + (f%1000 - f%10)/10; // convert from LLU to ULL
word = putField(word, f, 33);
break;
case 19: // resolved address only in (04)
word = putField(word, f, 4);
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 "S":
case "T":
case "Z":
do {
--count;
codes += c;
} while (count > 0);
count = 0;
break;
case "B":
do {
--count;
if (overpunch) {
codes += "Y"; // half column: standalone sign
overpunch = false;
} else {
codes += c;
}
} while (count > 0);
count = 0;
break;
case "N":
do {
--count;
if (overpunch) {
codes += "D"; // half column: overpunched sign
overpunch = false;
} else {
codes += c;
}
} while (count > 0);
count = 0;
break;
case "X":
overpunch = true;
do {
--count;
codes += c; // half-column zone digit
} while (count > 0);
count = 0;
break;
case "(":
do {
--count;
tx = expandPhrase(px, level+1);
} while (count > 0);
count = 0;
px = tx;
break;
case ")":
if (level < 1) {
printError("EXCESS RIGHT PARENTHESIS");
}
done = true;
break;
case ",":
if (level > 0) {
printError("COMMA IN NESTED PHRASE");
}
codes += c;
count = 0;
break;
default:
printError("INVALID FORMAT BAND CHARACTER: " + c);
break;
} // switch c
}
} while (!done);
return px;
}
//--------------------------------------
function buildInputBand(cols) {
/* Builds an input band from the expanded phrase code string
in "codes", placing the result in the "band" array */
var c; // current phrase code
var colCount = 0; // number of card columns
var cx; // current offset into codes
var dx = 0; // current memory word digit index
for (cx=codes.length-1; cx>=0; --cx) {
c = codes.charAt(cx);
switch (c) {
case "A": // copy alphanumeric column
if (dx & 1) {
printError("A CODE ON ODD DIGIT: " + cx);
} else if (dx > 9) {
printError("A CODE ON SIGN DIGIT: " + cx);
} else {
emitBandDigits(11, 2);
}
colCount += 1;
dx += 2;
break;
case "B": // ignore column
emitBandDigits(33, 2);
colCount += 1;
break;
case "D": // copy numeric half-column for overpunched sign
emitBandDigits(1, 1);
colCount +=1;
dx += 1;
break;
case "N": // copy numeric column
emitBandDigits(31, 2);
colCount += 1;
dx += 1;
break;
case "P": // insert zero in memory word for sign
if (dx < 10) {
printError("P CODE NOT FOR SIGN DIGIT: " + cx);
}
emitBandDigits(0, 1);
dx += 1;
break;
case "S": // delete numeric half-column, copy zone for standalone sign
if (dx < 10) {
printError("S CODE NOT FOR SIGN DIGIT: " + cx);
}
emitBandDigits(13, 2);
colCount += 1;
dx += 1;
break;
case "T": // insert 2 in memory word sign
if (dx < 10) {
printError("T CODE NOT FOR SIGN DIGIT: " + cx);
}
emitBandDigits(2, 1);
dx += 1;
break;
case "X": // copy zone half-column for sign
emitBandDigits(1, 1);
dx += 1;
break;
case "Y": // delete half-column (usually for overpunched sign)
emitBandDigits(3, 1);
colCount += 1;
break;
case "Z": // insert zero in memory word
emitBandDigits(0, 1);
dx += 1;
break;
case ",": // check that phrase/word boundaries match
if (dx > 0) {
printError("WRONG NUMBER OF DIGITS IN WORD: " + dx);
}
break;
default:
printError("INVALID INPUT BAND CODE: " + c);
break;
} // switch c
if (dx > 10) {
if (dx > 11) {
printError("EXCESS NUMBER OF DIGITS IN WORD: " + dx);
}
dx = 0;
}
} // for cx
if (dx > 0) {
printError("FINAL BAND WORD INCOMPLETE: " + dx);
}
if (colCount != cols) {
printError("WRONG NUMBER OF COLUMNS: " + colCount);
}
emitBandDigits(0, 11); // push out last word from Cardatron
flushBandWord(3); // pad out the last word with 3s
}
//--------------------------------------
function buildOutputBand(cols) {
/* Builds an output band from the expanded phrase code string
in "codes", placing the result in the "band" array */
var c; // current phrase code
var colCount = 0; // number of columns on card/line
var cx; // current offset into codes
var dx = 0; // current memory word digit index
for (cx=codes.length-1; cx>=0; --cx) {
c = codes.charAt(cx);
switch (c) {
case "A": // copy alphanumeric column
if (dx & 1) {
printError("A CODE ON ODD DIGIT: " + cx);
} else if (dx > 9) {
printError("A CODE ON SIGN DIGIT: " + cx);
} else {
emitBandDigits(11, 2);
}
colCount += 1;
dx += 2;
break;
case "B": // supply zeroes for blank column
emitBandDigits(0, 2);
colCount += 1;
break;
case "D": // copy numeric half-column for overpunched sign
emitBandDigits(1, 1);
colCount +=1;
dx += 1;
break;
case "N": // copy numeric column
emitBandDigits(2, 1);
colCount += 1;
dx += 1;
break;
case "P": // ignore sign digit in word
case "T":
if (dx < 10) {
printError(c + " CODE NOT FOR SIGN DIGIT: " + cx);
}
emitBandDigits(3, 1);
dx += 1;
break;
case "S": // copy numeric column for standalone sign
if (dx < 10) {
printError("S CODE NOT FOR SIGN DIGIT: " + cx);
}
emitBandDigits(2, 1);
colCount += 1;
dx += 1;
break;
case "X": // copy zone digit for sign
emitBandDigits(1, 1);
dx += 1;
break;
case "Y": // supply numeric half-column for standalone sign
emitBandDigits(0, 1);
colCount += 1;
break;
case "Z": // ignore digit in memory word
emitBandDigits(3, 1);
dx += 1;
break;
case ",": // check that phrase/word boundaries match
if (dx > 0) {
printError("WRONG NUMBER OF DIGITS IN WORD: " + dx);
}
break;
default:
printError("INVALID OUTPUT BAND CODE: " + c);
break;
} // switch c
if (dx > 10) {
if (dx > 11) {
printError("EXCESS NUMBER OF DIGITS IN WORD: " + dx);
}
dx = 0;
}
} // for cx
if (dx > 0) {
printError("FINAL BAND WORD INCOMPLETE: " + dx);
}
if (colCount != cols) {
printError("WRONG NUMBER OF COLUMNS: " + colCount);
}
flushBandWord(3); // pad out the last word with 3s
}
//-------------------------------------- start of generateFormatBand()
x = operand.indexOf(" ");
oLen = (x < 0 ? operand.length : x);
x = operand.indexOf(",");
bandType = (x < 0 ? "" : operand.substring(0,x));
cx = x+1;
while (cx < oLen) {
cx = expandPhrase(cx, 0);
}
//printLine(codes); // DEBUG
switch (bandType) {
case "INPUT":
buildInputBand(80);
break;
case "PUNCH":
buildOutputBand(80);
break;
case "PRINT":
buildOutputBand(120);
break;
default:
printError("INVALID FORMAT BAND TYPE: " + bandType);
break;
}
while (band.length < 29) {
band.unshift(33333333333);
}
return band;
}
/**************************************/
function 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);
switch (opCode) {
case "ASMBL":
case "HEAD ":
case "REORD":
printLine(padRight("", 8+5+4+3+6) +
cardData.text.substring(opCodeIndex, operandIndex+operandLength).trim());
break;
default:
done = true;
if (pass1List) {
printLine("START PASS 1");
printLine("");
}
setTimeout(assemblePass1, 100);
break;
}
}
} while (!done);
}
/*******************************************************************
* Pass 1 *
*******************************************************************/
/**************************************/
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 keys; // addresses for pool words
var label; // pool entry ID
var text; // scratch string
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.charAt(0) != ".") {
text = label;
} else {
token.offset = 1;
poolTab[label] = location;
parseAddressPrimary(label, token, true);
text = token.text;
delete poolTab[label];
}
if (text in symTab) {
poolTab[text] = symTab[text];
} else {
poolTab[text] = location;
symTab[text] = location;
++location;
}
xref[padLeft(symTab[text], 4, "0")] = text;
} // 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];
printPass1("", symTab[label], "", "", "", label);
} // for x
}
/**************************************/
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);
if (values.length > 0) {
startAddress = values[0];
}
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();
if (pass1List && !pass2List) {
dumpSymbolTable();
}
if (pass1List || errorCount > 0) {
printLine("");
printLine("END PASS 1, ERRORS = " + errorCount);
}
if (errorCount == 0) { // advance to Pass 2
setTimeout(initializePass2, 100);
} else {
$$("TextDiv").removeChild($$("Spinner"));
// And... we're done at this point -- just exit the script
}
}
}
/*******************************************************************
* Pass 2 *
*******************************************************************/
/**************************************/
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;
}
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);
switch (opCode) {
case "ASMBL":
case "HEAD ":
case "REORD":
break;
default:
done = true;
if (pass2List) {
if (pass1List) {
printLine("\f"); // output a form-feed
}
printLine("START PASS 2");
printLine("");
}
setTimeout(assemblePass2, 100);
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);
if (values.length > 0) {
startAddress = values[0];
}
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);
}
}
dumpErrorTank();
if (pass2List) {
dumpSymbolTable();
}
buffer = ""; // release the card buffer area
if (pass2List || errorCount > 0) {
printLine("");
printLine("END PASS 2, ERRORS = " + errorCount);
}
setTimeout(finishAssembly, 100);
}
}
/*******************************************************************
* Object Code Output *
*******************************************************************/
/**************************************/
function generateFormat6LoadableDeck(asmCode, outputChecksum, startAddress) {
/* Formats the assembled object code as a standard Cardatron format-6
loadable deck in a temporary window. From there it can be save, copied,
etc. */
var crdSkeleton = ""; // skeleton card-read instruction
var doc = null; // temp window document object
var deck = null; // temp window text area
var pval = p10[10]; // modulus for a word's absolute value
var psign = pval*2; // modulus for a word's sign bit
var title = "220 BAC-Assembler Band-6 Loadable Deck";
var win = window.open("../../webUI/B220FramePaper.html", "BAC-Asm-Object",
"scrollbars,resizable,width=600,height=500");
function checksum(cksum, word) {
var sw = word%psign; // will be the signed algebraic value of word
if (sw >= pval) { // check if word has sign bit=1
sw = pval - sw; // if so, make it algebraically negative
}
return (cksum+sw)%pval; // compute the algebraic checksum
}
function writeCard(card, addr) {
deck.appendChild(doc.createTextNode("666" +
padLeft(crdSkeleton + padLeft(addr, 4, "0") + card, 77, " ") + "\n"));
}
function generateDeck(ev) {
var addr = 0; // assembled code address
var card = ""; // text line for an 80-column card image
var cksum = 0; // algebraic value of the checksum word
var count = 0; // number of words on card
var gapCount = 0; // number of consecutive undefined words
var priorAddr = 0; // CRD address for prior card image
var priorCard = ""; // prior image, awaiting terminating CRD instruction
var word = undefined; // object code word
var x = 0; // scratch index
win.removeEventListener("load", generateDeck, false);
doc = win.document;
doc.title = title;
deck = doc.getElementById("Paper");
while (addr < asmCode.length && asmCode[addr] === undefined) {
++addr;
}
while (addr < asmCode.length) {
word = asmCode[addr];
if (word === undefined) {
++gapCount;
} else {
if (count > 5 || gapCount > 0) {
writeCard(priorCard, priorAddr);
priorCard = card;
card = "";
gapCount = 0;
count = 0;
}
priorAddr = addr;
card += padLeft(word, 11, "0");
++count;
if (outputChecksum) {
cksum = checksum(cksum, word);
}
}
++addr;
} // while addr
writeCard(priorCard, priorAddr);
if (outputChecksum) {
// Compute and output a card with the 220 negative checksum at the next address
writeCard(card, priorAddr+1);
card = crdSkeleton + "9999";
if (cksum < 0) {
card += padLeft(-cksum, 11, "0");
} else {
card += padLeft(cksum+pval, 11, "0");
}
card = "***CHECKSUM FOR " + sourceName + " " + card;
}
writeCard(card, 9999);
if (startAddress >= 0) {
writeCard("6000030" + padLeft(startAddress, 4, "0"), 9999); // BUN to start
} else {
writeCard("69999009999", 9999); // HLT 9999
}
}
crdSkeleton = "6" + padLeft(cardReaderUnit, 1, "0") + "00060";
win.addEventListener("load", generateDeck, false);
win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
}
/**************************************/
function generateMachineLanguageDeck(asmCode, outputChecksum, startAddress) {
/* Formats the assembled object code as a BALGOL Machine-Language deck
in a temporary window. From there it can be save, copied, etc. */
var doc = null; // temp window document object
var deck = null; // temp window text area
var pval = p10[10]; // modulus for a word's absolute value
var psign = pval*2; // modulus for a word's sign bit
var title = "220 BAC-Assembler Machine-Language Deck";
var win = window.open("../../webUI/B220FramePaper.html", "BAC-Asm-Object",
"scrollbars,resizable,width=600,height=500");
function checksum(cksum, word) {
var sw = word%psign; // will be the signed algebraic value of word
if (sw >= pval) { // check if word has sign bit=1
sw = pval - sw; // if so, make it algebraically negative
}
return (cksum+sw)%pval; // compute the algebraic checksum
}
function writeCard(card, count, addr, seq) {
deck.appendChild(doc.createTextNode(
"60" + padLeft(count, 1, "0") + padLeft(seq, 7, "0") +
padLeft(addr, 4, "0") + card + "\n"));
}
function generateDeck(ev) {
var addr = 0; // assembled code address
var card = ""; // text line for an 80-column card image
var cardAddr = 0; // first address of words on card
var cksum = 0; // algebraic value of the checksum word
var count = 0; // number of words on card
var gapCount = 0; // number of consecutive undefined words
var seq = 10; // card sequence number
var word = undefined; // object code word
var x = 0; // scratch index
win.removeEventListener("load", generateDeck, false);
doc = win.document;
doc.title = title;
deck = doc.getElementById("Paper");
while (addr < asmCode.length) {
word = asmCode[addr];
if (word === undefined) {
++gapCount;
} else {
if (count > 5 || gapCount > 0) {
writeCard(card, count, cardAddr, seq);
card = "";
cardAddr = addr;
gapCount = 0;
count = 0;
seq += 10;
}
card += padLeft(word, 11, "0");
++count;
if (outputChecksum) {
cksum = checksum(cksum, word);
}
}
++addr;
} // while addr
writeCard(card, count, cardAddr, seq);
if (outputChecksum) {
// Compute and output a card with the 220 negative checksum at the next address
if (cksum < 0) {
card = padLeft(-cksum, 11, "0");
} else {
card = padLeft(cksum+pval, 11, "0");
}
writeCard(card + " ***CHECKSUM FOR " + sourceName, 1, addr, 9999990);
}
}
win.addEventListener("load", generateDeck, false);
win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
}
/**************************************/
function generateObjectTape(asmCode, outputChecksum, startAddress) {
/* Formats the assembled object code as a 220 tape image in a temporary
window. From there it can be save, copied, etc. */
var doc = null; // temp window document object
var pval = p10[10]; // modulus for a word's absolute value
var psign = pval*2; // modulus for a word's sign bit
var tape = null; // temp window text area
var title = "220 BAC-Assembler Object Tape";
var win = window.open("../../webUI/B220FramePaper.html", "BAC-Asm-Object",
"scrollbars,resizable,width=500,height=500");
function checksum(cksum, word) {
var sw = word%psign; // will be the signed algebraic value of word
if (sw >= pval) { // check if word has sign bit=1
sw = pval - sw; // if so, make it algebraically negative
}
return (cksum+sw)%pval; // compute the algebraic checksum
}
function generateTape(ev) {
var addr = 0; // assembled code address
var block = ""; // text line for a 100-word tape block
var blockPrefix = "0,100"; // lane and prefix word count for block
var count = 0; // count of words in block
var cksum = 0; // algebraic value of the checksum word
var length = asmCode.length;// number of words to output
var word = 0; // object code word
win.removeEventListener("load", generateTape, false);
doc = win.document;
doc.title = title;
tape = doc.getElementById("Paper");
block = blockPrefix;
while (addr < length) {
if (count >= 100) {
tape.appendChild(doc.createTextNode(block + "\n"));
count = 0;
block = blockPrefix;
}
word = asmCode[addr] || 0;
block += "," + word.toString();
++count;
++addr;
if (outputChecksum) {
cksum = checksum(cksum, word);
}
} // while addr
if (outputChecksum) {
// Compute and output a block with the 220 negative checksum at the next address
if (count >= 100) {
tape.appendChild(doc.createTextNode(block + "\n"));
count = 0;
block = blockPrefix;
}
if (cksum < 0) {
block += "," + padLeft(-cksum, 11, "0");
} else {
block += "," + padLeft(cksum+pval, 11, "0");
}
}
tape.appendChild(doc.createTextNode(block + "\n"));
count = Math.floor((addr+99)/100); // number of blocks in program
count = Math.floor(3480*208.333/106) - count; // blocks left on tape
tape.appendChild(doc.createTextNode(count.toString() + "*0,100\n"));
tape.appendChild(doc.createTextNode("0,1,1\n")); // EOT control word for lane 0
tape.appendChild(doc.createTextNode("6839*0,100\n1,1,1\n")); // zero blocks & EOT for lane 1
}
win.addEventListener("load", generateTape, false);
win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
}
/*******************************************************************
* Initialization and Termination *
*******************************************************************/
/**************************************/
function finishAssembly() {
/* Finishes the assembly of a program unit, and if appropriate, starts
assembly of the next one */
var oms = $$("OutputModeSelect");
var x = oms.selectedIndex;
if (errorCount == 0 && x >= 0) {
switch(oms.options[x].value) {
case "L":
generateFormat6LoadableDeck(asmCode, outputChecksum, startAddress);
break;
case "M":
generateMachineLanguageDeck(asmCode, outputChecksum, startAddress);
break;
case "T":
generateObjectTape(asmCode, outputChecksum, startAddress);
break;
}// switch oms.options
}
$$("CRFileSelector").value = null; // reset the <input> elements
$$("CRPoolSelector").value = null; // so they can be reused
poolSets.length = 1; // discard any poolSets that were loaded
$$("TextDiv").removeChild($$("Spinner")); // remove the spinner image
// And... we're done -- just fall out of the script.
}
/**************************************/
function initializeLiteralPool(unitNr) {
/* Initializes the literal pool prior to assembly. If data for a pool
has been pre-loaded for this program, then initialize the pool with
that location and data; otherwise initialize it to the default in
poolSets[0] */
var thisSet = poolSets[unitNr] || poolSets[0];
var x = 0; // scratch index
poolLocation = thisSet.poolLoc;
for (x=0; x<thisSet.poolData.length; ++x) {
symTab[thisSet.poolData[x]] = poolLocation+x;
} // for x
}
/**************************************/
function assembleFile() {
/* Initializes or reinitializes the assembler for a new file */
var stamp = new Date();
clearPanel();
printLine("Assembler for the Burroughs 220 BALGOL Compiler & Library -- " +
stamp.getFullYear().toString() + "-" +
padLeft(stamp.getMonth()+1, 2, "0") + "-" +
padLeft(stamp.getDate(), 2, "0") + " " +
padLeft(stamp.getHours(), 2, "0") + ":" +
padLeft(stamp.getMinutes(), 2, "0"));
printLine("");
printLine("Source File: " + sourceName);
errorCount = 0;
errorTank = [];
location = 0;
cardData.atEOF = false;
cardData.serial = 0;
asmCode = [];
autoSymTab = {};
pointTab = {};
poolTab = {};
startAddress = -1;
symTab = {};
symTab["RLO"] = 1; // kludge for CRD reload-lockout bit
symTab["BMOD"] = 8; // kludge for MRD/MNC B-modify address bit
initializeLiteralPool(1);
startPass1();
}
/**************************************/
function loadPoolsets(ev) {
/* Handle the <input type=file> onchange event when a file for a
pre-loaded literal pool set is selected */
var f = ev.target.files[0];
var reader = new FileReader();
function poolLoader_onLoad(ev) {
/* Handles the onload event for a readAsText FileReader */
var sets = null;
var thisSet = null;
var unitNr = 0;
var x = 0;
poolSets.length = 1; // remove any old entries
try {
sets = JSON.parse(ev.target.result);
if (!(typeof sets == "object" && "poolSet" in sets)) {
printError("POOL DATA MUST BE AN OBJECT AND CONTAIN A \"POOLSET\" PROPERTY");
} else if (!(sets.poolSet instanceof Array)) {
printError("POOL DATA \"POOLSET\" PROPERTY MUST BE AN ARRAY");
} else {
sets = sets.poolSet;
for (x=0; x<sets.length; ++x) {
thisSet = sets[x];
unitNr = x+1;
if (thisSet === null) {
poolSets[unitNr] = null; // allow an empty poolSet entry
} else if (typeof thisSet != "object") {
printError("POOLSET FOR PROGRAM UNIT #" + unitNr + " MUST BE AN OBJECT");
} else if (!("poolLoc" in thisSet && typeof thisSet["poolLoc"] == "number")) {
printError("POOLSET FOR PROGRAM UNIT #" + unitNr + " MUST HAVE NUMERIC \"POOLLOC\" PROPERTY");
} else if (!("poolData" in thisSet && thisSet["poolData"] instanceof Array)) {
printError("POOLSET FOR PROGRAM UNIT #" + unitNr + " MUST HAVE ARRAY \"POOLDATA\" PROPERTY");
} else {
poolSets[unitNr] = thisSet;
}
} // for x
}
} catch (e) {
printError("ERROR PARSING PRE-LOADED POOL DATA JSON:");
printError(e.toString());
}
if (errorCount > 0) {
dumpErrorTank();
errorCount = 0;
$$("CRPoolSelector").value = null;
}
}
poolSetName = f.name;
/********************
alert("Pool Data file selected: " + f.name +
"\nModified " + f.lastModifiedDate +
"\nType=" + f.type + ", Size=" + f.size + " octets");
********************/
reader.onload = poolLoader_onLoad;
reader.readAsText(f);
}
/**************************************/
function loadSourceFile(ev) {
/* Handle the <input type=file> onchange event when a file is selected */
var e; // spinner image DOM element
var f = ev.target.files[0];
var reader = new FileReader();
function fileLoader_onLoad(ev) {
/* Handles the onload event for a readAsText FileReader */
buffer = ev.target.result;
bufferOffset = 0;
bufferLength = buffer.length;
setTimeout(assembleFile, 100);
}
sourceName = f.name;
/********************
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 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;
}
$$("CRPoolSelector").value = null; // clear any prior pool selection
$$("CRPoolSelector").addEventListener("change", loadPoolsets, false);
$$("CRFileSelector").value = null; // clear any prior file selection
$$("CRFileSelector").addEventListener("change", loadSourceFile, false);
pass1List = $$("Pass1ListCheck").checked;
$$("Pass1ListCheck").addEventListener("click", function(ev) {
pass1List = ev.target.checked;
});
pass2List = $$("Pass2ListCheck").checked;
$$("Pass2ListCheck").addEventListener("click", function(ev) {
pass2List = ev.target.checked;
});
outputChecksum = $$("ChecksumCheck").checked;
$$("ChecksumCheck").addEventListener("click", function(ev) {
outputChecksum = ev.target.checked;
});
$$("SelectListing").addEventListener("click", function(ev) {
window.getSelection().selectAllChildren($$("TextPanel"));
});
}, false);
</script>
</body>
</html>