mirror of
https://github.com/pkimpel/retro-220.git
synced 2026-02-11 10:35:30 +00:00
1. Updated compiler and library sources after second proofing pass. 2. Updated Generator and Compiler object code and build decks. 3. Updated Generator and Compiler loadable object tapes. 4. Corrections to BAC-Assembler and enhancements for additional card/tape output options. 5. Minor corrections to GEN-Assembler. 6. Updates to miscellaneous utilities and example programs.
2931 lines
119 KiB
HTML
2931 lines
119 KiB
HTML
<!DOCTYPE html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
<title>BAC-220 Assembler</title>
|
|
<!--
|
|
/***********************************************************************
|
|
* 220/software/tools BAC-Assembler.html
|
|
************************************************************************
|
|
* Copyright (c) 2016, Paul Kimpel.
|
|
* Licensed under the MIT License, see
|
|
* http://www.opensource.org/licenses/mit-license.php
|
|
************************************************************************
|
|
* Cross-Assembler for the Burroughs 220 Algebraic Compiler (BALGOL).
|
|
*
|
|
* Assembles source for 220 machine language programs as used in the
|
|
* BALGOL compiler. Source is read from an emulated card deck -- a text
|
|
* file with the following format:
|
|
*
|
|
* col 1 Cardatron format band selection digit. Can be anything,
|
|
* but is typically 1.
|
|
* col 5-9 symbolic label or point label (*label).
|
|
* col 11-14 symbolic op code (standard 220 mnemonics).
|
|
* col 15 override sign (0-9, +, -), blank => 0.
|
|
* col 17... operands (terminated by first free space).
|
|
* col 73-80 sequence and identification.
|
|
*
|
|
* All other columns are ignored by this assembler.
|
|
*
|
|
* Output is a simulated line printer listing in the <iframe> of the web
|
|
* page from which the assembler is run. The output of Pass 1 is designed
|
|
* to match the listing from which the compiler was transcribed, so that
|
|
* it may be compared for proofing purposes.
|
|
*
|
|
* Output of Pass 2 is a traditional assembler listing, with words of
|
|
* generated object code and data.
|
|
*
|
|
* Output of the generated code and data is... *TBD*
|
|
*
|
|
************************************************************************
|
|
* 2016-12-09 P.Kimpel
|
|
* Original version, cloned from retro-b5500 B5500CardReaderPrototype.html.
|
|
***********************************************************************/
|
|
-->
|
|
<meta name="Author" content="Paul Kimpel">
|
|
<meta http-equiv="Content-Script-Type" content="text/javascript">
|
|
<meta http-equiv="Content-Style-Type" content="text/css">
|
|
|
|
<style>
|
|
HTML {
|
|
height: 100%}
|
|
|
|
BODY {
|
|
position: relative;
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
height: 100%;
|
|
margin: 1ex}
|
|
|
|
DIV.heading {
|
|
margin-top: 12px;
|
|
margin-bottom: 6px;
|
|
font-weight: bold}
|
|
|
|
LABEL {
|
|
font-size: smaller}
|
|
|
|
#CardReaderPanel {
|
|
position: relative;
|
|
color: white;
|
|
background-color: #666;
|
|
width: 640px;
|
|
border: 1px solid black;
|
|
border-radius: 8px;
|
|
font-size: smaller;
|
|
padding: 8px}
|
|
|
|
#CardReaderTable {
|
|
border-spacing: 0;
|
|
border-collapse: collapse;
|
|
table-layout: fixed;
|
|
width: 100%}
|
|
#CardReaderCol1 {
|
|
width: 18ex}
|
|
#CardReaderCol2 {
|
|
}
|
|
#CardReaderTable TD {
|
|
text-align: left;
|
|
vertical-align: top;
|
|
padding-top: 1px;
|
|
padding-bottom: 1px;
|
|
padding-left: 2px;
|
|
padding-right: 2px}
|
|
|
|
#CRFileSelector {
|
|
width: 100%;
|
|
border: 1px solid white}
|
|
|
|
#CRPoolSelector {
|
|
width: 100%;
|
|
border: 1px solid white}
|
|
|
|
#OptionsTable {
|
|
position: relative;
|
|
margin-top: 4px;
|
|
margin-bottom: 4px;
|
|
width: 640px}
|
|
|
|
#TextDiv {
|
|
position: relative;
|
|
height: 75%;
|
|
width: 640px}
|
|
|
|
#TextPanel {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
bottom: 8px;
|
|
width: 100%;
|
|
overflow: scroll;
|
|
padding: 4px;
|
|
border: 1px solid black;
|
|
color: black;
|
|
background-color: white;
|
|
font-family: DejaVu Sans Mono, Consolas, Courier, monospace;
|
|
font-size: 8pt;
|
|
font-weight: normal}
|
|
|
|
#Spinner {
|
|
position: absolute;
|
|
top: 200px;
|
|
left: 200px;
|
|
z-index: 10}
|
|
|
|
.center {
|
|
text-align: center}
|
|
.rj {
|
|
text-align: right}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class=heading>
|
|
Assembler for the Burroughs 220 BALGOL Compiler & Library
|
|
</div>
|
|
|
|
<div id=CardReaderPanel>
|
|
<table id=CardReaderTable>
|
|
<colgroup><col id=CardReaderCol1><col id=CardReaderCol2></colgroup>
|
|
<tr><td>Pre-load Pool
|
|
<td><input id=CRPoolSelector type=file size=90>
|
|
<tr><td>Load Source & Go
|
|
<td><input id=CRFileSelector type=file size=90>
|
|
</table>
|
|
</div>
|
|
|
|
<table id=OptionsTable>
|
|
<thead>
|
|
<tr>
|
|
<th><label for=Pass1ListCheck>List Pass 1</label>
|
|
<th><label for=Pass2ListCheck>List Pass 2</label>
|
|
<th><label for=ChecksumCheck>Write Checksum</label>
|
|
<th><label for=OutputModeSelect>Output Mode</label>
|
|
<th>
|
|
<tbody>
|
|
<tr>
|
|
<td class=center>
|
|
<input id=Pass1ListCheck type=checkbox value=1>
|
|
<td class=center>
|
|
<input id=Pass2ListCheck type=checkbox value=1 CHECKED>
|
|
<td class=center>
|
|
<input id=ChecksumCheck type=checkbox value=1>
|
|
<td class=center>
|
|
<select id=OutputModeSelect>
|
|
<option value="" >No Object
|
|
<option value=L SELECTED>Loadable Deck
|
|
<option value=M >BALGOL ML Deck
|
|
<option value=T >Object Tape
|
|
</select>
|
|
<td class=rj>
|
|
<button id=SelectListing type=button>Select Listing</button>
|
|
</table>
|
|
|
|
<div id=TextDiv><pre id=TextPanel></pre></div> <!-- Don't add any whitespace! -->
|
|
|
|
|
|
<script>
|
|
"use strict";
|
|
|
|
window.addEventListener("load", function() {
|
|
|
|
// Card reader properties
|
|
var buffer = "";
|
|
var bufferLength = 0;
|
|
var bufferOffset = 0;
|
|
var sourceName = "?";
|
|
|
|
var eolRex = /([^\n\r\f]*)((:?\r[\n\f]?)|\n|\f)?/g;
|
|
var rTrimRex = /\s*$/;
|
|
var sprintLimit = 100; // cards processed before yielding control
|
|
|
|
// Card 0-relative column offsets
|
|
var labelIndex = 4;
|
|
var opCodeIndex = labelIndex + 6;
|
|
var operandIndex = labelIndex + 12;
|
|
var operandLength = 55;
|
|
|
|
// Card data structure
|
|
var cardData = {
|
|
atEOF: false,
|
|
offset: 0,
|
|
length: 0,
|
|
serial: 0,
|
|
text: ""}
|
|
|
|
var pass1List = false;
|
|
var pass2List = true;
|
|
var outputChecksum = false;
|
|
|
|
var panel = $$("TextPanel");
|
|
var cardReaderUnit = 1; // Cardatron output unit number for card decks
|
|
|
|
// Token.type values
|
|
var tokEmpty = 0; // empty primary
|
|
var tokLocation = 1; // *, the current location counter
|
|
var tokInteger = 2; // integer literal
|
|
var tokLabel = 3; // regular label
|
|
var tokBackwardPoint = 4; // backward point label (without the leading * or the trailing -)
|
|
var tokForwardPoint = 5; // forward point label (ditto, without the trailing +)
|
|
var tokLiteral = 6; // pool literal, 1-11 digit number with a leading + or -)
|
|
var tokIncompleteString = 7; // unterminated $-delimited string literal (probably continued on next card)
|
|
var tokString = 8; // $-delimited string literal
|
|
|
|
// token structure for operand parsing
|
|
var token = {
|
|
type: tokEmpty,
|
|
offset: 0,
|
|
text: "",
|
|
word: 0,
|
|
newOffset: -1};
|
|
|
|
// Assembly storage
|
|
var asmCode = []; // buffer for assembled words of code
|
|
var autoSymTab = {}; // auto-declared (undefined) symbol table
|
|
var errorCount = 0; // assembler error count
|
|
var errorTank = []; // holding area for errors on current line
|
|
var location = 0; // current instruction address
|
|
var pointTab = {}; // Point-label table: holds the current sequence number for each point label
|
|
var poolLocation = NaN; // address of the current literal pool
|
|
var poolSetName = ""; // name of optional pool set data file
|
|
var poolTab = {}; // Pool literal table: holds address of the literal word
|
|
var startAddress = -1; // starting execution address, from FINI card
|
|
var symTab = {}; // Symbol table: holds the address value for each label
|
|
|
|
var poolSets = [ // skeleton table of pre-loaded pools for default program unit #0
|
|
{poolLoc: NaN, poolData: []}];
|
|
|
|
var p10 = [ 1, // powers of 10 table
|
|
10,
|
|
100,
|
|
1000,
|
|
10000,
|
|
100000,
|
|
1000000,
|
|
10000000,
|
|
100000000,
|
|
1000000000,
|
|
10000000000,
|
|
100000000000,
|
|
1000000000000,
|
|
10000000000000,
|
|
100000000000000,
|
|
1000000000000000,
|
|
10000000000000000];
|
|
|
|
var xlateANSI220 = [ // translate ANSI to 220 internal character codes
|
|
// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-0F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10-1F
|
|
0, 0, 0, 33, 13, 24, 10, 34, 24, 4, 14, 10, 23, 20, 3, 21, // 20-2F
|
|
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 0, 13, 4, 33, 0, 0, // 30-3F
|
|
34, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, // 40-4F
|
|
57, 58, 59, 62, 63, 64, 65, 66, 67, 68, 69, 0, 0, 0, 0, 0, // 50-5F
|
|
0, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, // 60-6F
|
|
57, 58, 59, 62, 63, 64, 65, 66, 67, 68, 69, 0, 0, 0, 0, 0]; // 70-7F
|
|
|
|
var xlate220ANSI = [ // translate internal B220 code to ANSI (Algol glyphs)
|
|
// 00 01 02 03 04 05 06 07 08 09
|
|
" ", "?", "9", ".", ")", "?", "?", "?", "?", "?", // 00-09
|
|
"+", "?", "?", "$", "*", "*", "$", "?", "?", "?", // 10-19
|
|
"-", "/", "?", ",", "(", "?", ",", "?", "?", "?", // 20-29
|
|
"?", "?", "?", "=", "@", "\\", "?", "?", "?", "?", // 30-39
|
|
"?", "A", "B", "C", "D", "E", "F", "G", "H", "I", // 40-49
|
|
"?", "J", "K", "L", "M", "N", "O", "P", "Q", "R", // 50-59
|
|
"?", "?", "S", "T", "U", "V", "W", "X", "Y", "Z", // 60-69
|
|
"?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 70-79
|
|
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", // 80-89
|
|
"?", "?", "?", "?", "?", "?", "?", "?", "?", "?"]; // 90-99
|
|
|
|
var signValues = { // numeric values of sign column
|
|
" ": 0,
|
|
"+": 0,
|
|
"0": 0,
|
|
"-": 1,
|
|
"1": 1,
|
|
"2": 2,
|
|
"3": 3,
|
|
"4": 4,
|
|
"5": 5,
|
|
"6": 6,
|
|
"7": 7,
|
|
"8": 8,
|
|
"9": 9};
|
|
|
|
/***************************************
|
|
* Opcode table:
|
|
* Each 220 assembler op code is defined by an array with a variable number
|
|
* of elements. The first element is the operation code, including any variant
|
|
* digit in (41) and is always required. The variant digit may be overlaid by
|
|
* other fields in the instruction. If the code is negative, it indicates a
|
|
* pseudo-operator or other special handling.
|
|
*
|
|
* The remainder of the array consists of number pairs, one for each comma-
|
|
* delimited operand field that may be present. The first number is a code
|
|
* indicating the type of field, the second is the default value for that
|
|
* field if it is not specified (or is empty) in the operand area of the
|
|
* instruction. If the default value is negative, that operand is not optional
|
|
* and must be specified.
|
|
*
|
|
* Type codes:
|
|
* 1 = address field, inserted in (04)
|
|
* 2 = secondary value inserted in (33)
|
|
* 3 = secondary value inserted in (44)
|
|
* 4 = unit or count digit inserted in (11)
|
|
* 5 = variant digit inserted in (41)
|
|
* 6 = sL field designator inserted in (22); if specified, insert 1 in (31)
|
|
* 7 = sL field designator inserted in (22); (31) is undisturbed
|
|
* 8 = value inserted in (32)
|
|
* 9 = value inserted in (42)
|
|
* 10 = value inserted in (21)
|
|
* 11 = value inserted in (62)
|
|
* 12 = value inserted in (64)
|
|
* 13 = BU pair for CRF/CWF: (B-1)*2 added to (41), U in (11)
|
|
* 14 = reload-lockout value added to (41)
|
|
* 15 = digit inserted in (11); if specified, insert 1 in (41)
|
|
* 16 = format band digit added to (41): (B-1)*2
|
|
* 17 = variant value added to (41)
|
|
* 18 = mag tape unit/lane as LLU, inserted as ULL in (33)
|
|
* 19 = resolved address only
|
|
***************************************/
|
|
|
|
// Pseudo-instruction codes
|
|
var pseudoDEFN = -1;
|
|
var pseudoLOCN = -2;
|
|
var pseudoCNST = -3;
|
|
var pseudoF244 = -4;
|
|
var pseudoF424 = -5;
|
|
var pseudoFBGR = -6;
|
|
var pseudoFINI = -9;
|
|
|
|
var opTab = {
|
|
"HLT": [ 0, 1, 0, 3, 0],
|
|
"NOP": [ 1, 1, 0, 3, 0],
|
|
"PRD": [ 3, 1, -1, 4, -1, 8, -1, 5, 0],
|
|
"PRB": [ 4, 1, -1, 4, -1, 5, 0, 8, 0],
|
|
"PRI": [ 5, 1, -1, 4, -1, 8, -1, 5, 0],
|
|
"PWR": [ 6, 1, -1, 4, -1, 8, -1],
|
|
"PWI": [ 7, 1, -1, 4, -1],
|
|
"KAD": [ 8, 1, 0, 3, 0],
|
|
"SPO": [ 9, 1, -1, 8, -1, 15, 0],
|
|
"CAD": [ 10, 1, -1],
|
|
"CAA": [ 110, 1, -1],
|
|
"CSU": [ 11, 1, -1],
|
|
"CSA": [ 111, 1, -1],
|
|
"ADD": [ 12, 1, -1],
|
|
"ADA": [ 112, 1, -1],
|
|
"SUB": [ 13, 1, -1],
|
|
"SUA": [ 113, 1, -1],
|
|
"MUL": [ 14, 1, -1, 3, 0],
|
|
"DIV": [ 15, 1, -1, 3, 0],
|
|
"RND": [ 16, 1, 0, 3, 0],
|
|
"EXT": [ 17, 1, -1, 3, 0],
|
|
"CFA": [ 18, 1, -1, 6, 0],
|
|
"CFR": [ 118, 1, -1, 6, 0],
|
|
"ADL": [ 19, 1, -1, 3, 0],
|
|
"IBB": [ 20, 1, -1, 3, -1],
|
|
"DBB": [ 21, 1, -1, 3, -1],
|
|
"FAD": [ 22, 1, -1, 4, 0],
|
|
"FAA": [ 122, 1, -1, 4, 0],
|
|
"FSU": [ 23, 1, -1, 4, 0],
|
|
"FSA": [ 123, 1, -1, 4, 0],
|
|
"FMU": [ 24, 1, -1, 3, 0],
|
|
"FDV": [ 25, 1, -1, 3, 0],
|
|
"IFL": [ 26, 1, -1, 7, -1, 9, -1],
|
|
"DFL": [ 27, 1, -1, 7, -1, 9, -1],
|
|
"DLB": [ 28, 1, -1, 7, -1, 9, -1],
|
|
"RTF": [ 29, 1, -1, 8, -1],
|
|
"BUN": [ 30, 1, -1, 3, 0],
|
|
"BOF": [ 31, 1, -1, 3, 0],
|
|
"BRP": [ 32, 1, -1, 3, 0],
|
|
"BSA": [ 33, 1, -1, 5, -1, 2, 0],
|
|
"BPA": [ 33, 1, -1, 5, 0, 2, 0],
|
|
"BMA": [ 33, 1, -1, 5, 1, 2, 0],
|
|
"BCH": [ 34, 1, -1, 2, 0],
|
|
"BCL": [ 134, 1, -1, 2, 0],
|
|
"BCE": [ 35, 1, -1, 2, 0],
|
|
"BCU": [ 135, 1, -1, 2, 0],
|
|
"BFA": [ 36, 1, -1, 7, -1, 9, -1],
|
|
"BZA": [ 36, 1, -1, 7, 0, 9, 0],
|
|
"BFR": [ 37, 1, -1, 7, -1, 9, -1],
|
|
"BZR": [ 37, 1, -1, 7, 0, 9, 0],
|
|
"BCS": [ 38, 1, -1, 4, 0],
|
|
"SOR": [ 39, 1, 0, 2, 0],
|
|
"SOH": [ 139, 1, 0, 2, 0],
|
|
"IOM": [ 239, 1, -1, 2, 0],
|
|
"STA": [ 40, 1, -1, 6, 0],
|
|
"STR": [ 140, 1, -1, 6, 0],
|
|
"STB": [ 240, 1, -1, 6, 0],
|
|
"LDR": [ 41, 1, -1, 3, 0],
|
|
"LDB": [ 42, 1, -1, 2, 0],
|
|
"LBC": [ 142, 1, -1, 2, 0],
|
|
"LSA": [ 43, 5, -1, 1, 0, 2, 0],
|
|
"STP": [ 44, 1, -1, 3, 0],
|
|
"CLA": [ 145, 1, 0, 2, 0],
|
|
"CLR": [ 245, 1, 0, 2, 0],
|
|
"CAR": [ 345, 1, 0, 2, 0],
|
|
"CLB": [ 445, 1, 0, 2, 0],
|
|
"CAB": [ 545, 1, 0, 2, 0],
|
|
"CRB": [ 645, 1, 0, 2, 0],
|
|
"CLT": [ 745, 1, 0, 2, 0],
|
|
"CLL": [ 46, 1, -1, 3, 0],
|
|
"SRA": [ 48, 1, -1, 2, 0],
|
|
"SRT": [ 148, 1, -1, 2, 0],
|
|
"SRS": [ 248, 1, -1, 2, 0],
|
|
"SLA": [ 49, 1, -1, 2, 0],
|
|
"SLT": [ 149, 1, -1, 2, 0],
|
|
"SLS": [ 249, 1, -1, 2, 0],
|
|
"MTS": [ 50, 1, -1, 18, -1],
|
|
"MFS": [4000050, 1, -1, 18, -1],
|
|
"MLS": [ 450, 18, -1, 1, 0],
|
|
"MRW": [ 850, 18, -1, 1, 0],
|
|
"MDA": [ 950, 18, -1, 1, 0],
|
|
"MTC": [ 51, 1, -1, 18, -1, 5, -1],
|
|
"MFC": [4000051, 1, -1, 18, -1, 5, -1],
|
|
"MRD": [ 52, 1, -1, 4, -1, 10, -1, 17, 0],
|
|
"MNC": [ 152, 1, -1, 4, -1, 10, -1, 17, 0],
|
|
"MRR": [ 53, 1, -1, 4, -1, 10, -1, 17, 0],
|
|
"MIW": [ 54, 1, -1, 4, -1, 10, -1, 9, 0],
|
|
"MIR": [ 55, 1, -1, 4, -1, 10, -1, 9, 0],
|
|
"MOW": [ 56, 1, -1, 4, -1, 10, -1, 9, 0],
|
|
"MOR": [ 57, 1, -1, 4, -1, 10, -1, 9, 0],
|
|
"MPF": [ 58, 4, -1, 10, -1, 1, 0],
|
|
"MPB": [ 158, 4, -1, 10, -1, 1, 0],
|
|
"MPE": [ 258, 4, -1, 1, 0],
|
|
"MIB": [ 59, 1, -1, 4, -1, 8, 0],
|
|
"MIE": [ 159, 1, -1, 4, -1, 8, 0],
|
|
"CRD": [ 60, 1, -1, 4, -1, 5, 0, 8, 0],
|
|
"CNC": [ 1060, 1, -1, 4, -1, 5, 0],
|
|
"CWR": [ 61, 1, -1, 13, -1, 16, 0, 8, 0],
|
|
"CRF": [ 62, 1, -1, 13, -1, 14, 0],
|
|
"CWF": [ 63, 1, -1, 13, -1, 14, 0],
|
|
"CRI": [ 64, 1, -1, 4, -1],
|
|
"CWI": [ 65, 1, -1, 4, -1],
|
|
"HPW": [ 66, 1, -1, 8, -1],
|
|
"HPI": [ 67, 1, 0, 3, 0],
|
|
|
|
// Pseudo-ops
|
|
"DEFN": [pseudoDEFN, // define symbol
|
|
19, -1],
|
|
"LOCN": [pseudoLOCN, // set location counter
|
|
19, -1],
|
|
"CNST": [pseudoCNST], // assemble list of literal values
|
|
"F244": [pseudoF244, // assemble word from 22-64-04 fields
|
|
7, 0, 12, 0, 1, 0],
|
|
"F424": [pseudoF424, // assemble word from 44-62-04 fields
|
|
3, 0, 11, 0, 1, 0],
|
|
"FBGR": [pseudoFBGR], // assemble Cardatron format band
|
|
"FINI": [pseudoFINI, // finish assembly, output literal pool
|
|
1, 0]
|
|
};
|
|
|
|
|
|
/*******************************************************************
|
|
* Miscellaneous Utility Functions *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function $$(id) {
|
|
return document.getElementById(id);
|
|
}
|
|
|
|
/**************************************/
|
|
function padLeft(s, len, fill) {
|
|
/* Pads the string "s" on the left to length "len" with the filler character
|
|
"fill". If fill is empty or missing, space is used. If the initial string is
|
|
longer than "len", it is truncated on the left to that length */
|
|
var pad = (fill || " ").charAt(0);
|
|
var result = s.toString();
|
|
var rLen = result.length;
|
|
|
|
if (rLen > len) {
|
|
result = result.substring(rLen-len);
|
|
} else {
|
|
while (rLen < len) {
|
|
result = pad + result;
|
|
++rLen;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**************************************/
|
|
function padRight(s, len, fill) {
|
|
/* Pads the string "s" on the right to length "len" with the filler character
|
|
"fill". If fill is empty or missing, space is used. If the initial string is
|
|
longer than "len", it is truncated on the right to that length */
|
|
var pad = (fill || " ").charAt(0);
|
|
var result = s.toString();
|
|
var rLen = s.length;
|
|
|
|
if (rLen > len) {
|
|
result = result.substring(0, len);
|
|
} else {
|
|
while (rLen < len) {
|
|
result = result + pad;
|
|
++rLen;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**************************************/
|
|
function rTrim(s) {
|
|
/* Returns the string "s" stripped of any trailing whitespace */
|
|
var x = s.search(rTrimRex);
|
|
|
|
if (x < 0 ) {
|
|
return s;
|
|
} else if (x < 1) {
|
|
return "";
|
|
} else {
|
|
return s.substring(0, x);
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function tensComp(value) {
|
|
/* If "value" is algebraically negative, returns its 11-digit tens
|
|
complement. Otherwise returns the 11-digit value */
|
|
|
|
if (value < 0) {
|
|
return p10[11] + value%p10[11];
|
|
} else {
|
|
return value%p10[11];
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function applySign(word, sign) {
|
|
/* Applies an unsigned "sign" digit to a 220 "word" value. If the word
|
|
value is algebraically negative, it is first converted to a 10-digit
|
|
number with a 220 sign in the 11-th high-order digit. The low-order bit
|
|
of "sign" and the low-order bit of the word's sign digit are XOR-ed so
|
|
that each of those bits designates negation. Returns the new value as an
|
|
11-digit unsigned 220 word in binary */
|
|
var s = 0;
|
|
var value = 0;
|
|
|
|
if (word < 0) {
|
|
value = (-word)%p10[10];
|
|
s = 1;
|
|
} else {
|
|
value = word%p10[10];
|
|
s = (word%p10[11] - value)/p10[10];
|
|
}
|
|
|
|
return (sign%10 ^ s)*p10[10] + value;
|
|
}
|
|
|
|
/**************************************/
|
|
function getField(word, sL) {
|
|
/* Extracts an n-digit value from an 11-digit "word" and returns it.
|
|
"sL" is the same as for putField(). The word is a Javascript Number
|
|
object, but is treated as if it represents an 11-digit decimal integer */
|
|
var L = sL%10;
|
|
var s = (sL%100 - L)/10;
|
|
var result = applySign(word, 0);
|
|
|
|
s = (s == 0 ? 0 : 10-s);
|
|
L = (L == 0 ? 10 : L);
|
|
|
|
if (sL < 0 || sL > 99 || s+L > 11) {
|
|
result = -1;
|
|
} else {
|
|
result = (result%p10[s+L] - result%p10[s])/p10[s];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**************************************/
|
|
function putField(word, value, sL) {
|
|
/* Inserts an n-digit "value" into designated digits of an 11-digit
|
|
"word". "sL" is the partial-word field in standard 220 start-Length
|
|
notation. Note that Javascript flags literal integers of the form "0n"
|
|
as the old C-style octal literal notation is deprecated. This routine
|
|
uses only the two low-order digits of "sL", however, so you can pass sL
|
|
literal values like 104 (or even 57321604) for /04 without ill effect.
|
|
The "value" and "word" are Javascript Number objects, but are treated as
|
|
if they represent 11-digit decimal integers. If value is negative, it is
|
|
converted to its 10s-complement value before insertion into word.
|
|
Returns a new word with the inserted field */
|
|
var L = sL%10;
|
|
var s = (sL%100 - L)/10;
|
|
var upperPart = 0;
|
|
var lowerPart = 0;
|
|
var result = applySign(word, 0);
|
|
|
|
s = (s == 0 ? 0 : 10-s);
|
|
L = (L == 0 ? 10 : L);
|
|
|
|
if (sL < 0 || s+L > 11) {
|
|
printError("INVALID /SL VALUE: ", sL);
|
|
} else {
|
|
upperPart = result%p10[11] - result%p10[s+L];
|
|
if (s > 0) {
|
|
lowerPart = result%p10[s];
|
|
}
|
|
|
|
result = (tensComp(value)%p10[L])*p10[s] + upperPart + lowerPart;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Listing Utilities *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function clearPanel() {
|
|
/* Clears the text panel */
|
|
var kid;
|
|
|
|
while (kid = panel.firstChild) {
|
|
panel.removeChild(kid);
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function readACard() {
|
|
/* Reads one card image from the buffer, pads or trims the image as
|
|
necessary to 80 columns, and fills in the global "cardData" structure
|
|
with the text and its location in the buffer */
|
|
var bx = bufferOffset; // current buffer offset
|
|
var card; // card image, padded/truncated to 80 columns
|
|
var cardLength; // original delimited card image length
|
|
var match; // regular expression match result
|
|
|
|
cardData.offset = bx;
|
|
if (bx >= bufferLength) {
|
|
cardData.atEOF = true;
|
|
cardData.length = 0;
|
|
cardData.text = "7 <<EOF>>";
|
|
} else {
|
|
eolRex.lastIndex = bx;
|
|
match = eolRex.exec(buffer);
|
|
if (!match) {
|
|
card = "";
|
|
} else {
|
|
bx += match[0].length;
|
|
card = match[1];
|
|
}
|
|
|
|
cardLength = card.length;
|
|
if (cardLength > 80) {
|
|
card = card.substring(0, 80);
|
|
} else {
|
|
while (card.length <= 70) {
|
|
card += " ";
|
|
}
|
|
while (card.length < 80) {
|
|
card += " ";
|
|
}
|
|
}
|
|
|
|
cardData.length = bx - bufferOffset;
|
|
++cardData.serial;
|
|
cardData.text = card;
|
|
bufferOffset = bx;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function printLine(text) {
|
|
/* Appends "text"+NL as a new text node to the panel DOM element */
|
|
var e = document.createTextNode(text + "\n");
|
|
|
|
panel.appendChild(e);
|
|
panel.scrollTop += 30
|
|
}
|
|
|
|
/**************************************/
|
|
function dumpErrorTank() {
|
|
/* Dumps the tank of error messages to the text panel */
|
|
var x;
|
|
|
|
for (x=0; x<errorTank.length; ++x) {
|
|
printLine(errorTank[x]);
|
|
}
|
|
|
|
printLine("");
|
|
errorTank = [];
|
|
}
|
|
|
|
/**************************************/
|
|
function printError(msg) {
|
|
/* Prints an error message to the text panel and bumps the error count */
|
|
|
|
++errorCount;
|
|
errorTank.push("******** " + msg);
|
|
}
|
|
|
|
/**************************************/
|
|
function dumpSymbolTable() {
|
|
/* Dumps the contents of the symbol table to the text panel */
|
|
var keys; // sorted symbol table keys
|
|
var offset = 0; // print line offset
|
|
var text = ""; // print line
|
|
var x; // scratch index
|
|
|
|
printLine("");
|
|
printLine("SYMBOL TABLE");
|
|
printLine("");
|
|
|
|
keys = Object.keys(symTab).sort();
|
|
for (x=0; x<keys.length; ++x) {
|
|
if (offset > 80) {
|
|
printLine(text);
|
|
text = "";
|
|
offset = 0;
|
|
}
|
|
|
|
text += padLeft(symTab[keys[x]], offset-text.length+5) + " " + keys[x];
|
|
offset += 20;
|
|
}
|
|
|
|
printLine(text);
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Assembler Primitives *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function declarePointLabel(label, location) {
|
|
/* For each instance of a point label in the label field, creates a
|
|
pseudo-label in the symbol table with the next number for that point */
|
|
var labelID; // unique symbol for the point label instance
|
|
var pointNr; // current point number
|
|
|
|
if (label in pointTab) {
|
|
pointNr = ++pointTab[label];
|
|
} else {
|
|
pointNr = pointTab[label] = 1;
|
|
}
|
|
|
|
labelID = label + padLeft(pointNr, 5-label.length, ".");
|
|
if (labelID in symTab) {
|
|
printError("DUPLICATE POINT LABEL -- IMPOSSIBLE: " + labelID);
|
|
} else {
|
|
symTab[labelID] = location;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function fetchPointLabel(label, forward) {
|
|
/* Accesses the location of the specified point label in the forward or
|
|
backward direction. Returns the location of that label, or -1 if the
|
|
label is currently undefined */
|
|
var labelID; // unique symbol for the point label instance
|
|
var pointNr; // current point number
|
|
|
|
if (label in pointTab) {
|
|
pointNr = pointTab[label];
|
|
} else {
|
|
pointNr = pointTab[label] = 0;
|
|
if (!forward) {
|
|
printError("BACKWARD REFERENCE TO UNDECLARED POINT LABEL: " + label);
|
|
}
|
|
}
|
|
|
|
if (forward) {
|
|
++pointNr;
|
|
}
|
|
|
|
labelID = label + padLeft(pointNr, 5-label.length, ".");
|
|
if (labelID in symTab) {
|
|
return symTab[labelID];
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function parseNumber(text, token, sign) {
|
|
/* Parses a decimal number up to 11 digits in length starting at the
|
|
current offset in "text". If the number is less than 11 digits long,
|
|
the sign is applied as the high-order digit; otherwise the sign is
|
|
ignored. Returns the decimal string in token.text and the binary value
|
|
as the function result */
|
|
var c; // current parse character
|
|
var length = text.length; // length of operand string
|
|
var raw = ""; // raw parsed token text
|
|
var x = token.offset; // current offset into operand string
|
|
|
|
while (x < length) {
|
|
c = text.charAt(x);
|
|
if (c >= "0" && c <= "9") {
|
|
++x;
|
|
raw += c;
|
|
} else {
|
|
break; // out of while loop
|
|
}
|
|
}
|
|
|
|
if (raw.length > 11) {
|
|
printError("NUMERIC LITERAL LONGER THAN 11 DIGITS: " + raw);
|
|
raw = raw.substring(raw.length-11);
|
|
} else if (raw.length < 11) {
|
|
raw = sign.toString() + padLeft(raw, 10, "0");
|
|
}
|
|
|
|
token.type = tokInteger;
|
|
token.newOffset = x;
|
|
token.text = raw;
|
|
return parseInt(raw, 10);
|
|
}
|
|
|
|
/**************************************/
|
|
function parseString(text, token, singleWord, continued) {
|
|
/* Parses a $-delimited string starting at the current offset in "text".
|
|
Returns the text of the string in token.text and an array of binary words
|
|
with the string translated to 220 character code as the function result.
|
|
The binary words will all have sign digits set to 2.
|
|
Complete strings less than multiple of five characters are padded to a
|
|
multiple of five with spaces (binary zeroes). If "singleWord" is true,
|
|
the string must be five or fewer characters in length.
|
|
Strings can be continued across card boundaries. The caller will indicate
|
|
this is a continuation of an existing string by setting "continued" to
|
|
true, and placing data for any prior partial word in token.text and
|
|
token.word */
|
|
var c; // current parse character
|
|
var code; // current 220 char code
|
|
var count = 0; // chars in current word
|
|
var doubleDollar = false; // $$-delimited string
|
|
var length = text.length; // length of operand text
|
|
var raw = ""; // raw (ASCII) parsed string text
|
|
var values = []; // words of 220 char codes
|
|
var word = 2; // current 220 word with sign of 2
|
|
var x = token.offset; // current parsing offset
|
|
|
|
//--------------------------------------
|
|
function appendCode(code) {
|
|
if (count >= 5) { // push out the full word
|
|
values.push(word);
|
|
word = 2;
|
|
count = 0;
|
|
}
|
|
|
|
word = word*100 + code;
|
|
++count;
|
|
}
|
|
|
|
//--------------------------------------
|
|
if (continued) {
|
|
raw = token.text;
|
|
count = raw.length;
|
|
word = token.word;
|
|
} else {
|
|
if (++x < length) { // bypass the initial "$"
|
|
c = text.charAt(x);
|
|
if (c == "$") {
|
|
++x; // bypass the second "$"
|
|
doubleDollar = true;
|
|
appendCode(16); // carriage return prints as $ on Cardatron
|
|
}
|
|
}
|
|
}
|
|
|
|
while (x < length) {
|
|
c = text.charAt(x);
|
|
if (c == "$") {
|
|
break;
|
|
} else {
|
|
++x;
|
|
raw += c;
|
|
code = c.charCodeAt(0);
|
|
appendCode((code < xlateANSI220.length ? xlateANSI220[code]
|
|
: (code == 0xA4 ? 4 : 0))); // the lozenge
|
|
}
|
|
}
|
|
|
|
if (x >= length) {
|
|
if (singleWord) {
|
|
printError("$-STRING NOT TERMINATED");
|
|
} else {
|
|
token.type = tokIncompleteString;
|
|
}
|
|
} else {
|
|
token.type = tokString; // string is complete, so...
|
|
++x; // bypass the terminating "$"
|
|
if (doubleDollar) {
|
|
if (x >= length) {
|
|
printError("$$-STRING NOT TERMINATED");
|
|
} else if (text.charAt(x) == "$") {
|
|
++x; // bypass the second terminating "$"
|
|
appendCode(16); // and append another carriage return
|
|
} else {
|
|
printError("$$-STRING NOT TERMINATED");
|
|
}
|
|
}
|
|
|
|
while (count < 5) { // pad out final word with spaces
|
|
appendCode(0);
|
|
}
|
|
|
|
values.push(word); // push out final word
|
|
if (singleWord && raw.length > 5) {
|
|
printError("STRING OCCUPIES MORE THAN ONE WORD");
|
|
}
|
|
}
|
|
|
|
token.newOffset = x;
|
|
token.text = raw;
|
|
token.word = word; // save any partial word for continuation
|
|
return values;
|
|
}
|
|
|
|
/**************************************/
|
|
function parseAddressPrimary(text, token, resolved) {
|
|
/* Parses the next address primary from "text" starting at "token.offset",
|
|
returning the parsed item in "token.text" and the updated parse offset
|
|
in "token.newOffset". "token.type" indicates the type */
|
|
var c; // current parse character
|
|
var length = text.length; // length of operand text
|
|
var raw = ""; // raw text of parsed token
|
|
var val = 0; // temp used with numeric pool literals
|
|
var x = token.offset; // current offset into operand string
|
|
|
|
if (x >= length) { // empty primary
|
|
token.type = tokEmpty;
|
|
token.newOffset = length;
|
|
token.text = "";
|
|
} else {
|
|
c = text.charAt(x);
|
|
switch (true) {
|
|
case (c == "*"): // parse current location counter
|
|
token.type = tokLocation;
|
|
token.text = c;
|
|
token.newOffset = x+1;
|
|
break;
|
|
|
|
case (c >= "0" && c <= "9"): // parse integer literal
|
|
val = parseNumber(text, token, 0);
|
|
token.type = tokInteger;
|
|
if (val > 9999) {
|
|
printError("INTEGER LITERAL LONGER THAN 4 DIGITS: " + token.text);
|
|
}
|
|
break;
|
|
|
|
case (c >= "A" && c <= "Z"): // regular or point label
|
|
raw = c;
|
|
while (++x < length) {
|
|
c = text.charAt(x);
|
|
if (c >= "A" && c <= "Z") {
|
|
raw += c;
|
|
} else if (c >= "0" && c <= "9") {
|
|
raw += c;
|
|
} else {
|
|
break; // out of while loop
|
|
}
|
|
}
|
|
|
|
if (raw.length > 5) {
|
|
printError("LABEL LONGER THAN 5 CHARACTERS: " + raw);
|
|
}
|
|
|
|
// Check if it's a point label (could be followed by an adding operator)
|
|
if (x >= length) {
|
|
token.type = tokLabel; // label is at the end, can't be a point label
|
|
} else if (c != "+" && c != "-") {
|
|
token.type = tokLabel; // not followed by +/-, not a point label
|
|
} else {
|
|
token.type = (c == "-" ? tokBackwardPoint : tokForwardPoint);
|
|
if (x+1 >= length) {
|
|
++x; // +/- is at the end, a point label
|
|
} else {
|
|
c = text.charAt(x+1);
|
|
if (c == " ") {
|
|
++x; // +/- followed by a space, a point label
|
|
} else if (c == ",") {
|
|
++x; // +/- followed by a comma, a point label
|
|
} else if (c == "+" || c == "-") {
|
|
++x; // +/- followed by a +/-, a point label
|
|
} else {
|
|
token.type = tokLabel; // not a point label
|
|
}
|
|
}
|
|
}
|
|
|
|
token.text = raw;
|
|
token.newOffset = x;
|
|
break;
|
|
|
|
case (c == "+"): // parse pool numeric literal
|
|
case (c == "-"):
|
|
raw = c;
|
|
if (++x >= length) {
|
|
printError("INVALID POOL LITERAL SYNTAX");
|
|
token.newOffset = x;
|
|
} else {
|
|
token.offset = x;
|
|
c = text.charAt(x);
|
|
if (c >= "0" && c <= "9") {
|
|
parseNumber(text, token, "");
|
|
if (token.text.length > 10) {
|
|
printError("POOL LITERAL LONGER THAN 10 DIGITS: " + token.text);
|
|
}
|
|
token.text = raw + padLeft(token.text, 10, "0");
|
|
} else if (c == " " || c == ",") {
|
|
printError("EMPTY POOL LITERAL");
|
|
} else {
|
|
val = evaluateAddress(text, token, resolved);
|
|
if (val >= 0) { // a resolved address
|
|
token.text = raw + padLeft(val, 10, "0");
|
|
} else { // unresolved, save address expression for end of pass
|
|
token.text = "." + raw + text.substring(x, token.newOffset);
|
|
}
|
|
}
|
|
}
|
|
|
|
token.type = tokLiteral;
|
|
break;
|
|
|
|
case (c == "$"): // parse pool string literal
|
|
parseString(text, token, true, false);
|
|
token.text = "$" + token.text.substring(0, 5);
|
|
token.type = tokLiteral;
|
|
break;
|
|
|
|
case (c == " "): // empty primary
|
|
token.type = tokEmpty;
|
|
token.newOffset = x;
|
|
token.text = "";
|
|
break;
|
|
|
|
default: // not valid
|
|
token.type = tokEmpty;
|
|
token.newOffset = x;
|
|
token.text = "";
|
|
printError("ADDRESS PRIMARY CANNOT START WITH \"" + c + "\"");
|
|
break;
|
|
} // switch true
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function evaluateAddress(text, token, resolved) {
|
|
/* Evaluates the current offset in "text" as an address expression and
|
|
returns its binary value. If "resolved" is true, the address must resolve
|
|
to a known address, otherwise an error is issued. If the address cannot
|
|
be resolved, returns -1.
|
|
Address expressions consist of a list of address primaries delimited
|
|
by + or - operators. See parseAddressPrimary() for a definition */
|
|
var address = 0; // result address
|
|
var label; // constructed point label ID
|
|
var length = text.length; // length of operand string
|
|
var adding = 1; // +/- operator
|
|
var unresolvable = false; // true if address cannot be resolved
|
|
var val; // value of primary
|
|
|
|
do {
|
|
parseAddressPrimary(text, token, resolved);
|
|
switch (token.type) {
|
|
case tokEmpty: // empty text
|
|
val = 0;
|
|
break;
|
|
case tokLocation: // *, current location counter
|
|
val = location;
|
|
break;
|
|
case tokInteger: // integer literal
|
|
val = parseInt(token.text, 10);
|
|
break;
|
|
case tokLabel: // regular label
|
|
if (token.text in symTab) {
|
|
val = symTab[token.text];
|
|
if (val < 0) {
|
|
unresolvable = true;
|
|
}
|
|
} else {
|
|
symTab[token.text] = val = -1;
|
|
unresolvable = true;
|
|
}
|
|
break;
|
|
case tokBackwardPoint: // backward point label
|
|
case tokForwardPoint: // forward point label
|
|
label = "*" + token.text;
|
|
val = fetchPointLabel(label, token.type == tokForwardPoint);
|
|
if (val < 0) {
|
|
val = -1;
|
|
unresolvable = true;
|
|
}
|
|
break;
|
|
case tokLiteral: // pool literal
|
|
if (token.text in poolTab) {
|
|
val = poolTab[token.text];
|
|
if (val < 0) {
|
|
unresolvable = true;
|
|
}
|
|
} else {
|
|
poolTab[token.text] = val = -1;
|
|
unresolvable = true;
|
|
}
|
|
break;
|
|
default:
|
|
printError("INVALID ADDRESS TOKEN TYPE CODE: " + token.type);
|
|
break;
|
|
} // switch token.type
|
|
|
|
if (resolved && unresolvable) {
|
|
printError("MUST BE A RESOLVED ADDRESS: " + token.text);
|
|
}
|
|
|
|
address += val*adding;
|
|
|
|
if (token.newOffset < length) {
|
|
switch (text.charAt(token.newOffset)) {
|
|
case "+":
|
|
adding = 1;
|
|
token.offset = ++token.newOffset;
|
|
break;
|
|
case "-":
|
|
adding = -1;
|
|
token.offset = ++token.newOffset;
|
|
break;
|
|
default:
|
|
length = -1; // exit do loop
|
|
break;
|
|
}
|
|
}
|
|
} while (token.newOffset < length);
|
|
|
|
return (unresolvable ? -1 : address);
|
|
}
|
|
|
|
/**************************************/
|
|
function evaluateOperand(text, token, resolved) {
|
|
/* Parses and evaluates the fields of a standard operand. An operand
|
|
consists of a comma-delimited list of address values. Each address value
|
|
is a sequence of address primaries delimited by + or - operators. The
|
|
operand is terminated by the first space character. If "resolved" is
|
|
true, all address values must resolve to defined addresses, otherwise
|
|
an error is produced and the resulting address value will be -1.
|
|
Returns an array of address values */
|
|
var c; // current parse character
|
|
var length = text.length; // length of operand string
|
|
var values = []; // array of operand field address values
|
|
|
|
if (text.charAt(token.offset) != " ") { // check for empty operand
|
|
while (token.offset < length) {
|
|
values.push(evaluateAddress(text, token, resolved));
|
|
token.offset = token.newOffset;
|
|
if (token.offset < length) {
|
|
c = text.charAt(token.offset);
|
|
if (c == ",") {
|
|
++token.offset; // continue with next field
|
|
} else if (c == " ") {
|
|
break; // out of while loop
|
|
} else {
|
|
++token.offset;
|
|
printError("INVALID OPERAND LIST: " + c);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//for (var x=0; x<values.length; ++x) {
|
|
// printLine(" DEBUG: " + padLeft(values[x], 11));
|
|
//}
|
|
|
|
return values;
|
|
}
|
|
|
|
/**************************************/
|
|
function parseConstantList(text, token, continued) {
|
|
/* Parses the operand text for the CNST pseudo-operator and returns an
|
|
array of binary words with the values found. This pseudo can continue
|
|
across multiple cards if the continuation cards have a blank opCode field.
|
|
The only time this is an issue is for continued strings, so the caller
|
|
must indicate this by setting "continued" to true. Only fully-parsed words
|
|
are returned in the result array. Any partial word of string text for an
|
|
unterminated string is left for the continuation call */
|
|
var c; // current parse character
|
|
var length = text.length; // length of operand string
|
|
var val2; // temp array of result words from parsing strings
|
|
var values = []; // result array of completely parsed words
|
|
var x; // scratch index
|
|
|
|
while (token.offset < length) { // parse comma-delimited values
|
|
c = text.charAt(token.offset);
|
|
if (continued || c == "$") {
|
|
val2 = parseString(text, token, false, continued);
|
|
for (x=0; x<val2.length; ++x) {
|
|
values.push(val2[x]);
|
|
}
|
|
} else if (c == "+") {
|
|
++token.offset;
|
|
values.push(parseNumber(text, token, 0));
|
|
} else if (c == "-") {
|
|
++token.offset;
|
|
values.push(parseNumber(text, token, 1));
|
|
} else if (c >= "0" && c <= "9") {
|
|
values.push(parseNumber(text, token, 0));
|
|
} else if (c == "," || c == " ") {
|
|
values.push(0);
|
|
token.newOffset = token.offset;
|
|
}
|
|
|
|
if (token.newOffset < length) {
|
|
c = text.charAt(token.newOffset);
|
|
if (c == ",") {
|
|
++token.newOffset; // continue with next value
|
|
} else if (c == " ") {
|
|
break; // out of while loop
|
|
} else {
|
|
printError("INVALID CONSTANT LIST: " + c);
|
|
break; // out of while loop
|
|
}
|
|
}
|
|
|
|
token.offset = token.newOffset;
|
|
} // while x
|
|
|
|
//for (x=0; x<values.length; ++x) {
|
|
// printLine(" DEBUG: " + padLeft(values[x], 11));
|
|
//}
|
|
|
|
return values;
|
|
}
|
|
|
|
/**************************************/
|
|
function emitWord(location, word) {
|
|
/* Outputs one word of assembled code to the object program */
|
|
|
|
asmCode[location] = word;
|
|
}
|
|
|
|
/**************************************/
|
|
function generateInstructionWord(opDesc, sign, values) {
|
|
/* Generates the word value for an executable instruction.
|
|
"opDesc" is the array of values for the op code from "opTab".
|
|
"values" is the list of address values parsed from the operand text.
|
|
Returns the binary value of the word */
|
|
var f; // current field value
|
|
var nsign = 0; // numeric sign value
|
|
var opTop = opDesc.length; // length of op code description array
|
|
var ox = 0; // opDesc[] index
|
|
var w1; // word field 1
|
|
var w2; // word field 2
|
|
var word = 0; // resultant binary word value
|
|
var vTop = values.length; // length of operand address values array
|
|
var vx = 0; // values[] index
|
|
|
|
if (!(sign in signValues)) {
|
|
printError("INVALID SIGN VALUE: " + sign);
|
|
} else {
|
|
nsign = signValues[sign];
|
|
}
|
|
|
|
if (opDesc[0] > 0) {
|
|
word = opDesc[0]*p10[4];
|
|
}
|
|
|
|
for (ox=1; ox<opDesc.length; ox+=2) {
|
|
if (vx < vTop) {
|
|
f = values[vx];
|
|
} else if (opDesc[ox+1] < 0) {
|
|
f = 0;
|
|
printError("OPERAND FIELD #" + (vx+1).toFixed(0) + " MISSING");
|
|
} else {
|
|
f = opDesc[ox+1];
|
|
}
|
|
|
|
switch (opDesc[ox]) {
|
|
case 1: // address field, inserted in (04)
|
|
word = putField(word, f, 4);
|
|
break;
|
|
case 2: // secondary value inserted in (33)
|
|
word = putField(word, f, 33);
|
|
break;
|
|
case 3: // secondary value inserted in (44)
|
|
word = putField(word, f, 44);
|
|
break;
|
|
case 4: // unit or count digit inserted in (11)
|
|
word = putField(word, f, 11);
|
|
break;
|
|
case 5: // variant digit inserted in (41)
|
|
word = putField(word, f, 41);
|
|
break;
|
|
case 6: // sL field designator inserted in (22); if specified, insert 1 in (31)
|
|
word = putField(word, f, 22);
|
|
if (vx < vTop) {
|
|
word = putField(word, 1, 31);
|
|
}
|
|
break;
|
|
case 7: // sL field designator inserted in (22); (31) is undisturbed
|
|
word = putField(word, f, 22);
|
|
break;
|
|
case 8: // value inserted in (32)
|
|
word = putField(word, f, 32);
|
|
break;
|
|
case 9: // value inserted in (42)
|
|
word = putField(word, f, 42);
|
|
break;
|
|
case 10: // value inserted in (21)
|
|
word = putField(word, f, 21);
|
|
break;
|
|
case 11: // value inserted in (62)
|
|
word = putField(word, f, 62);
|
|
break;
|
|
case 12: // value inserted in (64)
|
|
word = putField(word, f, 64);
|
|
break;
|
|
case 13: // BU pair for CRF/CWF: (B-1)*2 added to (41), U in (11)
|
|
w1 = f%10; // unit value for (11)
|
|
w2 = (f%100-w1)/10; // band value for (41)
|
|
word = putField(word, w1, 11);
|
|
if (w2 > 0) {
|
|
word = putField(word, getField(word, 41) + (w2-1)*2, 41);
|
|
}
|
|
break;
|
|
case 14: // reload-lockout value added to (41)
|
|
word = putField(word, getField(word, 41) + f%10, 41);
|
|
break;
|
|
case 15: // digit inserted in (11); if specified, insert 1 in (41)
|
|
word = putField(word, f, 11);
|
|
if (vx < vTop) {
|
|
word = putField(word, 1, 41);
|
|
}
|
|
break;
|
|
case 16: // format band digit added to (41): (B-1)*2
|
|
w1 = f%10;
|
|
if (w1 > 0) {
|
|
word = putField(word, getField(word, 41) + (w1-1)*2, 41);
|
|
}
|
|
break;
|
|
case 17: // BMOD value added to (41)
|
|
word = putField(word, getField(word, 41) + f%10, 41);
|
|
break;
|
|
case 18: // mag tape unit/lane as LLU, inserted as ULL in (33)
|
|
f = (f%10)*100 + (f%1000 - f%10)/10; // convert from LLU to ULL
|
|
word = putField(word, f, 33);
|
|
break;
|
|
case 19: // resolved address only in (04)
|
|
word = putField(word, f, 4);
|
|
break;
|
|
default:
|
|
printError("INVALID OPDESC INDEX: " + opDesc[ox]);
|
|
break;
|
|
} // switch opDesc[ox]
|
|
|
|
++vx;
|
|
} // for ox
|
|
|
|
if (vx < vTop) {
|
|
printError("EXTRANEOUS OPERAND FIELDS IGNORED: " + values[vx]);
|
|
}
|
|
|
|
if (sign != " ") {
|
|
word = nsign*p10[10] + word % p10[10];
|
|
}
|
|
|
|
return word;
|
|
}
|
|
|
|
/**************************************/
|
|
function generateFormatBand(operand) {
|
|
/* Generates the 29 words of a Cardatron format band from the operand
|
|
text and returns an array of 29 words with band data. Examples:
|
|
|
|
FBGR INPUT,16(P5A),P10Z
|
|
FBGR PRINT,49B,T5A,T1A1B2A4Z,T10N,T8Z1A,XB6Z2A,48B MONITOR
|
|
FBGR PRINT,32B,11(T5A),33B ERROR MESSAGE FORMAT BAND
|
|
FBGR INPUT,T2Z1B4A,15(T5A)
|
|
FBGR INPUT,16(P5A),P10Z
|
|
FBGR PRINT,49B,TZZZZZZNNNN,BBB,SBNNNNBNNBNNNN,BT5A,44B
|
|
FBGR PRINT,49B,TZZZZZZNNNN,BBB,SBNNNNBNNBZZZZ,5BT5A,44B
|
|
FBGR PRINT,49B,TZZZZZZNNNN,BBB,T6Z10BNNNN,50B
|
|
FBGR PRINT,7(T5A),85B
|
|
FBGR PRINT,TZZNNNNZZZZ,4B,16(T5A),32B
|
|
|
|
The first parameter can be "INPUT", "PUNCH", or "PRINT". The first two
|
|
values generate bands for 80 card columns; the third generates a band
|
|
for 120 print columns. The remaining comma-delimited parameters are
|
|
format phrases, one per 220 word. Each phrase may be prefixed by a
|
|
numeric repeat count. Allowable format codes are:
|
|
|
|
A: copy two zone/numeric digits (one character)
|
|
B: input: ignore two digits in memory
|
|
output: supply two zero zone/numeric digits on output
|
|
N: copy one digit to/from memory and a card column, normally
|
|
supplying a zero for the zone
|
|
P: input: store a zero for the sign digit
|
|
output: ignore sign digit in memory
|
|
S: input: same as P
|
|
output: copy zone digit as a separate column
|
|
T: input: like P, but store a 2 for the sign instead of zero
|
|
output: same as P
|
|
X: input: store zone digit in memory
|
|
output: copy a zone digit as an overpunch for next code
|
|
Z: input: store zero digit in memory
|
|
output: supply a zero zone/numeric digit to the device
|
|
(: start of repeating group
|
|
): end of repeating group
|
|
|
|
This generator works by expanding all repeat counts and groups to a linear
|
|
string of format codes, then walking that string backwards to generate the
|
|
Cardatron format band digits. If this is an input format, the result is
|
|
padded with 11 zero codes to push the final word into the 220's D register.
|
|
For both input and output bands, the result is padded with 3 (delete) codes
|
|
out to a total of 29 words. Note that the band words are returned in
|
|
reverse order, to match the right-to-left processing of card columns by the
|
|
Cardatron and the order in which the 220 fetched words from memory when
|
|
loading format bands. If all that sounds very confusing, it's why we have
|
|
format band generators */
|
|
var band = []; // generated format band words
|
|
var bandType; // band type: INPUT, PRINT, PUNCH
|
|
var codes = ""; // expanded format band phrases
|
|
var cx = 0; // current operand char offset
|
|
var dx = 0; // band word digit index
|
|
var oLen; // length of phrase text in operand
|
|
var word = 0; // generated band word
|
|
var wx = 0; // band word digit index (power of 10)
|
|
var x; // scratch index
|
|
|
|
//--------------------------------------
|
|
function emitBandDigits(digits, count) {
|
|
/* Moves digits into format band words starting at the low-order
|
|
digit of the word. Handles overflow to the next word */
|
|
var digit; // current output digit
|
|
|
|
while (count > 0) {
|
|
--count;
|
|
digit = digits%10;
|
|
digits = (digits-digit)/10;
|
|
if (wx < 11) {
|
|
word += digit*p10[wx];
|
|
++wx;
|
|
} else {
|
|
band.unshift(word);
|
|
word = digit;
|
|
wx = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------
|
|
function flushBandWord(digit) {
|
|
/* If a partial word exists, fill the remainder with "digit" values,
|
|
then push it to the band array */
|
|
|
|
while (wx < 11) {
|
|
emitBandDigits(digit, 1);
|
|
}
|
|
|
|
band.unshift(word);
|
|
wx = 0;
|
|
word = 0;
|
|
}
|
|
|
|
//--------------------------------------
|
|
function expandPhrase(cx, level) {
|
|
/* Expands repeat counts and groups to a string of individual phrase
|
|
codes in "codes"; adjusts phrase codes for X-code zone-digit behavior */
|
|
var c; // current phrase character
|
|
var count = 0; // repeat count
|
|
var done = false; // loop control
|
|
var overpunch = false; // X-code zone punch flag
|
|
var px = cx; // current phrease char offset
|
|
var tx; // temp offset for repeating groups
|
|
|
|
do {
|
|
if (px >= oLen) {
|
|
done = true;
|
|
} else {
|
|
c = operand.charAt(px);
|
|
++px;
|
|
switch (c) {
|
|
case "0":
|
|
case "1":
|
|
case "2":
|
|
case "3":
|
|
case "4":
|
|
case "5":
|
|
case "6":
|
|
case "7":
|
|
case "8":
|
|
case "9":
|
|
count = count*10 + (c.charCodeAt(0) - ("0").charCodeAt(0));
|
|
break;
|
|
case "A":
|
|
case "P":
|
|
case "T":
|
|
case "Z":
|
|
do {
|
|
--count;
|
|
codes += c;
|
|
} while (count > 0);
|
|
count = 0;
|
|
break;
|
|
case "B":
|
|
do {
|
|
--count;
|
|
if (overpunch) {
|
|
codes += "S"; // half column: standalone sign
|
|
overpunch = false;
|
|
} else {
|
|
codes += c;
|
|
}
|
|
} while (count > 0);
|
|
count = 0;
|
|
break;
|
|
case "N":
|
|
do {
|
|
--count;
|
|
if (overpunch) {
|
|
codes += "D"; // half column: overpunched sign
|
|
overpunch = false;
|
|
} else {
|
|
codes += c;
|
|
}
|
|
} while (count > 0);
|
|
count = 0;
|
|
break;
|
|
case "S":
|
|
codes += "XS"; // acts like XB
|
|
count = 0;
|
|
break;
|
|
case "X":
|
|
overpunch = true;
|
|
codes += c; // half-column: zone digit
|
|
count = 0;
|
|
break;
|
|
case "(":
|
|
do {
|
|
--count;
|
|
tx = expandPhrase(px, level+1);
|
|
} while (count > 0);
|
|
count = 0;
|
|
px = tx;
|
|
break;
|
|
case ")":
|
|
if (level < 1) {
|
|
printError("EXCESS RIGHT PARENTHESIS");
|
|
}
|
|
done = true;
|
|
break;
|
|
case ",":
|
|
if (level > 0) {
|
|
printError("COMMA IN NESTED PHRASE");
|
|
}
|
|
codes += c;
|
|
count = 0;
|
|
break;
|
|
default:
|
|
printError("INVALID FORMAT BAND CHARACTER: " + c);
|
|
break;
|
|
} // switch c
|
|
}
|
|
} while (!done);
|
|
|
|
return px;
|
|
}
|
|
|
|
//--------------------------------------
|
|
function buildInputBand(cols) {
|
|
/* Builds an input band from the expanded phrase code string
|
|
in "codes", placing the result in the "band" array */
|
|
var c; // current phrase code
|
|
var colCount = 0; // number of card columns
|
|
var cx; // current offset into codes
|
|
var dx = 0; // current memory word digit index
|
|
|
|
for (cx=codes.length-1; cx>=0; --cx) {
|
|
c = codes.charAt(cx);
|
|
switch (c) {
|
|
case "A": // copy alphanumeric column
|
|
if (dx & 1) {
|
|
printError("A CODE ON ODD DIGIT: " + cx);
|
|
} else if (dx > 9) {
|
|
printError("A CODE ON SIGN DIGIT: " + cx);
|
|
} else {
|
|
emitBandDigits(11, 2);
|
|
}
|
|
colCount += 1;
|
|
dx += 2;
|
|
break;
|
|
case "B": // ignore column
|
|
emitBandDigits(33, 2);
|
|
colCount += 1;
|
|
break;
|
|
case "D": // copy numeric half-column for overpunched sign
|
|
emitBandDigits(1, 1);
|
|
colCount +=1;
|
|
dx += 1;
|
|
break;
|
|
case "N": // copy numeric column
|
|
emitBandDigits(31, 2);
|
|
colCount += 1;
|
|
dx += 1;
|
|
break;
|
|
case "P": // insert zero in memory word for sign
|
|
if (dx < 10) {
|
|
printError("P CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(0, 1);
|
|
dx += 1;
|
|
break;
|
|
case "S": // delete numeric half-column for standalone sign
|
|
if (dx < 10) {
|
|
printError("P CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(3, 1);
|
|
colCount += 1;
|
|
break;
|
|
case "T": // insert 2 in memory word sign
|
|
if (dx < 10) {
|
|
printError("T CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(2, 1);
|
|
dx += 1;
|
|
break;
|
|
case "X": // copy zone half-column for sign
|
|
emitBandDigits(1, 1);
|
|
dx += 1;
|
|
break;
|
|
case "Z": // insert zero in memory word
|
|
emitBandDigits(0, 1);
|
|
dx += 1;
|
|
break;
|
|
case ",": // check that phrase/word boundaries match
|
|
if (dx > 0) {
|
|
printError("WRONG NUMBER OF DIGITS IN WORD: " + dx);
|
|
}
|
|
break;
|
|
default:
|
|
printError("INVALID INPUT BAND CODE: " + c);
|
|
break;
|
|
} // switch c
|
|
|
|
if (dx > 10) {
|
|
if (dx > 11) {
|
|
printError("EXCESS NUMBER OF DIGITS IN WORD: " + dx);
|
|
}
|
|
dx = 0;
|
|
}
|
|
} // for cx
|
|
|
|
if (dx > 0) {
|
|
printError("FINAL BAND WORD INCOMPLETE: " + dx);
|
|
}
|
|
|
|
if (colCount != cols) {
|
|
printError("WRONG NUMBER OF COLUMNS: " + colCount);
|
|
}
|
|
|
|
emitBandDigits(0, 11); // push out last word from Cardatron
|
|
flushBandWord(3); // pad out the last word with 3s
|
|
}
|
|
|
|
//--------------------------------------
|
|
function buildOutputBand(cols) {
|
|
/* Builds an output band from the expanded phrase code string
|
|
in "codes", placing the result in the "band" array */
|
|
var c; // current phrase code
|
|
var colCount = 0; // number of columns on card/line
|
|
var cx; // current offset into codes
|
|
var dx = 0; // current memory word digit index
|
|
|
|
for (cx=codes.length-1; cx>=0; --cx) {
|
|
c = codes.charAt(cx);
|
|
switch (c) {
|
|
case "A": // copy alphanumeric column
|
|
if (dx & 1) {
|
|
printError("A CODE ON ODD DIGIT: " + cx);
|
|
} else if (dx > 9) {
|
|
printError("A CODE ON SIGN DIGIT: " + cx);
|
|
} else {
|
|
emitBandDigits(11, 2);
|
|
}
|
|
colCount += 1;
|
|
dx += 2;
|
|
break;
|
|
case "B": // supply zeroes for blank column
|
|
emitBandDigits(0, 2);
|
|
colCount += 1;
|
|
break;
|
|
case "D": // copy numeric half-column for overpunched sign
|
|
emitBandDigits(1, 1);
|
|
colCount +=1;
|
|
dx += 1;
|
|
break;
|
|
case "N": // copy numeric column
|
|
emitBandDigits(2, 1);
|
|
colCount += 1;
|
|
dx += 1;
|
|
break;
|
|
case "P": // ignore sign digit in word
|
|
case "T":
|
|
if (dx < 10) {
|
|
printError("P,T CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(3, 1);
|
|
dx += 1;
|
|
break;
|
|
case "S": // supply numeric half-column for standalone sign
|
|
if (dx < 10) {
|
|
printError("P,T CODE NOT FOR SIGN DIGIT: " + cx);
|
|
}
|
|
emitBandDigits(0, 1);
|
|
colCount += 1;
|
|
break;
|
|
case "X": // copy zone digit for sign
|
|
emitBandDigits(1, 1);
|
|
dx += 1;
|
|
break;
|
|
case "Z": // ignore digit in memory word
|
|
emitBandDigits(3, 1);
|
|
dx += 1;
|
|
break;
|
|
case ",": // check that phrase/word boundaries match
|
|
if (dx > 0) {
|
|
printError("WRONG NUMBER OF DIGITS IN WORD: " + dx);
|
|
}
|
|
break;
|
|
default:
|
|
printError("INVALID OUTPUT BAND CODE: " + c);
|
|
break;
|
|
} // switch c
|
|
|
|
if (dx > 10) {
|
|
if (dx > 11) {
|
|
printError("EXCESS NUMBER OF DIGITS IN WORD: " + dx);
|
|
}
|
|
dx = 0;
|
|
}
|
|
} // for cx
|
|
|
|
if (dx > 0) {
|
|
printError("FINAL BAND WORD INCOMPLETE: " + dx);
|
|
}
|
|
|
|
if (colCount != cols) {
|
|
printError("WRONG NUMBER OF COLUMNS: " + colCount);
|
|
}
|
|
|
|
flushBandWord(3); // pad out the last word with 3s
|
|
}
|
|
|
|
//-------------------------------------- start of generateFormatBand()
|
|
x = operand.indexOf(" ");
|
|
oLen = (x < 0 ? operand.length : x);
|
|
|
|
x = operand.indexOf(",");
|
|
bandType = (x < 0 ? "" : operand.substring(0,x));
|
|
|
|
cx = x+1;
|
|
while (cx < oLen) {
|
|
cx = expandPhrase(cx, 0);
|
|
}
|
|
|
|
//printLine(codes); // DEBUG
|
|
|
|
switch (bandType) {
|
|
case "INPUT":
|
|
buildInputBand(80);
|
|
break;
|
|
case "PUNCH":
|
|
buildOutputBand(80);
|
|
break;
|
|
case "PRINT":
|
|
buildOutputBand(120);
|
|
break;
|
|
default:
|
|
printError("INVALID FORMAT BAND TYPE: " + bandType);
|
|
break;
|
|
}
|
|
|
|
while (band.length < 29) {
|
|
band.unshift(33333333333);
|
|
}
|
|
|
|
return band;
|
|
}
|
|
|
|
/**************************************/
|
|
function startPass1() {
|
|
/* Reads the control cards and then passes control to Pass 1 of the assembler */
|
|
var done = false; // loop control
|
|
var opCode; // op code field of current card
|
|
|
|
do {
|
|
readACard();
|
|
if (cardData.atEOF) {
|
|
done = true;
|
|
printError("EOF ENCOUNTERED BEFORE PASS 1");
|
|
} else {
|
|
opCode = cardData.text.substring(opCodeIndex, opCodeIndex+5);
|
|
switch (opCode) {
|
|
case "ASMBL":
|
|
case "HEAD ":
|
|
case "REORD":
|
|
printLine(padRight("", 8+5+4+3+6) +
|
|
cardData.text.substring(opCodeIndex, operandIndex+operandLength).trim());
|
|
break;
|
|
default:
|
|
done = true;
|
|
if (pass1List) {
|
|
printLine("START PASS 1");
|
|
printLine("");
|
|
}
|
|
|
|
setTimeout(assemblePass1, 100);
|
|
break;
|
|
}
|
|
}
|
|
} while (!done);
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Pass 1 *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function printPass1(seq, location, label, opCode, sign, operand) {
|
|
/* Prints a line of output from Pass 1 of the assembly to the text panel */
|
|
var text; // formatted line image
|
|
|
|
if (pass1List) {
|
|
if (opCode === null) {
|
|
text = padRight(" ", 8+5+4+3, " ");
|
|
} else {
|
|
text = padLeft(seq, 8, " ") + " " + padLeft(location, 4, "0") + " ";
|
|
}
|
|
|
|
text += padRight(label, 6) + padRight(opCode || " ", 4) +
|
|
padRight(sign, 2, " ") + rTrim(operand);
|
|
printLine(text);
|
|
if (errorTank.length > 0) {
|
|
dumpErrorTank();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function buildPoolPass1() {
|
|
/* Builds the initial literal pool from symbols encountered in Pass 1 */
|
|
var keys; // addresses for pool words
|
|
var label; // pool entry ID
|
|
var text; // scratch string
|
|
var x; // scratch index
|
|
var xref = {}; // hash used to build address keys for sorting
|
|
|
|
// First, extract all the viable pool entries for sorting
|
|
for (label in poolTab) {
|
|
if (label.charAt(0) != ".") {
|
|
text = label;
|
|
} else {
|
|
token.offset = 1;
|
|
poolTab[label] = location;
|
|
parseAddressPrimary(label, token, true);
|
|
text = token.text;
|
|
delete poolTab[label];
|
|
}
|
|
|
|
if (text in symTab) {
|
|
poolTab[text] = symTab[text];
|
|
} else {
|
|
poolTab[text] = location;
|
|
symTab[text] = location;
|
|
++location;
|
|
}
|
|
|
|
xref[padLeft(symTab[text], 4, "0")] = text;
|
|
} // for label
|
|
|
|
// Now extract the address keys and sort them.
|
|
keys = Object.keys(xref).sort();
|
|
|
|
// Finally, build the pool in address sequence from the sorted keys
|
|
for (x=0; x<keys.length; ++x) {
|
|
label = xref[keys[x]];
|
|
location = symTab[label];
|
|
printPass1("", symTab[label], "", "", "", label);
|
|
} // for x
|
|
}
|
|
|
|
/**************************************/
|
|
function assemblePass1() {
|
|
/* Processes card images for Pass 1 of the assembler. Enters with the
|
|
first card in "cardData. Every "sprintCount" cards, this routine reschedules
|
|
itself after a short delay and then exits. This allows the text panel to be
|
|
updated in the browser window */
|
|
var card; // current card to be assembled
|
|
var continuedString = false; // true if processing a string split across cards
|
|
var done = false; // loop control
|
|
var finito = false; // true if the FINI pseudo-op has been sensed
|
|
var label; // label symbol from the card
|
|
var opCode; // op code field from the card
|
|
var opDesc; // array of op code description words from opTab{}
|
|
var operand; // operand field from the card
|
|
var origLoc; // original location for this line
|
|
var seq; // sequence field from the card
|
|
var sign; // sign-override field from the card
|
|
var sprintCount = 0; // counter for periodic refreshing of the text panel
|
|
var text; // scratch string
|
|
var thisLoc; // current location for this line (changed by DEFN)
|
|
var values; // array of address or word values from parsing operands
|
|
var x; // scratch index
|
|
|
|
do {
|
|
card = cardData.text;
|
|
label = card.substring(labelIndex, labelIndex+5).trim();
|
|
opCode = card.substring(opCodeIndex, opCodeIndex+4).trim();
|
|
operand = card.substring(operandIndex, operandIndex+operandLength); // no trim
|
|
seq = card.substring(72, 80);
|
|
sign = card.substring(opCodeIndex+4, opCodeIndex+5);
|
|
origLoc = thisLoc = location;
|
|
|
|
if (opCode == "REM") {
|
|
printPass1(seq, location, label, null, sign, operand);
|
|
readACard();
|
|
} else {
|
|
token.offset = 0;
|
|
if (opCode.length <= 0) { // treat like a CNST pseudo-op
|
|
opDesc = opTab["CNST"];
|
|
} else if (!(opCode in opTab)) {// treat like a NOP
|
|
printError("INVALID OP CODE");
|
|
opDesc = opTab["NOP"];
|
|
} else {
|
|
opDesc = opTab[opCode];
|
|
}
|
|
|
|
if (opDesc[0] >= 0) { // normal instruction
|
|
values = evaluateOperand(operand, token, false); // discard result, take only side effects
|
|
++location; // normal instructions bump the location counter
|
|
readACard();
|
|
} else {
|
|
// Parse the pseudo-op for size and location
|
|
switch (opDesc[0]) {
|
|
case pseudoDEFN:
|
|
values = evaluateOperand(operand, token, false);
|
|
if (values.length > 0) {
|
|
thisLoc = values[0];
|
|
} else {
|
|
printError("OPERAND ADDRESS REQUIRED");
|
|
}
|
|
readACard();
|
|
break;
|
|
case pseudoLOCN:
|
|
values = evaluateOperand(operand, token, true);
|
|
if (values.length < 1) {
|
|
printError("OPERAND ADDRESS REQUIRED");
|
|
} else if (values[0] >= 0) {
|
|
location = values[0];
|
|
}
|
|
readACard();
|
|
break;
|
|
case pseudoCNST:
|
|
values = parseConstantList(operand, token, continuedString);
|
|
location += values.length;
|
|
continuedString = (token.type == tokIncompleteString);
|
|
readACard();
|
|
if (continuedString) {
|
|
if (cardData.atEOF) {
|
|
printError("$-STRING NOT TERMINATED AT EOF");
|
|
} else {
|
|
text = cardData.text.substring(opCodeIndex, opCodeIndex+4).trim();
|
|
if (text.length > 0) {
|
|
printError("$-STRING NOT TERMINATED");
|
|
} else { // extract the partial word of the incomplete string
|
|
x = token.text.length % 5;
|
|
token.text = token.text.substring(token.text.length - x);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case pseudoF244:
|
|
case pseudoF424:
|
|
values = evaluateOperand(operand, token, false); // discard result, take only side effects
|
|
++location; // word-builders merely bump the location counter
|
|
readACard();
|
|
break;
|
|
case pseudoFBGR:
|
|
location += 29; // all format bands are 29 words long
|
|
readACard();
|
|
break;
|
|
case pseudoFINI:
|
|
done = finito = true;
|
|
values = evaluateOperand(operand, token, true);
|
|
if (values.length > 0) {
|
|
startAddress = values[0];
|
|
}
|
|
break;
|
|
default:
|
|
printError("INVALID PSEUDO INSTRUCTION CODE: " + opDesc[0]);
|
|
} // switch
|
|
}
|
|
|
|
if (label.length > 0) { // enter the label into the symbol table
|
|
if (label.charAt(0) == "*") {
|
|
declarePointLabel(label, thisLoc);
|
|
} else if (!(label in symTab)) {
|
|
symTab[label] = thisLoc;
|
|
} else if (symTab[label] >= 0) {
|
|
printError("DUPLICATE LABEL DEFINITION");
|
|
} else {
|
|
symTab[label] = thisLoc;
|
|
}
|
|
}
|
|
|
|
printPass1(seq, origLoc, label, opCode, sign, operand);
|
|
}
|
|
|
|
if (cardData.atEOF) {
|
|
done = true;
|
|
if (!finito) {
|
|
finito = true;
|
|
printError("EOF encountered before FINI in Pass 1");
|
|
}
|
|
} else if (++sprintCount > sprintLimit && !continuedString) {
|
|
done = true;
|
|
}
|
|
} while (!done);
|
|
|
|
if (!finito) {
|
|
Promise.resolve().then(assemblePass1);
|
|
} else {
|
|
// Wrap up Pass 1 and check for undefined symbols.
|
|
// Oddly, undefined symbols appear to have been implicitly defined at the end,
|
|
// so build a table of them and assign locations.
|
|
for (label in symTab) {
|
|
if (symTab[label] < 0) {
|
|
autoSymTab[label] = location;
|
|
symTab[label] = location;
|
|
printPass1("", location, label, "", "", "");
|
|
++location;
|
|
}
|
|
}
|
|
|
|
buildPoolPass1();
|
|
dumpErrorTank();
|
|
if (pass1List && !pass2List) {
|
|
dumpSymbolTable();
|
|
}
|
|
|
|
if (pass1List || errorCount > 0) {
|
|
printLine("");
|
|
printLine("END PASS 1, ERRORS = " + errorCount);
|
|
}
|
|
|
|
if (errorCount == 0) { // advance to Pass 2
|
|
setTimeout(initializePass2, 100);
|
|
} else {
|
|
$$("TextDiv").removeChild($$("Spinner"));
|
|
// And... we're done at this point -- just exit the script
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Pass 2 *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function initializePass2() {
|
|
/* Reinitializes the assembler to reread the input file for Pass 2 */
|
|
var e; // point label ID
|
|
|
|
errorCount = 0;
|
|
errorTank = [];
|
|
location = 0;
|
|
bufferOffset = 0; // reset the card buffer position
|
|
cardData.atEOF = false;
|
|
cardData.serial = 0;
|
|
for (e in pointTab) { // reset the point label table
|
|
pointTab[e] = 0;
|
|
}
|
|
|
|
startPass2();
|
|
}
|
|
|
|
/**************************************/
|
|
function startPass2() {
|
|
/* Sets up for Pass 2 of the assembly and initiates it */
|
|
var done = false; // loop control
|
|
var opCode = ""; // op code parsed from card
|
|
|
|
do {
|
|
readACard();
|
|
if (cardData.atEOF) {
|
|
done = true;
|
|
printError("EOF encountered before Pass 2");
|
|
} else {
|
|
opCode = cardData.text.substring(opCodeIndex, opCodeIndex+5);
|
|
switch (opCode) {
|
|
case "ASMBL":
|
|
case "HEAD ":
|
|
case "REORD":
|
|
break;
|
|
default:
|
|
done = true;
|
|
if (pass2List) {
|
|
if (pass1List) {
|
|
printLine("\f"); // output a form-feed
|
|
}
|
|
|
|
printLine("START PASS 2");
|
|
printLine("");
|
|
}
|
|
|
|
setTimeout(assemblePass2, 100);
|
|
break;
|
|
}
|
|
}
|
|
} while (!done);
|
|
}
|
|
|
|
/**************************************/
|
|
function printPass2(seq, serial, location, word, label, opCode, sign, operand) {
|
|
/* Prints a line of output from Pass 2 of the assembly to the text panel */
|
|
var addr; // address (04) field of instruction word
|
|
var op; // op code (62) field of instruction word
|
|
var text; // scratch string
|
|
var variant; // variant (44) field of instruction word
|
|
var w; // scratch variable for word field partitioning
|
|
var wordText; // instruction word as text "9 9999 99 9999"
|
|
|
|
if (pass2List) {
|
|
if (opCode === null) {
|
|
text = padRight(" ", 8+6+6+4+16+3, " ");
|
|
} else {
|
|
if (word === null) {
|
|
wordText = padRight(" ", 16, " ");
|
|
} else {
|
|
w = word;
|
|
addr = w % 10000;
|
|
w = (w-addr)/10000;
|
|
op = w % 100;
|
|
w = (w-op)/100;
|
|
variant = w % 10000;
|
|
w = (w-variant)/10000; // should be just the sign digit left
|
|
wordText = padLeft(w, 3, " ") + " " + padLeft(variant, 4, "0") + " " +
|
|
padLeft(op, 2, "0") + " " + padLeft(addr, 4, "0");
|
|
}
|
|
|
|
text = padLeft(seq, 8, " ") + padLeft(serial || " ", 6, " ") +
|
|
" " + padLeft(location, 4, "0") + wordText + " ";
|
|
}
|
|
|
|
text += padRight(label, 6) + padRight(opCode || " ", 4) +
|
|
padRight(sign, 2, " ") + rTrim(operand);
|
|
printLine(text);
|
|
if (errorTank.length > 0) {
|
|
dumpErrorTank();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function buildPoolPass2() {
|
|
/* Builds the final literal pool */
|
|
var keys; // addresses for pool words
|
|
var label; // pool entry ID
|
|
var text; // scratch string
|
|
var values; // parseConstantList() result
|
|
var x; // scratch index
|
|
var xref = {}; // hash used to build address keys for sorting
|
|
|
|
// First, extract all the viable pool entries for sorting
|
|
for (label in poolTab) {
|
|
if (!(label in symTab)) {
|
|
printError("POOL LITERAL NOT IN SYMBOL TABLE: " + label);
|
|
} else {
|
|
location = symTab[label];
|
|
if (location < 0) {
|
|
printError("POOL LITERAL LOCATION NOT ASSIGNED: " + label);
|
|
} else {
|
|
text = padLeft(location, 4, "0");
|
|
xref[text] = label;
|
|
}
|
|
}
|
|
} // for label
|
|
|
|
// Now extract the address keys and sort them.
|
|
keys = Object.keys(xref).sort();
|
|
|
|
// Finally, build the pool in address sequence from the sorted keys
|
|
for (x=0; x<keys.length; ++x) {
|
|
label = xref[keys[x]];
|
|
location = symTab[label];
|
|
if (label.charAt(0) == "$") {
|
|
text = label + "$"
|
|
} else {
|
|
text = label;
|
|
}
|
|
|
|
token.offset = 0;
|
|
values = parseConstantList(text, token, false);
|
|
printPass2("", null, location, values[0], "", "", "", label);
|
|
emitWord(location, values[0]);
|
|
} // for x
|
|
}
|
|
|
|
/**************************************/
|
|
function assemblePass2() {
|
|
/* Processes card images for Pass 2 of the assembler. Enters with the
|
|
first card in "cardData. Every "sprintCount" cards, this routine reschedules
|
|
itself after a short delay and then exits. This allows the text panel to be
|
|
updated in the browser window */
|
|
var card; // current card to be assembled
|
|
var continuedString = false; // true if processing a string split across cards
|
|
var done = false; // loop control
|
|
var finito = false; // true if the FINI pseudo-op has been sensed
|
|
var label; // label symbol from the card
|
|
var opCode; // op code field from the card
|
|
var opDesc; // array of op code description words from opTab{}
|
|
var operand; // operand field from the card
|
|
var origLoc; // original location for this line
|
|
var seq; // sequence field from the card
|
|
var serial; // card serial number for printPass2()
|
|
var sign; // sign-override field from the card
|
|
var sprintCount = 0; // counter for periodic refreshing of the text panel
|
|
var text; // scratch string
|
|
var thisLoc; // current location for this line (changed by DEFN)
|
|
var values; // array of address or word values from parsing operands
|
|
var word; // assembled instruction word
|
|
var x; // scratch index
|
|
|
|
do {
|
|
card = cardData.text;
|
|
label = card.substring(labelIndex, labelIndex+5).trim();
|
|
opCode = card.substring(opCodeIndex, opCodeIndex+4).trim();
|
|
operand = card.substring(operandIndex, operandIndex+operandLength);
|
|
seq = card.substring(72, 80);
|
|
serial = cardData.serial;
|
|
sign = card.substring(opCodeIndex+4, opCodeIndex+5);
|
|
origLoc = thisLoc = location;
|
|
|
|
if (opCode == "REM") {
|
|
printPass2(seq, serial, location, word, label, null, sign, operand);
|
|
readACard();
|
|
} else {
|
|
token.offset = 0;
|
|
if (opCode.length <= 0) { // treat line a CNST pseudo-op
|
|
opDesc = opTab["CNST"];
|
|
} else if (!(opCode in opTab)) {// treat like a NOP
|
|
printError("INVALID OP CODE");
|
|
opDesc = opTab["NOP"];
|
|
} else {
|
|
opDesc = opTab[opCode];
|
|
}
|
|
|
|
if (opDesc[0] >= 0) { // normal instruction
|
|
values = evaluateOperand(operand, token, true);
|
|
word = generateInstructionWord(opDesc, sign, values);
|
|
printPass2(seq, serial, origLoc, word, label, opCode, sign, operand);
|
|
emitWord(location, word);
|
|
++location; // normal instructions bump the location counter
|
|
readACard();
|
|
} else {
|
|
// Parse the pseudo-op
|
|
switch (opDesc[0]) {
|
|
case pseudoDEFN:
|
|
values = evaluateOperand(operand, token, true);
|
|
if (values.length > 0) {
|
|
thisLoc = values[0];
|
|
} else {
|
|
printError("OPERAND ADDRESS REQUIRED");
|
|
}
|
|
printPass2(seq, serial, origLoc, null, label, opCode, sign, operand);
|
|
readACard();
|
|
break;
|
|
case pseudoLOCN:
|
|
values = evaluateOperand(operand, token, true);
|
|
if (values.length < 1) {
|
|
printError("OPERAND ADDRESS REQUIRED");
|
|
} else if (values[0] >= 0) {
|
|
location = values[0];
|
|
}
|
|
printPass2(seq, serial, origLoc, null, label, opCode, sign, operand);
|
|
readACard();
|
|
break;
|
|
case pseudoCNST:
|
|
values = parseConstantList(operand, token, continuedString);
|
|
printPass2(seq, serial, origLoc, values[0], label, opCode, sign, operand);
|
|
emitWord(location, values[0]);
|
|
++location;
|
|
for (x=1; x<values.length; ++x) {
|
|
printPass2("", null, origLoc+x, values[x], "", "", "", "", "");
|
|
emitWord(location, values[x]);
|
|
++location;
|
|
}
|
|
continuedString = (token.type == tokIncompleteString);
|
|
readACard();
|
|
if (continuedString) {
|
|
if (cardData.atEOF) {
|
|
printError("$-STRING NOT TERMINATED AT EOF");
|
|
} else {
|
|
text = cardData.text.substring(opCodeIndex, opCodeIndex+4).trim();
|
|
if (text.length > 0) {
|
|
printError("$-STRING NOT TERMINATED");
|
|
} else { // extract the partial word of the incomplete string
|
|
x = token.text.length % 5;
|
|
token.text = token.text.substring(token.text.length - x);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case pseudoF244:
|
|
case pseudoF424:
|
|
values = evaluateOperand(operand, token, true);
|
|
word = generateInstructionWord(opDesc, sign, values);
|
|
printPass2(seq, serial, origLoc, word, label, opCode, sign, operand);
|
|
emitWord(location, word);
|
|
++location;
|
|
readACard();
|
|
break;
|
|
case pseudoFBGR:
|
|
values = generateFormatBand(operand);
|
|
printPass2(seq, serial, origLoc, values[0], label, opCode, sign, operand);
|
|
emitWord(location, values[0]);
|
|
++location;
|
|
for (x=1; x<values.length; ++x) {
|
|
printPass2("", null, origLoc+x, values[x], "", "", "", "");
|
|
emitWord(location, values[x]);
|
|
++location;
|
|
}
|
|
readACard();
|
|
break;
|
|
case pseudoFINI:
|
|
done = finito = true;
|
|
values = evaluateOperand(operand, token, true);
|
|
printPass2(seq, serial, origLoc, null, label, opCode, sign, operand);
|
|
if (values.length > 0) {
|
|
startAddress = values[0];
|
|
}
|
|
break;
|
|
default:
|
|
printError("INVALID PSEUDO INSTRUCTION CODE: " + opDesc[0]);
|
|
readACard();
|
|
} // switch
|
|
}
|
|
|
|
if (label.length > 0) { // increment any point label counter
|
|
if (label.charAt(0) == "*") {
|
|
if (label in pointTab) {
|
|
++pointTab[label];
|
|
} else {
|
|
pointTab[label] = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cardData.atEOF) {
|
|
done = true;
|
|
if (!finito) {
|
|
finito = true;
|
|
printError("EOF encountered before FINI in Pass 2");
|
|
}
|
|
} else if (++sprintCount > sprintLimit && !continuedString) {
|
|
done = true;
|
|
}
|
|
} while (!done);
|
|
|
|
if (!finito) {
|
|
Promise.resolve().then(assemblePass2);
|
|
} else {
|
|
// dump the auto-defined symbols
|
|
for (label in autoSymTab) {
|
|
printPass2("", null, autoSymTab[label], 0, label, "", "", "");
|
|
}
|
|
|
|
buildPoolPass2();
|
|
|
|
// Wrap up Pass 2, check again for undefined symbols (shouldn't be any)
|
|
for (text in symTab) {
|
|
if (symTab[text] < 0) {
|
|
printError("SYMBOL NOT DEFINED: " + text);
|
|
}
|
|
}
|
|
|
|
dumpErrorTank();
|
|
if (pass2List) {
|
|
dumpSymbolTable();
|
|
}
|
|
|
|
buffer = ""; // release the card buffer area
|
|
if (pass2List || errorCount > 0) {
|
|
printLine("");
|
|
printLine("END PASS 2, ERRORS = " + errorCount);
|
|
}
|
|
|
|
setTimeout(finishAssembly, 100);
|
|
}
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Object Code Output *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function generateFormat6LoadableDeck(asmCode, outputChecksum, startAddress) {
|
|
/* Formats the assembled object code as a standard Cardatron format-6
|
|
loadable deck in a temporary window. From there it can be save, copied,
|
|
etc. */
|
|
var crdSkeleton = ""; // skeleton card-read instruction
|
|
var doc = null; // temp window document object
|
|
var deck = null; // temp window text area
|
|
var pval = p10[10]; // modulus for a word's absolute value
|
|
var psign = pval*2; // modulus for a word's sign bit
|
|
var title = "220 BAC-Assembler Band-6 Loadable Deck";
|
|
var win = window.open("../../webUI/B220FramePaper.html", "BAC-Asm-Object",
|
|
"scrollbars,resizable,width=600,height=500");
|
|
|
|
function checksum(cksum, word) {
|
|
var sw = word%psign; // will be the signed algebraic value of word
|
|
|
|
if (sw >= pval) { // check if word has sign bit=1
|
|
sw = pval - sw; // if so, make it algebraically negative
|
|
}
|
|
|
|
return (cksum+sw)%pval; // compute the algebraic checksum
|
|
}
|
|
|
|
function writeCard(card, addr) {
|
|
|
|
deck.appendChild(doc.createTextNode("666" +
|
|
padLeft(crdSkeleton + padLeft(addr, 4, "0") + card, 77, " ") + "\n"));
|
|
}
|
|
|
|
function generateDeck(ev) {
|
|
var addr = 0; // assembled code address
|
|
var card = ""; // text line for an 80-column card image
|
|
var cksum = 0; // algebraic value of the checksum word
|
|
var count = 0; // number of words on card
|
|
var gapCount = 0; // number of consecutive undefined words
|
|
var priorAddr = 0; // CRD address for prior card image
|
|
var priorCard = ""; // prior image, awaiting terminating CRD instruction
|
|
var word = undefined; // object code word
|
|
var x = 0; // scratch index
|
|
|
|
win.removeEventListener("load", generateDeck, false);
|
|
doc = win.document;
|
|
doc.title = title;
|
|
deck = doc.getElementById("Paper");
|
|
|
|
while (addr < asmCode.length && asmCode[addr] === undefined) {
|
|
++addr;
|
|
}
|
|
|
|
while (addr < asmCode.length) {
|
|
word = asmCode[addr];
|
|
if (word === undefined) {
|
|
++gapCount;
|
|
} else {
|
|
if (count > 5 || gapCount > 0) {
|
|
writeCard(priorCard, priorAddr);
|
|
priorCard = card;
|
|
card = "";
|
|
gapCount = 0;
|
|
count = 0;
|
|
}
|
|
|
|
priorAddr = addr;
|
|
card += padLeft(word, 11, "0");
|
|
++count;
|
|
if (outputChecksum) {
|
|
cksum = checksum(cksum, word);
|
|
}
|
|
}
|
|
|
|
++addr;
|
|
} // while addr
|
|
|
|
writeCard(priorCard, priorAddr);
|
|
if (outputChecksum) {
|
|
// Compute and output a card with the 220 negative checksum at the next address
|
|
writeCard(card, priorAddr+1);
|
|
card = crdSkeleton + "9999";
|
|
if (cksum < 0) {
|
|
card += padLeft(-cksum, 11, "0");
|
|
} else {
|
|
card += padLeft(cksum+pval, 11, "0");
|
|
}
|
|
|
|
card = "***CHECKSUM FOR " + sourceName + " " + card;
|
|
}
|
|
|
|
writeCard(card, 9999);
|
|
if (startAddress >= 0) {
|
|
writeCard("6000030" + padLeft(startAddress, 4, "0"), 9999); // BUN to start
|
|
} else {
|
|
writeCard("69999009999", 9999); // HLT 9999
|
|
}
|
|
}
|
|
|
|
crdSkeleton = "6" + padLeft(cardReaderUnit, 1, "0") + "00060";
|
|
win.addEventListener("load", generateDeck, false);
|
|
win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
|
|
}
|
|
|
|
/**************************************/
|
|
function generateMachineLanguageDeck(asmCode, outputChecksum, startAddress) {
|
|
/* Formats the assembled object code as a BALGOL Machine-Language deck
|
|
in a temporary window. From there it can be save, copied, etc. */
|
|
var doc = null; // temp window document object
|
|
var deck = null; // temp window text area
|
|
var pval = p10[10]; // modulus for a word's absolute value
|
|
var psign = pval*2; // modulus for a word's sign bit
|
|
var title = "220 BAC-Assembler Machine-Language Deck";
|
|
var win = window.open("../../webUI/B220FramePaper.html", "BAC-Asm-Object",
|
|
"scrollbars,resizable,width=600,height=500");
|
|
|
|
function checksum(cksum, word) {
|
|
var sw = word%psign; // will be the signed algebraic value of word
|
|
|
|
if (sw >= pval) { // check if word has sign bit=1
|
|
sw = pval - sw; // if so, make it algebraically negative
|
|
}
|
|
|
|
return (cksum+sw)%pval; // compute the algebraic checksum
|
|
}
|
|
|
|
function writeCard(card, count, addr, seq) {
|
|
|
|
deck.appendChild(doc.createTextNode(
|
|
"60" + padLeft(count, 1, "0") + padLeft(seq, 7, "0") +
|
|
padLeft(addr, 4, "0") + card + "\n"));
|
|
}
|
|
|
|
function generateDeck(ev) {
|
|
var addr = 0; // assembled code address
|
|
var card = ""; // text line for an 80-column card image
|
|
var cardAddr = 0; // first address of words on card
|
|
var cksum = 0; // algebraic value of the checksum word
|
|
var count = 0; // number of words on card
|
|
var gapCount = 0; // number of consecutive undefined words
|
|
var seq = 10; // card sequence number
|
|
var word = undefined; // object code word
|
|
var x = 0; // scratch index
|
|
|
|
win.removeEventListener("load", generateDeck, false);
|
|
doc = win.document;
|
|
doc.title = title;
|
|
deck = doc.getElementById("Paper");
|
|
|
|
while (addr < asmCode.length) {
|
|
word = asmCode[addr];
|
|
if (word === undefined) {
|
|
++gapCount;
|
|
} else {
|
|
if (count > 5 || gapCount > 0) {
|
|
writeCard(card, count, cardAddr, seq);
|
|
card = "";
|
|
cardAddr = addr;
|
|
gapCount = 0;
|
|
count = 0;
|
|
seq += 10;
|
|
}
|
|
|
|
card += padLeft(word, 11, "0");
|
|
++count;
|
|
if (outputChecksum) {
|
|
cksum = checksum(cksum, word);
|
|
}
|
|
}
|
|
|
|
++addr;
|
|
} // while addr
|
|
|
|
writeCard(card, count, cardAddr, seq);
|
|
if (outputChecksum) {
|
|
// Compute and output a card with the 220 negative checksum at the next address
|
|
if (cksum < 0) {
|
|
card = padLeft(-cksum, 11, "0");
|
|
} else {
|
|
card = padLeft(cksum+pval, 11, "0");
|
|
}
|
|
|
|
writeCard(card + " ***CHECKSUM FOR " + sourceName, 1, addr, 9999990);
|
|
}
|
|
}
|
|
|
|
win.addEventListener("load", generateDeck, false);
|
|
win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
|
|
}
|
|
|
|
/**************************************/
|
|
function generateObjectTape(asmCode, outputChecksum, startAddress) {
|
|
/* Formats the assembled object code as a 220 tape image in a temporary
|
|
window. From there it can be save, copied, etc. */
|
|
var doc = null; // temp window document object
|
|
var pval = p10[10]; // modulus for a word's absolute value
|
|
var psign = pval*2; // modulus for a word's sign bit
|
|
var tape = null; // temp window text area
|
|
var title = "220 BAC-Assembler Object Tape";
|
|
var win = window.open("../../webUI/B220FramePaper.html", "BAC-Asm-Object",
|
|
"scrollbars,resizable,width=500,height=500");
|
|
|
|
function checksum(cksum, word) {
|
|
var sw = word%psign; // will be the signed algebraic value of word
|
|
|
|
if (sw >= pval) { // check if word has sign bit=1
|
|
sw = pval - sw; // if so, make it algebraically negative
|
|
}
|
|
|
|
return (cksum+sw)%pval; // compute the algebraic checksum
|
|
}
|
|
|
|
function generateTape(ev) {
|
|
var addr = 0; // assembled code address
|
|
var block = ""; // text line for a 100-word tape block
|
|
var blockPrefix = "0,100"; // lane and prefix word count for block
|
|
var count = 0; // count of words in block
|
|
var cksum = 0; // algebraic value of the checksum word
|
|
var length = asmCode.length;// number of words to output
|
|
var word = 0; // object code word
|
|
var x = 0; // scratch index
|
|
|
|
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"));
|
|
while (addr < 10000) {
|
|
addr += 100;
|
|
tape.appendChild(doc.createTextNode("0,100,0\n"));
|
|
}
|
|
|
|
tape.appendChild(doc.createTextNode("0,1,1")); // EOT control word for lane 0
|
|
tape.appendChild(doc.createTextNode("1,1,1")); // EOT control word for lane 1
|
|
}
|
|
|
|
win.addEventListener("load", generateTape, false);
|
|
win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
* Initialization and Termination *
|
|
*******************************************************************/
|
|
|
|
/**************************************/
|
|
function finishAssembly() {
|
|
/* Finishes the assembly of a program unit, and if appropriate, starts
|
|
assembly of the next one */
|
|
var oms = $$("OutputModeSelect");
|
|
var x = oms.selectedIndex;
|
|
|
|
if (errorCount == 0 && x >= 0) {
|
|
switch(oms.options[x].value) {
|
|
case "L":
|
|
generateFormat6LoadableDeck(asmCode, outputChecksum, startAddress);
|
|
break;
|
|
case "M":
|
|
generateMachineLanguageDeck(asmCode, outputChecksum, startAddress);
|
|
break;
|
|
case "T":
|
|
generateObjectTape(asmCode, outputChecksum, startAddress);
|
|
break;
|
|
}// oms.options
|
|
}
|
|
|
|
$$("CRFileSelector").value = null; // reset the <input> elements
|
|
$$("CRPoolSelector").value = null; // so they can be reused
|
|
poolSets.length = 1; // discard any poolSets that were loaded
|
|
$$("TextDiv").removeChild($$("Spinner")); // remove the spinner image
|
|
// And... we're done -- just fall out of the script.
|
|
}
|
|
|
|
/**************************************/
|
|
function initializeLiteralPool(unitNr) {
|
|
/* Initializes the literal pool prior to assembly. If data for a pool
|
|
has been pre-loaded for this program, then initialize the pool with
|
|
that location and data; otherwise initialize it to the default in
|
|
poolSets[0] */
|
|
var thisSet = poolSets[unitNr] || poolSets[0];
|
|
var x = 0; // scratch index
|
|
|
|
poolLocation = thisSet.poolLoc;
|
|
for (x=0; x<thisSet.poolData.length; ++x) {
|
|
symTab[thisSet.poolData[x]] = poolLocation+x;
|
|
} // for x
|
|
}
|
|
|
|
/**************************************/
|
|
function assembleFile() {
|
|
/* Initializes or reinitializes the assembler for a new file */
|
|
var stamp = new Date();
|
|
|
|
clearPanel();
|
|
printLine("Assembler for the Burroughs 220 BALGOL Compiler & Library -- " +
|
|
stamp.getFullYear().toString() + "-" +
|
|
padLeft(stamp.getMonth()+1, 2, "0") + "-" +
|
|
padLeft(stamp.getDate(), 2, "0") + " " +
|
|
padLeft(stamp.getHours(), 2, "0") + ":" +
|
|
padLeft(stamp.getMinutes(), 2, "0"));
|
|
printLine("");
|
|
printLine("Source File: " + sourceName);
|
|
|
|
errorCount = 0;
|
|
errorTank = [];
|
|
location = 0;
|
|
cardData.atEOF = false;
|
|
cardData.serial = 0;
|
|
asmCode = [];
|
|
autoSymTab = {};
|
|
pointTab = {};
|
|
poolTab = {};
|
|
startAddress = -1;
|
|
symTab = {};
|
|
symTab["RLO"] = 1; // kludge for CRD reload-lockout bit
|
|
symTab["BMOD"] = 8; // kludge for MRD/MNC B-modify address bit
|
|
initializeLiteralPool(1);
|
|
|
|
startPass1();
|
|
}
|
|
|
|
/**************************************/
|
|
function loadPoolsets(ev) {
|
|
/* Handle the <input type=file> onchange event when a file for a
|
|
pre-loaded literal pool set is selected */
|
|
var f = ev.target.files[0];
|
|
var reader = new FileReader();
|
|
|
|
function poolLoader_onLoad(ev) {
|
|
/* Handles the onload event for a readAsText FileReader */
|
|
var sets = null;
|
|
var thisSet = null;
|
|
var unitNr = 0;
|
|
var x = 0;
|
|
|
|
poolSets.length = 1; // remove any old entries
|
|
try {
|
|
sets = JSON.parse(ev.target.result);
|
|
if (!(typeof sets == "object" && "poolSet" in sets)) {
|
|
printError("POOL DATA MUST BE AN OBJECT AND CONTAIN A \"POOLSET\" PROPERTY");
|
|
} else if (!(sets.poolSet instanceof Array)) {
|
|
printError("POOL DATA \"POOLSET\" PROPERTY MUST BE AN ARRAY");
|
|
} else {
|
|
sets = sets.poolSet;
|
|
for (x=0; x<sets.length; ++x) {
|
|
thisSet = sets[x];
|
|
unitNr = x+1;
|
|
if (thisSet === null) {
|
|
poolSets[unitNr] = null; // allow an empty poolSet entry
|
|
} else if (typeof thisSet != "object") {
|
|
printError("POOLSET FOR PROGRAM UNIT #" + unitNr + " MUST BE AN OBJECT");
|
|
} else if (!("poolLoc" in thisSet && typeof thisSet["poolLoc"] == "number")) {
|
|
printError("POOLSET FOR PROGRAM UNIT #" + unitNr + " MUST HAVE NUMERIC \"POOLLOC\" PROPERTY");
|
|
} else if (!("poolData" in thisSet && thisSet["poolData"] instanceof Array)) {
|
|
printError("POOLSET FOR PROGRAM UNIT #" + unitNr + " MUST HAVE ARRAY \"POOLDATA\" PROPERTY");
|
|
} else {
|
|
poolSets[unitNr] = thisSet;
|
|
}
|
|
} // for x
|
|
}
|
|
} catch (e) {
|
|
printError("ERROR PARSING PRE-LOADED POOL DATA JSON:");
|
|
printError(e.toString());
|
|
}
|
|
|
|
if (errorCount > 0) {
|
|
dumpErrorTank();
|
|
errorCount = 0;
|
|
$$("CRPoolSelector").value = null;
|
|
}
|
|
}
|
|
|
|
poolSetName = f.name;
|
|
/********************
|
|
alert("Pool Data file selected: " + f.name +
|
|
"\nModified " + f.lastModifiedDate +
|
|
"\nType=" + f.type + ", Size=" + f.size + " octets");
|
|
********************/
|
|
|
|
reader.onload = poolLoader_onLoad;
|
|
reader.readAsText(f);
|
|
}
|
|
|
|
/**************************************/
|
|
function loadSourceFile(ev) {
|
|
/* Handle the <input type=file> onchange event when a file is selected */
|
|
var e; // spinner image DOM element
|
|
var f = ev.target.files[0];
|
|
var reader = new FileReader();
|
|
|
|
function fileLoader_onLoad(ev) {
|
|
/* Handles the onload event for a readAsText FileReader */
|
|
|
|
buffer = ev.target.result;
|
|
bufferOffset = 0;
|
|
bufferLength = buffer.length;
|
|
setTimeout(assembleFile, 100);
|
|
}
|
|
|
|
sourceName = f.name;
|
|
/********************
|
|
alert("File selected: " + f.name +
|
|
"\nModified " + f.lastModifiedDate +
|
|
"\nType=" + f.type + ", Size=" + f.size + " octets");
|
|
********************/
|
|
|
|
// initiate the spinner to run while compiling
|
|
e = document.createElement("img");
|
|
e.src = "../../webUI/resources/ajax-spinner.gif";
|
|
e.id = "Spinner";
|
|
$$("TextDiv").appendChild(e);
|
|
|
|
reader.onload = fileLoader_onLoad;
|
|
reader.readAsText(f);
|
|
}
|
|
|
|
/**************************************/
|
|
function checkBrowser() {
|
|
/* Checks whether this browser can support the necessary stuff */
|
|
var missing = "";
|
|
|
|
if (!window.File ) {missing += ", File"}
|
|
if (!window.FileReader ) {missing += ", FileReader"}
|
|
if (!window.FileList ) {missing += ", FileList"}
|
|
if (!window.DOMTokenList) {missing += ", DOMTokenList"}
|
|
if (!window.ArrayBuffer ) {missing += ", ArrayBuffer"}
|
|
if (!window.DataView ) {missing += ", DataView"}
|
|
if (!window.Promise ) {missing += ", Promise"}
|
|
|
|
if (missing.length == 0) {
|
|
return false;
|
|
} else {
|
|
alert("No can do... your browser does not\n" +
|
|
"support the following features:\n" + missing.substring(2));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/******************** Start of window.onload() ********************/
|
|
if (checkBrowser()) {
|
|
return;
|
|
}
|
|
|
|
$$("CRPoolSelector").value = null; // clear any prior pool selection
|
|
$$("CRPoolSelector").addEventListener("change", loadPoolsets, false);
|
|
$$("CRFileSelector").value = null; // clear any prior file selection
|
|
$$("CRFileSelector").addEventListener("change", loadSourceFile, false);
|
|
|
|
pass1List = $$("Pass1ListCheck").checked;
|
|
$$("Pass1ListCheck").addEventListener("click", function(ev) {
|
|
pass1List = ev.target.checked;
|
|
});
|
|
|
|
pass2List = $$("Pass2ListCheck").checked;
|
|
$$("Pass2ListCheck").addEventListener("click", function(ev) {
|
|
pass2List = ev.target.checked;
|
|
});
|
|
|
|
outputChecksum = $$("ChecksumCheck").checked;
|
|
$$("ChecksumCheck").addEventListener("click", function(ev) {
|
|
outputChecksum = ev.target.checked;
|
|
});
|
|
|
|
$$("SelectListing").addEventListener("click", function(ev) {
|
|
window.getSelection().selectAllChildren($$("TextPanel"));
|
|
});
|
|
}, false);
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|