mirror of
https://github.com/pkimpel/retro-220.git
synced 2026-02-10 10:10:49 +00:00
Transcribed from "SAMPLE PROGRAM 2" in Appendix D of the
"Burroughs 220 Assembler-Compiler" manual, Bulletin 5024, April 1960.
Assemble with the BAC-Assembler. This manual was found at CBI:
Burroughs Corporation Records, Product Literature (CBI 90),
Charles Babbage Institute, University of Minnesota, Minneapolis.
https://archives.lib.umn.edu/repositories/3/resources/186.
Series 74, box 5, folder 17.
3128 lines
124 KiB
HTML
3128 lines
124 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 four forms:
|
|
*
|
|
* - Punched cards in a self-loading band-6 format (default).
|
|
*
|
|
* - Punched paper tape in self-loading format.
|
|
*
|
|
* - 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 under the heading "PREPARATION OF
|
|
* EXTERNAL PROGRAMS".
|
|
*
|
|
* - Punched cards in the BALGOL Generator program deck format for
|
|
* INPUTMEDIA and OUTPUTMEDIA routines. See Appendix F of the BAC-220
|
|
* reference manual under the heading "INPUT-OUTPUT PROCEDURES".
|
|
*
|
|
* - 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;
|
|
background-color: #FFC;
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
height: calc(100% - 3ex);
|
|
margin: 1ex}
|
|
|
|
DIV.heading {
|
|
margin-top: 12px;
|
|
margin-bottom: 6px;
|
|
width: 640px;
|
|
font-weight: bold}
|
|
|
|
LABEL {
|
|
font-size: smaller}
|
|
|
|
#CardReaderPanel {
|
|
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}
|
|
#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 {
|
|
margin-top: 4px;
|
|
margin-bottom: 4px;
|
|
width: 640px}
|
|
|
|
#TextPanel {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 160px; /* initially */
|
|
bottom: 0px;
|
|
width: 640px;
|
|
white-space: pre;
|
|
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}
|
|
.floatRight {
|
|
float: right}
|
|
.rj {
|
|
text-align: right}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class=heading>
|
|
<div class=floatRight>
|
|
<a href="https://github.com/pkimpel/retro-220/wiki/UsingBACAssembler"
|
|
target="_new" title="Wiki page for the BAC-Assembler">Help</a>
|
|
</div>
|
|
Assembler for the Burroughs 220 BALGOL Compiler & Library
|
|
</div>
|
|
|
|
<div id=OptionsDiv>
|
|
<table id=OptionsTable>
|
|
<thead>
|
|
<tr>
|
|
<th><label for=Pass1ListCheck>List Pass 1</label>
|
|
<th><label for=Pass2ListCheck>List Pass 2</label>
|
|
<th><label for=ChecksumCheck>Write Checksum</label>
|
|
<th><label for=OutputModeSelect>Output Mode</label>
|
|
<th>
|
|
<tbody>
|
|
<tr>
|
|
<td class=center>
|
|
<input id=Pass1ListCheck type=checkbox value=1>
|
|
<td class=center>
|
|
<input id=Pass2ListCheck type=checkbox value=1 CHECKED>
|
|
<td class=center>
|
|
<input id=ChecksumCheck type=checkbox value=1>
|
|
<td class=center>
|
|
<select id=OutputModeSelect>
|
|
<option value="" >No Object
|
|
<option value=L SELECTED>Loadable Deck
|
|
<option value=R >Paper Tape
|
|
<option value=M >BALGOL ML Deck
|
|
<option value=P >Gen MEDIA Deck
|
|
<option value=T >Object Tape
|
|
</select>
|
|
<td class=rj>
|
|
<button id=ExtractListing type=button>Extract Listing</button>
|
|
</table>
|
|
|
|
<div id=CardReaderPanel>
|
|
<table id=CardReaderTable>
|
|
<colgroup><col id=CardReaderCol1><col id=CardReaderCol2></colgroup>
|
|
<tr><td>Pre-load Pool
|
|
<td><input id=CRPoolSelector type=file size=90>
|
|
<tr><td>Load Source & Go
|
|
<td><input id=CRFileSelector type=file size=90>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div id=TextPanel></div> <!-- Don't add any whitespace inside the div! -->
|
|
|
|
|
|
<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
|
|
var paperTapeUnit = 1; // Paper tape reader unit number for object tapes
|
|
|
|
// 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, 15, 2, // 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, 16, 0, 26, 0]; // 70-7F
|
|
|
|
var xlate220ANSI = [ // translate internal B220 code to ANSI (Algol glyphs)
|
|
// 00 01 02 03 04 05 06 07 08 09
|
|
" ", "?", "_", ".", ")", "?", "?", "?", "?", "?", // 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 = Cardatron c-digit inserted in (31) for T-relays (carriage control)
|
|
* 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, 3, 0],
|
|
"CAA": [ 110, 1, -1, 3, 0],
|
|
"CSU": [ 11, 1, -1, 3, 0],
|
|
"CSA": [ 111, 1, -1, 3, 0],
|
|
"ADD": [ 12, 1, -1, 3, 0],
|
|
"ADA": [ 112, 1, -1, 3, 0],
|
|
"SUB": [ 13, 1, -1, 3, 0],
|
|
"SUA": [ 113, 1, -1, 3, 0],
|
|
"MUL": [ 14, 1, -1, 3, 0],
|
|
"DIV": [ 15, 1, -1, 3, 0],
|
|
"RND": [ 16, 1, 0, 3, 0],
|
|
"EXT": [ 17, 1, -1, 3, 0],
|
|
"CFA": [ 18, 1, -1, 6, 0],
|
|
"CFR": [ 118, 1, -1, 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, -1],
|
|
"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],
|
|
"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 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 {
|
|
++x; // bypass the initial "$"
|
|
}
|
|
|
|
while (x < length) {
|
|
c = text.charAt(x);
|
|
if (c == "$") {
|
|
break;
|
|
} else {
|
|
++x;
|
|
raw += c;
|
|
code = c.charCodeAt(0);
|
|
appendCode((code < xlateANSI220.length ? xlateANSI220[code]
|
|
: (code == 0xA4 ? 4 : 0))); // the lozenge
|
|
}
|
|
}
|
|
|
|
if (x >= length) {
|
|
if (singleWord) {
|
|
printError("$-STRING NOT TERMINATED");
|
|
} else {
|
|
token.type = tokIncompleteString;
|
|
}
|
|
} else {
|
|
token.type = tokString; // string is complete, so...
|
|
++x; // bypass the terminating "$"
|
|
while (count < 5) { // pad out final word with spaces
|
|
appendCode(0);
|
|
}
|
|
|
|
values.push(word); // push out final word
|
|
if (singleWord && raw.length > 5) {
|
|
printError("STRING OCCUPIES MORE THAN ONE WORD");
|
|
}
|
|
}
|
|
|
|
token.newOffset = x;
|
|
token.text = raw;
|
|
token.word = word; // save any partial word for continuation
|
|
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<opTop; 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: // Cardatron c-digit inserted in (31) for T-relays (carriage control)
|
|
word = putField(word, f, 31);
|
|
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: copy one numeric digit, must be in sign-digit position
|
|
output: copy zone digit as a separate column
|
|
T: input: like P, but store a 2 for the sign instead of zero
|
|
output: same as P
|
|
X: input: store zone digit in memory
|
|
output: copy a zone digit as an overpunch for next code
|
|
Z: input: store zero digit in memory
|
|
output: supply a zero zone/numeric digit to the device
|
|
(: start of repeating group
|
|
): end of repeating group
|
|
|
|
This generator works by expanding all repeat counts and groups to a linear
|
|
string of format codes, then walking that string backwards to generate the
|
|
Cardatron format band digits. If this is an input format, the result is
|
|
padded with 11 zero codes to push the final word into the 220's D register.
|
|
For both input and output bands, the result is padded with 3 (delete) codes
|
|
out to a total of 29 words. Note that the band words are returned in
|
|
reverse order, to match the right-to-left processing of card columns by the
|
|
Cardatron and the order in which the 220 fetched words from memory when
|
|
loading format bands. If all that sounds very confusing, it's why we have
|
|
format band generators */
|
|
var band = []; // generated format band words
|
|
var bandType; // band type: INPUT, PRINT, PUNCH
|
|
var codes = ""; // expanded format band phrases
|
|
var cx = 0; // current operand char offset
|
|
var dx = 0; // band word digit index
|
|
var oLen; // length of phrase text in operand
|
|
var word = 0; // generated band word
|
|
var wx = 0; // band word digit index (power of 10)
|
|
var x; // scratch index
|
|
|
|
//--------------------------------------
|
|
function emitBandDigits(digits, count) {
|
|
/* Moves digits into format band words starting at the low-order
|
|
digit of the word. Handles overflow to the next word */
|
|
var digit; // current output digit
|
|
|
|
while (count > 0) {
|
|
--count;
|
|
digit = digits%10;
|
|
digits = (digits-digit)/10;
|
|
if (wx < 11) {
|
|
word += digit*p10[wx];
|
|
++wx;
|
|
} else {
|
|
band.unshift(word);
|
|
word = digit;
|
|
wx = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------
|
|
function flushBandWord(digit) {
|
|
/* If a partial word exists, fill the remainder with "digit" values,
|
|
then push it to the band array */
|
|
|
|
while (wx < 11) {
|
|
emitBandDigits(digit, 1);
|
|
}
|
|
|
|
band.unshift(word);
|
|
wx = 0;
|
|
word = 0;
|
|
}
|
|
|
|
//--------------------------------------
|
|
function expandPhrase(cx, level) {
|
|
/* Expands repeat counts and groups to a string of individual phrase
|
|
codes in "codes"; adjusts phrase codes for X-code zone-digit behavior */
|
|
var c; // current phrase character
|
|
var count = 0; // repeat count
|
|
var done = false; // loop control
|
|
var overpunch = false; // X-code zone punch flag
|
|
var px = cx; // current phrease char offset
|
|
var tx; // temp offset for repeating groups
|
|
|
|
do {
|
|
if (px >= oLen) {
|
|
done = true;
|
|
} else {
|
|
c = operand.charAt(px);
|
|
++px;
|
|
switch (c) {
|
|
case "0":
|
|
case "1":
|
|
case "2":
|
|
case "3":
|
|
case "4":
|
|
case "5":
|
|
case "6":
|
|
case "7":
|
|
case "8":
|
|
case "9":
|
|
count = count*10 + (c.charCodeAt(0) - ("0").charCodeAt(0));
|
|
break;
|
|
case "A":
|
|
case "P":
|
|
case "S":
|
|
case "T":
|
|
case "Z":
|
|
do {
|
|
--count;
|
|
codes += c;
|
|
} while (count > 0);
|
|
count = 0;
|
|
break;
|
|
case "B":
|
|
do {
|
|
--count;
|
|
if (overpunch) {
|
|
codes += "Y"; // half column: standalone sign
|
|
overpunch = false;
|
|
} else {
|
|
codes += c;
|
|
}
|
|
} while (count > 0);
|
|
count = 0;
|
|
break;
|
|
case "N":
|
|
do {
|
|
--count;
|
|
if (overpunch) {
|
|
codes += "D"; // half column: overpunched sign
|
|
overpunch = false;
|
|
} else {
|
|
codes += c;
|
|
}
|
|
} while (count > 0);
|
|
count = 0;
|
|
break;
|
|
case "X":
|
|
overpunch = true;
|
|
do {
|
|
--count;
|
|
codes += c; // half-column zone digit
|
|
} while (count > 0);
|
|
count = 0;
|
|
break;
|
|
case "(":
|
|
do {
|
|
--count;
|
|
tx = expandPhrase(px, level+1);
|
|
} while (count > 0);
|
|
count = 0;
|
|
px = tx;
|
|
break;
|
|
case ")":
|
|
if (level < 1) {
|
|
printError("EXCESS RIGHT PARENTHESIS");
|
|
}
|
|
done = true;
|
|
break;
|
|
case ",":
|
|
if (level > 0) {
|
|
printError("COMMA IN NESTED PHRASE");
|
|
}
|
|
codes += c;
|
|
count = 0;
|
|
break;
|
|
default:
|
|
printError("INVALID FORMAT BAND CHARACTER: " + c);
|
|
break;
|
|
} // switch c
|
|
}
|
|
} while (!done);
|
|
|
|
return px;
|
|
}
|
|
|
|
//--------------------------------------
|
|
function buildInputBand(cols) {
|
|
/* Builds an input band from the expanded phrase code string
|
|
in "codes", placing the result in the "band" array */
|
|
var c; // current phrase code
|
|
var colCount = 0; // number of card columns
|
|
var cx; // current offset into codes
|
|
var dx = 0; // current memory word digit index
|
|
|
|
for (cx=codes.length-1; cx>=0; --cx) {
|
|
c = codes.charAt(cx);
|
|
switch (c) {
|
|
case "A": // copy alphanumeric column
|
|
if (dx & 1) {
|
|
printError("A CODE ON ODD DIGIT: " + cx);
|
|
} else if (dx > 9) {
|
|
printError("A CODE ON SIGN DIGIT: " + cx);
|
|
} else {
|
|
emitBandDigits(11, 2);
|
|
}
|
|
colCount += 1;
|
|
dx += 2;
|
|
break;
|
|
case "B": // ignore column
|
|
emitBandDigits(33, 2);
|
|
colCount += 1;
|
|
break;
|
|
case "D": // copy numeric half-column for overpunched sign
|
|
emitBandDigits(1, 1);
|
|
colCount +=1;
|
|
dx += 1;
|
|
break;
|
|
case "N": // copy numeric column
|
|
emitBandDigits(31, 2);
|
|
colCount += 1;
|
|
dx += 1;
|
|
break;
|
|
case "P": // insert zero in memory word for sign
|
|
if (dx < 10) {
|
|
printError("P CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(0, 1);
|
|
dx += 1;
|
|
break;
|
|
case "S": // copy numeric column for sign
|
|
if (dx < 10) {
|
|
printError("S CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(31, 2);
|
|
colCount += 1;
|
|
dx += 1;
|
|
break;
|
|
case "T": // insert 2 in memory word sign
|
|
if (dx < 10) {
|
|
printError("T CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(2, 1);
|
|
dx += 1;
|
|
break;
|
|
case "X": // copy zone half-column for sign
|
|
emitBandDigits(1, 1);
|
|
dx += 1;
|
|
break;
|
|
case "Y": // delete half-column (usually for overpunched sign)
|
|
emitBandDigits(3, 1);
|
|
colCount += 1;
|
|
break;
|
|
case "Z": // insert zero in memory word
|
|
emitBandDigits(0, 1);
|
|
dx += 1;
|
|
break;
|
|
case ",": // check that phrase/word boundaries match
|
|
if (dx > 0) {
|
|
printError("WRONG NUMBER OF DIGITS IN WORD: " + dx);
|
|
}
|
|
break;
|
|
default:
|
|
printError("INVALID INPUT BAND CODE: " + c);
|
|
break;
|
|
} // switch c
|
|
|
|
if (dx > 10) {
|
|
if (dx > 11) {
|
|
printError("EXCESS NUMBER OF DIGITS IN WORD: " + dx);
|
|
}
|
|
dx = 0;
|
|
}
|
|
} // for cx
|
|
|
|
if (dx > 0) {
|
|
printError("FINAL BAND WORD INCOMPLETE: " + dx);
|
|
}
|
|
|
|
if (colCount != cols) {
|
|
printError("WRONG NUMBER OF COLUMNS: " + colCount);
|
|
}
|
|
|
|
emitBandDigits(0, 11); // push out last word from Cardatron
|
|
flushBandWord(3); // pad out the last word with 3s
|
|
}
|
|
|
|
//--------------------------------------
|
|
function buildOutputBand(cols) {
|
|
/* Builds an output band from the expanded phrase code string
|
|
in "codes", placing the result in the "band" array */
|
|
var c; // current phrase code
|
|
var colCount = 0; // number of columns on card/line
|
|
var cx; // current offset into codes
|
|
var dx = 0; // current memory word digit index
|
|
|
|
for (cx=codes.length-1; cx>=0; --cx) {
|
|
c = codes.charAt(cx);
|
|
switch (c) {
|
|
case "A": // copy alphanumeric column
|
|
if (dx & 1) {
|
|
printError("A CODE ON ODD DIGIT: " + cx);
|
|
} else if (dx > 9) {
|
|
printError("A CODE ON SIGN DIGIT: " + cx);
|
|
} else {
|
|
emitBandDigits(11, 2);
|
|
}
|
|
colCount += 1;
|
|
dx += 2;
|
|
break;
|
|
case "B": // supply zeroes for blank column
|
|
emitBandDigits(0, 2);
|
|
colCount += 1;
|
|
break;
|
|
case "D": // copy numeric half-column for overpunched sign
|
|
emitBandDigits(1, 1);
|
|
colCount +=1;
|
|
dx += 1;
|
|
break;
|
|
case "N": // copy numeric column
|
|
emitBandDigits(2, 1);
|
|
colCount += 1;
|
|
dx += 1;
|
|
break;
|
|
case "P": // ignore sign digit in word
|
|
case "T":
|
|
if (dx < 10) {
|
|
printError(c + " CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(3, 1);
|
|
dx += 1;
|
|
break;
|
|
case "S": // copy numeric column for standalone sign
|
|
if (dx < 10) {
|
|
printError("S CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(2, 1);
|
|
colCount += 1;
|
|
dx += 1;
|
|
break;
|
|
case "X": // copy zone digit for sign
|
|
emitBandDigits(1, 1);
|
|
dx += 1;
|
|
break;
|
|
case "Y": // supply numeric half-column for standalone sign
|
|
emitBandDigits(0, 1);
|
|
colCount += 1;
|
|
break;
|
|
case "Z": // ignore digit in memory word
|
|
emitBandDigits(3, 1);
|
|
dx += 1;
|
|
break;
|
|
case ",": // check that phrase/word boundaries match
|
|
if (dx > 0) {
|
|
printError("WRONG NUMBER OF DIGITS IN WORD: " + dx);
|
|
}
|
|
break;
|
|
default:
|
|
printError("INVALID OUTPUT BAND CODE: " + c);
|
|
break;
|
|
} // switch c
|
|
|
|
if (dx > 10) {
|
|
if (dx > 11) {
|
|
printError("EXCESS NUMBER OF DIGITS IN WORD: " + dx);
|
|
}
|
|
dx = 0;
|
|
}
|
|
} // for cx
|
|
|
|
if (dx > 0) {
|
|
printError("FINAL BAND WORD INCOMPLETE: " + dx);
|
|
}
|
|
|
|
if (colCount != cols) {
|
|
printError("WRONG NUMBER OF COLUMNS: " + colCount);
|
|
}
|
|
|
|
flushBandWord(3); // pad out the last word with 3s
|
|
}
|
|
|
|
//-------------------------------------- start of generateFormatBand()
|
|
x = operand.indexOf(" ");
|
|
oLen = (x < 0 ? operand.length : x);
|
|
|
|
x = operand.indexOf(",");
|
|
bandType = (x < 0 ? "" : operand.substring(0,x));
|
|
|
|
cx = x+1;
|
|
while (cx < oLen) {
|
|
cx = expandPhrase(cx, 0);
|
|
}
|
|
|
|
//printLine(codes); // DEBUG
|
|
|
|
switch (bandType) {
|
|
case "INPUT":
|
|
buildInputBand(80);
|
|
break;
|
|
case "PUNCH":
|
|
buildOutputBand(80);
|
|
break;
|
|
case "PRINT":
|
|
buildOutputBand(120);
|
|
break;
|
|
default:
|
|
printError("INVALID FORMAT BAND TYPE: " + bandType);
|
|
break;
|
|
}
|
|
|
|
while (band.length < 29) {
|
|
band.unshift(33333333333);
|
|
}
|
|
|
|
return band;
|
|
}
|
|
|
|
/**************************************/
|
|
function 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 {
|
|
finishAssembly();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* 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) {
|
|
emitWord(autoSymTab[label], 0);
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Assembler Output *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function extractListing(ev) {
|
|
/* Copies the text contents of the "paper" area of the assembler listing,
|
|
opens a new temporary window, and pastes that text into the window so it
|
|
can be copied or saved by the user */
|
|
var doc = null; // temp window document object
|
|
var text = panel.textContent;
|
|
var title = "BAC-Assembler Listing";
|
|
var win = window.open("../../webUI/B220FramePaper.html", "BAC-Asm-Listing",
|
|
"scrollbars,resizable,width=600,height=500");
|
|
|
|
function copyText(ev) {
|
|
var doc = ev.target;
|
|
var win = doc.defaultView;
|
|
|
|
win.removeEventListener("load", copyText, false);
|
|
doc = win.document;
|
|
doc.title = title;
|
|
doc.getElementById("Paper").textContent = text;
|
|
}
|
|
|
|
//clearPanel();
|
|
win.addEventListener("load", copyText, false);
|
|
win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
}
|
|
|
|
/**************************************/
|
|
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 generatePaperTape(asmCode, outputChecksum, startAddress) {
|
|
/* Formats the assembled object code as a standard self-loading paper
|
|
tape image in a temporary window. From there it can be save, copied,
|
|
etc. */
|
|
var doc = null; // temp window document object
|
|
var tape = 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 Loadable Paper Tape";
|
|
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 writeTape(word) {
|
|
var char = 0;
|
|
var text = "";
|
|
|
|
if (word < 20000000000 || word >= 30000000000) {
|
|
text = padLeft(word, 11, "0");
|
|
} else {
|
|
while (word >= 200) {
|
|
char = word%100;
|
|
text = xlate220ANSI[char] + text;
|
|
word = (word-char)/100;
|
|
}
|
|
|
|
text = "2" + text;
|
|
}
|
|
|
|
tape.appendChild(doc.createTextNode(text + "\n"));
|
|
}
|
|
|
|
function generateTape(ev) {
|
|
var addr = 0; // assembled code address
|
|
var cksum = 0; // algebraic value of the checksum word
|
|
var gapCount = 1; // number of consecutive undefined words
|
|
var priorAddr = 0; // CRD address for prior card image
|
|
var word = undefined; // object code word
|
|
var x = 0; // scratch index
|
|
|
|
win.removeEventListener("load", generateTape, false);
|
|
doc = win.document;
|
|
doc.title = title;
|
|
tape = doc.getElementById("Paper");
|
|
|
|
while (addr < asmCode.length && asmCode[addr] === undefined) {
|
|
++addr;
|
|
}
|
|
|
|
while (addr < asmCode.length) {
|
|
word = asmCode[addr];
|
|
if (word === undefined) {
|
|
++gapCount;
|
|
} else {
|
|
if (gapCount > 0) {
|
|
writeTape(60000040000 + paperTapeUnit*1000000000 + addr);
|
|
gapCount = 0;
|
|
}
|
|
|
|
priorAddr = addr;
|
|
writeTape(word);
|
|
if (outputChecksum) {
|
|
cksum = checksum(cksum, word);
|
|
}
|
|
}
|
|
|
|
++addr;
|
|
} // while addr
|
|
|
|
if (outputChecksum) {
|
|
// Compute and output a word with the 220 negative checksum at the next address
|
|
if (cksum < 0) {
|
|
cksum = -cksum;
|
|
} else {
|
|
cksum += pval;
|
|
}
|
|
|
|
writeTape(cksum);
|
|
}
|
|
|
|
if (startAddress >= 0) {
|
|
writeTape(60000300000 + startAddress); // BUN to start
|
|
} else {
|
|
writeTape(69999009999); // HLT 9999
|
|
}
|
|
}
|
|
|
|
win.addEventListener("load", generateTape, 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 seq = 10; // card sequence number
|
|
var word = undefined; // object code word
|
|
var x = 0; // scratch index
|
|
|
|
win.removeEventListener("load", generateDeck, false);
|
|
doc = win.document;
|
|
doc.title = title;
|
|
deck = doc.getElementById("Paper");
|
|
|
|
while (addr < asmCode.length) {
|
|
word = asmCode[addr] || 0;
|
|
if (count > 5) {
|
|
writeCard(card, count, cardAddr, seq);
|
|
card = "";
|
|
cardAddr = addr;
|
|
count = 0;
|
|
seq += 10;
|
|
}
|
|
|
|
card += padLeft(word, 11, "0");
|
|
++count;
|
|
if (outputChecksum) {
|
|
cksum = checksum(cksum, word);
|
|
}
|
|
|
|
++addr;
|
|
} // while addr
|
|
|
|
writeCard(card, count, cardAddr, seq);
|
|
if (outputChecksum) {
|
|
// Compute and output a card with the 220 negative checksum at the next address
|
|
if (cksum < 0) {
|
|
card = padLeft(-cksum, 11, "0");
|
|
} else {
|
|
card = padLeft(cksum+pval, 11, "0");
|
|
}
|
|
|
|
writeCard(card + " ***CHECKSUM FOR " + sourceName, 1, addr, 9999990);
|
|
}
|
|
}
|
|
|
|
win.addEventListener("load", generateDeck, false);
|
|
win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
|
|
}
|
|
|
|
/**************************************/
|
|
function generateGeneratorMediaDeck(asmCode, outputChecksum, startAddress) {
|
|
/* Formats the assembled object code as a Generator INPUTMEDIA/OUTPUTMEDIA
|
|
deck in a temporary window. From there it can be save, copied, etc. */
|
|
var doc = null; // temp window document object
|
|
var deck = null; // temp window text area
|
|
var pval = p10[10]; // modulus for a word's absolute value
|
|
var psign = pval*2; // modulus for a word's sign bit
|
|
var title = "220 BAC-Assembler Generator MEDIA Deck";
|
|
var win = window.open("../../webUI/B220FramePaper.html", "BAC-Asm-MEDIA",
|
|
"scrollbars,resizable,width=600,height=500");
|
|
|
|
function writeCard(word, addr, seq) {
|
|
|
|
deck.appendChild(doc.createTextNode(
|
|
"600" + padLeft(seq, 7, "0") + padLeft(addr, 4, "0") +
|
|
padRight(" ", 22, " ") + padLeft(word, 11, "0") + "\n"));
|
|
}
|
|
|
|
function generateDeck(ev) {
|
|
var addr = 0; // assembled code address
|
|
var seq = 10; // card sequence number
|
|
var word = undefined; // object code word
|
|
|
|
win.removeEventListener("load", generateDeck, false);
|
|
doc = win.document;
|
|
doc.title = title;
|
|
deck = doc.getElementById("Paper");
|
|
|
|
while (addr < asmCode.length) {
|
|
word = asmCode[addr] || 0;
|
|
writeCard(word, addr, seq);
|
|
seq += 10;
|
|
++addr;
|
|
} // while addr
|
|
}
|
|
|
|
win.addEventListener("load", generateDeck, false);
|
|
win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
|
|
}
|
|
|
|
/**************************************/
|
|
function generateObjectTape(asmCode, outputChecksum, startAddress) {
|
|
/* Formats the assembled object code as a 220 tape image in a temporary
|
|
window. From there it can be save, copied, etc. */
|
|
var doc = null; // temp window document object
|
|
var pval = p10[10]; // modulus for a word's absolute value
|
|
var psign = pval*2; // modulus for a word's sign bit
|
|
var tape = null; // temp window text area
|
|
var title = "220 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 "R":
|
|
generatePaperTape(asmCode, outputChecksum, startAddress);
|
|
break;
|
|
case "M":
|
|
generateMachineLanguageDeck(asmCode, outputChecksum, startAddress);
|
|
break;
|
|
case "P":
|
|
generateGeneratorMediaDeck(asmCode, outputChecksum, startAddress);
|
|
break;
|
|
case "T":
|
|
generateObjectTape(asmCode, outputChecksum, startAddress);
|
|
break;
|
|
}// switch oms.options
|
|
}
|
|
|
|
$$("CRFileSelector").value = null; // reset the <input> elements
|
|
$$("CRPoolSelector").value = null; // so they can be reused
|
|
poolSets.length = 1; // discard any poolSets that were loaded
|
|
document.body.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";
|
|
document.body.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;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function repositionTextPanel() {
|
|
/* Repositions and resizes the #TextPanel element so that it fills the
|
|
bottom portion of the window */
|
|
var opts = $$("OptionsDiv");
|
|
var panel = $$("TextPanel");
|
|
|
|
panel.style.top = (opts.offsetTop + opts.offsetHeight + 8).toFixed() + "px";
|
|
}
|
|
|
|
/******************** Start of window.onload() ********************/
|
|
repositionTextPanel();
|
|
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;
|
|
});
|
|
|
|
$$("ExtractListing").addEventListener("click", extractListing);
|
|
}, false);
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|