mirror of
https://github.com/pkimpel/retro-220.git
synced 2026-04-29 13:13:45 +00:00
Commit BALGOL-FLOAT.baca intrinsic function transcription. Commit corrections to BALGOL-Main.baca discovered during debugging of BAC-Assembler. Commit tools/BAC-Xscript-Reformatter.wsf script to extract card decks from transcription files. Commit BAC-Assembler corrections and enhancements. Commit minor additions to B220ControlConsole.
2278 lines
83 KiB
HTML
2278 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}
|
|
|
|
BUTTON.greenButton {
|
|
background-color: #060;
|
|
color: white;
|
|
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
|
font-size: 9pt;
|
|
font-weight: bold;
|
|
width: 60px;
|
|
height: 40px;
|
|
border: 1px solid #DDD;
|
|
border-radius: 4px}
|
|
|
|
BUTTON.whiteButton {
|
|
background-color: #999;
|
|
color: black;
|
|
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
|
font-size: 9pt;
|
|
font-weight: bold;
|
|
width: 60px;
|
|
height: 40px;
|
|
border: 1px solid #DDD;
|
|
border-radius: 4px}
|
|
|
|
BUTTON.redButton {
|
|
background-color: #900;
|
|
color: white;
|
|
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
|
font-size: 9pt;
|
|
font-weight: bold;
|
|
width: 60px;
|
|
height: 40px;
|
|
border: 1px solid #DDD;
|
|
border-radius: 4px}
|
|
|
|
BUTTON.greenLit {
|
|
background-color: green}
|
|
|
|
BUTTON.whiteLit {
|
|
background-color: white}
|
|
|
|
BUTTON.redLit {
|
|
background-color: #F00}
|
|
|
|
DIV.heading {
|
|
margin-top: 12px;
|
|
margin-bottom: 6px;
|
|
font-weight: bold}
|
|
|
|
#CardReaderPanel {
|
|
position: relative;
|
|
color: white;
|
|
background-color: #666;
|
|
width: 600px;
|
|
height: 150px;
|
|
border: 1px solid black;
|
|
border-radius: 8px;
|
|
padding: 0;
|
|
vertical-align: top}
|
|
|
|
#CRNotReadyLight {
|
|
position: absolute;
|
|
top: 8px;
|
|
left: 8px}
|
|
|
|
#CREOFBtn {
|
|
position: absolute;
|
|
top: 8px;
|
|
left: 76px}
|
|
|
|
#CRStopBtn {
|
|
position: absolute;
|
|
top: 8px;
|
|
left: 144px}
|
|
|
|
#CRStartBtn {
|
|
position: absolute;
|
|
top: 8px;
|
|
left: 212px;}
|
|
|
|
#CRFileSelector {
|
|
position: absolute;
|
|
top: 56px;
|
|
left: 8px;
|
|
width: 580px;
|
|
border: 1px solid white}
|
|
|
|
#CRProgressBar {
|
|
position: absolute;
|
|
top: 84px;
|
|
left: 8px;
|
|
width: 580px;
|
|
border: 1px solid white}
|
|
|
|
#CROutHopperFrame {
|
|
position: absolute;
|
|
top: 106px;
|
|
left: 8px;
|
|
width: 580px;
|
|
height: 3em;
|
|
margin-top: 1px;
|
|
border: 1px solid white;
|
|
color: black;
|
|
background-color: white;
|
|
font-family: DejaVu Sans Mono, Consolas, Courier, monospace;
|
|
font-size: 9pt;
|
|
font-weight: normal}
|
|
|
|
#TextPanel {
|
|
position: absolute;
|
|
top: 180px;
|
|
left: 0;
|
|
bottom: 8px;
|
|
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}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class=heading>
|
|
Burroughs 220 BALGOL Assembler
|
|
</div>
|
|
|
|
<div id=CardReaderPanel>
|
|
<button id=CRNotReadyLight class="whiteButton whiteLit">NOT READY</button>
|
|
<button id=CRStartBtn class="greenButton">START</button>
|
|
<button id=CREOFBtn class="redButton">EOF</button>
|
|
<button id=CRStopBtn class="redButton">STOP</button>
|
|
|
|
<input id=CRFileSelector type=file size=90>
|
|
|
|
<meter id=CRProgressBar min=0 max=100 value=0 title="Click to clear input hopper"></meter>
|
|
|
|
<iframe id=CROutHopperFrame scrolling=auto></iframe>
|
|
</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 cardHandler = initializePass1;
|
|
var cardsPerMinute = 10000;
|
|
var eofArmed = 0;
|
|
var eolRex = /([^\n\r\f]*)((:?\r[\n\f]?)|\n|\f)?/g;
|
|
var millisPerCard = 60000/cardsPerMinute;
|
|
var nextReaderStamp = 0;
|
|
var outHopper;
|
|
var outHopperFrame = $$("CROutHopperFrame");
|
|
var panel = $$("TextPanel");
|
|
var readerState = 0;
|
|
|
|
// Input field 0-relative column locations
|
|
var labelIndex = 4;
|
|
var opCodeIndex = labelIndex + 6;
|
|
var operandIndex = labelIndex + 12;
|
|
|
|
// Card reader ready state
|
|
var readerNotReady = 0;
|
|
var readerReady = 1;
|
|
|
|
// Card data structure
|
|
var cardData = {
|
|
offset: 0,
|
|
length: 0,
|
|
serial: 0,
|
|
text: ""}
|
|
|
|
var p10 = [ 1,
|
|
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, -1],
|
|
"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]
|
|
};
|
|
|
|
|
|
// 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
|
|
|
|
|
|
/**************************************/
|
|
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 setReaderReady(ready) {
|
|
/* Controls the ready-state of the card reader */
|
|
|
|
$$("CRFileSelector").disabled = ready;
|
|
if (ready) {
|
|
readerState = readerReady;
|
|
$$("CRStartBtn").classList.add("greenLit");
|
|
$$("CRNotReadyLight").classList.remove("whiteLit");
|
|
} else {
|
|
readerState = readerNotReady;
|
|
$$("CRStartBtn").classList.remove("greenLit");
|
|
$$("CRNotReadyLight").classList.add("whiteLit");
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function armEOF(armed) {
|
|
/* Controls the arming/disarming of the EOF signal when starting with
|
|
an empty input hopper */
|
|
|
|
if (armed) {
|
|
eofArmed = 1;
|
|
$$("CREOFBtn").classList.add("redLit");
|
|
} else {
|
|
eofArmed = 0;
|
|
$$("CREOFBtn").classList.remove("redLit");
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function readACard(successor, cardData) {
|
|
/* Reads one card image from the buffer, pads or trims the image as
|
|
necessary to 80 columns, and calls the "successor" function with it
|
|
and its location in the buffer.
|
|
If the reader is not ready, nothing happens */
|
|
var bx = bufferOffset;
|
|
var card;
|
|
var cardLength;
|
|
var line;
|
|
var match;
|
|
var stamp = performance.now();
|
|
var delta = nextReaderStamp - stamp;
|
|
|
|
cardData.offset = bx;
|
|
|
|
if (readerState != readerReady) {
|
|
cardData.length = -1; // just exit
|
|
} else if (bx >= bufferLength) {
|
|
cardData.length = 0;
|
|
setReaderReady(false);
|
|
$$("CRProgressBar").value = 0;
|
|
} else {
|
|
nextReaderStamp += millisPerCard;
|
|
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;
|
|
|
|
$$("CRProgressBar").value = bufferLength-bx;
|
|
while (outHopper.childNodes.length > 1) {
|
|
outHopper.removeChild(outHopper.firstChild);
|
|
}
|
|
|
|
outHopper.appendChild(document.createTextNode("\n"));
|
|
outHopper.appendChild(document.createTextNode(line));
|
|
setTimeout(successor, delta, cardData);
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function CRStartBtn_onclick(ev) {
|
|
/* Handle the click event for the START button */
|
|
|
|
if (readerState != readerReady) {
|
|
if (bufferOffset >= bufferLength) {
|
|
//alert("Empty hopper.");
|
|
if (eofArmed) {
|
|
printLine("\\\\\\\\\\ [EOF] /////");
|
|
armEOF(false);
|
|
}
|
|
} else {
|
|
setReaderReady(true);
|
|
setTimeout(startReader, 500); // delay until the reader can come up to speed...
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function CRStopBtn_onclick(ev) {
|
|
/* Handle the click event for the STOP button */
|
|
|
|
$$("CRFileSelector").value = null; // reset the control so the same file can be reloaded
|
|
if (readerState == readerNotReady) {
|
|
armEOF(false);
|
|
} else if (readerState == readerReady) {
|
|
setReaderReady(false);
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function CREOFBtn_onclick(ev) {
|
|
/* Handle the click event for the EOF button */
|
|
|
|
armEOF(!eofArmed);
|
|
}
|
|
|
|
/**************************************/
|
|
function CRProgressBar_onclick(ev) {
|
|
/* Handle the click event for the "input hopper" progress bar */
|
|
|
|
if (bufferOffset < bufferLength && readerState == readerNotReady) {
|
|
if (confirm("Do you want to clear the reader input hopper?")) {
|
|
buffer = "";
|
|
bufferLength = 0;
|
|
bufferOffset = 0;
|
|
$$("CRProgressBar").value = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function fileLoader_onLoad(ev) {
|
|
/* Handle the onload event for a Text FileReader */
|
|
|
|
if (bufferOffset < bufferLength) {
|
|
buffer = buffer.substring(bufferOffset);
|
|
} else {
|
|
clearPanel();
|
|
buffer = "";
|
|
}
|
|
|
|
buffer += ev.target.result;
|
|
bufferOffset = 0;
|
|
bufferLength = buffer.length;
|
|
$$("CRProgressBar").value = buffer.length;
|
|
$$("CRProgressBar").max = buffer.length;
|
|
}
|
|
|
|
/**************************************/
|
|
function fileSelector_onChange(ev) {
|
|
/* Handle the <input type=file> onchange event when a file is selected */
|
|
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");
|
|
********************/
|
|
|
|
reader.onload = fileLoader_onLoad;
|
|
reader.readAsText(f);
|
|
}
|
|
|
|
/**************************************/
|
|
function startReader() {
|
|
/* Reads a deck of cards and displays them until the reader goes empty */
|
|
|
|
readACard(cardHandler, cardData);
|
|
}
|
|
|
|
/**************************************/
|
|
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 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 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 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.newOffset = x;
|
|
token.text = raw;
|
|
return parseInt(raw, 10);
|
|
}
|
|
|
|
/**************************************/
|
|
function parseString(text, token, singleWord) {
|
|
/* 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.
|
|
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 */
|
|
var c;
|
|
var code;
|
|
var count = 0;
|
|
var doubleDollar = false;
|
|
var length = text.length
|
|
var raw = "";
|
|
var values = [];
|
|
var word = 2;
|
|
var x = token.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 (++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 {
|
|
raw += c;
|
|
code = c.charCodeAt(0);
|
|
appendCode((code < asciiFilter.length ? asciiFilter[code]
|
|
: (code == 0xA4 ? 4 : 0))); // the lozenge
|
|
++x;
|
|
}
|
|
}
|
|
|
|
if (x >= length) {
|
|
printError("$-STRING NOT TERMINATED");
|
|
} else {
|
|
++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 && values.length > 1) {
|
|
printError("STRING OCCUPIES MORE THAN ONE WORD");
|
|
}
|
|
|
|
token.newOffset = x;
|
|
token.text = raw;
|
|
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:
|
|
0: empty primary
|
|
1: *, the current location counter
|
|
2: integer constant
|
|
3: regular label
|
|
4: backward point label (without the leading * or the trailing -)
|
|
5: forward point label (ditto, without the trailing +)
|
|
6: pool constant, 1-11 digit number with a leading + or -)
|
|
*/
|
|
var c;
|
|
var length = text.length;
|
|
var raw = "";
|
|
var val = 0;
|
|
var x = token.offset;
|
|
|
|
if (x >= length) { // empty primary
|
|
token.type = 0;
|
|
token.newOffset = length;
|
|
token.text = "";
|
|
} else {
|
|
c = text.charAt(x);
|
|
switch (true) {
|
|
case (c == "*"): // parse current location counter
|
|
token.type = 1;
|
|
token.text = c;
|
|
token.newOffset = x+1;
|
|
break;
|
|
|
|
case (c >= "0" && c <= "9"): // parse integer constant
|
|
val = parseNumber(text, token, 0);
|
|
token.type = 2;
|
|
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 = 3; // label is at the end, can't be a point label
|
|
} else if (c != "+" && c != "-") {
|
|
token.type = 3; // not followed by +/-, not a point label
|
|
} else {
|
|
token.type = (c == "-" ? 4 : 5);
|
|
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 = 3; // 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);
|
|
token.text = raw + padLeft(val, 10, "0");
|
|
}
|
|
}
|
|
|
|
token.type = 6;
|
|
break;
|
|
|
|
case (c == "$"): // parse pool string constant
|
|
parseString(text, token, true);
|
|
token.text = "$" + token.text.substring(0, 5);
|
|
token.type = 6;
|
|
break;
|
|
|
|
case (c == " "): // empty primary
|
|
token.type = 0;
|
|
token.newOffset = x;
|
|
token.text = "";
|
|
break;
|
|
|
|
default: // not valid
|
|
token.type = 0;
|
|
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 0: // empty text
|
|
val = 0;
|
|
break;
|
|
case 1: // *, current location counter
|
|
val = location;
|
|
break;
|
|
case 2: // integer constant
|
|
val = parseInt(token.text, 10);
|
|
break;
|
|
case 3: // regular label
|
|
if (token.text in symTab) {
|
|
val = symTab[token.text];
|
|
} else {
|
|
symTab[token.text] = val = -1;
|
|
unresolvable = true;
|
|
}
|
|
break;
|
|
case 4: // backward point label
|
|
case 5: // forward point label
|
|
label = "*" + token.text;
|
|
val = fetchPointLabel(label, token.type == 5);
|
|
if (val < 0) {
|
|
val = -1;
|
|
unresolvable = true;
|
|
}
|
|
break;
|
|
case 6: // pool constant
|
|
if (token.text in poolTab) {
|
|
val = poolTab[token.text];
|
|
} 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 = [];
|
|
|
|
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) {
|
|
/* Parses the operand text for the CNST pseudo-operator and returns an
|
|
array of binary words with the values found */
|
|
var c;
|
|
var length = text.length;
|
|
var val2;
|
|
var values = [];
|
|
var x;
|
|
|
|
while (token.offset < length) { // parse comma-delimited values
|
|
c = text.charAt(token.offset);
|
|
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));
|
|
} else if (c == "$") {
|
|
val2 = parseString(text, token, false);
|
|
for (x=0; x<val2.length; ++x) {
|
|
values.push(val2[x]);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 initializePass1(cardData) {
|
|
/* Initializes or reinitializes the assembler for Pass 1 */
|
|
|
|
errorCount = 0;
|
|
errorTank = [];
|
|
location = 0;
|
|
cardData.serial = 0;
|
|
symTab = {};
|
|
pointTab = {};
|
|
poolTab = {};
|
|
symTab["RLO"] = 1; // kludge for CWR reload-lockout field
|
|
|
|
nextReaderStamp = performance.now();
|
|
cardHandler = startPass1;
|
|
}
|
|
|
|
/**************************************/
|
|
function startPass1(cardData) {
|
|
/* Callback function for the card reader. Reads the control cards and
|
|
then passes control to pass 1 of the assembler */
|
|
var card = cardData.text;
|
|
var opCode = card.substring(opCodeIndex, opCodeIndex+5);
|
|
|
|
switch (opCode) {
|
|
case "ASMBL":
|
|
readACard(startPass1, cardData);
|
|
break;
|
|
case "REORD":
|
|
readACard(startPass1, cardData);
|
|
break;
|
|
default:
|
|
cardHandler = assemblePass1;
|
|
assemblePass1(cardData);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function assemblePass1(cardData) {
|
|
/* Callback function for the card reader. Processes the card image for
|
|
pass 1 of the assembler */
|
|
var card = cardData.text;
|
|
var finito = false;
|
|
var label = card.substring(labelIndex, labelIndex+5).trim();
|
|
var opCode = card.substring(opCodeIndex, opCodeIndex+4).trim();
|
|
var opDesc;
|
|
var operand = card.substring(operandIndex, operandIndex+56);
|
|
var origLoc = location;
|
|
var seq = card.substring(72, 80);
|
|
var sign = card.substring(opCodeIndex+4, opCodeIndex+5);
|
|
var text;
|
|
var thisLoc = location;
|
|
var values;
|
|
var x;
|
|
|
|
var token = { // token structure for operand parsing
|
|
type: 0,
|
|
offset: 0,
|
|
text: "",
|
|
newOffset: -1};
|
|
|
|
if (opCode == "REM") {
|
|
printPass1(seq, location, label, null, sign, operand);
|
|
} else {
|
|
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
|
|
} 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");
|
|
}
|
|
break;
|
|
case pseudoLOCN:
|
|
values = evaluateOperand(operand, token, true);
|
|
if (values.length < 1) {
|
|
printError("OPERAND ADDRESS REQUIRED");
|
|
} else if (values[0] >= 0) {
|
|
location = values[0];
|
|
}
|
|
break;
|
|
case pseudoCNST:
|
|
values = parseConstantList(operand, token);
|
|
location += values.length;
|
|
break;
|
|
case pseudoF244:
|
|
case pseudoF424:
|
|
values = evaluateOperand(operand, token, false); // discard result, take only side effects
|
|
++location; // word-builders merely bump the location counter
|
|
break;
|
|
case pseudoFBGR:
|
|
location += 29; // all format bands are 29 words long
|
|
break;
|
|
case pseudoFINI:
|
|
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 (!finito) {
|
|
readACard(assemblePass1, cardData);
|
|
} else {
|
|
// Build the constant pool
|
|
for (text in poolTab) {
|
|
symTab[text] = location;
|
|
printLine(" " + padLeft(location, 4, "0") + " " + text);
|
|
++location;
|
|
}
|
|
|
|
// Wrap up Pass 1
|
|
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("");
|
|
|
|
if (errorCount == 0) {
|
|
initializePass2(cardData);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function initializePass2(cardData) {
|
|
/* 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.serial = 0;
|
|
nextReaderStamp = performance.now();
|
|
for (e in pointTab) { // reset the point label table
|
|
pointTab[e] = 0;
|
|
}
|
|
|
|
cardHandler = startPass2;
|
|
readACard(startPass2, cardData);
|
|
}
|
|
|
|
/**************************************/
|
|
function startPass2(cardData) {
|
|
/* Sets up for Pass 2 of the assembly and initiates it */
|
|
var card = cardData.text;
|
|
var opCode = card.substring(opCodeIndex, opCodeIndex+5);
|
|
|
|
switch (opCode) {
|
|
case "ASMBL":
|
|
readACard(startPass2, cardData);
|
|
break;
|
|
case "REORD":
|
|
readACard(startPass2, cardData);
|
|
break;
|
|
default:
|
|
cardHandler = assemblePass2;
|
|
assemblePass2(cardData);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function assemblePass2(cardData) {
|
|
/* Callback function for the card reader. Processes the card image for
|
|
pass 1 of the assembler */
|
|
var card = cardData.text;
|
|
var finito = false;
|
|
var label = card.substring(labelIndex, labelIndex+5).trim();
|
|
var opCode = card.substring(opCodeIndex, opCodeIndex+4).trim();
|
|
var opDesc;
|
|
var operand = card.substring(operandIndex, operandIndex+56);
|
|
var origLoc = location;
|
|
var seq = card.substring(72, 80);
|
|
var serial = cardData.serial;
|
|
var sign = card.substring(opCodeIndex+4, opCodeIndex+5);
|
|
var text;
|
|
var thisLoc = location;
|
|
var values;
|
|
var word;
|
|
var x;
|
|
|
|
var token = { // token structure for operand parsing
|
|
type: 0,
|
|
offset: 0,
|
|
text: "",
|
|
newOffset: -1};
|
|
|
|
if (opCode == "REM") {
|
|
printPass1(seq, location, label, null, sign, operand);
|
|
} else {
|
|
if (opCode.length <= 0) { // treat line a CNST pseudo-op
|
|
opDesc = opTab["CNST"];
|
|
} else if (!(opCode in opTab)) {
|
|
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
|
|
} 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);
|
|
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); break;
|
|
break;
|
|
case pseudoCNST:
|
|
values = parseConstantList(operand, token);
|
|
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;
|
|
}
|
|
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;
|
|
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;
|
|
}
|
|
break;
|
|
case pseudoFINI:
|
|
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]);
|
|
} // 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 (!finito) {
|
|
readACard(assemblePass2, cardData);
|
|
} else {
|
|
// Output the constant pool
|
|
for (text in poolTab) {
|
|
location = symTab[text];
|
|
token.offset = 0;
|
|
values = parseConstantList(text, token);
|
|
printPass2(seq, serial, location, values[0], "", "", "", text);
|
|
emitWord(location, values[0]);
|
|
}
|
|
|
|
// Wrap up Pass 2
|
|
for (text in symTab) {
|
|
if (symTab[text] < 0) {
|
|
printError("SYMBOL NOT DEFINED: " + text);
|
|
}
|
|
}
|
|
|
|
printLine("");
|
|
printLine("END OF PASS 2, ERRORS = " + errorCount);
|
|
|
|
setReaderReady(false);
|
|
initializePass1(cardData);
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function processCard(card) {
|
|
/* Callback function for the card reader. Processes the card image */
|
|
var count = 0;
|
|
var entry;
|
|
var match;
|
|
var opCode;
|
|
var operand;
|
|
var operandRex = /^(\S+)/;
|
|
var x;
|
|
|
|
// Accumulate statistics
|
|
// printLine(card);
|
|
opCode = card.substring(26, 30).trim();
|
|
|
|
if (opCode.length > 0) {
|
|
operandRex.lastIndex = 0;
|
|
match = operandRex.exec(card.substring(32));
|
|
if (match) {
|
|
operand = match[1];
|
|
count = 1;
|
|
if (operand.charAt(0) != "$") {
|
|
x = -1;
|
|
do {
|
|
x = operand.indexOf(",", x+1);
|
|
if (x >= 0) {
|
|
++count;
|
|
}
|
|
} while (x >= 0);
|
|
}
|
|
}
|
|
|
|
if (opCode in opTab) {
|
|
entry = opTab[opCode];
|
|
} else {
|
|
opTab[opCode] = entry = [0];
|
|
}
|
|
|
|
while (entry.length <= count) {
|
|
entry.push(0);
|
|
}
|
|
|
|
++entry[count];
|
|
}
|
|
|
|
if (bufferOffset < bufferLength && opCode != "FINI") {
|
|
readACard(processCard);
|
|
} else {
|
|
// Report statistics
|
|
setReaderReady(false);
|
|
printLine("");
|
|
printLine("___________________________");
|
|
printLine("");
|
|
for (opCode in opTab) {
|
|
entry = opTab[opCode];
|
|
operand = padRight(opCode, 5);
|
|
for (x=0; x<entry.length; ++x) {
|
|
operand += padLeft(entry[x].toString(), 5);
|
|
}
|
|
|
|
printLine(operand);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
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;
|
|
}
|
|
|
|
armEOF(false);
|
|
setReaderReady(false);
|
|
|
|
$$("CRFileSelector").addEventListener("change", fileSelector_onChange, false);
|
|
$$("CRStartBtn").addEventListener("click", CRStartBtn_onclick, false);
|
|
$$("CRStopBtn").addEventListener("click", CRStopBtn_onclick, false);
|
|
$$("CREOFBtn").addEventListener("click", CREOFBtn_onclick, false);
|
|
$$("CRProgressBar").addEventListener("click", CRProgressBar_onclick, false);
|
|
|
|
outHopperFrame.contentDocument.head.innerHTML += "<style>" +
|
|
"BODY {background-color: #F0DCB0; margin: 0; padding: 0} " +
|
|
"PRE {margin: 0; font-size: 9pt; font-family: DejaVu Sans Mono, Consolas, Courier, monospace}" +
|
|
"</style>";
|
|
outHopper = document.createElement("pre");
|
|
outHopperFrame.contentDocument.body.appendChild(outHopper);
|
|
|
|
initializePass1(cardData);
|
|
}, false);
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|