mirror of
https://github.com/pkimpel/retro-220.git
synced 2026-02-15 04:06:34 +00:00
1. Remove emulated card reader UI and related timing delays.
2. Implement CNST string operands spanning card images.
3. Correct parsing of address pool constants and building of constant pools.
4. Correct parsing of empty operands.
5. Rearrange portions of the code, eliminate old stats-gathering prototype code.
Minor corrections to BALGOL-Main transcription.
Corrections and additional transcription for BALGOL-Overlay.
2185 lines
83 KiB
HTML
2185 lines
83 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}
|
|
|
|
#CRFileSelector {
|
|
position: absolute;
|
|
top: 8px;
|
|
left: 8px;
|
|
width: 580px;
|
|
border: 1px solid white}
|
|
|
|
#TextPanel {
|
|
position: absolute;
|
|
top: 80px;
|
|
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: auto;
|
|
right: auto;
|
|
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=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 panel = $$("TextPanel");
|
|
|
|
// 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 constant
|
|
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 tokConstant = 6; // pool constant, 1-11 digit number with a leading + or -)
|
|
var tokIncompleteString = 7; // unterminated $-delimited string constant (probably continued on next card)
|
|
var tokString = 8; // $-delimited string constant
|
|
|
|
// token structure for operand parsing
|
|
var token = {
|
|
type: tokEmpty,
|
|
offset: 0,
|
|
text: "",
|
|
word: 0,
|
|
newOffset: -1};
|
|
|
|
// Assembly storage
|
|
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 constant table: holds address of the constant 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, 0, 24, 4, 14, 0, 23, 20, 3, 21, // 20-2F
|
|
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 0, 13, 0, 0, 0, 0, // 30-3F
|
|
34, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, // 40-4F
|
|
57, 58, 59, 62, 63, 64, 65, 66, 67, 68, 69, 0, 0, 0, 0, 0, // 50-5F
|
|
0, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, // 60-6F
|
|
57, 58, 59, 62, 63, 64, 65, 66, 67, 68, 69, 0, 0, 0, 0, 0]; // 70-7F
|
|
|
|
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 overlayed by
|
|
* other fields in the instruction. If this value is negative, it indicates a
|
|
* pseudo-operator or other special handling.
|
|
*
|
|
* The remainder of the array consists of number pairs, one for each comma-
|
|
* delimited operand field that may be present. The first number is a code
|
|
* indicating the type of field, the second is the default value for that
|
|
* field if it is not specified (or is empty) in the operand area of the
|
|
* instruction. If the default value is negative, that operand is not optional
|
|
* and must be specified.
|
|
*
|
|
* Type codes:
|
|
* 1 = address field, inserted in (04)
|
|
* 2 = secondary value inserted in (33)
|
|
* 3 = secondary value inserted in (44)
|
|
* 4 = unit or count digit inserted in (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)
|
|
* 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 = {
|
|
"CAD": [ 10, 1, -1, 2, 0],
|
|
"CAA": [110, 1, -1, 2, 0],
|
|
"ADD": [ 12, 1, -1, 2, 0],
|
|
"ADA": [112, 1, -1, 2, 0],
|
|
"ADL": [ 10, 1, -1, 3, 0],
|
|
"CSU": [ 11, 1, -1, 2, 0],
|
|
"CSA": [111, 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],
|
|
"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],
|
|
"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],
|
|
"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],
|
|
"STA": [ 40, 1, -1, 6, 0],
|
|
"STR": [140, 1, -1, 6, 0],
|
|
"STB": [240, 1, -1, 6, 0],
|
|
"STP": [ 44, 1, -1, 3, 0],
|
|
"RTF": [ 29, 1, -1, 8, -1],
|
|
"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],
|
|
"EXT": [ 17, 1, -1, 3, 0],
|
|
"CFA": [ 18, 1, -1, 6, 0],
|
|
"CFR": [118, 1, -1, 2, 0],
|
|
"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],
|
|
"HLT": [ 0, 1, 0, 3, 0],
|
|
"NOP": [ 1, 1, 0, 3, 0],
|
|
"IBB": [ 20, 1, -1, 3, -1],
|
|
"DBB": [ 21, 1, -1, 3, -1],
|
|
"IFL": [ 26, 1, -1, 7, -1, 9, -1],
|
|
"DFL": [ 27, 1, -1, 7, -1, 9, -1],
|
|
"DLB": [ 28, 1, -1, 7, -1, 9, -1],
|
|
"MTS": [ 50, 1, -1, 4, -1, 8, -1],
|
|
"MFS": [4000050,
|
|
1, -1, 4, -1, 8, 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],
|
|
"MIR": [ 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],
|
|
"MLS": [450, 4, -1, 8, 0, 1, 0],
|
|
"MRW": [850, 4, -1, 8, 0, 1, 0],
|
|
"MDA": [950, 4, -1, 8, -1, 1, 0],
|
|
"MIB": [ 59, 1, -1, 4, -1, 8, 0],
|
|
"MIE": [159, 1, -1, 4, -1, 8, 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],
|
|
"CRD": [ 60, 1, -1, 4, -1, 5, 0, 8, 0],
|
|
"CWR": [ 61, 1, -1, 4, -1, 13, -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],
|
|
"KAD": [ 8, 1, 0, 3, 0],
|
|
"SPO": [ 9, 1, -1, 8, -1, 15, 0],
|
|
|
|
// Pseudo-ops
|
|
"DEFN": [pseudoDEFN, // define symbol
|
|
19, -1],
|
|
"LOCN": [pseudoLOCN, // set location counter
|
|
19, -1],
|
|
"CNST": [pseudoCNST], // assemble list of constant 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 constant 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 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;
|
|
var card;
|
|
var cardLength;
|
|
var line;
|
|
var match;
|
|
|
|
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) {
|
|
line = card = card.substring(0, 80);
|
|
} else {
|
|
line = card;
|
|
while (card.length <= 70) {
|
|
card += " ";
|
|
}
|
|
while (card.length < 80) {
|
|
card += " ";
|
|
}
|
|
}
|
|
|
|
cardData.length = bx - bufferOffset;
|
|
++cardData.serial;
|
|
cardData.text = card;
|
|
bufferOffset = bx;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function fileLoader_onLoad(ev) {
|
|
/* Handle the onload event for a Text 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;
|
|
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 */
|
|
var x;
|
|
|
|
for (x=0; x<errorTank.length; ++x) {
|
|
printLine(errorTank[x]);
|
|
}
|
|
|
|
printLine("");
|
|
errorTank = [];
|
|
}
|
|
|
|
/**************************************/
|
|
function printError(msg) {
|
|
++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;
|
|
var pointNr;
|
|
|
|
if (label in pointTab) {
|
|
pointNr = ++pointTab[label];
|
|
} else {
|
|
pointNr = pointTab[label] = 1;
|
|
}
|
|
|
|
labelID = label + padLeft(pointNr, 5-label.length, ".");
|
|
if (labelID in symTab) {
|
|
printError("DUPLICATE POINT LABEL -- IMPOSSIBLE")
|
|
} else {
|
|
symTab[labelID] = location;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function fetchPointLabel(label, forward) {
|
|
/* Accesses the location of the specified point label in the forward or
|
|
backward direction. Returns the location of that label, or -1 if the
|
|
label is currently undefined */
|
|
var labelID;
|
|
var pointNr;
|
|
|
|
if (label in pointTab) {
|
|
pointNr = pointTab[label];
|
|
} else {
|
|
pointNr = pointTab[label] = 0;
|
|
if (!forward) {
|
|
printError("BACKWARD REFERENCE TO UNDECLARED POINT LABEL: " + label);
|
|
}
|
|
}
|
|
|
|
if (forward) {
|
|
++pointNr;
|
|
}
|
|
|
|
labelID = label + padLeft(pointNr, 5-label.length, ".");
|
|
if (labelID in symTab) {
|
|
return symTab[labelID];
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function parseNumber(text, token, sign) {
|
|
/* Parses a decimal number up to 11 digits in length starting at the
|
|
current offset in "text". If the number is less than 11 digits long,
|
|
the sign is applied as the high-order digit. Returns the decimal string
|
|
in token.text and the binary value as the function result */
|
|
var c;
|
|
var length = text.length;
|
|
var raw = "";
|
|
var x = token.offset;
|
|
|
|
while (x < length) {
|
|
c = text.charAt(x);
|
|
if (c >= "0" && c <= "9") {
|
|
++x;
|
|
raw += c;
|
|
} else {
|
|
break; // out of while loop
|
|
}
|
|
}
|
|
|
|
if (raw.length > 11) {
|
|
printError("NUMERIC CONSTANT LONGER THAN 11 DIGITS: " + raw);
|
|
raw = raw.substring(raw.length-11);
|
|
} else if (raw.length < 11) {
|
|
raw = sign.toString() + padLeft(raw, 10, "0");
|
|
}
|
|
|
|
token.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 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;// don't know what this means yet
|
|
appendCode(16); // carriage return
|
|
}
|
|
}
|
|
}
|
|
|
|
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); // 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;
|
|
var length = text.length;
|
|
var raw = "";
|
|
var val = 0;
|
|
var x = token.offset;
|
|
|
|
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 constant
|
|
val = parseNumber(text, token, 0);
|
|
token.type = tokInteger;
|
|
if (val > 9999) {
|
|
printError("INTEGER CONSTANT LONGER THAN 4 DIGITS: " + token.text);
|
|
}
|
|
break;
|
|
|
|
case (c >= "A" && c <= "Z"): // regular or point label
|
|
raw = c;
|
|
while (++x < length) {
|
|
c = text.charAt(x);
|
|
if (c >= "A" && c <= "Z") {
|
|
raw += c;
|
|
} else if (c >= "0" && c <= "9") {
|
|
raw += c;
|
|
} else {
|
|
break; // out of while loop
|
|
}
|
|
}
|
|
|
|
if (raw.length > 5) {
|
|
printError("LABEL LONGER THAN 5 CHARACTERS: " + raw);
|
|
}
|
|
|
|
// Check if it's a point label (could be followed by an adding operator)
|
|
if (x >= length) {
|
|
token.type = 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 constant
|
|
case (c == "-"):
|
|
raw = c;
|
|
if (++x >= length) {
|
|
printError("INVALID POOL CONSTANT SYNTAX");
|
|
token.newOffset = x;
|
|
} else {
|
|
token.offset = x;
|
|
c = text.charAt(x);
|
|
if (c >= "0" && c <= "9") {
|
|
parseNumber(text, token, "");
|
|
if (token.text.length > 10) {
|
|
printError("POOL CONSTANT LONGER THAN 10 DIGITS: " + token.text);
|
|
}
|
|
token.text = raw + padLeft(token.text, 10, "0");
|
|
} else if (c == " " || c == ",") {
|
|
printError("EMPTY POOL CONSTANT");
|
|
} else {
|
|
val = evaluateAddress(text, token, resolved);
|
|
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 = tokConstant;
|
|
break;
|
|
|
|
case (c == "$"): // parse pool string constant
|
|
parseString(text, token, true, false);
|
|
token.text = "$" + token.text.substring(0, 5);
|
|
token.type = tokConstant;
|
|
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;
|
|
var length = text.length; // length of operand text
|
|
var adding = 1; // +/- operator
|
|
var unresolvable = false; // true if address cannot be resolved
|
|
var val; // value of primary
|
|
|
|
do {
|
|
parseAddressPrimary(text, token, resolved);
|
|
switch (token.type) {
|
|
case tokEmpty: // empty text
|
|
val = 0;
|
|
break;
|
|
case tokLocation: // *, current location counter
|
|
val = location;
|
|
break;
|
|
case tokInteger: // integer constant
|
|
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 == 5);
|
|
if (val < 0) {
|
|
val = -1;
|
|
unresolvable = true;
|
|
}
|
|
break;
|
|
case tokConstant: // pool constant
|
|
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;
|
|
var length = text.length;
|
|
var 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
|
|
will indicate this by setting "continued" to true */
|
|
var c;
|
|
var length = text.length;
|
|
var opCode;
|
|
var val2;
|
|
var values = [];
|
|
var x;
|
|
|
|
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;
|
|
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;
|
|
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;
|
|
}
|
|
|
|
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[8] - 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[7] - 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 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
|
|
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
|
|
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
|
|
*/
|
|
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
|
|
}
|
|
|
|
//--------------------------------------
|
|
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 assembleFile() {
|
|
/* Initializes or reinitializes the assembler for a new file */
|
|
|
|
errorCount = 0;
|
|
errorTank = [];
|
|
location = 0;
|
|
cardData.atEOF = false;
|
|
cardData.serial = 0;
|
|
pointTab = {};
|
|
poolTab = {};
|
|
symTab = {};
|
|
symTab["RLO"] = 1; // kludge for CWR reload-lockout field
|
|
symTab["BMOD"] = 1; // kludge for MTR B-modify address field
|
|
|
|
clearPanel();
|
|
startPass1();
|
|
|
|
if (errorCount == 0) {
|
|
initializePass2();
|
|
}
|
|
|
|
$$("TextDiv").removeChild($$("Spinner"));
|
|
}
|
|
|
|
/**************************************/
|
|
function startPass1() {
|
|
/* Reads the control cards and then passes control to Pass 1 of the assembler */
|
|
var done = false;
|
|
var opCode;
|
|
|
|
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":
|
|
break;
|
|
case "REORD":
|
|
break;
|
|
default:
|
|
done = true;
|
|
assemblePass1();
|
|
break;
|
|
}
|
|
}
|
|
} while (!done);
|
|
}
|
|
|
|
/**************************************/
|
|
function printPass1(seq, location, label, opCode, sign, operand) {
|
|
/* Prints a line of output from Pass 1 of the assembly */
|
|
var text;
|
|
|
|
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, " ") + operand;
|
|
printLine(text);
|
|
if (errorTank.length > 0) {
|
|
dumpErrorTank();
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function buildPoolPass1() {
|
|
/* Builds the initial constant 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 */
|
|
var card;
|
|
var continuedString = false;
|
|
var done = false;
|
|
var finito = false;
|
|
var label;
|
|
var opCode;
|
|
var opDesc;
|
|
var operand;
|
|
var origLoc;
|
|
var seq;
|
|
var sign;
|
|
var text;
|
|
var thisLoc;
|
|
var values;
|
|
var x;
|
|
|
|
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
|
|
origLoc = location;
|
|
seq = card.substring(72, 80);
|
|
sign = card.substring(opCodeIndex+4, opCodeIndex+5);
|
|
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;
|
|
}
|
|
} while (!done);
|
|
|
|
if (!finito) {
|
|
printError("EOF encountered before FINI in Pass 1");
|
|
} else {
|
|
buildPoolPass1();
|
|
|
|
// Wrap up Pass 1, check for undefined symbols
|
|
for (text in symTab) {
|
|
if (symTab[text] < 0) {
|
|
printError("SYMBOL NOT DEFINED: " + text);
|
|
}
|
|
}
|
|
|
|
dumpErrorTank();
|
|
printLine("END OF PASS 1, ERRORS = " + errorCount);
|
|
printLine("");
|
|
printLine("");
|
|
printLine("SYMBOL TABLE");
|
|
text = "";
|
|
thisLoc = 0;
|
|
values = Object.keys(symTab).sort();
|
|
for (x=0; x<values.length; ++x) {
|
|
if (thisLoc > 80) {
|
|
printLine(text);
|
|
text = "";
|
|
thisLoc = 0;
|
|
}
|
|
|
|
text += padLeft(symTab[values[x]], thisLoc-text.length+5) + " " + values[x];
|
|
thisLoc += 20;
|
|
}
|
|
|
|
printLine(text);
|
|
printLine("");
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function initializePass2() {
|
|
/* Reinitializes the assembler to reread the input file for Pass 2 */
|
|
var e;
|
|
|
|
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;
|
|
var opCode;
|
|
|
|
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;
|
|
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 */
|
|
var addr;
|
|
var op;
|
|
var text;
|
|
var variant;
|
|
var w;
|
|
var wordText;
|
|
|
|
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, " ") + operand;
|
|
printLine(text);
|
|
if (errorTank.length > 0) {
|
|
dumpErrorTank();
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function buildPoolPass2() {
|
|
/* Builds the final constant pool */
|
|
var keys;
|
|
var label;
|
|
var text;
|
|
var values;
|
|
var x;
|
|
var xref = {};
|
|
|
|
// First, extract all the viable pool entries for sorting
|
|
for (label in poolTab) {
|
|
if (!(label in symTab)) {
|
|
printError("POOL CONSTANT NOT IN SYMBOL TABLE: " + label);
|
|
} else {
|
|
location = symTab[label];
|
|
if (location < 0) {
|
|
printError("POOL CONSTANT 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() {
|
|
/* Callback function for the card reader. Processes the card image for
|
|
pass 1 of the assembler */
|
|
var card;
|
|
var continuedString = false;
|
|
var done = false;
|
|
var finito = false;
|
|
var label;
|
|
var opCode;
|
|
var opDesc;
|
|
var operand;
|
|
var origLoc;
|
|
var seq;
|
|
var serial;
|
|
var sign;
|
|
var text;
|
|
var thisLoc;
|
|
var values;
|
|
var word;
|
|
var x;
|
|
|
|
do {
|
|
card = cardData.text;
|
|
label = card.substring(labelIndex, labelIndex+5).trim();
|
|
opCode = card.substring(opCodeIndex, opCodeIndex+4).trim();
|
|
operand = card.substring(operandIndex, operandIndex+operandLength);
|
|
origLoc = location;
|
|
seq = card.substring(72, 80);
|
|
serial = cardData.serial;
|
|
sign = card.substring(opCodeIndex+4, opCodeIndex+5);
|
|
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;
|
|
}
|
|
} while (!done);
|
|
|
|
if (!finito) {
|
|
printError("EOF encountered before FINI in Pass 2");
|
|
} else {
|
|
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);
|
|
}
|
|
}
|
|
|
|
printLine("");
|
|
printLine("END OF PASS 2, ERRORS = " + errorCount);
|
|
buffer = ""; // release the card buffer area
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function checkBrowser() {
|
|
/* Checks whether this browser can support the necessary stuff */
|
|
var missing = "";
|
|
|
|
if (!window.File) {missing += ", File"}
|
|
if (!window.FileReader) {missing += ", FileReader"}
|
|
if (!window.FileList) {missing += ", FileList"}
|
|
if (!window.DOMTokenList) {missing += ", DOMTokenList"}
|
|
if (!window.ArrayBuffer) {missing += ", ArrayBuffer"}
|
|
if (!window.DataView) {missing += ", DataView"}
|
|
if (!(window.performance && "now" in performance)) {missing += ", performance.now"}
|
|
|
|
if (missing.length == 0) {
|
|
return false;
|
|
} else {
|
|
alert("No can do... your browser does not support the following features:\n" + missing.substring(2));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/******************** Start of window.onload() ********************/
|
|
if (checkBrowser()) {
|
|
return;
|
|
}
|
|
|
|
$$("CRFileSelector").value = null; // clear any prior file selection
|
|
$$("CRFileSelector").addEventListener("change", fileSelector_onChange, false);
|
|
}, false);
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|