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