1<!DOCTYPE html> 2<html> 3<head> 4 <title>Apple II+ in Javascript</title> 5 <link rel="icon" type="image/x-icon" href="res/favicon.ico"> 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 7 <link rel="icon" type="image/png" sizes="16x16" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAEXRFWHRTb2Z0d2FyZQBKVEwtRGV2J4CxQ84AAAAwUExURf7///7///j4+PLy8u3t7eXl5dvb283NzcLCwrW1ta6urqenp5qamo6OjnV1dWhoaJTpFr4AAAABYktHRACIBR1IAAAACXBIWXMAAB7CAAAewgFu0HU+AAAAAXRSTlMAQObYZgAAAIFJREFUeJwtzTEKwkAQBdC9wp/NkqTcsJC0IVXKlCE3SCrBxjKI6D3sBY9g4R1EEEEbr5AD2Psn8VeP+bM7BoxkwIzhvEAe4wI3QSGnqb43RHdsr/kLRg6lt2Ek9lxIPasLkSh2+o4VCiL6ErFO3oRbA/1Wf948b5/5RBXs6n9d8wM0Ahk+0eMaYgAAAABJRU5ErkJggg==" /> 8 <!--link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css"--> 9 <link rel="stylesheet" href="res/COM_fontawesome/all.css"> 10 <link rel="stylesheet" href="res/COM_MAIN.css"> 11 <link rel="stylesheet" href="res/COM_oTERM.css"> 12 <link rel="stylesheet" href="res/DBG_testbench.css"> 13
13<script type="text/javascript" src="res/COM_MAIN.js"></script>
13 14
14<script type="text/javascript" src="res/COM_A2P_LAYOUT.js"></script>
14 15
15<script type="text/javascript" src="res/COM_LAYOUT_CONFIG.js"></script>
15 16
16<script type="text/javascript" src="res/COM_oTERM.js"></script>
16 17
17<script type="text/javascript" src="res/DBG_testbench.js"></script>
17 18
18<script type="text/javascript" src="res/COM_CONFIG.js"></script>
18 <!-- load all the help, documentation & configuration data--> 19
19<script type="text/javascript" src="res/pako.min.js"></script>
19 <!-- compression/decompression library --> 20 <!--script type="text/javascript" src="res/purify.min.js"></script--> <!-- safely filter out all HTML tags --> 21 <!--script type="text/javascript" src="res/ayumi.js"></script--> <!-- AY-3-8910 chip emulator --> 22
22<script type="text/javascript" src="https://unpkg.com/[email protected]/build/three.min.js"></script>
22 23
23<script type="text/javascript" src="https://unpkg.com/[email protected]/examples/js/controls/OrbitControls.js"></script>
23 24
24<script type="text/javascript" src="https://unpkg.com/[email protected]/examples/js/controls/TransformControls.js"></script>
24 25
25<script type="text/javascript" src="res/THREEJS_scene_v0.js"></script>
25 26 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 27
27<script id="rendered-js"> 28 29// ____ ____ _ 30// |_ \ / _| (_) 31// | \/ | ,--. __ _ .--. 32// | |\ /| | `'_\ : [ | [ `.-. | 33// _| |_\/_| |_ // | |, | | | | | | 34// |_____||_____|\'-;__/[___][___||__] 35// âââââââ ââââââ ââââââ ââ ââââââ ââââââââ âââââââ 36// ââ ââ ââ ââ ââ ââ ââ ââ ââ 37// âââââââ ââ ââââââ ââ ââââââ ââ âââââââ 38// ââ ââ ââ ââ ââ ââ ââ ââ 39// âââââââ ââââââ ââ ââ ââ ââ ââ âââââââ 40 41 ////////////////////// 42 // TAB ANIMATOR // 43 ////////////////////// 44 45 var oTAB = null; 46 oCOM.addToEventStack("onload",COM_switchTab); 47 oCOM.addToEventStack("onhashchange",COM_switchTab); 48 49 /////////////////////// 50 // TAB HANDLER // 51 /////////////////////// 52 53 function COM_switchTab(sel) 54 { 55 if(!oTAB) 56 oTAB = oCOM.tabSwitch({ 57 menuID:"#app ul", 58 contentCL:".tabs", 59 animDuration:0.1 60 }).init(); 61 62 if(typeof(sel) == "string" && location.hash.slice(1) != sel) 63 location.hash = "#" + sel; 64 65 return oTAB.switchTab(sel); 66 } 67 68 //////////////////// 69 // ON-SCREEN HELP // 70 //////////////////// 71 72 function onhelp(txt,target) 73 { 74 if( typeof(_o.tools[target.id])=="undefined" ) 75 _o.tools[target.id] = {"toggle":false,"cache":""} 76 77 var t = target.tagName; 78 switch(target.tagName.toUpperCase()) 79 { 80 case "DIV": 81 _o.tools[target.id].toggle = !_o.tools[target.id].toggle 82 if(_o.tools[target.id].toggle) 83 { 84 _o.tools[target.id].cache = target.innerHTML; 85 target.innerHTML = txt; 86 } 87 else 88 target.innerHTML = _o.tools[target.id].cache; 89 break; 90 case "TEXTAREA": 91 target.innerHTML = txt; 92 break; 93 } 94 } 95</script>
95 96 97 <style id="lironSurfaceMap_style"> 98 #lironSurfaceMap_popup { box-sizing:border-box; overflow:hidden; } 99 #lironSurfaceMap_popup .liron-surface-title { font-weight:bold; padding:2px 0 4px 0; } 100 #lironSurfaceMap_popup .liron-surface-meta { padding:0 0 7px 0; } 101 #lironSurfaceMap_popup .liron-surface-status { padding:12px 4px; } 102 #lironSurfaceMap_popup .liron-surface-panels { display:flex; gap:10px; width:100%; } 103 #lironSurfaceMap_popup .liron-surface-side { flex:1 1 0; min-width:0; } 104 #lironSurfaceMap_popup .liron-surface-side-title { text-align:center; padding-bottom:3px; } 105 #lironSurfaceMap_popup .liron-surface-grid { display:grid; grid-template-columns:repeat(80,minmax(3px,1fr)); grid-template-rows:repeat(12,8px); gap:1px; width:100%; overflow:hidden; } 106 #lironSurfaceMap_popup .liron-surface-cell { min-width:0; height:8px; box-sizing:border-box; border:1px solid rgba(0,0,0,0.35); } 107 #lironSurfaceMap_popup .liron-surface-cell.active { background:rgba(0,0,0,0.38); } 108 #lironSurfaceMap_popup .liron-surface-cell.inactive { opacity:0.12; } 109 #lironSurfaceMap_popup .liron-surface-cell.zone-end { border-right-width:2px; } 110 </style> 111</head> 112 113 114<!-- 115 ____ ____ _ 116 |_ \ / _| (_) 117 | \/ | ,--. __ _ .--. 118 | |\ /| | `'_\ : [ | [ `.-. | 119 _| |_\/_| |_ // | |, | | | | | | 120 |_____||_____|\'-;__/[___][___||__] 121 ââ ââ ââââââââ âââ âââ ââ 122 ââ ââ ââ ââââ ââââ ââ 123 âââââââ ââ ââ ââââ ââ ââ 124 ââ ââ ââ ââ ââ ââ ââ 125 ââ ââ ââ ââ ââ âââââââ 126--> 127<body> 128</script>
128 129 130 <div class=appbut id=COM_popup style="position: absolute; z-index: 3; left: 0px; top: 120px; " hidden=""> 131 <div class="appbut" onclick="oCOM.POPUP.toggle('COM_popup');document.getElementById('COM_popup_text').innerHTML=''" style="text-align:center;float:right;">x</div> 132 <div id=COM_popup_text></div> 133 </div> 134 135 136 <div class="appbox" id="lironSurfaceMap_popup" style="position:absolute;z-index:3;left:350px;top:120px;width:930px;max-width:calc(100vw - 370px);text-align:left;padding:7px;margin:0px" hidden=""> 137 <div class="appbut" onclick="oCOM.POPUP.off('lironSurfaceMap_popup')" style="text-align:center;float:right;cursor:pointer">x</div> 138 <div id="lironSurfaceMap_popup_text"></div> 139 </div> 140 141 <table id=box style=width:450px;margin-top:0px> 142 <tr> 143 <td valign=top> 144 <!--i class="fa fa-bars" onmouseover="document.getElementById('app').style.display=''" style=float:left;></i!--> 145 146 <!-- tab menu --> 147 <div id=app> 148 <ul style="width:350px;float:left"> 149 <!-- links --> 150 <a id="tab0" href="#tab1">Emulator</a> 151 <a id="tab0" href="#tab2">Assembler</a> 152 <a id="tab0" href="#tab3">Debugger</a> 153 <div class="focus-el el--1"></div> 154 <div class="focus-el el--2"></div> 155 </ul> 156 </div> 157 158 <!-- tab content --> 159 <div class="tabs"> 160 <div id="tab1" style="height:1300px;width:1300px;user-select:none;-moz-user-select:none;-webkit-user-drag:none;-webkit-user-select:none;-ms-user-select:none;"> 161 <!--form name="tab1" class="tabdata"><input name="tabdata" value='{"SYS":"A2P"}'></form!--> 162 <!-- 163 âââââââ âââ âââ ââ ââ ââ âââââ ââââââââ ââââââ ââââââ 164 ââ ââââ ââââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ 165 âââââ ââ ââââ ââ ââ ââ ââ âââââââ ââ ââ ââ ââââââ 166 ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ 167 âââââââ ââ ââ ââââââ âââââââ ââ ââ ââ ââââââ ââ ââ 168 --> 169 170
170<script type="text/javascript" src="res/EMU_apple2main.js"></script>
170 171
171<script type="text/javascript" src="res/EMU_apple2hw.js"></script>
171 172
172<script type="text/javascript" src="res/EMU_cpu6502.js"></script>
172 173
173<script type="text/javascript" src="res/EMU_WASMcpu6502.js"></script>
173 174
174<script type="text/javascript" src="res/EMU_apple2debug.js"></script>
174 175
175<script type="text/javascript" src="res/EMU_DEVICE_speaker.js"></script>
175 176
176<script type="text/javascript" src="res/EMU_DEVICE_gameport.js"></script>
176 177
177<script type="text/javascript" src="res/EMU_apple2roms.js"></script>
177 178
178<script type="text/javascript" src="res/gpu-browser.min.js"></script>
178 179 180
180<script type="text/javascript" src="res/EMU_DEVICE_video_GPU.js"></script>
180<script>var Apple2VideoGPU = Apple2Video;</script>
180 181
181<script type="text/javascript" src="res/EMU_DEVICE_video_wave.js"></script>
181<script>var Apple2VideoWave = Apple2Video; Apple2Video = Apple2VideoGPU;</script>
181 182
182<script type="text/javascript" src="res/EMU_DEVICE_video_THREE.js"></script>
182 183
183<script type="text/javascript" src="res/EMU_DEVICE_video_canvas.js"></script>
183<script>var Apple2VideoCanvas = Apple2Video;</script>
183 184
184<script type="text/javascript" src="res/EMU_DEVICE_video_MUX.js"></script>
184 185
185<script type="text/javascript" src="res/EMU_DEVICE_video_text.js"></script>
185 186
186<script type="text/javascript" src="res/EMU_DEVICE_videx_normal.js"></script>
186 187
187<script type="text/javascript" src="res/EMU_DEVICE_videx_MUX.js"></script>
187 188
188<script type="text/javascript" src="res/EMU_DEVICE_pasteboard.js"></script>
188 189
189<script type="text/javascript" src="res/EMU_DEVICE_serialpro_line.js"></script>
189 190
190<script type="text/javascript" src="res/EMU_DEVICE_serialpro_terminal.js"></script>
190 191
191<script type="text/javascript" src="res/EMU_DEVICE_UNIDISK35.js"></script>
191 192
192<script type="text/javascript" src="res/EMU_DEVICE_HD20.js"></script>
192 193 194
194<script type="text/javascript" src="res/EMU_DEVICE_camera.js"></script>
194 195
195<script type="text/javascript" src="res/EMU_CARD_dithertizer.js"></script>
195 196
196<script type="text/javascript" src="res/EMU_DITHERTIZER_converthgr_worker.js"></script>
196 197
197<script type="text/javascript" src="res/EMU_DITHERTIZER_converthgr.js"></script>
197 198
198<script type="text/javascript" src="res/EMU_CARD_dithertizer2.js"></script>
198 199
199<script type="text/javascript" src="res/EMU_CARD_LIRON.js"></script>
199 200
200<script type="text/javascript" src="res/EMU_CARD_applemouse.js"></script>
200 201
201<script type="text/javascript" src="res/EMU_CARD_appledisk2.js"></script>
201 202
202<script type="text/javascript" src="res/EMU_CARD_ramcard.js"></script>
202 203 <!--script type="text/javascript" src="res/EMU_saturnRAM.js"></script--> 204
204<script type="text/javascript" src="res/EMU_CARD_videx.js"></script>
204 205
205<script type="text/javascript" src="res/EMU_CARD_mockingboard.js"></script>
205 206 <!--script type="text/javascript" src="res/EMU_CARD_superserial.js"></script--> 207
207<script type="text/javascript" src="res/EMU_CARD_serialpro.js"></script>
207 208
208<script type="text/javascript" src="res/EMU_CARD_serialpro_gpt.js"></script>
208 209
209<script type="text/javascript" src="res/EMU_CARD_thunderclock.js"></script>
209 210
210<script type="text/javascript" src="res/EMU_CARD_hostio.js"></script>
210 211 212
212<script type="text/javascript" src="res/EMU_apple2io.js"></script>
212 213
213<script type="text/javascript" src="res/EMU_DEVICE_keyboard.js"></script>
213 214
214<script type="text/javascript" src="res/EMU_apple2plus.js"></script>
214 215 216 <!-- EMULATOR TAB PANE --> 217 <div style="height:115px;padding:0px 0px 0px 0px;z-index:1;"> 218 219 <div style="height:30px;padding:0px 90px 0px 0px;"> 220 <button class=appbut value="Settings" onclick="javascript:oCOM.POPUP.runRule('tab1.1');" title="Settings"><i class="fa fa fa-sliders-h"></i></button> 221 <button class=appbut value="Tools" onclick="javascript:oCOM.POPUP.runRule('tab1.2');onBoardPasteRefresh();" title="Tools"><i class="fa fa-toolbox"></i></button> </div> 222 223 <!-- TOOLS TAB WINDOW --> 224 <div class="popupHost" id="tab1.2"> 225 <!-- PASTE-BOARD TOOLBOX --> 226 <div class=toolbox> 227 <form name="EMUboard"> 228 <textarea id="EMUboardText" cols=40 rows=4 placeholder="Paste here â 0:A2KBD:text" name=boardText style="float:left"></textarea><br> 229 <input type="button" value="Paste" onclick="javascript:oCOM.POPUP.toggle('tab1.2');oEMUI.pauseBtn({id:'pausebutton',class1:'fa-play',class2:'fa-pause',pause:false});onBoardPaste()"> 230 <input type="button" value="TxtCap" id=TxtCapture onclick="javascript:onTxtCapture(this)"> 231 <input type="button" value="MemCap" id=MemCapture onclick="javascript:onMemCapture('CapF','CapT')"> 232 <input type="text" value="$0000" size=5 id="CapF" onkeyup="onCaptureAddr(this)"><font color="#FFFFF">âº</font><input type="text" value="$FFFF" size=5 id="CapT" onkeyup="onCaptureAddr(this)"> 233 <input type="button" value="Clear" onclick="javascript:onEMUBoardClear()"> 234 <input type="button" value="X" onclick="javascript:oCOM.POPUP.off('tab1.2')"> 235 </form> 236 </div> 237 <!-- EMULATOR ACTION BUTTONS --> 238 <div class=toolbox> 239 <button class=appbut onclick="javascript:oEMUI.muteBtn({id:'mutebutton',class1:'fa-volume-up',class2:'fa-volume-mute'}).muteAct()" title="Sound"><i id="mutebutton" class="fa fa-volume-mute"></i></button> 240 <button class=appbut onclick="javascript:oEMUI.pauseBtn({id:'pausebutton',class1:'fa-play',class2:'fa-pause'})" id="pausebutton" title="Pause"><i class="fa fa-play"></i></button> 241 <br> 242 <button class="appbut" 243 value="Restart" 244 id="restartbutton" 245 title="Short press: warm boot / hold 1/2s: cold boot" 246 onmousedown="EMU_audio_event_unlock();return oEMUI.restartBtnDown();" 247 onmouseup="return oEMUI.restartBtnUp();" 248 onmouseleave="return oEMUI.restartBtnCancel();" 249 ontouchstart="EMU_audio_event_unlock();return oEMUI.restartBtnDown();" 250 ontouchend="return oEMUI.restartBtnUp();" 251 ontouchcancel="return oEMUI.restartBtnCancel();"> 252 <i class="fa fa-power-off"></i></button> 253 <button class=appbut onclick="javascript:apple2plus.hwObj().io.deviceBtn({id:'devices'})" id="devices" title="Peripheral controls" data-slot=""></button> 254 255 </div> 256 <!-- DEVICE TOOLBOX --> 257 258 <div class=toolbox id="device_toolbox_body"></div> 259 260 <div class="toolbox"> 261 <div id=LEDS class=appbox style="text-align:left;padding:0px 0px 0px 0px;margin:0px 0px 0px 0px; z-index:0"> 262 </div> 263 </div> 264 265 266 <!-- DEBUG --> 267 <!--div class=toolbox> 268 <div class=appbox style="text-align:left;height:80px;padding:0px 0px 0px 0px;margin:0px 0px 0px 0px"> 269 <canvas id="scopeCanvas" width="300" height="55" style="border:0px solid #E0E0E0;padding:0px 0px 0px 0px;margin:0px 0px 0px 0px"></canvas> 270 <div style="border:0px solid #E0E0E0;padding:0px 0px 0px 0px;margin:0px 0px 0px 0px"> 271 <input type="range" min="0" max="250" value="
2710" class="slider" onchange="slider_scroll(this,250)" style="width:250px"> 272 <i class="fa fa-arrow-circle-left"></i>932<i class="fa fa-arrow-circle-right"></i> 273 </div> 274 </div> 275 </div--> 276 277 </div> 278 279 <!-- SETTINGS TAB WINDOW --> 280 <div class="popupHost" id="tab1.1" hidden> 281 282 <!-- SYSTEM SETTINGS --> 283 <div class=toolbox> 284 <div class=appbox title="SYSTEM" style="text-align:left;height:92px;padding:0px 6px 0px 6px;"> 285 SYSTEM 286 <div style="font-family:arial;font-size:11px"> 287 <div style=" 288 display:flex; 289 flex-direction:column; 290 width:110px; 291 min-width:110px; 292"> 293 294 <!-- CPU load --> 295 <div style=" 296 display:flex; 297 flex-flow:row nowrap; 298 align-items:center; 299 width:110px; 300 white-space:nowrap; 301 "> 302 <span id="cpu_component" style="flex:0 0 auto;"></span> 303 304 <span style="flex:0 0 auto;margin-left:3px;">load</span> 305 306 <i class="fa fa-sync-alt" 307 id="CPU_monitoring" 308 style="flex:0 0 auto;margin-left:3px;" 309 onclick=" 310 oCOM.POPUP.toggle_class(this,'fa-stop-circle','fa-sync-alt'); 311 oCOM.toggleRefreshEvent('CPU_monitoring'); 312 document.getElementById('cpu_pct').textContent=''; 313 "></i> 314 315 <span id="cpu_pct" 316 style=" 317 flex:1 1 auto; 318 min-width:24px; 319 margin-left:3px; 320 text-align:right; 321 white-space:nowrap; 322 "></span> 323 </div> 324 325 <!-- CPU speed --> 326 <div style=" 327 display:flex; 328 flex-flow:row nowrap; 329 align-items:center; 330 width:110px; 331 "> 332 <button type="button" 333 id="slider_1v" 334 class="appbut skinny" 335 style=" 336 flex:0 0 45px; 337 width:45px; 338 padding:0; 339 font-family:arial; 340 font-size:11px; 341 " 342 onclick="oEMUI.cpuSldIndicator(event,this,'myRange','feature_box')" 343 title="CPU speed: 100%. Click to switch to 0-100%." 344 aria-label="CPU speed: 100%. Click to switch to 0-100%.">100%</button> 345 346 <input id="myRange" 347 type="range" 348 min="0" 349 max="400" 350 value="100" 351 step="20" 352 class="slider" 353 data-speed-mode="pct400" 354 onchange="oEMUI.cpuSld(this,'slider_1v')" 355 style=" 356 flex:1 1 auto; 357 min-width:0; 358 width:0; 359 "> 360 </div> 361 362 <!-- FPS --> 363 <div style=" 364 display:flex; 365 flex-flow:row nowrap; 366 align-items:center; 367 width:110px; 368 "> 369 <span id="slider_fps_v" 370 style=" 371 flex:0 0 35px; 372 width:35px; 373 ">10fps</span> 374 375 <input id="fpsRange" 376 type="range" 377 min="10" 378 max="100" 379 value="10" 380 step="1" 381 class="slider" 382 oninput="document.getElementById('slider_fps_v').textContent=this.value+'fps'" 383 onchange="oEMUI.fpsSld(this,'slider_fps_v')" 384 title="Processing frames per second" 385 style=" 386 flex:1 1 auto; 387 min-width:0; 388 width:0; 389 "> 390 </div> 391 392</div> 393 394<div id="all_components"></div> 395 <div id="all_components"></div> 396
396<script> 397 oCOM.POPUP.addRule("tab1.1", function(o) { o.toggle("tab1.1"); o.off("tab1.2") }); 398 oCOM.POPUP.addRule("tab1.2", function(o) { o.toggle("tab1.2"); o.off("tab1.1") }); 399 400 /* 401 * Floating feature dialogs belong to one emulator 402 * toolbox. They are suspended outside that toolbox, 403 * but return in the open/closed state the user left. 404 */ 405 oCOM.POPUP.addScope("slotConfig_popup","tab1.1"); 406 oCOM.POPUP.addScope("softwareCat_popup","tab1.2"); 407 oCOM.POPUP.addScope("surfaceMap_popup","tab1.2"); 408 oCOM.POPUP.addScope("lironSurfaceMap_popup","tab1.2"); 409 oCOM.POPUP.addScope("serialProTerminal_popup","tab1.2"); 410 411 function system_tab_update() 412 { 413 var video = apple2plus.vidObj(); 414 var charRomKey = video.getCharRomKey(); 415 416 document.getElementById("cpu_component").innerHTML = 417 _CFG_SYSCODE.A2P.CPU; 418 419 document.getElementById("all_components").innerHTML = 420 _CFG_SYSCODE.A2P.Display 421 + " Vid " 422 + "<div class=\"appbut skinny\" style=\"display:inline\">" 423 + "<i class=\"fa fa-palette\" id=\"clr\" onclick=\"this.name=(this.name?Number(this.name)+1:1);" 424 + "var m=apple2plus.monitor(this.name); this.style.color=m.color;this.title=m.name\" title=\"Color mode\"></i>" 425 + "</div>" 426 + "<div class=\"appbut skinny\" style=\"display:inline\">" 427 + "<i class=\"fa fa-eye\" id=\"render_mode\" title=\"GPU\"" 428 + " onclick=\"if(typeof(apple2plus)=='object') apple2plus.vidObj().nextMode(this);\"></i>" 429 + "</div><br>" 430 + charRomKey + " ROM " 431 + "<div class=\"appbut skinny\" style=\"display:inline\">" 432 + "<i class=\"fa fa-keyboard\" id=\"char_rom\"" 433 + " title=\"Character ROM: " + charRomKey + "\"" 434 + " onclick=\"apple2plus.vidObj().nextCharRom(this);" 435 + " system_tab_update();\"></i>" 436 + "</div>" 437 + "<i class=\"fa fa-cog\"></i>"; 438 } 439 </script>
439 440 </div> 441 </div> 442 </div> 443 444 445 <!-- SLOT SETTINGS --> 446 <div class=toolbox> 447 <div class=appbox style="text-align:left;height:92px;padding:0px 6px 0px 6px;"> 448 SLOTS<br> 449 <div id="peripheral_slots"></div> 450 </div> 451 </div> 452 453 <div id=feature_box class=toolbox style="margin:0px 0px 0px 1px;"></div> 454 455 </div> 456 </div> 457 458
458<script> 459 function slider_scroll(obj,max) 460 { 461 var pct = Math.round(1000*obj.value/max)/10; 462 463 // Plot Line 464 var canvas = slider_canvas; 465 var ctx = slider_scroll_ctx; 466 467 ctx.font = "8px Arial"; 468 //ctx.font = "8px Tahoma"; 469 //ctx.font = "8px MS Sans Serif"; 470 ctx.fillText("1A2B", 10, 50-3); 471 ctx.fillText("3C4D", 10, 58-3); 472 473 ctx.strokeStyle = '#ff0000'; 474 ctx.beginPath(); 475 ctx.moveTo(0, 40); 476 ctx.lineTo(300, 40); 477 ctx.stroke(); 478 479 //ctx.clearRect(0, 0, canvas.width, canvas.height); 480 } 481 const slider_canvas = document.getElementById("scopeCanvas"); 482 if(slider_canvas) var slider_scroll_ctx = slider_canvas.getContext("2d"); 483 484 485 oCOM.addToEventStack("onload",EMU_mem_map) 486 function EMU_mem_map() 487 { 488 var el = document.getElementById("EMU_mem_map"); 489 if(!el) return; 490 if(typeof(apple2plus)!="object" || apple2plus==null || !apple2plus.hwObj().mem_layout) return; 491 492 var linestep = 4096; 493 var cnf = {id_prefix:"m",digits:4}; 494 // Apple slot 0 is internal slot-array index 1. 495 var ramcard = apple2plus.hwObj().io.SLOT2obj(1); 496 497 var bLC = ramcard && ramcard.id?.PCODE=="MS16K" 498 && ramcard.state && ramcard.state.active 499 && ramcard.MEM_grid 500 && typeof(ramcard.build_MEM_map)=="function"; 501 el.innerHTML = 502 "<div style='display:flex;'>" 503 + oMEMGRID.build_grid(7*linestep,8,-linestep,cnf) 504 + oMEMGRID.build_grid(15*linestep,8,-linestep,cnf) 505 + (bLC ? ramcard.build_MEM_map(linestep) : "") 506 + "</div>"; 507 oMEMGRID.paint_grid(apple2plus.hwObj().mem_layout,cnf); 508 if(bLC && typeof(ramcard.paint_MEM_map)=="function") ramcard.paint_MEM_map(); 509 510 } 511 512 function isHidden(el) 513 { 514 var style = window.getComputedStyle(el); 515 return (style.display === 'none') 516 } 517 518 function onBoardPasteIO() 519 { 520 return typeof(apple2plus)=="object" && apple2plus 521 ? apple2plus.hwObj().io 522 : null; 523 } 524 525 function onBoardPasteDevice(io) 526 { 527 if(!io || typeof(io.pipeResolve)!="function") return null; 528 529 var source = io.pipeResolve("0:PASTEBO:text"); 530 return source ? source.device : null; 531 } 532 533 function onBoardPasteTarget() 534 { 535 var io = onBoardPasteIO(); 536 var pasteboard = onBoardPasteDevice(io); 537 538 return pasteboard && typeof(pasteboard.getTargetAddress)=="function" 539 ? pasteboard.getTargetAddress(io) 540 : "0:A2KBD:text"; 541 } 542 543 function onBoardPasteRefresh() 544 { 545 var target = onBoardPasteTarget(); 546 var textarea = document.getElementById("EMUboardText"); 547 548 if(textarea) 549 { 550 textarea.placeholder = "Paste here â " + target; 551 textarea.title = "Pasteboard pipe: 0:PASTEBO:text â " + target; 552 } 553 554 var io = onBoardPasteIO(); 555 var pasteboard = onBoardPasteDevice(io); 556 var captureButton = document.getElementById("TxtCapture"); 557 var captureSource = 558 pasteboard && 559 typeof(pasteboard.getCaptureSourceAddress)=="function" 560 ? pasteboard.getCaptureSourceAddress(io) 561 : null; 562 563 if(captureButton) 564 captureButton.title = 565 "Text capture: " + 566 (captureSource 567 ? captureSource + " â 0:PASTEBO:text" 568 : "Apple II 40-column display"); 569 570 return target; 571 } 572 573 var onBoardPasteRouteUnsubscribe = null; 574 575 function onBoardPasteBindRouteState() 576 { 577 var io = onBoardPasteIO(); 578 579 if(onBoardPasteRouteUnsubscribe) 580 { 581 onBoardPasteRouteUnsubscribe(); 582 onBoardPasteRouteUnsubscribe = null; 583 } 584 585 if(io && typeof(io.subscribePipeStateChange)=="function") 586 onBoardPasteRouteUnsubscribe = 587 io.subscribePipeStateChange(onBoardPasteRefresh); 588 589 return onBoardPasteRefresh(); 590 } 591 592 oCOM.addToEventStack("onload",onBoardPasteBindRouteState); 593 594 function onBoardPaste() 595 { 596 var io = onBoardPasteIO(); 597 var pasteboard = onBoardPasteDevice(io); 598 599 if(!io || typeof(io.pipeSend)!="function") 600 { 601 console.error("Apple2IO pipe router is not available"); 602 return false;
603 } 604 if(!pasteboard || typeof(pasteboard.sendText)!="function") 605 { 606 console.error("PASTEBO device is not attached to A2BO"); 607 return false; 608 } 609 610 onBoardPasteRefresh(); 611 612 return pasteboard.sendText( 613 io, 614 EMUboard.boardText.value 615 616 ); 617 } 618 619 function onTxtCapture(el) 620 { 621 622 var io = onBoardPasteIO(); 623 var pasteboard = onBoardPasteDevice(io); 624 625 if(!pasteboard || 626 typeof(pasteboard.captureText)!="function") 627 { 628 console.error("PASTEBO text capture is not available"); 629 return false; 630 } 631 632 /* 633 * TxtCap is now entirely device/pipe based: 634 * 635 * 0:A2VIDEOTXT:text -> 0:PASTEBO:text 636 * n:VIDEXTXT:text -> 0:PASTEBO:text 637 * 638 * PASTEBO selects the active semantic text source. 639 */ 640 var captured = pasteboard.captureText(io); 641 642 if(captured===null) 643 { 644 console.error("No text capture source is available"); 645 return false; 646 } 647 EMUboard.boardText.value = captured; 648 return true; 649 } 650 651 function onEMUBoardClear() { EMUboard.boardText.value = "" }; 652 function onCaptureBase(tag) 653 { 654 tag.value=tag.value=="hex"?"dec":"hex"; 655 } 656 function EMU_safeBusRead(addr) 657 { 658 var hw = apple2plus.hwObj(); 659 addr = Number(addr) & 0xFFFF; 660 661 // Prefer the hardware-level safe reader when available. 662 if(hw && typeof(hw.safe_read)=="function") 663 return hw.safe_read(addr) & 0xFF; 664 665 // Fallback for builds where Apple2Hw.safe_read() has not been added yet. 666 // This still uses the current CPU-visible RD[] callback map and raises 667 // hw.bRO so hardware dependencies can suppress soft-switch side effects. 668 if(!hw || !hw.RD || typeof(hw.lineDecode)!="function") return 0x00; 669 670 var old_bRO = hw.bRO; 671 hw.bRO = true; 672 673 try 674 { 675 var line = hw.lineDecode(addr); 676 var fn = hw.RD[line]; 677 var d8 = typeof(fn)=="function" ? fn(addr) : 0x00; 678 return (d8==null ? 0x00 : d8) & 0xFF; 679 } 680 finally 681 { 682 hw.bRO = old_bRO; 683 } 684 } 685 686 function EMU_safeBusDump(from,to) 687 { 688 var hw = apple2plus.hwObj(); 689 from = Number(from) & 0xFFFF; 690 to = Number(to) & 0xFFFF; 691 692 // Prefer the hardware-level safe dump when available. 693 if(hw && typeof(hw.safe_dump)=="function") 694 return hw.safe_dump(from,to); 695 696 var len = ((to - from) & 0xFFFF) + 1; 697 var out = new Uint8Array(len); 698 699 if(!hw || !hw.RD || typeof(hw.lineDecode)!="function") return out; 700 701 var old_bRO = hw.bRO; 702 hw.bRO = true; 703 704 try 705 { 706 for(var i=0;i<len;i++) 707 { 708 var addr = (from + i) & 0xFFFF; 709 var line = hw.lineDecode(addr); 710 var fn = hw.RD[line]; 711 var d8 = typeof(fn)=="function" ? fn(addr) : 0x00; 712 out[i] = (d8==null ? 0x00 : d8) & 0xFF; 713 } 714 } 715 finally 716 { 717 hw.bRO = old_bRO; 718 } 719 720 return out; 721 } 722 723 function onMemCapture(id1,id2) 724 { 725 var hw = apple2plus.hwObj(); 726 var id1 = document.getElementById(id1).value; 727 var id2 = document.getElementById(id2).value; 728 var base = id1.charAt(0)=="$" && id2.charAt(0)=="$"?"hex":"dec"; 729 var val = id1+"âº"+id2; 730 731 var template = {"hex":["\\$([0-9A-F]{1,4})âº\\$([0-9A-F]{1,4})",16], 732 "dec":["([0-9]{1,5})âº([0-9]{1,5})",10]}; 733 // VALIDATE 734 var re = new RegExp(template[base][0],'g'); 735 var addr_arr = val.replace(re,'$1,$2').split(","); 736 if(base=="hex") addr_arr = [Number(oCOM.base_convert(addr_arr[0],["0123456789ABCDEF"],["0123456789"])),Number(oCOM.base_convert(addr_arr[1],16,10))]; 737 738 if(addr_arr.length==2) 739 { 740 var from = Math.max(0,Math.min(0xFFFF,addr_arr[0] & 0xFFFF)); 741 var to = Math.max(0,Math.min(0xFFFF,addr_arr[1] & 0xFFFF)); 742 743 if(to < from) 744 { 745 var tmp = from; 746 from = to; 747 to = tmp; 748 } 749 750 var s = []; 751 var dump = EMU_safeBusDump(from,to); 752 753 for(var i=from;i<=to;i++) 754 { 755 var idx = Math.floor((i-from)/8); 756 if((i-from)%8==0) 757 { 758 var adr = oCOM.getHexWord(i); 759 s[idx] = adr+":"; 760 } 761 762 var b = oCOM.getHexByte(dump[i-from]); 763 s[idx] += " "+b; 764 } 765 EMUboard.boardText.value = s.join("\n"); 766 } 767 } 768 769 function onCaptureAddr(el) 770 { 771 var s = el.value.toUpperCase(); 772 var base = s.charAt(0)=="$" ? 16 : 10; 773 if(base==16) s = s.substring(1); 774 var n = oCOM.base_convert(s,[oCOM.getsymbols(base)],[oCOM.getsymbols(10)]); 775 var bValid = n!==null && Number(n)<=0xFFFF; 776 document.getElementById('MemCapture').disabled = !bValid; 777 return bValid; 778 } 779 780 function onSnapshot(canvas) 781 { 782 var canvas = document.getElementById("applescreen"); 783 const dataURL = canvas.toDataURL('image/png'); 784 document.write(dataURL); 785 } 786 787 </script>
787 788 789 <!--input id=foo type=text oninput="apple2plus.keystroke(event)" onclick=document.getElementById('foo').focus()--> 790 <canvas class="appvid" id="applescreen" width="560" height="384" tabindex="0" style="outline: none !important;padding:0px 0px 0px 0px;" onclick="EMU_audio_event_unlock();" onkeydown="EMU_audio_event_unlock();if(keys) keys.events(event);" onkeyup="if(keys) keys.events(event);"></canvas> 791 <!--/input--> 792 793<!--button onclick="onSnapshot()">SNAP</button--> 794 795 <div id="kbd" style="position:relative; z-index:1;"></div> 796 797 798 <!-- 799 <br> 800 <img src=res/appleIIplus_kbd_650.png id=kbdimg style="padding-top:0px;padding-left:105px;opacity:0.5;image-rendering:pixelated;user-drag:none;user-select:none;-moz-user-select:none;-webkit-user-drag:none;-webkit-user-select:none;-ms-user-select:none;" ontouchstart=apple2OnKeyHover_b(event) onclick=apple2OnKeyHover_b(event) onmousemove=apple2OnKeyHover_b(event) onmouseover="apple2OnKeyHover_b(event)" onmouseout="apple2OnKeyHover_b(event)"> 801 --> 802 <br> 803 <input hidden id="debug" style="width:400px" value=""> 804 </div> 805 806 <!-- 807 âââââ âââââââ âââââââ âââââââ âââ âââ ââââââ ââ âââââââ ââââââ 808 ââ ââ ââ ââ ââ ââââ ââââ ââ ââ ââ ââ ââ ââ 809 âââââââ âââââââ âââââââ âââââ ââ ââââ ââ ââââââ ââ âââââ ââââââ 810 ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ 811 ââ ââ âââââââ âââââââ âââââââ ââ ââ ââââââ âââââââ âââââââ ââ ââ 812 --> 813 <div id="tab2" style="width:1300px;user-select:none;-moz-user-select:none;-webkit-user-drag:none;-webkit-user-select:none;-ms-user-select:none;"> 814 <!--form name="tab2" class="tabdata"><input name="tabdata" value='{"SYS":"A2P"}'></form!--> 815
815<script type="text/javascript" src="res/ASM_core.js"></script>
815 816
816<script type="text/javascript" src="res/ASM_OFFLINE.js"></script>
816 817 818 <style> 819 /* 820 * ASM namespace rules 821 * Keep tab2 itself owned by the common tab system. 822 * All assembler-specific layout and styling lives below ASM_root. 823 */ 824 .ASM_root { 825 --ASM_bg: #0d1117; 826 --ASM_panel: #161b22; 827 --ASM_panel2: #0b0f14; 828 --ASM_border: #30363d; 829 --ASM_text: #e6edf3; 830 --ASM_muted: #8b949e; 831 --ASM_accent: #79b8ff; 832 --ASM_ok: #8bd17c; 833 --ASM_warn: #ffd166; 834 --ASM_bad: #ff8a8a; 835 --ASM_key: #79c0ff; 836 --ASM_string: #a5d6ff; 837 --ASM_number: #f2cc60; 838 --ASM_bool: #ffab70; 839 --ASM_monoFont: "DejaVu Sans Mono","Menlo","Consolas","Courier New",monospace; 840 --ASM_monoSize: 14px; 841 --ASM_monoLineHeight: 21px; 842 --ASM_paneBodyGap: 4px; 843 844 width: 1300px; 845 margin: 0px; 846 padding: 0px 0px 0px 0px; 847 color: var(--ASM_text); 848 box-sizing: border-box; 849 font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; 850 } 851 852 .ASM_root * { 853 box-sizing: border-box; 854 } 855 856 857 .ASM_paneHost { 858 display: grid; 859 grid-template-columns: repeat(2, minmax(0, 1fr)); 860 align-items: start; 861 gap: 10px; 862 width: calc(100% - 10px); 863 max-width: calc(100% - 10px); 864 } 865 866 .ASM_paneHost.ASM_showInclude { 867 grid-template-columns: repeat(3, minmax(0, 1fr)); 868 } 869 870 .ASM_paneHost .ASM_includePane { 871 display: none; 872 } 873 874 .ASM_paneHost.ASM_showInclude .ASM_includePane { 875 display: flex; 876 } 877 878 .ASM_pane { 879 display: flex; 880 flex-direction: column; 881 gap: var(--ASM_paneBodyGap); 882 min-width: 0; 883 max-width: 100%; 884 line-height: normal; 885 margin-top: 2px; 886 margin-bottom: 10px; 887 } 888 889 .ASM_editor, 890 .ASM_jsonMount { 891 width: 100%; 892 max-width: 100%; 893 min-height: 420px; 894 display: block; 895 border: 0; 896 outline: 0; 897 padding: 0px 30px; 898 color: #000000; 899 -webkit-text-fill-color: #000000; 900 caret-color: #000000; 901 font-family: var(--ASM_monoFont); 902 font-size: var(--ASM_monoSize); 903 font-weight: 500; 904 line-height: var(--ASM_monoLineHeight); 905 tab-size: 4; 906 white-space: pre; 907 resize: none; 908 } 909 910 .ASM_editor { 911 height: auto; 912 overflow-y: hidden; 913 overflow-x: auto; 914 } 915 916 .ASM_jsonMount { 917 height: 420px; 918 max-height: 420px; 919 overflow-y: auto; 920 overflow-x: auto; 921 resize: none; 922 scrollbar-color: rgba(0,0,0,0.30) transparent; 923 scrollbar-width: thin; 924 } 925 926 .ASM_jsonMount::placeholder { 927 color: rgba(0,0,0,0.55); 928 font: inherit; 929 line-height: inherit; 930 opacity: 1; 931 } 932 933 934 .ASM_jsonMount::-webkit-scrollbar { width: 10px; height: 10px; } 935 .ASM_jsonMount::-webkit-scrollbar-track { background: transparent; } 936 .ASM_jsonMount::-webkit-scrollbar-corner { background: transparent; } 937 .ASM_jsonMount::-webkit-scrollbar-thumb { 938 background-color: rgba(0,0,0,0.30); 939 border-radius: 10px; 940 border: 2px solid transparent; 941 background-clip: content-box; 942 } 943 944 .ASM_jsonView { 945 margin: 0; 946 padding: 0; 947 background: transparent !important; 948 white-space: pre; 949 min-width: max-content; 950 max-width: none; 951 color: inherit; 952 font: inherit; 953 line-height: inherit; 954 } 955 956 /* oCOM.JSONprettify() creates <pre class="json-view">. 957 Keep JSON output aligned with Source and Listing. */ 958 .ASM_jsonMount > pre, 959 .ASM_jsonMount .json-view, 960 .ASM_jsonMount .ASM_jsonView { 961 margin: 0; 962 padding: 0; 963 background: transparent !important; 964 color: inherit; 965 font: inherit; 966 line-height: inherit; 967 white-space: pre; 968 min-width: max-content; 969 max-width: none; 970 } 971 972 973 .ASM_jsonMount *, 974 .ASM_jsonKey, 975 .ASM_jsonString, 976 .ASM_jsonNumber, 977 .ASM_jsonBoolean, 978 .ASM_jsonNull { color: #000000 !important; } 979 980 981 .ASM_findReplaceMount { 982 display: inline-flex; 983 align-items: center;
984 min-height: 26px; 985 max-width: 430px; 986 margin: 2px 0px 0px 0px; 987 color: #000000; 988 font-family: arial; 989 font-size: 11px; 990 vertical-align: middle; 991 } 992 993 .ASM_findReplaceMount .com-pane-fr { 994 color: #000000; 995 } 996 997 .ASM_findReplaceMount input { 998 color: #000000; 999 } 1000 1001 .ASM_paneTitle { 1002 display: flex; 1003 align-items: center; 1004 justify-content: space-between; 1005 gap: 10px; 1006 min-height: 34px; 1007 margin: 0px; 1008 padding: 4px 8px; 1009 border-radius: 10px; 1010 border: 1px solid rgba(0,0,0,0.18); 1011 background: rgba(244,244,244,0.88); 1012 color: #000000; 1013 font-family: arial, sans-serif; 1014 font-size: 12px; 1015 line-height: 20px; 1016 overflow: visible; 1017 position: relative; 1018 } 1019 1020 .ASM_root .ASM_paneTitle.com-pane-title-equalized { 1021 height: var(--com-pane-title-height, auto); 1022 min-height: var(--com-pane-title-height, 34px); 1023 } 1024 1025 .ASM_paneTitle strong { 1026 color: #000000; 1027 font-size: 13px; 1028 font-weight: 700; 1029 } 1030 1031 .ASM_paneTitleMain { 1032 display: flex; 1033 align-items: center; 1034 gap: 8px; 1035 min-width: 0; 1036 } 1037 1038 .ASM_listingPresetList, 1039 .DBG_listingPresetList { 1040 display: flex; 1041 flex-flow: row wrap; 1042 align-items: flex-start; 1043 align-content: flex-start; 1044 gap: 4px; 1045 margin-top: 5px; 1046 width: 100%; 1047 min-width: 0; 1048 max-width: 100%; 1049 } 1050 1051 .ASM_listingPreset, 1052 .DBG_listingPreset { 1053 -webkit-appearance: none; 1054 appearance: none; 1055 display: inline-flex; 1056 align-items: center; 1057 justify-content: flex-start; 1058 flex: 0 0 auto; 1059 gap: 6px; 1060 min-height: 22px; 1061 padding: 2px 6px; 1062 border: 1px solid rgba(0,0,0,0.24); 1063 border-radius: 4px; 1064 background: rgba(255,255,255,0.55); 1065 color: #000000; 1066 font: 11px/1.2 arial, sans-serif; 1067 cursor: pointer; 1068 } 1069 1070 .ASM_listingPreset:hover, 1071 .ASM_listingPreset:focus, 1072 .DBG_listingPreset:hover, 1073 .DBG_listingPreset:focus { 1074 border-color: #0078d4; 1075 outline: none; 1076 } 1077 1078 .ASM_listingPreset i, 1079 .DBG_listingPreset i { 1080 width: 14px; 1081 text-align: center; 1082 } 1083 1084 .ASM_paneInfo { 1085 color: rgba(0,0,0,0.70); 1086 font-family: arial, sans-serif; 1087 font-size: 11px; 1088 white-space: nowrap; 1089 } 1090 1091 /* Explicit source dependencies loaded for S-C .IN directives. */ 1092 .ASM_includeLabelWrap { 1093 display: flex; 1094 flex-wrap: wrap; 1095 align-items: flex-start; 1096 gap: 2px 3px; 1097 width: 100%; 1098 min-width: 0; 1099 max-width: 100%; 1100 margin: 2px 0px; 1101 } 1102 1103 .ASM_includeLabelWrap .appbut.label { 1104 float: none !important; 1105 display: inline-flex; 1106 align-items: center; 1107 gap: 3px; 1108 width: auto !important; 1109 min-width: 0; 1110 height: auto; 1111 min-height: 16px; 1112 line-height: 14px; 1113 margin: 0px; 1114 border-radius: 3px; 1115 background: rgba(128,128,128,0.08); 1116 border: 1px solid black; 1117 color: #000000; 1118 font-family: arial; 1119 font-size: 11px; 1120 padding: 0px 2px; 1121 box-sizing: border-box; 1122 white-space: nowrap; 1123 cursor: pointer; 1124 } 1125 1126 .ASM_includeLabelWrap .appbut.label.ASM_includeSelected { 1127 outline: 2px solid #0078d4; 1128 outline-offset: 1px; 1129 background: rgba(0,120,212,0.10); 1130 } 1131 1132 .ASM_includeLabelWrap .ASM_includeRemove { 1133 float: none !important; 1134 display: inline-flex; 1135 align-items: center; 1136 justify-content: center; 1137 width: 14px; 1138 min-width: 14px; 1139 height: 14px; 1140 min-height: 14px; 1141 line-height: 12px; 1142 margin: 0px; 1143 padding: 0px; 1144 cursor: pointer; 1145 } 1146 1147 #ASM_diagnosticsButton[hidden], 1148 #ASM_outputPopupBack[hidden] { 1149 display: none !important; 1150 } 1151 1152 .ASM_outputPopupBack { 1153 position: fixed; 1154 inset: 0; 1155 z-index: 2200; 1156 display: flex; 1157 align-items: center; 1158 justify-content: center; 1159 padding: 28px; 1160 background: rgba(0,0,0,0.38); 1161 } 1162 1163 .ASM_outputPopup { 1164 display: flex; 1165 flex-direction: column; 1166 width: min(1100px,calc(100vw - 56px)); 1167 height: min(760px,calc(100vh - 56px)); 1168 min-width: 360px; 1169 min-height: 260px; 1170 border: 1px solid rgba(0,0,0,0.42); 1171 border-radius: 10px; 1172 background: #f4f4f4; 1173 box-shadow: 0px 8px 30px rgba(0,0,0,0.42); 1174 overflow: hidden; 1175 } 1176 1177 .ASM_outputPopupHeader { 1178 display: flex; 1179 align-items: center; 1180 justify-content: space-between; 1181 gap: 10px; 1182 min-height: 38px; 1183 padding: 5px 8px; 1184 border-bottom: 1px solid rgba(0,0,0,0.22); 1185 color: #000000; 1186 font: 12px/20px arial,sans-serif; 1187 } 1188 1189 .ASM_outputPopupHeader strong { font-size: 13px; } 1190 1191 .ASM_outputPopupActions { 1192 display: inline-flex; 1193 align-items: center; 1194 gap: 5px; 1195 } 1196 1197 .ASM_outputPopupText { 1198 display: block; 1199 flex: 1 1 auto; 1200 width: 100%; 1201 min-height: 0; 1202 margin: 0px; 1203 padding: 10px 14px; 1204 border: 0px; 1205 outline: 0px; 1206 resize: none; 1207 overflow: auto; 1208 background: #ffffff; 1209 color: #000000; 1210 -webkit-text-fill-color: #000000; 1211 font-family: var(--ASM_monoFont); 1212 font-size: var(--ASM_monoSize); 1213 font-weight: 500; 1214 line-height: var(--ASM_monoLineHeight); 1215 white-space: pre; 1216 tab-size: 4; 1217 user-select: text !important; 1218 -moz-user-select: text !important; 1219 -webkit-user-select: text !important;
1220 -ms-user-select: text !important; 1221 } 1222 1223 .ASM_root.ASM_paneDark .ASM_outputPopup { 1224 background: #111820; 1225 border-color: #30363d; 1226 color: #e6edf3; 1227 } 1228 1229 .ASM_root.ASM_paneDark .ASM_outputPopupHeader { 1230 color: #e6edf3; 1231 border-color: #30363d; 1232 } 1233 1234 .ASM_root.ASM_paneDark .ASM_outputPopupText { 1235 background: #000000; 1236 color: #e6edf3; 1237 -webkit-text-fill-color: #e6edf3; 1238 } 1239 1240 .ASM_paneTitleButton { 1241 -webkit-appearance: none; 1242 appearance: none; 1243 border: 1px solid rgba(0,0,0,0.35); 1244 border-radius: 4px; 1245 background: #f4f4f4; 1246 color: #000000; 1247 padding: 2px 6px; 1248 font: 11px/1.2 arial, sans-serif; 1249 cursor: pointer; 1250 } 1251 1252 .ASM_paneTitleButton:hover { 1253 border-color: #0078d4; 1254 } 1255 1256 .ASM_paneTitle .ASM_findReplaceMount { 1257 margin: 0px; 1258 max-width: 430px; 1259 min-height: 24px; 1260 } 1261 1262 .ASM_root.ASM_headerDark .ASM_paneTitle { 1263 background: #111820; 1264 border-color: #30363d; 1265 color: #e6edf3; 1266 } 1267 1268 .ASM_root.ASM_headerDark .ASM_paneTitle strong { 1269 color: #e6edf3; 1270 } 1271 1272 .ASM_root.ASM_headerDark .ASM_paneInfo { 1273 color: #8b949e; 1274 } 1275 1276 .ASM_root.ASM_headerDark { 1277 background: #0b0f14; 1278 border-color: #30363d; 1279 color: #e6edf3; 1280 } 1281 1282 .ASM_root.ASM_headerDark .ASM_paneTitleButton { 1283 background: #21262d; 1284 border-color: #30363d; 1285 color: #e6edf3; 1286 } 1287 1288 .ASM_root .com-pane-header-default-widget { 1289 gap: 4px; 1290 } 1291 1292 .ASM_root .com-pane-fr-wrap { 1293 width: 100%; 1294 max-width: 100%; 1295 min-height: 420px; 1296 padding: 0px; 1297 overflow: hidden; 1298 } 1299 1300 .ASM_root .com-pane-fr-wrap textarea { 1301 background-color: transparent !important; 1302 background-image: none !important; 1303 color: #000000 !important; 1304 -webkit-text-fill-color: #000000 !important; 1305 caret-color: #000000; 1306 } 1307 1308 #ASM_sourcePane, 1309 .ASM_root #ASM_sourcePane, 1310 .ASM_root .com-pane-fr-wrap #ASM_sourcePane { 1311 color: #000000 !important; 1312 -webkit-text-fill-color: #000000 !important; 1313 caret-color: #000000; 1314 } 1315 1316 /* Let the ASM dot-matrix wrapper background remain visible through the 1317 find/replace overlay. Do not blanket-clear every com-pane-fr element, 1318 because the match spans also use com-pane-fr-* classes. */ 1319 .ASM_root .com-pane-fr-wrap > .com-pane-fr-overlay { 1320 background-color: transparent !important; 1321 background-image: none !important; 1322 } 1323 1324 /* Keep the textarea itself transparent so the wrapper pattern shows through. */ 1325 .ASM_root .com-pane-fr-wrap > textarea { 1326 background-color: transparent !important; 1327 background-image: none !important; 1328 } 1329 1330 /* Restore actual find/replace highlights. */ 1331 .ASM_root .com-pane-fr-wrap .com-pane-fr-selection { 1332 background-color: rgba(190,190,190,.35) !important; 1333 } 1334 1335 .ASM_root .com-pane-fr-wrap .com-pane-fr-match { 1336 background-color: rgba(255,210,70,.28) !important; 1337 } 1338 1339 .ASM_root .com-pane-fr-wrap .com-pane-fr-current { 1340 background-color: rgba(255,185,45,.85) !important; 1341 color: #000000 !important; 1342 } 1343 1344 1345 /* Keep the source pane rendering identical to the listing pane, even after 1346 PANE.bindFindReplace() wraps ASM_sourcePane and injects its own CSS. */ 1347 .ASM_root .com-pane-fr-wrap, 1348 .ASM_root .com-pane-fr-wrap textarea,
1349 .ASM_root .com-pane-fr-wrap pre, 1350 .ASM_root .com-pane-fr-wrap code, 1351 .ASM_root .com-pane-fr-wrap .com-pane-fr-overlay, 1352 .ASM_root .com-pane-fr-wrap .com-pane-fr-layer, 1353 .ASM_root .com-pane-fr-wrap .com-pane-fr-text, 1354 .ASM_root .com-pane-fr-wrap .com-pane-fr-match-layer { 1355 font-family: var(--ASM_monoFont) !important; 1356 font-size: var(--ASM_monoSize) !important; 1357 font-weight: 500 !important; 1358 line-height: var(--ASM_monoLineHeight) !important; 1359 letter-spacing: normal !important; 1360 font-kerning: none; 1361 font-variant-ligatures: none; 1362 text-rendering: auto; 1363 } 1364 1365 .ASM_root .com-pane-fr-wrap #ASM_sourcePane { 1366 font-family: var(--ASM_monoFont) !important; 1367 font-size: var(--ASM_monoSize) !important; 1368 font-weight: 500 !important; 1369 line-height: var(--ASM_monoLineHeight) !important; 1370 } 1371 1372 1373 /* Match the effective Source pane inset to the Listing pane. 1374 The listing textarea gets its final padding from dotmatrix_bck
1375 (0px top/bottom, 30px left/right). After find/replace wraps 1376 ASM_sourcePane, the wrapper/overlay stack must not add another 1377 inset or fall back to the generic 14px editor padding. */ 1378 .ASM_root .com-pane-fr-wrap { 1379 padding: 0px !important; 1380 } 1381 1382 .ASM_root .com-pane-fr-wrap #ASM_sourcePane, 1383 .ASM_root .com-pane-fr-wrap textarea, 1384 .ASM_root .com-pane-fr-wrap .com-pane-fr-overlay, 1385 .ASM_root .com-pane-fr-wrap .com-pane-fr-layer, 1386 .ASM_root .com-pane-fr-wrap .com-pane-fr-text, 1387 .ASM_root .com-pane-fr-wrap .com-pane-fr-match-layer { 1388 padding: 0px 30px !important; 1389 box-sizing: border-box; 1390 } 1391 1392/* Source pane: PANE.bindFindReplace() adds wrapper/overlay layers. 1393 Keep those transparent so the shared dotmatrix_bck skin remains visible. */ 1394.ASM_root .com-pane-fr-wrap textarea, 1395.ASM_root .com-pane-fr-wrap > .com-pane-fr-overlay, 1396.ASM_root .com-pane-fr-wrap .com-pane-fr-layer, 1397.ASM_root .com-pane-fr-wrap .com-pane-fr-text, 1398.ASM_root .com-pane-fr-wrap .com-pane-fr-match-layer { 1399 background-color: transparent !important; 1400 background-image: none !important; 1401} 1402 1403/* JSON pane: the generated <pre> must not cover ASM_jsonMount's background. */ 1404.ASM_jsonMount .ASM_jsonView { 1405 background: transparent !important; 1406} 1407 1408/* Source pane light mode: 1409 PANE.bindFindReplace() wraps the source textarea in .com-pane-fr-wrap. 1410 The wrapper can otherwise keep/receive PANE's plain dark background. 1411 Put the shared COM_MAIN.css dotmatrix_bck skin on the source section and 1412 let the generated wrapper inherit that skin. This avoids duplicating the 1413 base64 background image in index.html. */ 1414.ASM_root:not(.ASM_paneDark) .ASM_pane.ASM_sourcePaneHost .com-pane-fr-wrap.dotmatrix_bck { 1415 background-color: inherit !important; 1416 background-image: inherit !important; 1417 background-size: inherit !important; 1418 background-attachment: inherit !important; 1419 background-repeat: inherit !important; 1420} 1421 1422 1423/* Theme the pane interiors, not only the pane headers. 1424 Light mode uses the shared .dotmatrix_bck pattern from COM_MAIN.css. 1425 Dark mode deliberately removes the image and uses plain black. */ 1426.ASM_root.ASM_paneDark .dotmatrix_bck, 1427.ASM_root.ASM_paneDark .com-pane-fr-wrap { 1428 background-color: #000000 !important; 1429 background-image: none !important; 1430 color: #e6edf3 !important; 1431 -webkit-text-fill-color: #e6edf3 !important; 1432 caret-color: #e6edf3; 1433} 1434 1435.ASM_root.ASM_paneDark .ASM_editor, 1436.ASM_root.ASM_paneDark .ASM_jsonMount, 1437.ASM_root.ASM_paneDark .com-pane-fr-wrap textarea, 1438.ASM_root.ASM_paneDark .com-pane-fr-overlay { 1439 color: #e6edf3 !important; 1440 -webkit-text-fill-color: #e6edf3 !important; 1441 caret-color: #e6edf3; 1442} 1443 1444.ASM_root.ASM_paneDark .ASM_jsonMount::placeholder { 1445 color: rgba(230,237,243,0.58); 1446 opacity: 1; 1447} 1448 1449.ASM_root.ASM_paneDark .ASM_jsonMount *, 1450.ASM_root.ASM_paneDark .ASM_jsonKey, 1451.ASM_root.ASM_paneDark .ASM_jsonString, 1452.ASM_root.ASM_paneDark .ASM_jsonNumber, 1453.ASM_root.ASM_paneDark .ASM_jsonBoolean, 1454.ASM_root.ASM_paneDark .ASM_jsonNull { 1455 color: #e6edf3 !important; 1456} 1457 1458/* Beat the earlier #ASM_sourcePane light-mode rule, which also uses 1459 !important and otherwise keeps the source text black in dark mode. */ 1460.ASM_root.ASM_paneDark #ASM_sourcePane, 1461.ASM_root.ASM_paneDark .com-pane-fr-wrap #ASM_sourcePane { 1462 color: #e6edf3 !important; 1463 -webkit-text-fill-color: #e6edf3 !important; 1464 caret-color: #e6edf3 !important; 1465} 1466 1467.ASM_root.ASM_paneDark .com-pane-fr-current { 1468 color: #000000 !important; 1469 -webkit-text-fill-color: #000000 !important; 1470} 1471 1472/* 1473 * The find/replace overlay should paint highlights only. ASM_sourcePane is 1474 * deliberately visible so Source and Listing are both rendered by a native 1475 * textarea with exactly the same font metrics. Leaving the overlay glyphs 1476 * visible draws every source character twice and produces the heavier, 1477 * slightly displaced text visible in the regression screenshot. 1478 */ 1479.ASM_root .com-pane-fr-wrap > .com-pane-fr-overlay, 1480.ASM_root .com-pane-fr-wrap > .com-pane-fr-overlay * { 1481 color: transparent !important; 1482 -webkit-text-fill-color: transparent !important; 1483} 1484 1485 </style> 1486 1487 <div id="ASM_root" class="ASM_root"> 1488 1489 <div style="height:30px;padding:0px 90px 0px 0px;"> 1490 <button class=appbut value="Settings" onclick="javascript:oCOM.POPUP.runRule('tab2.1');" title="Settings"><i class="fa fa fa-sliders-h"></i></button> 1491 <button class=appbut value="Tools" onclick="javascript:oCOM.POPUP.runRule('tab2.2');" title="Tools"><i class="fa fa-toolbox"></i></button> 1492 </div> 1493 1494 <!-- ASSEMBLER SETTINGS --> 1495 <div class="popupHost" id="tab2.1" hidden> 1496 <div class=toolbox> 1497 <div class=appbox title="Assembler settings" style="min-height:63px;height:auto;padding:0px 6px 0px 6px;color:black;text-align: left;"> 1498 COMPILER<br> 1499 <label title="Legacy double-quote mode: when checked, double-quoted character literals force Apple II high-bit text. When unchecked, double quotes compile like single quotes."> 1500 Quote legacy <input id="ASM_dQuoteLegacyInput" type="checkbox"> 1501 </label> 1502 </div> 1503 </div> 1504 1505 <div class=toolbox> 1506 <div class=appbox title="Assembler settings" style="min-height:63px;height:auto;padding:0px 6px 0px 6px;color:black;text-align: left;"> 1507 LISTING<br> 1508 <label> 1509 Columns <input id="ASM_settingsListingColumnsInput" type="text" value="{adr:0,code:6,lin:15,num:{col:23,dig:6},lbl:30,ins:40,opr:45,com:61}" title="Listing columns: address, code bytes, jump lines, num:{col,dig}, label, instruction, operand, compatible assemblers for pragmas, comment" spellcheck="false" style="font-family:monospace;font-size:11px"> 1510 </label> 1511 <div class="ASM_listingPresetList" aria-label="Listing column presets"> 1512 <button type="button" class="ASM_listingPreset" data-listing-preset
1512="default" title="Use default listing columns and assemble"> 1513 <span>default</span> 1514 <i class="fa fa-table"></i> 1515 </button> 1516 <button type="button" class="ASM_listingPreset" data-listing-preset="print" title="Use print listing columns and assemble"> 1517 <span>print</span> 1518 <i class="fa fa-table"></i> 1519 </button> 1520 <button type="button" class="ASM_listingPreset" data-listing-preset="compile" title="Use compact compile listing columns and assemble"> 1521 <span>compile</span> 1522 <i class="fa fa-table"></i> 1523 </button> 1524 </div> 1525 </div> 1526 </div> 1527 1528 1529 </div> 1530 1531 <!-- ASSEMBLER TOOLS --> 1532 <div class="popupHost" id="tab2.2" hidden> 1533 <div class=toolbox> 1534 <form name="ASMboard" onsubmit="return false"> 1535 <textarea id="ASM_boardText" cols=40 rows=4 style="float:left" placeholder="Byte-code output" name="boardText"></textarea><br> 1536 <input title="Send byte code to emulator" id="ASM_toEmulatorButton" class="ASM_toEmulatorButton" type="button" value="to emulator"> 1537 <input title="Send byte code to debugger" id="ASM_toDebuggerButton" class="ASM_toDebuggerButton" type="button" value="to debugger"> 1538 <input title="Copy byte code to clipboard" type="button" id="ASM_copyByteCodeButton" value="copy"> 1539 <input title="Clear byte code" type="button" value="Clear" onclick="javascript:ASM_clearBoard()"> 1540 <span id="ASM_byteCodeInfo" style="color:#000000;font-family:arial;font-size:11px;padding:0px 6px;white-space:nowrap;">0 bytes</span> 1541 <input type="button" value="X" onclick="javascript:oCOM.POPUP.off('tab2.2')"> 1542 </form> 1543 </div> 1544 1545 <!-- ASM ACTION BUTTONS --> 1546 <div class=toolbox> 1547 <!--button id="ASM_assembleButton" class=appbut type="button" title="Assemble"><i class="fa fa-cogs"></i></button--> 1548 <button id="ASM_toggleIncludeButton" class="appbut greyed" type="button" title="No include file loaded" disabled><i class="fa fa-file-code"></i></button> 1549 <br> 1550 <button id="ASM_assembleButton" class=appbut type="button" data-pane-default-act="assemble" title="Assemble"><i class="fa fa-play"></i></button> 1551 </div> 1552 1553 <!-- ASM FILE TOOLBOX --> 1554 <div class=toolbox> 1555 <div class=appbox style="min-height:63px;height:auto;padding:0px 6px 0px 6px;"> 1556 <div class=appbut style="color:black"> 1557 load include files<input id="ASM_includeFileInput" style="display:inline-block" type="file" multiple accept=".INC,.inc,.S,.s,.ASM,.asm,.txt,.src,.a65,.mac,text/plain"> 1558 </div> 1559 <div id="ASM_includeLabelWrap" class="ASM_includeLabelWrap"></div> 1560 <div class=appbut style="color:black"> 1561 load assembler code<input id="ASM_fileInput" style="display:inline-block" type="file" accept=".S,.s,.ASM,.asm,.INC,.inc,.txt,text/plain"> 1562 </div> 1563 <div class="appbut" style="color:black" 1564 title="Start address used when byte-encoding the source pane into monitor byte code"> 1565 byte encode source 1566 <input id="ASM_sourceByteCodeStartInput" 1567 type="text" 1568 value="$6000" 1569 maxlength="6" 1570 spellcheck="false" 1571 style="width:64px;min-height:20px;font:12px/1.2 courier,monospace;text-transform:uppercase;box-sizing:border-box;"> 1572 <div id="ASM_encodeSourceButton" 1573 class="appbut mini" 1574 style="color:black" 1575 title="Byte encode source at this address" 1576 role="button" 1577 tabindex="0"><i class="fa fa-scroll"></i></div>
1578 <span id="ASM_sourceByteCodeRangeInfo" 1579 style="color:#000000;font-family:arial;font-size:11px;padding:0px 4px;white-space:nowrap;">A$6000 L$0000</span> 1580 </div> 1581 <!-- Status is logged through ASM_setStatus(); find/replace lives in the Source pane header. --> 1582 </div> 1583 </div> 1584 1585 <!-- ASM SOURCE CATALOG TOOLBOX --> 1586 <div class=toolbox> 1587 <div class=appbox style="text-align:left;height:63px;padding:0px 6px 0px 6px;"> 1588 <button id="ASM_sourceCatalogButton" 1589 class=appbut 1590 type="button" 1591 title="Source Code Catalog" 1592 onclick="ASM_sourceCatalogOpen();event.stopPropagation();"> 1593 <i class="fa fa-cat"></i> 1594 </button> 1595 </div> 1596 </div> 1597 1598 </div> 1599 1600 <div id="ASM_paneHost" class="ASM_paneHost"> 1601 1602 <section class="ASM_pane ASM_sourcePaneHost dotmatrix_bck"> 1603 <div class="ASM_paneTitle"> 1604 <span class="ASM_paneTitleMain"> 1605 <strong>Source</strong> 1606 <input id="ASM_sourceFileNameInput" 1607 type="text" 1608 value="sample.S" 1609 title="Source filename" 1610 spellcheck="false" 1611 style="width:150px;max-width:22vw;border:1px solid rgba(0,0,0,0.25);border-radius:4px;background:#ffffff;color:#000000;padding:2px 5px;font:11px/1.2 courier,monospace;"> 1612 <button id="ASM_downloadSourceButton" 1613 class="ASM_paneTitleButton" 1614 type="button" 1615 title="Download source"><i class="fa fa-cloud-download-alt"></i></button> 1616 </span> 1617 <span id="ASM_sourceFindReplaceMount" class="ASM_findReplaceMount pane-title-search-host"></span> 1618 </div> 1619 <textarea id="ASM_sourcePane" class="ASM_editor dotmatrix_bck" spellcheck="false" wrap="off" placeholder="Source code">ORG $6000 1620 LDX #0 1621Label1 TXA 1622 STA $0400,X 1623 LDA #1 1624 STA $D800,X 1625 INX 1626 BNE Label1 1627 RTS 1628.END</textarea> 1629 </section> 1630 <section class="ASM_pane"> 1631 <div class="ASM_paneTitle"> 1632 <span class="ASM_paneTitleMain"> 1633 <strong>Listing</strong> 1634 <input id="ASM_symbolFileNameInput" 1635 type="text" 1636 value="sample.SYM" 1637 title="Symbol filename" 1638 spellcheck="false" 1639 style="width:150px;max-width:22vw;border:1px solid rgba(0,0,0,0.25);border-radius:4px;background:#ffffff;color:#000000;padding:2px 5px;font:11px/1.2 courier,monospace;"> 1640 <button id="ASM_exportSymbolsButton" 1641 class="ASM_paneTitleButton" 1642 type="button" 1643 title="Download symbols" 1644 onclick="ASM_exportSymbols()"> 1645 <i class="fa fa-cloud-download-alt"></i> 1646 </button> 1647 1648 </span> 1649 1650 1651 <span class="ASM_paneTitleMain"> 1652 1653 <div title="Send byte code to emulator" id="ASM_toEmulatorHeaderButton" class="ASM_toEmulatorButton appbut skinny"><i class="fa fa-apple-alt"></i> <i class="fa fa-caret-left"></i></div> | 1654 <div title="Send byte code to debugger" id="ASM_toDebuggerHeaderButton" class="ASM_toDebuggerButton appbut skinny"><i class="fa fa-caret-right"></i> <i class="fa fa-bug"></i></div> 1655 1656 <button type="button" id="ASM_diagnosticsButton" class="appbut skinny" title="No assembly diagnostics" hidden disabled> 1657 <i class="fa fa-exclamation-triangle"></i> 1658 </button> 1659 <button type="button" id="ASM_jsonPopupButton" class="appbut skinny" title="Show JSON output"> 1660 <i class="fa fa-stethoscope"></i> 1661 </button> 1662 1663 <!--button id="ASM_exportSymbolsButton" 1664 class="ASM_paneTitleButton" 1665 type="button" 1666 title="Export symbols" 1667 onclick="ASM_exportSymbols()"> 1668 <i class="fa fa-download"></i> 1669 </button--> 1670 </span> 1671 </div> 1672 <textarea id="ASM_listingPane" class="ASM_editor dotmatrix_bck" readonly="readonly" spellcheck="false" wrap="off" placeholder="Listing"></textarea> 1673 </section> 1674 1675 <section id="ASM_includePane" class="ASM_pane ASM_includePane"> 1676 <div class="ASM_paneTitle"> 1677 <strong>Include</strong>
1678 <span class="ASM_paneTitleMain"> 1679 <span id="ASM_includeInfo" class="ASM_paneInfo"></span> 1680 <div id="ASM_copyIncludeButton" class="ASM_paneTitleButton" title="Copy include file"><i class="fa fa-copy"></i></div> 1681 <button type="button" id="ASM_closeIncludeButton" class="appbut skinny" title="Hide include file"> 1682 <i class="fa fa-times"></i> 1683 </button> 1684 </span> 1685 </div> 1686 <textarea id="ASM_includeMount" 1687 class="ASM_jsonMount dotmatrix_bck" 1688 aria-label="Include file source" 1689 readonly="readonly" 1690 spellcheck="false" 1691 wrap="off" 1692 placeholder="Include file source"></textarea> 1693 </section> 1694 1695 </div> 1696 1697 <div id="ASM_outputPopupBack" class="ASM_outputPopupBack" hidden aria-hidden="true"> 1698 <section class="ASM_outputPopup" role="dialog" aria-modal="true" aria-labelledby="ASM_outputPopupTitle"> 1699 <div class="ASM_outputPopupHeader"> 1700 <strong id="ASM_outputPopupTitle">JSON output</strong> 1701 <span class="ASM_outputPopupActions"> 1702 <button type="button" id="ASM_outputPopupCopy" class="appbut skinny" title="Copy"><i class="fa fa-copy"></i></button> 1703 <button type="button" id="ASM_outputPopupClose" class="appbut skinny" title="Close"><i class="fa fa-times"></i></button> 1704 </span> 1705 </div> 1706 <textarea id="ASM_outputPopupText" class="ASM_outputPopupText" readonly="readonly" spellcheck="false" wrap="off"></textarea> 1707 </section> 1708 </div> 1709 1710 <section id="ASM_manualPane" class="CRT_TV DOUBL_TV" style="width:100%;max-width:100%;margin:10px 0px 0px 0px;padding:0px;border-radius:10px;overflow:hidden;"> 1711 <div style="height:120px"></div> 1712 <div name="codefield" id="ASM_manual" class="markdown_mon" style="border:1px solid transparent;width:600px;height:420px;background-color:transparent;resize:none;overflow-y:auto;"></div> 1713 <div style="height:300px"></div> 1714 </section> 1715 </div> 1716 1717
1717<script type="text/javascript"> 1718 var ASM_currentFileName = "sample.S"; 1719 var ASM_includeFiles = []; 1720 var ASM_nextIncludeFileId = 1; 1721 var ASM_selectedIncludeId = null; 1722 1723 /* ASM INCLUDE FILES BEGIN */ 1724 function ASM_includeNameKey(name) 1725 { 1726 return String(name || "").trim().toUpperCase(); 1727 } 1728 1729 function ASM_findIncludeIndexByName(name) 1730 { 1731 var key = ASM_includeNameKey(name); 1732 for(var i=0;i<ASM_includeFiles.length;i++) 1733 if(ASM_includeNameKey(ASM_includeFiles[i].name) === key) return i; 1734 return -1; 1735 } 1736 1737 function ASM_findIncludeIndexById(id) 1738 { 1739 id = String(id || ""); 1740 for(var i=0;i<ASM_includeFiles.length;i++) 1741 if(String(ASM_includeFiles[i].id) === id) return i; 1742 return -1; 1743 } 1744 1745 function ASM_currentIncludeFile() 1746 { 1747 var index = ASM_findIncludeIndexById(ASM_selectedIncludeId); 1748 return index >= 0 ? ASM_includeFiles[index] : null; 1749 } 1750 1751 function ASM_buildIncludeMap() 1752 { 1753 var includes = {}; 1754 for(var i=0;i<ASM_includeFiles.length;i++) 1755 { 1756 var file = ASM_includeFiles[i]; 1757 includes[file.name] = { 1758 source:String(file.source == null ? "" : file.source), 1759 sourceName:file.sourceName || file.name 1760 }; 1761 } 1762 return includes; 1763 } 1764 1765 function ASM_updateIncludePane() 1766 { 1767 var file = ASM_currentIncludeFile(); 1768 var mount = ASM_el("ASM_includeMount"); 1769 var info = ASM_el("ASM_includeInfo"); 1770 1771 if(mount) 1772 { 1773 mount.value = file ? String(file.source == null ? "" : file.source) : ""; 1774 mount.scrollTop = 0; 1775 mount.scrollLeft = 0; 1776 } 1777 1778 if(info) 1779 { 1780 info.textContent = file ? file.name : ""; 1781 info.title = file ? (file.sourceName || file.name) : ""; 1782 } 1783 } 1784 1785 function ASM_syncIncludeToggleButton() 1786 { 1787 var host = ASM_el("ASM_paneHost"); 1788 var btn = ASM_el("ASM_toggleIncludeButton"); 1789 if(!host || !btn) return; 1790 1791 var hasIncludes = ASM_includeFiles.length > 0; 1792 if(!hasIncludes) host.classList.remove("ASM_showInclude"); 1793 1794 btn.disabled = !hasIncludes; 1795 btn.classList.toggle("greyed",!hasIncludes); 1796 btn.title = !hasIncludes 1797 ? "No include file loaded" 1798 : (host.classList.contains("ASM_showInclude") ? "Hide include file" : "Show include file"); 1799 } 1800 1801 function ASM_selectIncludeFile(id,showPane) 1802 { 1803 var index = ASM_findIncludeIndexById(id); 1804 if(index < 0 && ASM_includeFiles.length) index = 0; 1805 1806 ASM_selectedIncludeId = index >= 0 ? String(ASM_includeFiles[index].id) : null; 1807 ASM_updateIncludePane(); 1808 ASM_renderIncludeLabels(); 1809 1810 if(showPane !== false && index >= 0) ASM_toggleIncludePane(true); 1811 return index >= 0; 1812 } 1813 1814 function ASM_renderIncludeLabels() 1815 { 1816 var wrap = ASM_el("ASM_includeLabelWrap"); 1817 if(!wrap) return; 1818 wrap.replaceChildren(); 1819 1820 for(var i=0;i<ASM_includeFiles.length;i++) 1821 { 1822 var file = ASM_includeFiles[i]; 1823 var chip = document.createElement("div"); 1824 chip.className = "appbut label" + (String(file.id) === String(ASM_selectedIncludeId) ? " ASM_includeSelected" : ""); 1825 chip.title = "Show " + (file.sourceName || file.name); 1826 chip.setAttribute("role","button"); 1827 chip.setAttribute("tabindex","0"); 1828 chip.setAttribute("data-include-id",file.id); 1829 chip.appendChild(document.createTextNode(file.name + " ")); 1830 chip.onclick = function(ev) 1831 { 1832 ev.preventDefault(); 1833 ASM_selectIncludeFile(this.getAttribute("data-include-id"),true); 1834 }; 1835 chip.onkeydown = function(ev) 1836 { 1837 if(ev.key == "Enter" || ev.key == " ") 1838 { 1839 ev.preventDefault(); 1840 ASM_selectIncludeFile(this.getAttribute("data-include-id"),true); 1841 } 1842 }; 1843 1844 var remove = document.createElement("div"); 1845 remove.className = "appbut skinny ASM_includeRemove"; 1846 remove.title = "Remove include file"; 1847 remove.textContent = "x"; 1848 remove.setAttribute("data-include-id",file.id); 1849 remove.onclick = function(ev) 1850 { 1851 ev.stopPropagation(); 1852 ASM_removeIncludeFile(this.getAttribute("data-include-id")); 1853 }; 1854 1855 chip.appendChild(remove); 1856 wrap.appendChild(chip); 1857 } 1858 1859 ASM_syncIncludeToggleButton(); 1860 } 1861 1862 function ASM_addIncludeText(name,text,sourceName) 1863 { 1864 name = String(name || "include.INC").trim() || "include.INC"; 1865 var index = ASM_findIncludeIndexByName(name); 1866 var replaced = index >= 0; 1867 1868 if(replaced) 1869 { 1870 ASM_includeFiles[index].name = name; 1871 ASM_includeFiles[index].source = String(text == null ? "" : text); 1872 ASM_includeFiles[index].sourceName = sourceName || name; 1873 } 1874 else 1875 { 1876 ASM_includeFiles.push({ 1877 id:"asminc_" + (ASM_nextIncludeFileId++), 1878 name:name, 1879 sourceName:sourceName || name, 1880 source:String(text == null ? "" : text) 1881 }); 1882 } 1883 1884 if(!ASM_currentIncludeFile() && ASM_includeFiles.length) 1885 ASM_selectedIncludeId = String(ASM_includeFiles[0].id); 1886 1887 ASM_updateIncludePane(); 1888 ASM_renderIncludeLabels(); 1889 return {name:name,replaced:replaced}; 1890 } 1891 1892 function ASM_removeIncludeFile(id) 1893 { 1894 id = String(id || ""); 1895 var removed = null; 1896 var host = ASM_el("ASM_paneHost"); 1897 var wasOpen = !!(host && host.classList.contains("ASM_showInclude")); 1898 var removedSelected = String(ASM_selectedIncludeId || "") === id; 1899 1900 ASM_includeFiles = ASM_includeFiles.filter(function(file) 1901 { 1902 if(String(file.id) === id) { removed = file; return false; } 1903 return true; 1904 }); 1905 1906 if(!removed) return false;
1907 1908 if(removedSelected || !ASM_currentIncludeFile()) 1909 ASM_selectedIncludeId = ASM_includeFiles.length ? String(ASM_includeFiles[0].id) : null; 1910 1911 ASM_updateIncludePane(); 1912 ASM_renderIncludeLabels(); 1913 1914 if(!ASM_includeFiles.length) ASM_toggleIncludePane(false); 1915 else if(wasOpen) ASM_toggleIncludePane(true); 1916 1917 ASM_clearOutputs( 1918 "Removed include " + removed.name + ". Reassemble to refresh the listing and byte code.", 1919 "warn" 1920 ); 1921 return true; 1922 } 1923 1924 async function ASM_loadIncludeFiles(fileList) 1925 { 1926 var files = Array.prototype.slice.call(fileList || []); 1927 if(!files.length) return {loaded:0,replaced:0}; 1928 1929 var loaded = 0; 1930 var replaced = 0; 1931 1932 for(var i=0;i<files.length;i++) 1933 { 1934 var file = files[i]; 1935 var result = ASM_addIncludeText( 1936 file.name || "include.INC", 1937 await file.text(), 1938 file.name || "include.INC" 1939 ); 1940 if(result.replaced) replaced++; else loaded++; 1941 } 1942 1943 var message = "Loaded " + loaded + " include file" + (loaded === 1 ? "" : "s"); 1944 if(replaced) message += "; replaced " + replaced + " existing include" + (replaced === 1 ? "" : "s"); 1945 message += ". Reassemble to refresh the listing and byte code."; 1946 ASM_clearOutputs(message,"ok"); 1947 1948 return {loaded:loaded,replaced:replaced}; 1949 } 1950 /* ASM INCLUDE FILES END */ 1951 1952 var ASM_currentJsonText = ""; 1953 var ASM_currentDiagnosticsText = ""; 1954 var ASM_currentByteCodeText = ""; 1955 var ASM_currentErrorCount = 0; 1956 var ASM_currentWarningCount = 0; 1957 var ASM_outputPopupKind = ""; 1958 var ASM_sourceByteCodeActive = false; 1959 var ASM_oPANE = null; 1960 var ASM_sourceFindReplace = null; 1961 var ASM_sourceHeaderWidgetManager = null; 1962 var ASM_sourceEditHistory = { 1963 undo: [], 1964 redo: [], 1965 outdent: [], 1966 max: 100 1967 }; 1968 var ASM_headerThemeDark = false; 1969 1970 var ASM_listingColumnPresets = { 1971 "default":"{adr:0,code:6,lin:15,num:{col:23,dig:6},lbl:30,ins:40,opr:45,com:61}", 1972 "print":"{adr:0,code:6,lin:15,num:{col:25,dig:6},lbl:32,ins:40,opr:45,asm:61,com:70}", 1973 "compile":"{adr:0,code:6,lbl:15,ins:25,opr:30,com:55}" 1974 }; 1975 1976 function ASM_el(id) { return document.getElementById(id); } 1977 1978 function ASM_applyConfigVars(vars, target, prefix) 1979 { 1980 target = target || document.documentElement; 1981 prefix = prefix || "--"; 1982 for(var k in vars) target.style.setProperty(prefix + k, vars[k]); 1983 } 1984 1985 var ASM_findReplaceScheme = { 1986 "bg":"#f3f3f3", 1987 "border":"#d2d2d2", 1988 "text":"#000000", 1989 "muted":"#666666", 1990 "field-bg":"#ffffff", 1991 "field-text":"#000000", 1992 "active":"#0078d4", 1993 "active-bg":"#e9f4ff", 1994 "active-text":"#000000", 1995 "hover-bg":"#ececec", 1996 "hover-text":"#000000", 1997 "expander":"#000000" 1998 }; 1999 2000 function ASM_applyFindReplaceScheme() 2001 { 2002 var mount = ASM_el("ASM_sourceFindReplaceMount"); 2003 if(mount) ASM_applyConfigVars(ASM_findReplaceScheme,mount,"--com-pane-fr-"); 2004 } 2005 2006 function ASM_countByteCodeText(text) 2007 { 2008 var s = String(text || "").toUpperCase(); 2009 2010 // Remove common monitor/listing address prefixes such as "6000:". 2011 s = s.replace(/(^|[\r\n])\s*[0-9A-F]{4}\s*:/g,"$1"); 2012 2013 var m = s.match(/\b[0-9A-F]{2}\b/g); 2014 return m ? m.length : 0; 2015 } 2016 2017 function ASM_updateByteCodeInfo(byteCount) 2018 { 2019 var info = ASM_el("ASM_byteCodeInfo"); 2020 if(info) info.textContent = (byteCount || 0) + " bytes"; 2021 } 2022 2023 function ASM_syncByteCodeFromBoard() 2024 { 2025 var board = ASM_el("ASM_boardText"); 2026 ASM_currentByteCodeText = board ? board.value : ""; 2027 ASM_updateByteCodeInfo(ASM_countByteCodeText(ASM_currentByteCodeText)); 2028 } 2029 2030 function ASM_setByteCodeOutput(text, byteCount) 2031 { 2032 ASM_currentByteCodeText = text || ""; 2033 2034 var board = ASM_el("ASM_boardText"); 2035 if(board) board.value = ASM_currentByteCodeText; 2036 2037 ASM_updateByteCodeInfo(typeof byteCount == "number" ? byteCount : ASM_countByteCodeText(ASM_currentByteCodeText)); 2038 } 2039 2040 /* 2041 * Source byte-code origin. 2042 * 2043 * This deliberately belongs to the assembler toolbox instead of the 2044 * debugger. The debugger's "Go to PC" controls execution/listing 2045 * position; this address controls where the source text buffer will 2046 * be pasted into Apple II memory before BSAVE ...,T writes it as a 2047 * DOS text file. 2048 */ 2049 function ASM_hexWord(v) 2050 { 2051 if(typeof oCOM != "undefined" && oCOM && typeof oCOM.getHexWord == "function") 2052 return oCOM.getHexWord(v & 0xFFFF); 2053 return ("000" + ((v & 0xFFFF).toString(16).toUpperCase())).slice(-4); 2054 } 2055 2056 function ASM_parseHexAddress(text) 2057 { 2058 text = String(text == null ? "" : text).trim(); 2059 text = text.replace(/^\$/,"").replace(/^0x/i,""); 2060 if(!/^[0-9A-Fa-f]{1,4}$/.test(text)) return null; 2061 2062 var value = parseInt(text,16); 2063 if(isNaN(value) || value < 0 || value > 0xFFFF) return null; 2064 return value & 0xFFFF; 2065 } 2066 2067 function ASM_sourceByteCodeOrigin(reportError) 2068 { 2069 var input = ASM_el("ASM_sourceByteCodeStartInput"); 2070 var raw = input ? input.value : "$6000"; 2071 var origin = ASM_parseHexAddress(raw); 2072 2073 if(origin === null) 2074 { 2075 if(reportError !== false) 2076 ASM_setStatus("Invalid source byte-code start address. Use hexadecimal $0000-$FFFF.","bad"); 2077 return null; 2078 } 2079 2080 return origin; 2081 } 2082 2083 function ASM_normalizeSourceByteCodeStartInput() 2084 { 2085 var input = ASM_el("ASM_sourceByteCodeStartInput"); 2086 var origin = ASM_sourceByteCodeOrigin(false); 2087 if(input && origin !== null) input.value = "$" + ASM_hexWord(origin); 2088 return origin; 2089 } 2090 2091 function ASM_sourceTextBytes(text) 2092 { 2093 /* 2094 * Apple DOS text uses CR as its line separator. Normalize every 2095 * browser/host newline form before encoding so CRLF does not 2096 * accidentally become two characters. 2097 */ 2098 text = String(text == null ? "" : text) 2099 .replace(/\r\n/g,"\n") 2100 .replace(/\r/g,"\n") 2101 .replace(/\n/g,"\r"); 2102 2103 var bytes = []; 2104 var replacements = 0; 2105 2106 for(var i = 0; i < text.length; i++) 2107 { 2108 var code = text.charCodeAt(i); 2109 2110 /* 2111 * The memory buffer intentionally contains ordinary 7-bit 2112 * ASCII. BSAVE ...,T performs the Apple high-bit conversion. 2113 * 2114 * Replace NUL and non-ASCII UTF-16 characters with "?" rather 2115 * than truncating them to misleading byte values. 2116 */ 2117 if(code === 0 || code > 0x7F) 2118 { 2119 code = 0x3F; 2120 replacements++; 2121 } 2122 2123 bytes.push(code & 0x7F); 2124 } 2125 2126 return { 2127 bytes: bytes, 2128 replacements: replacements 2129 }; 2130 } 2131 2132 function ASM_formatSourceByteCode(bytes,origin) 2133 { 2134 var records = []; 2135 for(var i = 0; i < bytes.length; i++) 2136 records.push({pc:(origin + i) & 0xFFFF,val:bytes[i] & 0xFF}); 2137 2138 /* 2139 * Reuse ASM_core's formatter whenever possible. It emits the 2140 * same eight-byte Apple monitor lines as assembled byte code. 2141 */ 2142 var formatter = window.oASM; 2143 if((!formatter || typeof formatter.formatByteCodeLines != "function") && 2144 typeof window.ASM == "function") 2145 formatter = new ASM(); 2146 2147 if(formatter && typeof formatter.formatByteCodeLines == "function") 2148 return formatter.formatByteCodeLines(records,8).join("\n"); 2149 2150 /* 2151 * Defensive fallback for a page where ASM_core.js failed to load. 2152 */ 2153 var lines = []; 2154 for(var p = 0; p < bytes.length; p += 8) 2155 { 2156 var hex = []; 2157 for(var j = p; j < Math.min(p + 8,bytes.length); j++) 2158 hex.push(("0" + bytes[j].toString(16).toUpperCase()).slice(-2)); 2159 2160 lines.push( 2161 ("000" + ((origin + p) & 0xFFFF).toString(16).toUpperCase()).slice(-4) 2162 + ": " 2163 + hex.join(" ") 2164 ); 2165 } 2166 return lines.join("\n"); 2167 } 2168 2169 function ASM_sourceByteCodeSaveCommand(filename,origin,length) 2170 { 2171 return "BSAVE " + filename 2172 + ",A$" + ASM_hexWord(origin) 2173 + ",L$" + ASM_hexWord(length) 2174 + ",T"; 2175 } 2176 2177 function ASM_sourceByteCodePasteText(bytes,origin,filename) 2178 { 2179 /* 2180 * Build a complete Apple II paste sequence: 2181 * 2182 * CALL-151 enter the monitor 2183 * xxxx: .. .. write the source buffer bytes 2184 * E003G return through DOS warm-start entry 2185 * BSAVE ...,T save the memory buffer as a DOS text file 2186 * 2187 * The final BSAVE line is intentionally not followed by an extra 2188 * newline here. This keeps the generated board text from 2189 * auto-submitting the final command when it is copied manually. 2190 */ 2191 return [ 2192 "CALL-151", 2193 ASM_formatSourceByteCode(bytes,origin), 2194 "E003G", 2195 ASM_sourceByteCodeSaveCommand(filename,origin,bytes.length) 2196 ].join("\n"); 2197 } 2198 2199 function ASM_setSourceByteCodeModeUI(active) 2200 { 2201 var btn = ASM_el("ASM_encodeSourceButton"); 2202 if(btn) 2203 { 2204 //btn.classList.toggle("blink",!!active); 2205 btn.title = active 2206 ? "Source byte-code mode is active; address/source changes refresh the byte code" 2207 : "Byte encode source at this address"; 2208 } 2209 } 2210 2211 function ASM_updateSourceByteCodeRangeInfo(origin,length) 2212 { 2213 var info = ASM_el("ASM_sourceByteCodeRangeInfo"); 2214 if(!info) return; 2215 2216 if(origin === null) 2217 {
2218 info.textContent = "invalid A"; 2219 info.title = "Invalid start address"; 2220 return; 2221 } 2222 2223 length = Number(length) || 0; 2224 info.textContent = "A$" + ASM_hexWord(origin) + " L$" + ASM_hexWord(length); 2225 info.title = "Use " + ASM_sourceByteCodeSaveCommand( 2226 ASM_currentFileName || "source.S", 2227 origin, 2228 length 2229 ); 2230 } 2231 2232 function ASM_renderSourceByteCode(options) 2233 { 2234 options = options || {}; 2235 2236 var sourcePane = ASM_el("ASM_sourcePane"); 2237 if(!sourcePane) 2238 { 2239 ASM_setStatus("Source pane is not available.","bad"); 2240 return false; 2241 } 2242 var origin = ASM_sourceByteCodeOrigin(options.status !== false); 2243 if(origin === null) 2244 { 2245 ASM_updateSourceByteCodeRangeInfo(null,0); 2246 return false; 2247 } 2248 2249 if(options.normalizeInput !== false) 2250 ASM_normalizeSourceByteCodeStartInput(); 2251 2252 2253 var encoded = ASM_sourceTextBytes(sourcePane.value); 2254 var bytes = encoded.bytes; 2255 2256 if(options.activate) ASM_sourceByteCodeActive = true; 2257 ASM_setSourceByteCodeModeUI(ASM_sourceByteCodeActive); 2258 2259 if(!bytes.length) 2260 { 2261 ASM_setByteCodeOutput("",0); 2262 ASM_updateSourceByteCodeRangeInfo(origin,0); 2263 if(options.status !== false) 2264 ASM_setStatus("Source pane is empty; no bytes were generated.","warn"); 2265 return false; 2266 } 2267 2268 if(bytes.length > (0x10000 - origin)) 2269 { 2270 ASM_updateSourceByteCodeRangeInfo(origin,bytes.length); 2271 ASM_setStatus( 2272 "Source text does not fit from $" 2273 + ASM_hexWord(origin) 2274 + " through $FFFF.", 2275 "bad" 2276 ); 2277 return false; 2278 } 2279 2280 var filename = ASM_setCurrentFileName( 2281 ASM_el("ASM_sourceFileNameInput") 2282 ? ASM_el("ASM_sourceFileNameInput").value 2283 : ASM_currentFileName 2284 ); 2285 2286 ASM_setByteCodeOutput( 2287 ASM_sourceByteCodePasteText(bytes,origin,filename), 2288 bytes.length 2289 ); 2290 2291 ASM_updateSourceByteCodeRangeInfo(origin,bytes.length); 2292 2293 if(options.status !== false) 2294 { 2295 ASM_setStatus( 2296 "Encoded " + bytes.length + " source byte(s) at $" + ASM_hexWord(origin) 2297 + (encoded.replacements 2298 ? "; replaced " + encoded.replacements + " non-ASCII/NUL character(s)" 2299 : "") 2300 + ". Save as text with " 2301 + ASM_sourceByteCodeSaveCommand(filename,origin,bytes.length) 2302 + ".", 2303 encoded.replacements ? "warn" : "ok" 2304 ); 2305 } 2306 2307 return true; 2308 } 2309 2310 2311 function ASM_byteEncodeSource() 2312 { 2313 return ASM_renderSourceByteCode({activate:true,status:true}); 2314 } 2315 2316 function ASM_refreshSourceByteCodeFromAddress() 2317 { 2318 var origin = ASM_sourceByteCodeOrigin(false); 2319 2320 if(!ASM_sourceByteCodeActive) 2321 { 2322 ASM_updateSourceByteCodeRangeInfo(origin,ASM_countByteCodeText(ASM_currentByteCodeText)); 2323 return origin !== null; 2324 } 2325 2326 return ASM_renderSourceByteCode({ 2327 status:false, 2328 normalizeInput:false 2329 }); 2330 } 2331 2332 function ASM_listingColumnsValue() 2333 { 2334 var headerInput = ASM_el("ASM_listingColumnsInput"); 2335 var settingsInput = ASM_el("ASM_settingsListingColumnsInput"); 2336 return headerInput ? headerInput.value : (settingsInput ? settingsInput.value : undefined); 2337 } 2338 2339 function ASM_dQuoteLegacyValue() 2340 { 2341 var input = ASM_el("ASM_dQuoteLegacyInput"); 2342 return !!(input && input.checked); 2343 } 2344 2345 function ASM_syncListingColumns(sourceInput) 2346 { 2347 var headerInput = ASM_el("ASM_listingColumnsInput"); 2348 var settingsInput = ASM_el("ASM_settingsListingColumnsInput");
2349 var value = sourceInput ? sourceInput.value : ASM_listingColumnsValue(); 2350 2351 if(headerInput && headerInput !== sourceInput) headerInput.value = value; 2352 if(settingsInput && settingsInput !== sourceInput) settingsInput.value = value; 2353 } 2354 2355 function ASM_applyListingColumnPreset(name) 2356 { 2357 var spec = ASM_listingColumnPresets[name]; 2358 if(!spec) return false; 2359 2360 var settingsInput = ASM_el("ASM_settingsListingColumnsInput"); 2361 var headerInput = ASM_el("ASM_listingColumnsInput"); 2362 2363 if(settingsInput) settingsInput.value = spec; 2364 if(headerInput) headerInput.value = spec; 2365 ASM_syncListingColumns(settingsInput || headerInput); 2366 return ASM_assembleCurrentSource(); 2367 } 2368 2369 function ASM_setPaneInfos(listingText, jsonState) 2370 { 2371 var listingInfo = ASM_el("ASM_listingInfo"); 2372 var jsonInfo = ASM_el("ASM_jsonInfo"); 2373 2374 if(listingInfo) 2375 { 2376 if(listingText) 2377 { 2378 var n = String(listingText).split(/\r\n|\r|\n/).length; 2379 listingInfo.textContent = n + " lines"; 2380 } 2381 else listingInfo.textContent = "compile()"; 2382 } 2383 2384 if(jsonInfo) 2385 { 2386 jsonInfo.textContent = jsonState || "not assembled"; 2387 jsonInfo.title = jsonInfo.textContent; 2388 } 2389 } 2390 2391 2392 function ASM_setErrorButtonState(errorCount,warningCount) 2393 { 2394 ASM_currentErrorCount = Number(errorCount) || 0; 2395 ASM_currentWarningCount = Number(warningCount) || 0; 2396 2397 var btn = ASM_el("ASM_diagnosticsButton"); 2398 if(!btn) return; 2399 2400 var hasDiagnostics = ASM_currentErrorCount > 0 || ASM_currentWarningCount > 0; 2401 2402 btn.hidden = !hasDiagnostics; 2403 btn.disabled = !hasDiagnostics; 2404 btn.classList.remove("blink"); 2405 var icon = btn.querySelector("i"); 2406 if(icon) icon.classList.toggle("blink",ASM_currentErrorCount > 0); 2407 2408 if(ASM_currentErrorCount && ASM_currentWarningCount) 2409 btn.title = ASM_currentErrorCount + " error(s), " + ASM_currentWarningCount + " warning(s)"; 2410 else if(ASM_currentErrorCount) 2411 btn.title = ASM_currentErrorCount + " assembly error(s)"; 2412 else if(ASM_currentWarningCount) 2413 btn.title = ASM_currentWarningCount + " assembly warning(s)"; 2414 else 2415 btn.title = "No assembly diagnostics"; 2416 2417 if(!hasDiagnostics && ASM_outputPopupKind == "diagnostics") ASM_closeOutputPopup(); 2418 } 2419 2420 function ASM_setDiagnosticsText(compiled) 2421 { 2422 compiled = compiled || {}; 2423 ASM_currentDiagnosticsText = JSON.stringify({ 2424 errors:Array.isArray(compiled.errors) ? compiled.errors : [], 2425 warnings:Array.isArray(compiled.warnings) ? compiled.warnings : [] 2426 },null,2); 2427 ASM_refreshOutputPopup(); 2428 } 2429 2430 function ASM_refreshOutputPopup() 2431 { 2432 var back = ASM_el("ASM_outputPopupBack"); 2433 var text = ASM_el("ASM_outputPopupText"); 2434 if(!back || !text || back.hidden) return; 2435 2436 text.value = ASM_outputPopupKind == "diagnostics" 2437 ? ASM_currentDiagnosticsText 2438 : ASM_currentJsonText; 2439 } 2440 2441 function ASM_openOutputPopup(kind) 2442 { 2443 var back = ASM_el("ASM_outputPopupBack"); 2444 var text = ASM_el("ASM_outputPopupText"); 2445 var title = ASM_el("ASM_outputPopupTitle"); 2446 if(!back || !text || !title) return; 2447 2448 ASM_outputPopupKind = kind == "diagnostics" ? "diagnostics" : "json"; 2449 title.textContent = ASM_outputPopupKind == "diagnostics" ? "Assembly diagnostics" : "JSON output"; 2450 text.value = ASM_outputPopupKind == "diagnostics" ? ASM_currentDiagnosticsText : ASM_currentJsonText; 2451 text.scrollTop = 0; 2452 text.scrollLeft = 0; 2453 back.hidden = false;
2454 back.setAttribute("aria-hidden","false"); 2455 text.focus(); 2456 } 2457 2458 function ASM_closeOutputPopup() 2459 { 2460 var back = ASM_el("ASM_outputPopupBack"); 2461 if(back) 2462 { 2463 back.hidden = true; 2464 back.setAttribute("aria-hidden","true"); 2465 } 2466 ASM_outputPopupKind = ""; 2467 } 2468 2469 function ASM_toggleOutputPopup(kind) 2470 { 2471 var back = ASM_el("ASM_outputPopupBack"); 2472 kind = kind == "diagnostics" ? "diagnostics" : "json"; 2473 2474 if(back && !back.hidden && ASM_outputPopupKind == kind) ASM_closeOutputPopup(); 2475 else ASM_openOutputPopup(kind); 2476 } 2477 2478 function ASM_findJsonKeyElement(key) 2479 { 2480 var mount = ASM_el("ASM_jsonMount"); 2481 if(!mount) return null; 2482 2483 /* 2484 * oCOM.JSONprettify() wraps JSON keys in .json-key spans. 2485 * Prefer the real rendered key element, because the JSON may contain 2486 * very large string values before "errors" (for example listingText). 2487 * A text-offset * line-height estimate does not survive that case. 2488 */ 2489 var keys = mount.querySelectorAll(".json-key,.ASM_jsonKey"); 2490 for(var i = 0; i < keys.length; i++) 2491 { 2492 var txt = String(keys[i].textContent || "").replace(/^\s+|\s+$/g,""); 2493 if(txt == '"' + key + '"' || txt == "'" + key + "'" || txt == key) 2494 return keys[i]; 2495 } 2496 2497 return null; 2498 } 2499 2500 function ASM_findJsonTextRangeForKey(key) 2501 { 2502 var mount = ASM_el("ASM_jsonMount"); 2503 if(!mount) return null; 2504 2505 /* 2506 * Fallback for non-prettified JSON. Find a key at the beginning of 2507 * a JSON line, not a random occurrence inside listingText/comments. 2508 */ 2509 var full = mount.textContent || ASM_currentJsonText || ""; 2510 var re = new RegExp("(^|\\n)\\s*[\\\"']" + key + "[\\\"']\\s*:", "m"); 2511 var m = re.exec(full); 2512 if(!m) return null; 2513 2514 var targetOffset = m.index + (m[1] ? m[1].length : 0); 2515 var seen = 0; 2516 var walker = document.createTreeWalker(mount,NodeFilter.SHOW_TEXT,null,false); 2517 var node; 2518 2519 while((node = walker.nextNode())) 2520 { 2521 var len = node.nodeValue ? node.nodeValue.length : 0; 2522 if(seen + len >= targetOffset) 2523 { 2524 var local = Math.max(0,targetOffset - seen); 2525 var range = document.createRange(); 2526 range.setStart(node,local); 2527 range.setEnd(node,Math.min(len,local + key.length + 2)); 2528 return range; 2529 } 2530 seen += len; 2531 } 2532 2533 return null; 2534 } 2535 2536 function ASM_scrollJsonTargetIntoView(target) 2537 { 2538 var mount = ASM_el("ASM_jsonMount"); 2539 if(!mount || !target) return false; 2540 2541 var targetRect; 2542 if(typeof target.getBoundingClientRect == "function") 2543 targetRect = target.getBoundingClientRect(); 2544 else if(typeof target.getClientRects == "function" && target.getClientRects().length) 2545 targetRect = target.getClientRects()[0]; 2546 2547 if(!targetRect) return false; 2548 2549 var mountRect = mount.getBoundingClientRect(); 2550 var lh = parseFloat(window.getComputedStyle(mount).lineHeight) || 21; 2551 2552 mount.scrollTop = Math.max( 2553 0, 2554 mount.scrollTop + (targetRect.top - mountRect.top) - (2 * lh) 2555 ); 2556 2557 mount.focus(); 2558 return true; 2559 } 2560 2561 function ASM_scrollJsonTextareaToKey(key) 2562 { 2563 var mount = ASM_el("ASM_jsonMount"); 2564 if(!mount || !("value" in mount)) return false;
2565 2566 var text = mount.value || ASM_currentJsonText || ""; 2567 var re = new RegExp("(^|\\n)\\s*[\\\"']" + key + "[\\\"']\\s*:", "m"); 2568 var m = re.exec(text); 2569 if(!m) return false; 2570 2571 var targetOffset = m.index + (m[1] ? m[1].length : 0); 2572 var before = text.slice(0,targetOffset); 2573 var line = before.split(/\r\n|\r|\n/).length - 1; 2574 var lh = parseFloat(window.getComputedStyle(mount).lineHeight) || 21; 2575 2576 mount.scrollTop = Math.max(0,(line - 2) * lh); 2577 mount.focus(); 2578 2579 try 2580 { 2581 mount.setSelectionRange(targetOffset,Math.min(text.length,targetOffset + key.length + 2)); 2582 } 2583 catch(_ignore) {} 2584 2585 return true; 2586 } 2587 2588 function ASM_scrollJsonToErrors() 2589 { 2590 ASM_toggleOutputPopup("diagnostics"); 2591 } 2592 2593 function ASM_jumpToErrors() 2594 { 2595 if(ASM_currentErrorCount <= 0 && ASM_currentWarningCount <= 0) 2596 { 2597 ASM_setErrorButtonState(0,0); 2598 return; 2599 } 2600 2601 ASM_scrollJsonToErrors(); 2602 } 2603 2604 function ASM_setStatus(text, kind) 2605 { 2606 var msg = "[ASM" + (kind ? " " + kind.toUpperCase() : "") + "] " + text; 2607 2608 if(typeof console == "undefined") return; 2609 2610 if(kind == "bad" && typeof console.error == "function") 2611 console.error(msg); 2612 else if(kind == "warn" && typeof console.warn == "function") 2613 console.warn(msg); 2614 else if(typeof console.log == "function") 2615 console.log(msg); 2616 } 2617 2618 function ASM_escapeHTML(str) 2619 { 2620 return String(str) 2621 .replace(/&/g,"&") 2622 .replace(/</g,"<") 2623 .replace(/>/g,">") 2624 .replace(/\"/g,""") 2625 .replace(/'/g,"'"); 2626 } 2627 2628 function ASM_prettifyJSON() 2629 { 2630 var mountEl = ASM_el("ASM_jsonMount"); 2631 if(!mountEl) return; 2632 2633 /* 2634 * The JSON output pane is now a real textarea so users can select 2635 * and copy arbitrary portions of the JSON. A textarea cannot host 2636 * syntax-highlight spans, so keep the raw pretty-printed JSON text. 2637 */ 2638 if("value" in mountEl) 2639 { 2640 mountEl.value = ASM_currentJsonText || mountEl.value || ""; 2641 return; 2642 } 2643 2644 var styleMap = { 2645 "{}": "color:#000000;font-weight:800", 2646 "[]": "color:#000000;font-weight:800" 2647 }; 2648 2649 if(typeof oCOM != "undefined" && oCOM && typeof oCOM.JSONprettify == "function") 2650 { 2651 oCOM.JSONprettify(mountEl,styleMap,2); 2652 return; 2653 } 2654 2655 mountEl.innerHTML = "<pre class=\"ASM_jsonView\">" + ASM_escapeHTML(mountEl.textContent) + "</pre>"; 2656 } 2657 2658 function ASM_setJsonText(text) 2659 { 2660 ASM_currentJsonText = String(text || ""); 2661 ASM_refreshOutputPopup(); 2662 } 2663 2664 function ASM_consoleTable(rows) 2665 { 2666 var newrows = JSON.parse(JSON.stringify(rows || [])); 2667 if(!window.oASM) return newrows; 2668 2669 for(var i=0;i<newrows.length;i++) 2670 { 2671 for(var j in newrows[i]) 2672 { 2673 if(j == "pc") newrows[i][j] = window.oASM.pcHex(newrows[i][j]); 2674 else if(j == "opc") newrows[i][j] = window.oASM.opcHex(newrows[i][j]); 2675 else if(j == "val") newrows[i][j] = window.oASM.valHex(newrows[i][j],newrows[i].oby); 2676 else if(j == "bytes") newrows[i][j] = window.oASM.bytesToHex(newrows[i][j]); 2677 else if(typeof newrows[i][j] == "object" && newrows[i][j] != null) 2678 newrows[i][j] = Array.isArray(newrows[i][j]) ? newrows[i][j].join("|") : JSON.stringify(newrows[i][j]); 2679 } 2680 } 2681 return newrows; 2682 } 2683 2684 function ASM_clearOutputs(message,kind) 2685 { 2686 ASM_setJsonText(""); 2687 ASM_currentDiagnosticsText = ""; 2688 ASM_sourceByteCodeActive = false;
2689 ASM_setSourceByteCodeModeUI(false); 2690 ASM_setByteCodeOutput("",0); 2691 ASM_updateSourceByteCodeRangeInfo(ASM_sourceByteCodeOrigin(false),0); 2692 window.asmTokeniseJSON = null; 2693 window.asmCompileResult = null; 2694 ASM_setErrorButtonState(0,0); 2695 2696 if(ASM_el("ASM_listingPane")) ASM_el("ASM_listingPane").value = ""; 2697 //ASM_setPaneInfos("", "not assembled"); 2698 ASM_resizeSourceListingPanes(); 2699 ASM_refreshFindReplaceOverlay(); 2700 2701 if(message) ASM_setStatus(message,kind || ""); 2702 } 2703 2704 function ASM_assembleCurrentSource() 2705 { 2706 if(typeof window.ASM != "function") 2707 { 2708 var msg = "ASM() was not found. Check res/ASM_core.js."; 2709 var failure = {errors:[{message:msg}],warnings:[]}; 2710 ASM_setStatus(msg,"bad"); 2711 ASM_setJsonText(JSON.stringify(failure,null,2)); 2712 ASM_setDiagnosticsText(failure); 2713 ASM_setErrorButtonState(1,0); 2714 ASM_setPaneInfos("", "error"); 2715 return false; 2716 } 2717 2718 try 2719 { 2720 var sourcePane = ASM_el("ASM_sourcePane"); 2721 var listingColumnsInput = ASM_el("ASM_listingColumnsInput") || ASM_el("ASM_settingsListingColumnsInput"); 2722 var listingPane = ASM_el("ASM_listingPane"); 2723 var jsonMount = ASM_el("ASM_jsonMount"); 2724 2725 ASM_syncListingColumns(listingColumnsInput); 2726 window.oASM = new ASM({ 2727 listingColumns:listingColumnsInput ? listingColumnsInput.value : undefined, 2728 dQuoteLegacy:ASM_dQuoteLegacyValue(), 2729 sourceName:ASM_currentFileName, 2730 includes:ASM_buildIncludeMap() 2731 }); 2732 if(listingColumnsInput && typeof window.oASM.setListingColumns == "function") 2733 window.oASM.setListingColumns(listingColumnsInput.value); 2734 2735 var rows = window.oASM.tokenise(sourcePane.value,ASM_currentFileName); 2736 var compiled = window.oASM.compile(rows); 2737 var coreVersion = window.oASM.version || "?"; 2738 2739 window.asmTokeniseJSON = rows; 2740 window.asmCompileResult = compiled; 2741 2742 listingPane.value = compiled.listingText || ""; 2743 2744 ASM_setJsonText(JSON.stringify(compiled,null,2)); 2745 //ASM_setPaneInfos(listingPane.value, "interactive JSON"); 2746 2747 console.table(ASM_consoleTable(compiled.rows || [])); 2748 2749 var rowCount = compiled.rows && compiled.rows.length ? compiled.rows.length : 0; 2750 var byteCount = compiled.bytes && compiled.bytes.length ? compiled.bytes.length : 0; 2751 var errorCount = compiled.errors && compiled.errors.length ? compiled.errors.length : 0; 2752 var warningCount = compiled.warnings && compiled.warnings.length ? compiled.warnings.length : 0; 2753 ASM_setDiagnosticsText(compiled); 2754 ASM_setErrorButtonState(errorCount,warningCount); 2755 2756 ASM_sourceByteCodeActive = false; 2757 ASM_setSourceByteCodeModeUI(false); 2758 ASM_setByteCodeOutput(compiled.byteCodeText || "", byteCount); 2759 ASM_updateSourceByteCodeRangeInfo(ASM_sourceByteCodeOrigin(false),byteCount); 2760 ASM_resizeSourceListingPanes(); 2761 ASM_refreshFindReplaceOverlay(); 2762 2763 if(errorCount || warningCount) 2764 { 2765 var diagnosticSummary = []; 2766 if(errorCount) diagnosticSummary.push(errorCount + " error(s)"); 2767 if(warningCount) diagnosticSummary.push(warningCount + " warning(s)"); 2768 ASM_setStatus("ASM core v" + coreVersion + ": assembled " + rowCount + " rows / " + byteCount + " bytes with " + diagnosticSummary.join(" / ") + ".","warn"); 2769 } 2770 else 2771 ASM_setStatus("ASM core v" + coreVersion + ": assembled " + rowCount + " rows / " + byteCount + " bytes from " + ASM_currentFileName + ".","ok"); 2772 2773 return true; 2774 } 2775 catch(err) 2776 { 2777 console.error(err); 2778 if(ASM_el("ASM_listingPane")) ASM_el("ASM_listingPane").value = ""; 2779 ASM_resizeSourceListingPanes(); 2780 ASM_refreshFindReplaceOverlay(); 2781 ASM_setByteCodeOutput("",0); 2782 2783 var message = err && err.message ? err.message :
2783String(err); 2784 var failure = { 2785 errors:[{message:message,stack:err && err.stack ? err.stack : ""}], 2786 warnings:[] 2787 }; 2788 ASM_setJsonText(JSON.stringify(failure,null,2)); 2789 ASM_setDiagnosticsText(failure); 2790 ASM_setErrorButtonState(1,0); 2791 ASM_setPaneInfos("", "error"); 2792 ASM_setStatus("Error: " + message,"bad"); 2793 return false; 2794 } 2795 } 2796 2797 function ASM_loadSourceText(name,text) 2798 { 2799 ASM_setCurrentFileName(name || "source.S"); 2800 ASM_el("ASM_sourcePane").value = String(text || ""); 2801 ASM_clearOutputs("Loaded " + ASM_currentFileName + ". Click assemble to generate the listing, JSON and byte code.","ok"); 2802 ASM_resizeSourceListingPanes(); 2803 ASM_refreshFindReplaceOverlay(); 2804 } 2805 2806 async function ASM_loadFile(file) 2807 { 2808 if(!file) return; 2809 ASM_loadSourceText(file.name || "source.S",await file.text()); 2810 } 2811 2812 function ASM_loadCatalogSource(arg) 2813 { 2814 var url = arg.download_url || oCOM.CATALOG.githubRawURL(arg); 2815 var name = arg.name || String(arg.path || "source.S").split("/").pop(); 2816 2817 function finish(text) 2818 { 2819 if(/\.inc$/i.test(name)) 2820 { 2821 var result = ASM_addIncludeText( 2822 name, 2823 text, 2824 arg.path || name 2825 ); 2826 2827 ASM_clearOutputs( 2828 (result.replaced ? "Replaced include " : "Loaded include ") 2829 + name 2830 + ". Reassemble to refresh the listing and byte code.", 2831 "ok" 2832 ); 2833 } 2834 else 2835 ASM_loadSourceText(name,text); 2836 2837 oCOM.CATALOG.close("sourceCat"); 2838 } 2839 2840 function loadOffline(reason) 2841 { 2842 if(typeof ASM_OfflineSources == "undefined") 2843 return false; 2844 2845 try 2846 { 2847 var text = oCOM.CATALOG.offlineData( 2848 ASM_OfflineSources, 2849 arg, 2850 url, 2851 "text" 2852 ); 2853 2854 if(text == null) 2855 return false; 2856 2857 console.warn( 2858 "Using offline source fallback for " + 2859 arg.path + ": " + reason 2860 ); 2861 2862 finish(text); 2863 return true; 2864 } 2865 catch(err) 2866 { 2867 ASM_setStatus( 2868 "Offline source decode failed: " + 2869 (err.message || String(err)), 2870 "bad" 2871 ); 2872 return true; 2873 } 2874 } 2875 2876 /* 2877 * Do not attempt raw GitHub when the browser already knows it is 2878 * offline. This also covers the case where the directory listing 2879 * came from cache before connectivity was lost. 2880 */ 2881 if(oCOM.CATALOG.isOffline()) 2882 { 2883 if(loadOffline("navigator.onLine=false")) 2884 return; 2885 2886 ASM_setStatus( 2887 "This source file is not available offline.", 2888 "warn" 2889 ); 2890 return; 2891 } 2892 2893 oCOM.GetHTTP( 2894 url, 2895 "text", 2896 function() 2897 { 2898 if(this.status != 200) 2899 { 2900 if(loadOffline("HTTP " + this.status)) 2901 return; 2902 2903 ASM_setStatus( 2904 "Source catalog load failed: HTTP " + this.status, 2905 "bad" 2906 ); 2907 return; 2908 } 2909 2910 finish( 2911 this.responseText || 2912 this.response || 2913 "" 2914 ); 2915 }, 2916 null, 2917 function() 2918 { 2919 if(loadOffline("network error")) 2920 return; 2921 2922 ASM_setStatus( 2923 "Source catalog load failed: network unavailable.", 2924 "bad" 2925 ); 2926 } 2927 ); 2928 } 2929 2930 function ASM_sourceCatalogOpen() 2931 { 2932 if(typeof oCOM == "undefined" || !oCOM.CATALOG) 2933 { 2934 ASM_setStatus("Shared catalog browser is unavailable.","bad"); 2935 return false;
2936 } 2937 2938 var catalog = oCOM.CATALOG.create({ 2939 id:"sourceCat" 2940 ,popupID:"sourceCat_popup" 2941 ,title:"SOURCE CODE CATALOG" 2942 ,owner:"RetroAppleJS" 2943 ,repo:"RetroAppleJS.github.io" 2944 ,ref:"main" 2945 ,basepath:"asm" 2946 ,path:"asm" 2947 ,fileIcon:"fa fa-file-code" 2948 ,offlineProvider:function(url) 2949 { 2950 if(typeof ASM_GitHubDirOffline == "undefined") 2951 return null; 2952 2953 var offline = oCOM.CATALOG.offlineDirectory( 2954 ASM_GitHubDirOffline, 2955 url 2956 ); 2957 2958 if(offline) 2959 console.warn("Using offline assembler source directory fallback"); 2960 2961 return offline; 2962 } 2963 ,filter:function(entry) 2964 { 2965 return entry.type == "dir" || /\.(s|asm|inc|txt|src|a65|mac)$/i.test(entry.name || ""); 2966 } 2967 ,onFile:ASM_loadCatalogSource 2968 ,onError:function(err) 2969 { 2970 ASM_setStatus("Source catalog failed: " + (err.message || ("HTTP " + err.status)),"bad"); 2971 } 2972 }); 2973 2974 return catalog.open(); 2975 } 2976 2977 function ASM_copyText(text,okMessage) 2978 { 2979 if(!navigator.clipboard) 2980 { 2981 ASM_setStatus("Clipboard unavailable; select and copy manually.","warn"); 2982 return; 2983 } 2984 2985 navigator.clipboard.writeText(text || "") 2986 .then(function(){ ASM_setStatus(okMessage,"ok"); }) 2987 .catch(function(){ ASM_setStatus("Clipboard copy failed; select manually.","warn"); }); 2988 } 2989 2990 function ASM_downloadText(filename,text,mime) 2991 { 2992 filename = filename || "symbols.json"; 2993 text = text || ""; 2994 mime = mime || "application/json;charset=utf-8"; 2995 2996 var blob = new Blob([text],{type:mime}); 2997 var url = URL.createObjectURL(blob); 2998 var a = document.createElement("a"); 2999 3000 a.href = url; 3001 a.download = filename; 3002 a.style.display = "none"; 3003 3004 document.body.appendChild(a); 3005 a.click(); 3006 document.body.removeChild(a); 3007 3008 URL.revokeObjectURL(url); 3009 } 3010 3011 function ASM_safeSourceFileName(name) 3012 { 3013 name = String(name || "").trim(); 3014 name = name.replace(/[\\/:*?\"<>|]+/g,"_"); 3015 return name || "source.S"; 3016 } 3017 3018 function ASM_updateSourceFileNameUI() 3019 { 3020 var input = ASM_el("ASM_sourceFileNameInput"); 3021 if(input && input.value !== ASM_currentFileName) 3022 input.value = ASM_currentFileName; 3023 } 3024 3025 function ASM_setCurrentFileName(name) 3026 { 3027 ASM_currentFileName = ASM_safeSourceFileName(name); 3028 ASM_updateSourceFileNameUI(); 3029 ASM_updateSymbolFileNameFromSource(); 3030 return ASM_currentFileName; 3031 } 3032 3033 3034 function ASM_sourceBaseName(name) 3035 { 3036 name = ASM_safeSourceFileName(name || ASM_currentFileName || "source.S"); 3037 name = name.replace(/\.[^.]*$/,""); 3038 name = name.replace(/[^A-Za-z0-9_.-]+/g,"_"); 3039 return name || "source"; 3040 } 3041 3042 function ASM_defaultSymbolFileName(sourceName) 3043 { 3044 return ASM_sourceBaseName(sourceName) + ".SYM"; 3045 } 3046 3047 function ASM_safeSymbolFileName(name) 3048 { 3049 name = String(name || "").trim(); 3050 name = name.replace(/[\\/:*?\"<>|]+/g,"_"); 3051 name = name.replace(/\.[^.]*$/,""); 3052 name = name.replace(/[^A-Za-z0-9_.-]+/g,"_"); 3053 return (name || ASM_sourceBaseName()) + ".SYM"; 3054 } 3055 3056 function ASM_updateSymbolFileNameFromSource() 3057 { 3058 var input = ASM_el("ASM_symbolFileNameInput"); 3059 if(input) input.value = ASM_defaultSymbolFileName(ASM_currentFileName); 3060 } 3061 3062 function ASM_getSymbolFileName() 3063 { 3064 var input = ASM_el("ASM_symbolFileNameInput"); 3065 var filename = ASM_safeSymbolFileName(input ? input.value : ASM_defaultSymbolFileName(ASM_currentFileName)); 3066 if(input && input.value !== filename) input.value = filename; 3067 return filename; 3068 } 3069 3070 3071 function ASM_downloadSource() 3072 { 3073 var sourcePane = ASM_el("ASM_sourcePane"); 3074 var input = ASM_el("ASM_sourceFileNameInput"); 3075 var filename = ASM_setCurrentFileName(input ? input.value : ASM_currentFileName); 3076 3077 ASM_downloadText( 3078 filename, 3079 sourcePane ? sourcePane.value : "", 3080 "text/plain;charset=utf-8" 3081 ); 3082 3083 ASM_setStatus("Downloaded source as " + filename + ".","ok"); 3084 } 3085 3086 function ASM_symbolScopeFromBytes() 3087 { 3088 var compiled = window.asmCompileResult || {}; 3089 var records = compiled.bytes || []; 3090 var ranges = []; 3091 3092 if(!records.length) return ranges; 3093 3094 var pcs = []; 3095 3096 for(var i=0;i<records.length;i++) 3097 { 3098 if(typeof records[i].pc == "number") 3099 pcs.push(records[i].pc & 0xFFFF); 3100 } 3101 3102 if(!pcs.length) return ranges; 3103 3104 pcs.sort(function(a,b){ return a-b; }); 3105 3106 var start = pcs[0]; 3107 var prev = pcs[0]; 3108 3109 for(var j=1;j<pcs.length;j++) 3110 { 3111 var pc = pcs[j]; 3112 3113 if(pc === prev || pc === ((prev + 1) & 0xFFFF)) 3114 { 3115 prev = pc; 3116 continue; 3117 } 3118 3119 ranges.push([start,prev]); 3120 start = pc; 3121 prev = pc; 3122 } 3123 3124 ranges.push([start,prev]); 3125 3126 return ranges; 3127 } 3128 3129 function ASM_scopeHex(scope) 3130 { 3131 var out = []; 3132 3133 for(var i=0;i<(scope || []).length;i++) 3134 { 3135 out.push([ 3136 ASM_symbolHex(scope[i][0],2), 3137 ASM_symbolHex(scope[i][1],2) 3138 ]); 3139 } 3140 3141 return out; 3142 } 3143 3144 function ASM_symbolHex(value,bytes) 3145 { 3146 value = Number(value) & 0xFFFF; 3147 bytes = bytes === 1 ? 1 : 2; 3148 3149 if(window.oASM && typeof window.oASM.valHex == "function") 3150 return window.oASM.valHex(value,bytes).replace(/^0x/i,"$"); 3151 3152 if(bytes === 1) 3153 return "$" + ("0" + (value & 0xFF).toString(16).toUpperCase()).slice(-2); 3154 3155 return "$" + ("000" + value.toString(16).toUpperCase()).slice(-4); 3156 } 3157 3158 3159 function ASM_cleanCommentText(comment) 3160 { 3161 comment = String(comment == null ? "" : comment).trim(); 3162 return comment.replace(/^;\s*/,""); 3163 } 3164 3165 function ASM_rowComment(row,asm) 3166 { 3167 row = row || {}; 3168 asm = asm || window.oASM; 3169 3170 if(asm && typeof asm.splitStatementAndComment == "function") 3171 { 3172 var split = asm.splitStatementAndComment(row.source != null ? row.source : row.statement); 3173 return split && split.comment ? String(split.comment) : ""; 3174 } 3175 3176 var src = String(row.source != null ? row.source : row.statement || ""); 3177 var quote = ""; 3178 var escaped = false;
3179 3180 for(var i=0;i<src.length;i++) 3181 { 3182 var ch = src.charAt(i); 3183 if(escaped) { escaped = false; continue; } 3184 if(ch == "\\") { escaped = true; continue; } 3185 if(quote) 3186 { 3187 if(ch == quote) quote = ""; 3188 continue; 3189 } 3190 if(ch == "'" || ch == '"') { quote = ch; continue; } 3191 if(ch == ";") return src.substring(i); 3192 } 3193 3194 return ""; 3195 } 3196 3197 function ASM_commentName(prefix,value,index) 3198 { 3199 prefix = String(prefix || "COMMENT").replace(/[^A-Za-z0-9_.-]+/g,"_"); 3200 if(value != null && !isNaN(Number(value))) 3201 return prefix + "_" + ASM_symbolHex(value,2).replace(/^\$/,"") + "_" + index; 3202 return prefix + "_" + index; 3203 } 3204 3205 function ASM_collectComments() 3206 { 3207 var out = []; 3208 var rows = window.asmCompileResult && window.asmCompileResult.rows ? window.asmCompileResult.rows : []; 3209 var asm = window.oASM; 3210 3211 if(!asm) return out; 3212 3213 for(var i=0;i<rows.length;i++) 3214 { 3215 var row = rows[i] || {}; 3216 var tag = row.tag || []; 3217 var sym = row.sym || []; 3218 var bytes = row.bytes || []; 3219 var comment = ASM_rowComment(row,asm); 3220 var commentText = ASM_cleanCommentText(comment); 3221 3222 if(!commentText) continue; 3223 3224 var lblIndex = asm.getTokenIndexByTag ? asm.getTokenIndexByTag(tag,"LBL") : -1; 3225 var pgmIndex = asm.getTokenIndexByTag ? asm.getTokenIndexByTag(tag,"PGM") : -1; 3226 var mneIndex = asm.getTokenIndexByTag ? asm.getTokenIndexByTag(tag,"MNE") : -1; 3227 var isEqu = false; 3228 3229 if(pgmIndex >= 0 && asm.pragmaInfo) 3230 { 3231 var pinfo = asm.pragmaInfo(sym[pgmIndex]); 3232 isEqu = !!(pinfo && pinfo.ref === "EQU"); 3233 } 3234 3235 /* 3236 * EQU comments are symbol comments: they belong to the EQU 3237 * symbol value rather than to an instruction PC. 3238 */ 3239 if(isEqu && lblIndex >= 0 && sym[lblIndex]) 3240 { 3241 var equName = asm.getID ? asm.getID(sym[lblIndex]) : String(sym[lblIndex]).substring(0,8); 3242 var equValue = typeof row.val == "number" ? row.val : (asm.symtab ? asm.symtab[equName] : undefined); 3243 if(typeof equValue == "number" && !isNaN(equValue)) 3244 { 3245 out.push({ 3246 name: "COMMENT_" + equName, 3247 type: "comment", 3248 targetType: "equ", 3249 value: equValue & 0xFFFF, 3250 comment: commentText 3251 }); 3252 } 3253 continue; 3254 } 3255 3256 /* 3257 * Instruction/data comments must be matched to a generated 3258 * address and opcode/data-byte sequence. Pure comment rows
3259 * do not have enough information for debugger-side matching. 3260 */ 3261 if(!bytes.length || typeof row.pc != "number") continue; 3262 3263 var value = row.pc & 0xFFFF; 3264 3265 out.push({ 3266 name: ASM_commentName("COMMENT",value,out.length), 3267 type: "comment", 3268 targetType: mneIndex >= 0 ? "instruction" : "data", 3269 value: value, 3270 comment: commentText, 3271 opcode: bytes.slice ? bytes.slice() : [] 3272 }); 3273 } 3274 3275 return out; 3276 } 3277 3278 function ASM_collectSymbols() 3279 { 3280 var out = []; 3281 var seen = {}; 3282 var rows = window.asmCompileResult && window.asmCompileResult.rows ? window.asmCompileResult.rows : []; 3283 var asm = window.oASM; 3284 3285 if(!asm) return out; 3286 3287 for(var i=0;i<rows.length;i++) 3288 { 3289 var row = rows[i] || {}; 3290 var tag = row.tag || []; 3291 var sym = row.sym || []; 3292 3293 if(tag[0] !== "LBL" || !sym[0]) continue; 3294 3295 var name = asm.getID ? asm.getID(sym[0]) : String(sym[0]).substring(0,8); 3296 if(!name || seen[name]) continue; 3297 3298 var pgmIndex = asm.getTokenIndexByTag ? asm.getTokenIndexByTag(tag,"PGM") : -1; 3299 var type = "label"; 3300 var value; 3301 var bytes = 2; 3302 3303 if(pgmIndex >= 0 && asm.pragmaInfo) 3304 { 3305 var pinfo = asm.pragmaInfo(sym[pgmIndex]); 3306 if(pinfo && pinfo.ref === "EQU") 3307 { 3308 type = "equ"; 3309 value = typeof row.val == "number" ? row.val : (asm.symtab ? asm.symtab[name] : undefined); 3310 bytes = row.oby === 1 ? 1 : 2; 3311 } 3312 } 3313 3314 if(type === "label") 3315 { 3316 value = typeof row.pc == "number" ? row.pc : (asm.symtab ? asm.symtab[name] : undefined); 3317 bytes = 2; 3318 } 3319 3320 if(typeof value != "number" || isNaN(value)) continue; 3321 3322 value = value & 0xFFFF; 3323 seen[name] = true; 3324 3325 out.push({ 3326 name: name, 3327 type: type, 3328 bytes: bytes, 3329 value: value, 3330 hex: ASM_symbolHex(value,bytes) 3331 }); 3332 } 3333 3334 out = out.concat(ASM_collectComments()); 3335 3336 return out; 3337 } 3338 3339 function ASM_buildSymbolExportObject() 3340 { 3341 var symbols = ASM_collectSymbols(); 3342 var scope = ASM_symbolScopeFromBytes(); 3343 3344 return { 3345 format: "RetroAppleJS-ASM-symbols", 3346 version: 2, 3347 source: ASM_currentFileName || "source.S", 3348 3349 /* 3350 * The table is valid only when the disassembled instruction address 3351 * is inside one of these ranges. 3352 */ 3353 scopeMode: "instruction-pc", 3354 scope: scope, 3355 scopeHex: ASM_scopeHex(scope), 3356 3357 count: symbols.length, 3358 symbols: symbols 3359 }; 3360 } 3361 3362 function ASM_debuggerOriginAddress() 3363 { 3364 /* 3365 * Prefer the first emitted byte from the assembler output. This is 3366 * the source/listing origin in normal ORG-based programs and stays 3367 * stable even when the code contains later ORG blocks. 3368 */ 3369 var compiled = window.asmCompileResult || {}; 3370 var records = compiled.bytes || []; 3371 3372 for(var i = 0; i < records.length; i++) 3373 { 3374 if(records[i] && typeof records[i].pc == "number") 3375 return records[i].pc & 0xFFFF; 3376 } 3377 3378 var scope = ASM_symbolScopeFromBytes(); 3379 if(scope && scope.length && scope[0] && typeof scope[0][0] == "number") 3380 return scope[0][0] & 0xFFFF; 3381 3382 return null; 3383 } 3384 3385 function ASM_exportSymbols() 3386 { 3387 /* 3388 * Always assemble before export, otherwise the symbol table can be stale 3389 * after source edits. 3390 */
3391 if(!ASM_assembleCurrentSource()) return; 3392 3393 var exportObject = ASM_buildSymbolExportObject(); 3394 var symbols = exportObject.symbols || []; 3395 var scope = exportObject.scope || []; 3396 3397 ASM_downloadText( 3398 ASM_getSymbolFileName(), 3399 JSON.stringify(exportObject,null,2), 3400 "application/json;charset=utf-8" 3401 ); 3402 3403 ASM_setStatus( 3404 "Exported " + symbols.length + " symbol(s) / " + scope.length + " scope range(s).", 3405 "ok" 3406 ); 3407 } 3408 3409 function ASM_showEmulatorTools() 3410 { 3411 var settings = ASM_el("tab1.1"); 3412 var tools = ASM_el("tab1.2"); 3413 3414 /* 3415 * Keep this helper in sync with the POPUP API. 3416 * 3417 * Do not force settings.style.display = "none" here. The Settings 3418 * button later calls oCOM.POPUP.toggle("tab1.1"), and that API only 3419 * flips the element's hidden property. 3420 */ 3421 if(typeof oCOM != "undefined" && oCOM.POPUP && typeof oCOM.POPUP.off == "function") 3422 oCOM.POPUP.off("tab1.1"); 3423 3424 if(tools) 3425 { 3426 if(typeof oCOM != "undefined" && oCOM.POPUP && typeof oCOM.POPUP.on == "function") 3427 oCOM.POPUP.on("tab1.2"); 3428 else 3429 { 3430 tools.hidden = false; 3431 tools.removeAttribute("hidden"); 3432 } 3433 } 3434 } 3435 3436 function ASM_toEmulator() 3437 { 3438 ASM_syncByteCodeFromBoard(); 3439 if(!ASM_currentByteCodeText) 3440 if(!ASM_assembleCurrentSource()) return; 3441 3442 if(typeof EMUboard != "undefined" && EMUboard.boardText) 3443 { 3444 var text = ASM_currentByteCodeText.replace(/\n/g,"\r\n"); 3445 3446 /* 3447 * Normal assembled byte code still needs the monitor prefix and 3448 * a trailing return. Source byte-code output is already a full 3449 * paste script ending with a BSAVE command, deliberately without 3450 * a trailing return after that final command. 3451 */ 3452 if(ASM_sourceByteCodeActive || /^\s*CALL-151(?:\r?\n|$)/i.test(ASM_currentByteCodeText)) 3453 EMUboard.boardText.value = text; 3454 else 3455 EMUboard.boardText.value = "CALL-151\r\n" + text + "\r\n"; 3456 ASM_showEmulatorTools(); 3457 COM_switchTab("tab1"); 3458 ASM_setStatus("Byte code copied to emulator paste board.","ok"); 3459 } 3460 else ASM_setStatus("Emulator paste board is not available.","warn"); 3461 } 3462 3463 function ASM_toDebugger() 3464 { 3465 /* 3466 * Always assemble first so byte code, labels, EQU values and 3467 * comments are generated from the same parser/compiler state. 3468 */ 3469 if(!ASM_assembleCurrentSource()) return; 3470 3471 ASM_syncByteCodeFromBoard(); 3472 3473 var symbolImported = false; 3474 var symbolObject = ASM_buildSymbolExportObject(); 3475 var originPc = ASM_debuggerOriginAddress(); 3476 3477 if(typeof DBG_receiveSymbolTable == "function") 3478 { 3479 symbolImported = DBG_receiveSymbolTable( 3480 symbolObject, 3481 ASM_currentFileName || "source.S", 3482 { 3483 overlap: "override", 3484 disassemble: false, 3485 status: false 3486 } 3487 ); 3488 } 3489 3490 if(typeof DBG_receiveByteCode == "function") 3491 { 3492 DBG_receiveByteCode(ASM_currentByteCodeText,true,originPc); 3493 ASM_setStatus( 3494 "Byte code" + (symbolImported ? " and symbol table" : "") + " imported into debugger tab.", 3495 "ok" 3496 ); 3497 return; 3498 } 3499 3500 /* 3501 * Fallback for older debugger builds. 3502 */ 3503 var debugfield = ASM_el("debugfield"); 3504 if(debugfield) 3505 { 3506 debugfield.innerHTML = ASM_escapeHTML(ASM_currentByteCodeText).replace(/\n/g,"<br>"); 3507 COM_switchTab("tab3"); 3508 ASM_setStatus("Byte code copied to debugger tab.","ok"); 3509 } 3510 else ASM_setStatus("Debugger output field is not available.","warn"); 3511 } 3512 3513 function DBG_receiveByteCode(text,switchToTab,originPc) 3514 { 3515 DBG_currentByteCodeText = String(text || ""); 3516 3517 var board = DBG_el("DBG_boardText"); 3518 if(board) board.value = DBG_currentByteCodeText; 3519 3520 var ok = DBG_importBoardByteCode(); 3521 3522 if(switchToTab !== false && typeof COM_switchTab == "function") 3523 COM_switchTab("tab3"); 3524 3525 /* 3526 * Scroll after the tab switch has applied, otherwise the hidden 3527 * tab3 listing pane can report a zero clientHeight and the centering 3528 * calculation in DBG_scrollListingToPC() becomes unreliable. 3529 */ 3530 if(originPc != null && !isNaN(Number(originPc))) 3531 { 3532 var pc = Number(originPc) & 0xFFFF; 3533 if(typeof DBG_prepareDebuggerStartPC == "function") 3534 DBG_prepareDebuggerStartPC(pc,{clearTrace:true}); 3535 if(typeof DBG_prepareDebuggerStartPC == "function") 3536 DBG_prepareDebuggerStartPC(pc,{clearTrace:true}); 3537 window.setTimeout(function(){ 3538 if(typeof DBG_scrollListingToPC != "function") return; 3539 3540 var listed = DBG_scrollListingToPC(pc,{behavior:"auto"}); 3541 if(listed) 3542 DBG_statusText( 3543 "Imported byte code and positioned listing at $" + DBG_hexWord(pc) + "." 3544 ); 3545 },0); 3546 } 3547 3548 return ok; 3549 } 3550 3551 function ASM_clearBoard() 3552 { 3553 ASM_setByteCodeOutput("",0); 3554 } 3555 3556 function ASM_resizeTextAreaToContent(el) 3557 { 3558 if(!el) return; 3559 var horizontalScroll = el.scrollLeft || 0; 3560 3561 var wrap = el.closest ? el.closest(".com-pane-fr-wrap") : null; 3562 3563 el.style.height = "auto"; 3564 el.style.overflowY = "hidden"; 3565 3566 if(wrap) 3567 { 3568 wrap.style.height = "auto"; 3569 wrap.style.overflow = "hidden"; 3570 } 3571 3572 var h = Math.max(420, el.scrollHeight); 3573 el.style.height = h + "px"; 3574 if(wrap) wrap.style.height = h + "px"; 3575 3576 /* 3577 * These editors grow to their complete content height, so a 3578 * vertical textarea scroll offset is always stale. An editable 3579 * source textarea can retain such an offset after caret/focus 3580 * activity; the find overlay copies it and shifts Source almost 3581 * one printer line above Listing. Preserve horizontal scrolling, 3582 * but anchor both text origins at the top of their pane bodies. 3583 */ 3584 el.scrollTop = 0; 3585 el.scrollLeft = horizontalScroll; 3586 if(wrap) {
3587 var overlay = wrap.querySelector(".com-pane-fr-overlay"); 3588 if(overlay) { 3589 overlay.scrollTop = 0; 3590 overlay.scrollLeft = horizontalScroll; 3591 } 3592 } 3593 3594 3595 } 3596 3597 function ASM_resizeSourceListingPanes() 3598 { 3599 var sourcePane = ASM_el("ASM_sourcePane"); 3600 var listingPane = ASM_el("ASM_listingPane"); 3601 var includeMount = ASM_el("ASM_includeMount"); 3602 3603 ASM_resizeTextAreaToContent(sourcePane); 3604 ASM_resizeTextAreaToContent(listingPane); 3605 3606 if(includeMount) 3607 { 3608 var sourceHeight = sourcePane ? sourcePane.offsetHeight : 420; 3609 var listingHeight = listingPane ? listingPane.offsetHeight : 420; 3610 includeMount.style.height = Math.max(420, sourceHeight, listingHeight) + "px"; 3611 includeMount.style.maxHeight = includeMount.style.height; 3612 } 3613 3614 ASM_equalizePaneHeaders(); 3615 } 3616 3617 function ASM_normalizeLineEndingsInfo(text) 3618 { 3619 var m = String(text || "").match(/\r\n|\r|\n/); 3620 return { 3621 eol: m ? m[0] : "\n", 3622 hasFinalEol: /(\r\n|\r|\n)$/.test(String(text || "")) 3623 }; 3624 } 3625 3626 function ASM_splitSourceLines(text) 3627 { 3628 return String(text || "").split(/\r\n|\r|\n/); 3629 } 3630 3631 function ASM_joinSourceLines(lines, info) 3632 { 3633 var out = lines.join(info.eol || "\n"); 3634 if(info.hasFinalEol && lines.length && lines[lines.length - 1] !== "") out += info.eol || "\n"; 3635 return out; 3636 } 3637 3638 function ASM_snapshotSourceEdit(label) 3639 { 3640 var sourcePane = ASM_el("ASM_sourcePane"); 3641 if(!sourcePane) return null; 3642 return { 3643 label: label || "edit", 3644 value: sourcePane.value, 3645 selectionStart: sourcePane.selectionStart || 0, 3646 selectionEnd: sourcePane.selectionEnd || 0, 3647 scrollTop: sourcePane.scrollTop || 0 3648 }; 3649 } 3650 3651 function ASM_restoreSourceSnapshot(snap) 3652 { 3653 var sourcePane = ASM_el("ASM_sourcePane"); 3654 if(!snap || !sourcePane) return; 3655 sourcePane.value = snap.value; 3656 ASM_resizeSourceListingPanes(); 3657 ASM_refreshFindReplaceOverlay(); 3658 sourcePane.focus(); 3659 try { sourcePane.setSelectionRange(snap.selectionStart, snap.selectionEnd); } catch(_ignore) {} 3660 sourcePane.scrollTop = snap.scrollTop || 0; 3661 } 3662 3663 function ASM_pushSourceUndo(label) 3664 { 3665 var snap = ASM_snapshotSourceEdit(label); 3666 if(!snap) return; 3667 ASM_sourceEditHistory.undo.push(snap); 3668 if(ASM_sourceEditHistory.undo.length > ASM_sourceEditHistory.max) ASM_sourceEditHistory.undo.shift(); 3669 ASM_sourceEditHistory.redo.length = 0; 3670 } 3671 3672 function ASM_sourceUndo() 3673 { 3674 if(!ASM_sourceEditHistory.undo.length) return; 3675 ASM_sourceEditHistory.redo.push(ASM_snapshotSourceEdit("redo")); 3676 ASM_restoreSourceSnapshot(ASM_sourceEditHistory.undo.pop()); 3677 } 3678 3679 function ASM_sourceRedo() 3680 { 3681 if(!ASM_sourceEditHistory.redo.length) return; 3682 ASM_sourceEditHistory.undo.push(ASM_snapshotSourceEdit("undo")); 3683 ASM_restoreSourceSnapshot(ASM_sourceEditHistory.redo.pop()); 3684 } 3685 3686 function ASM_sourceOutdent() 3687 { 3688 var sourcePane = ASM_el("ASM_sourcePane"); 3689 if(!sourcePane) return; 3690 3691 ASM_pushSourceUndo("outdent"); 3692 3693 var text = sourcePane.value; 3694 var info = ASM_normalizeLineEndingsInfo(text); 3695 var lines = ASM_splitSourceLines(text); 3696 var removed = []; 3697 3698 for(var i=0;i<lines.length;i++) 3699 { 3700 if(lines[i].length) 3701 { 3702 removed[i] = lines[i].charAt(0); 3703 lines[i] = lines[i].slice(1); 3704 } 3705 else removed[i] = ""; 3706 } 3707 3708 ASM_sourceEditHistory.outdent.push(removed); 3709 sourcePane.value = ASM_joinSourceLines(lines, info); 3710 ASM_resizeSourceListingPanes(); 3711 ASM_refreshFindReplaceOverlay(); 3712 } 3713 3714 function ASM_sourceIndent() 3715 { 3716 var sourcePane = ASM_el("ASM_sourcePane"); 3717 if(!sourcePane) return; 3718 3719 ASM_pushSourceUndo("indent"); 3720 3721 var text = sourcePane.value; 3722 var info = ASM_normalizeLineEndingsInfo(text); 3723 var lines = ASM_splitSourceLines(text); 3724 var restore = ASM_sourceEditHistory.outdent.length ? ASM_sourceEditHistory.outdent.pop() : null; 3725 var nbsp = "Â "; 3726 3727 for(var i=0;i<lines.length;i++) 3728 lines[i] = (restore ? (restore[i] || "") : nbsp) + lines[i]; 3729 3730 sourcePane.value = ASM_joinSourceLines(lines, info); 3731 ASM_resizeSourceListingPanes(); 3732 ASM_refreshFindReplaceOverlay(); 3733 } 3734 3735 function ASM_syncSourceFindReplaceSkin() 3736 { 3737 var sourcePane = ASM_el("ASM_sourcePane"); 3738 var wrap = sourcePane && sourcePane.closest ? sourcePane.closest(".com-pane-fr-wrap") : null; 3739 3740 /* 3741 * Source is wrapped by PANE.bindFindReplace(). Put the common 3742 * COM_MAIN.css dot-matrix skin on the wrapper instead of 3743 * duplicating the background-image in index.html. 3744 */ 3745 if(wrap) wrap.classList.add("dotmatrix_bck"); 3746 } 3747
3748 function ASM_syncSourceFileNameTheme() 3749 { 3750 var input = ASM_el("ASM_sourceFileNameInput"); 3751 if(!input) return; 3752 3753 if(ASM_headerThemeDark) 3754 { 3755 input.style.background = "#0b0f14"; 3756 input.style.borderColor = "#30363d"; 3757 input.style.color = "#e6edf3"; 3758 } 3759 else 3760 { 3761 input.style.background = "#ffffff"; 3762 input.style.borderColor = "rgba(0,0,0,0.25)"; 3763 input.style.color = "#000000"; 3764 } 3765 } 3766 3767 function ASM_toggleHeaderTheme() 3768 { 3769 var root = ASM_el("ASM_root"); 3770 if(!root) return; 3771 ASM_headerThemeDark = !ASM_headerThemeDark; 3772 root.classList.toggle("ASM_headerDark", ASM_headerThemeDark); 3773 root.classList.toggle("ASM_paneDark", ASM_headerThemeDark); 3774 ASM_syncSourceFileNameTheme(); 3775 ASM_refreshFindReplaceOverlay(); 3776 } 3777 3778 function defaultPaneHeaderWidgetHTML() 3779 { 3780 return '' 3781 + '<span class="com-pane-header-default-widget com-pane-header-widget">' 3782 + '<button type="button" class="appbut skinny" data-pane-default-act="undo" title="Undo"><i class="fa fa-undo"></i></button>' 3783 + '<button type="button" class="appbut skinny" data-pane-default-act="redo" title="Redo"><i class="fa fa-redo"></i></button>' 3784 + '<button type="button" class="appbut skinny" data-pane-default-act="indent" title="Indent"><i class="fa fa-indent"></i></button>' 3785 + '<button type="button" class="appbut skinny" data-pane-default-act="outdent" title="Outdent"><i class="fa fa-outdent"></i></button>' 3786 + '<button type="button" class="appbut skinny" data-pane-default-act="theme" title="Toggle dark/light mode"><i class="fa fa-adjust"></i></button>' 3787 + '<button type="button" class="appbut skinny" data-pane-default-act="bottom" title="Jump to the bottom"><i class="fa fa-angle-double-down"></i></button>' 3788 + '</span>'; 3789 }; 3790 3791 function ASM_sourceDefaultPaneHeaderHTML() 3792 { 3793 if(!ASM_oPANE || typeof defaultPaneHeaderWidgetHTML != "function") 3794 return '<span class="com-pane-header-default-widget com-pane-header-widget">' 3795 + '<button type="button" class="appbut skinny" data-pane-default-act="assemble" title="Assemble"><i class="fa fa-play blink"></i></button>' 3796 + '</span>'; 3797 3798 return defaultPaneHeaderWidgetHTML().replace( 3799 '<span class="com-pane-header-default-widget com-pane-header-widget">', 3800 '<span class="com-pane-header-default-widget com-pane-header-widget">' 3801 + '<button type="button" class="appbut skinny" data-pane-default-act="assemble" title="Assemble"><i class="fa fa-play blink"></i></button>' 3802 ); 3803 } 3804 3805 function ASM_bindSourceDefaultWidgetActions() 3806 { 3807 var host = ASM_el("ASM_sourceFindReplaceMount"); 3808 if(!host) return; 3809 3810 host.addEventListener("click", function(ev){ 3811 var btn = ev.target && ev.target.closest ? ev.target.closest("[data-pane-default-act]") : null; 3812 if(!btn) return; 3813 3814 var act = btn.getAttribute("data-pane-default-act"); 3815 if(act == "assemble") ASM_assembleCurrentSource(); 3816 else if(act == "undo") ASM_sourceUndo(); 3817 else if(act == "redo") ASM_sourceRedo(); 3818 else if(act == "indent") ASM_sourceIndent(); 3819 else if(act == "outdent") ASM_sourceOutdent(); 3820 else if(act == "theme") ASM_toggleHeaderTheme(); 3821 else if(act == "bottom") window.scrollTo(0, document.body.scrollHeight); 3822 else if(act == "top") window.scrollTo({ top: 0 }); 3823 3824 ev.preventDefault(); 3825 }); 3826 } 3827 3828 function ASM_equalizePaneHeaders() 3829 { 3830 if(ASM_oPANE && typeof ASM_oPANE.equalizePaneHeaderHeights == "function") 3831 ASM_oPANE.equalizePaneHeaderHeights("#ASM_root .ASM_paneTitle"); 3832 } 3833 3834 function ASM_toggleIncludePane(forceShow) 3835 { 3836 var host = ASM_el("ASM_paneHost"); 3837 if(!host) return false;
3838 3839 if(!ASM_includeFiles.length) 3840 { 3841 host.classList.remove("ASM_showInclude"); 3842 ASM_syncIncludeToggleButton(); 3843 ASM_equalizePaneHeaders(); 3844 return false; 3845 } 3846 3847 if(!ASM_currentIncludeFile()) 3848 ASM_selectedIncludeId = String(ASM_includeFiles[0].id); 3849 3850 var show = (typeof forceShow == "boolean") ? forceShow : !host.classList.contains("ASM_showInclude"); 3851 host.classList.toggle("ASM_showInclude",show); 3852 3853 ASM_updateIncludePane(); 3854 ASM_renderIncludeLabels(); 3855 ASM_syncIncludeToggleButton(); 3856 ASM_resizeSourceListingPanes(); 3857 ASM_equalizePaneHeaders(); 3858 return show; 3859 } 3860 3861 function ASM_refreshFindReplaceOverlay() 3862 { 3863 if(ASM_sourceFindReplace && typeof ASM_sourceFindReplace.render == "function") 3864 ASM_sourceFindReplace.render(); 3865 } 3866 3867 function ASM_bindSourceFindReplace() 3868 { 3869 if(typeof PANE != "function") 3870 { 3871 ASM_setStatus("PANE() was not found. Search & replace is unavailable.","warn"); 3872 return; 3873 } 3874 3875 ASM_oPANE = new PANE(); 3876 3877 if(typeof ASM_oPANE.injectFindReplaceCSS == "function") 3878 ASM_oPANE.injectFindReplaceCSS(); 3879 3880 ASM_applyFindReplaceScheme(); 3881 3882 if(typeof ASM_oPANE.createPaneHeaderWidgetManager == "function" 3883 && typeof defaultPaneHeaderWidgetHTML == "function") 3884 { 3885 ASM_sourceHeaderWidgetManager = ASM_oPANE.createPaneHeaderWidgetManager("ASM_sourceFindReplaceMount", { 3886 defaultId:"default", 3887 defaultHTML:ASM_sourceDefaultPaneHeaderHTML(), 3888 onChange:function(){ ASM_equalizePaneHeaders(); } 3889 }); 3890 ASM_bindSourceDefaultWidgetActions(); 3891 } 3892 3893 if(typeof ASM_oPANE.bindFindReplace == "function") 3894 { 3895 ASM_sourceFindReplace = ASM_oPANE.bindFindReplace("ASM_sourcePane", { 3896 id:"ASM_sourceFindReplace", 3897 mount:"ASM_sourceFindReplaceMount", 3898 inline:true, 3899 overlayExpand:true, 3900 floatingNav:true, 3901 pageScrollOnMatch:true, 3902 pageScrollOffset:72, 3903 manager:ASM_sourceHeaderWidgetManager, 3904 scheme:ASM_findReplaceScheme, 3905 barWidth:"390px", 3906 debounce_ms:500, 3907 onChange:function(){ 3908 ASM_resizeSourceListingPanes(); 3909 ASM_equalizePaneHeaders(); 3910 ASM_setStatus("Source updated by search & replace. Click assemble to regenerate output.","warn"); 3911 } 3912 }); 3913 3914 var sourceWrap = document.querySelector("#ASM_sourcePane") && 3915 document.querySelector("#ASM_sourcePane").closest(".com-pane-fr-wrap"); 3916 3917 if(sourceWrap) sourceWrap.classList.add("dotmatrix_bck"); 3918 } 3919 else ASM_setStatus("PANE.bindFindReplace() was not found. Search & replace is unavailable.","warn"); 3920 } 3921 3922 function ASM_manualDocHTML(key) 3923 { 3924 if(typeof _DOCS == "undefined" || !_DOCS || !_DOCS[key]) return ""; 3925 3926 return String(_DOCS[key]) 3927 .replace(/https\:\/\/github\.com\/RetroAppleJS\/RetroAppleJS\.github\.io\/raw\/main\//g,""); 3928 } 3929 3930 function ASM_loadManualPane() 3931 { 3932 var manualEl = ASM_el("ASM_manual"); 3933 if(!manualEl) return; 3934 3935 var assemblerDoc = ASM_manualDocHTML("ASSEMBLER"); 3936 var cpuDoc = ASM_manualDocHTML("6502"); 3937 3938 if(!assemblerDoc && !cpuDoc) 3939 { 3940 manualEl.textContent = "Assembler manual is not available."; 3941 return; 3942 } 3943 3944 manualEl.innerHTML = 3945 assemblerDoc 3946 + (cpuDoc 3947 ? [ 3948 "", 3949 "<hr>", 3950 "<h1>6502 CPU Reference</h1>", 3951 cpuDoc 3952 ].join("\n") 3953 : ""); 3954 3955 } 3956 3957 function ASM_registerPopupRules() 3958 { 3959 /* 3960 * Keep tab2 popup handling consistent with tab1 and tab3:
3961 * buttons call oCOM.POPUP.runRule(), rules toggle one overlay 3962 * and close the other one. 3963 * 3964 * Rule names: 3965 * tab2.1 = Settings 3966 * tab2.2 = Tools 3967 */ 3968 if(typeof oCOM == "undefined" || !oCOM || !oCOM.POPUP || typeof oCOM.POPUP.addRule != "function") return; 3969 oCOM.POPUP.addRule("tab2.1",function(o){ o.toggle("tab2.1"); o.off("tab2.2"); }); 3970 oCOM.POPUP.addRule("tab2.2",function(o){ o.toggle("tab2.2"); o.off("tab2.1"); }); 3971 } 3972 3973 function ASM_bindEvents() 3974 { 3975 var fileInput = ASM_el("ASM_fileInput"); 3976 var includeFileInput = ASM_el("ASM_includeFileInput"); 3977 var assembleButton = ASM_el("ASM_assembleButton"); 3978 var toggleIncludeButton = ASM_el("ASM_toggleIncludeButton"); 3979 var sourceFileNameInput = ASM_el("ASM_sourceFileNameInput"); 3980 var symbolFileNameInput = ASM_el("ASM_symbolFileNameInput"); 3981 var downloadSourceButton = ASM_el("ASM_downloadSourceButton"); 3982 var copyByteCodeButton = ASM_el("ASM_copyByteCodeButton"); 3983 var copyIncludeButton = ASM_el("ASM_copyIncludeButton"); 3984 var closeIncludeButton = ASM_el("ASM_closeIncludeButton"); 3985 var jsonPopupButton = ASM_el("ASM_jsonPopupButton"); 3986 var diagnosticsButton = ASM_el("ASM_diagnosticsButton"); 3987 var outputPopupBack = ASM_el("ASM_outputPopupBack"); 3988 var outputPopupCopy = ASM_el("ASM_outputPopupCopy"); 3989 var outputPopupClose = ASM_el("ASM_outputPopupClose"); 3990 var encodeSourceButton = ASM_el("ASM_encodeSourceButton"); 3991 var sourceByteCodeStartInput = ASM_el("ASM_sourceByteCodeStartInput"); 3992 var toEmulatorButtons = document.querySelectorAll(".ASM_toEmulatorButton"); 3993 var toDebuggerButtons = document.querySelectorAll(".ASM_toDebuggerButton"); 3994 var sourcePane = ASM_el("ASM_sourcePane"); 3995 var boardText = ASM_el("ASM_boardText"); 3996 var listingColumnsInput = ASM_el("ASM_listingColumnsInput"); 3997 var settingsListingColumnsInput = ASM_el("ASM_settingsListingColumnsInput"); 3998 3999 var dQuoteLegacyInput = ASM_el("ASM_dQuoteLegacyInput"); 4000 var listingPresetButtons = document.querySelectorAll(".ASM_listingPreset[data-listing-preset]"); 4001 if(fileInput) fileInput.addEventListener("change",function(){ ASM_loadFile(fileInput.files && fileInput.files[0]); }); 4002 if(includeFileInput) includeFileInput.addEventListener("change",async function() 4003 { 4004 try 4005 { 4006 await ASM_loadIncludeFiles(includeFileInput.files); 4007 } 4008 catch(err) 4009 { 4010 ASM_setStatus("Include load failed: " + (err && err.message ? err.message : err),"bad"); 4011 } 4012 finally 4013 { 4014 includeFileInput.value = ""; 4015 } 4016 }); 4017 if(assembleButton) assembleButton.addEventListener("click",function(ev){ ev.preventDefault(); ASM_assembleCurrentSource(); }); 4018 if(toggleIncludeButton) toggleIncludeButton.addEventListener("click",function(){ ASM_toggleIncludePane(); }); 4019 if(sourceFileNameInput) sourceFileNameInput.addEventListener("change",function(){ ASM_setCurrentFileName(sourceFileNameInput.value); }); 4020 if(sourceFileNameInput) sourceFileNameInput.addEventListener("keydown",function(ev){ 4021 if(ev.key == "Enter") { ev.preventDefault(); ASM_setCurrentFileName(sourceFileNameInput.value); sourceFileNameInput.blur(); } 4022 }); 4023 if(symbolFileNameInput) symbolFileNameInput.addEventListener("change",function(){ ASM_getSymbolFileName(); }); 4024 if(symbolFileNameInput) symbolFileNameInput.addEventListener("keydown",function(ev){ 4025 if(ev.key == "Enter") { ev.preventDefault(); ASM_getSymbolFileName(); symbolFileNameInput.blur(); } 4026 }); 4027 if(downloadSourceButton) downloadSourceButton.addEventListener("click",ASM_downloadSource); 4028 if(copyByteCodeButton) copyByteCodeButton.addEventListener("click",function(){ ASM_syncByteCodeFromBoard(); ASM_copyText(ASM_currentByteCodeText,"Byte code copied to clipboard."); }); 4029 if(copyIncludeButton) copyIncludeButton.addEventListener("click",function(){ 4030 var includeMount = ASM_el("ASM_includeMount"); 4031 ASM_copyText(includeMount ? includeMount.value : "","Include file copied to clipboard."); 4032 }); 4033 if(closeIncludeButton) closeIncludeButton.addEventListener("click",function(){ ASM_toggleIncludePane(false); }); 4034 if(jsonPopupButton) jsonPopupButton.addEventListener("click",function(){ ASM_toggleOutputPopup("json"); }); 4035 if(diagnosticsButton) diagnosticsButton.addEventListener("click",ASM_jumpToErrors); 4036 if(outputPopupCopy) outputPopupCopy.addEventListener("click",function(){ 4037 var text = ASM_el("ASM_outputPopupText"); 4038 ASM_copyText( 4039 text ? text.value : "", 4040 ASM_outputPopupKind == "diagnostics" ? "Diagnostics copied to clipboard." : "JSON copied to clipboard." 4041 ); 4042 }); 4043 if(outputPopupClose) outputPopupClose.addEventListener("click",ASM_closeOutputPopup); 4044 if(outputPopupBack) outputPopupBack.addEventListener("click",function(ev){ 4045 if(ev.target === outputPopupBack) ASM_closeOutputPopup(); 4046 }); 4047 document.addEventListener("keydown",function(ev){ 4048 if(ev.key == "Escape" && outputPopupBack && !outputPopupBack.hidden) ASM_closeOutputPopup(); 4049 }); 4050 if(boardText) boardText.addEventListener("input",function(){ 4051 ASM_sourceByteCodeActive = false;
4052 ASM_setSourceByteCodeModeUI(false); 4053 ASM_syncByteCodeFromBoard(); 4054 ASM_updateSourceByteCodeRangeInfo(ASM_sourceByteCodeOrigin(false),ASM_countByteCodeText(ASM_currentByteCodeText)); 4055 }); 4056 if(encodeSourceButton) 4057 { 4058 encodeSourceButton.addEventListener("click",ASM_byteEncodeSource); 4059 encodeSourceButton.addEventListener("keydown",function(ev){ 4060 if(ev.key == "Enter" || ev.key == " ") 4061 { 4062 ev.preventDefault(); 4063 ASM_byteEncodeSource(); 4064 } 4065 }); 4066 } 4067 4068 if(sourceByteCodeStartInput) 4069 { 4070 sourceByteCodeStartInput.addEventListener("input",ASM_refreshSourceByteCodeFromAddress); 4071 sourceByteCodeStartInput.addEventListener("change",function(){ 4072 ASM_normalizeSourceByteCodeStartInput(); 4073 ASM_refreshSourceByteCodeFromAddress(); 4074 }); 4075 sourceByteCodeStartInput.addEventListener("keydown",function(ev){ 4076 if(ev.key == "Enter") 4077 { 4078 ev.preventDefault(); 4079 ASM_normalizeSourceByteCodeStartInput(); 4080 if(!ASM_sourceByteCodeActive) ASM_byteEncodeSource(); 4081 else ASM_refreshSourceByteCodeFromAddress(); 4082 sourceByteCodeStartInput.blur(); 4083 } 4084 }); 4085 } 4086 4087 4088 for(var i = 0; i < toEmulatorButtons.length; i++) 4089 toEmulatorButtons[i].addEventListener("click",ASM_toEmulator); 4090 4091 for(var j = 0; j < toDebuggerButtons.length; j++) 4092 toDebuggerButtons[j].addEventListener("click",ASM_toDebugger); 4093 4094 if(sourcePane) 4095 { 4096 /* 4097 * Source is an auto-growing editor. It must never retain a 4098 * vertical internal scroll offset; page scrolling owns the 4099 * vertical navigation for these side-by-side panes. 4100 */ 4101 sourcePane.addEventListener("scroll",function(){ 4102 if(sourcePane.scrollTop) sourcePane.scrollTop = 0; 4103 }); 4104 4105 sourcePane.addEventListener("input",function(){ 4106 ASM_resizeSourceListingPanes(); 4107 ASM_refreshFindReplaceOverlay(); 4108 if(ASM_sourceByteCodeActive) 4109 ASM_renderSourceByteCode({status:false,normalizeInput:false}); 4110 }); 4111 sourcePane.addEventListener("keydown",function(ev){ 4112 if((ev.ctrlKey || ev.metaKey) && ev.key == "Enter") 4113 { 4114 ev.preventDefault(); 4115 ASM_assembleCurrentSource(); 4116 } 4117 }); 4118 } 4119 4120 if(listingColumnsInput) listingColumnsInput.addEventListener("change",function(){ 4121 ASM_syncListingColumns(listingColumnsInput); 4122 ASM_clearOutputs("Listing columns changed. Click assemble to regenerate output.","warn"); 4123 }); 4124 4125 if(settingsListingColumnsInput) settingsListingColumnsInput.addEventListener("change",function(){ 4126 ASM_syncListingColumns(settingsListingColumnsInput); 4127 ASM_clearOutputs("Listing columns changed. Click assemble to regenerate output.","warn"); 4128 }); 4129 4130 for(var p = 0; p < listingPresetButtons.length; p++) 4131 { 4132 listingPresetButtons[p].addEventListener("click",function(){ 4133 ASM_applyListingColumnPreset(this.getAttribute("data-listing-preset")); 4134 }); 4135 } 4136 4137 if(dQuoteLegacyInput) dQuoteLegacyInput.addEventListener("change",function(){ 4138 ASM_clearOutputs("dQuoteLegacy changed. Click assemble to regenerate output.","warn"); 4139 }); 4140 4141 ASM_syncSourceFindReplaceSkin(); 4142 } 4143 4144 4145 ASM_registerPopupRules(); 4146 ASM_bindEvents();
4147 ASM_updateSourceFileNameUI(); 4148 ASM_updateSymbolFileNameFromSource(); 4149 ASM_syncSourceFileNameTheme(); 4150 ASM_syncListingColumns(); 4151 ASM_bindSourceFindReplace(); 4152 ASM_loadManualPane(); 4153 ASM_toggleIncludePane(false); 4154 ASM_clearOutputs("Sample source loaded. Click assemble to generate output.",""); 4155 ASM_updateSourceByteCodeRangeInfo(ASM_sourceByteCodeOrigin(false),0); 4156 ASM_resizeSourceListingPanes(); 4157 ASM_refreshFindReplaceOverlay(); 4158 ASM_equalizePaneHeaders(); 4159 </script>
4159 4160 4161 </div> 4162 4163 4164 <!-- 4165 ââââââ âââââââ ââââââ ââ ââ ââââââ ââââââ âââââââ ââââââ 4166 ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ 4167 ââ ââ âââââ ââââââ ââ ââ ââ âââ ââ âââ âââââ ââââââ 4168 ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ ââ 4169 ââââââ âââââââ ââââââ ââââââ ââââââ ââââââ âââââââ ââ ââ 4170 --> 4171 4172 <div id="tab3" style="width:1300px;min-height:720px;border-radius:10px;background-color:#76B9CE;padding: 0px 0px 14px 0px;box-sizing: border-box;user-select:none;-moz-user-select:none;-webkit-user-drag:none;-webkit-user-select:none;-ms-user-select:none;"> 4173 4174 4175<style> 4176 /* 4177 Layout-first prototype for redesigned Debugger tab. 4178 This version intentionally uses mock content and lightweight UI toggles only. 4179 */ 4180 4181 #DBG_root { 4182 position: relative; 4183 } 4184 4185 .DBG_paneHost { 4186 display: grid; 4187 grid-template-columns: repeat(3, minmax(0, 1fr)); 4188 gap: 10px; 4189 width: calc(100% - 10px); 4190 max-width: calc(100% - 10px); 4191 align-items: stretch; 4192 } 4193 4194 .DBG_paneHost.DBG_hideWatch { 4195 grid-template-columns: repeat(2, minmax(0, 1fr)); 4196 } 4197 4198 .DBG_paneHost.DBG_hideWatch #DBG_watchPane { 4199 display: none; 4200 } 4201 4202 .DBG_pane { 4203 min-width: 0; 4204 border-radius: 10px; 4205 overflow: visible; 4206 } 4207 4208 .DBG_paneTitle, 4209 .com-pane-title.DBG_paneTitle { 4210 display: flex; 4211 align-items: center; 4212 justify-content: space-between; 4213 gap: 10px; 4214 min-height: 34px; 4215 margin: 0px 0px 2px 0px; 4216 padding: 4px 8px; 4217 border-radius: 10px; 4218 border: 1px solid rgba(0,0,0,0.18); 4219 background: rgba(244,244,244,0.88); 4220 color: #000000; 4221 font-family: arial, sans-serif; 4222 font-size: 12px; 4223 line-height: 20px; 4224 box-sizing: border-box; 4225 overflow: visible; 4226 position: relative; 4227 } 4228 4229 .DBG_paneTitleMain { 4230 display: flex; 4231 align-items: center; 4232 gap: 8px; 4233 min-width: 0; 4234 white-space: nowrap; 4235 } 4236 4237 .DBG_paneTitleMain strong { 4238 color: #000000; 4239 font-size: 13px; 4240 font-weight: 700; 4241 } 4242 4243 .DBG_paneInfo { 4244 color: rgba(0,0,0,0.70); 4245 font-family: arial, sans-serif; 4246 font-size: 11px; 4247 white-space: nowrap; 4248 } 4249 4250 .DBG_headerActions, 4251 .com-pane-header-default-widget.DBG_headerActions { 4252 display: inline-flex; 4253 align-items: center; 4254 justify-content: flex-end; 4255 gap: 4px; 4256 flex-wrap: nowrap; 4257 } 4258 4259 .DBG_headerActions .appbut i { 4260 pointer-events: none; 4261 } 4262 4263 .DBG_headerActions.DBG_requestActive [data-dbg-action] { 4264 display: none !important; 4265 } 4266 4267 .DBG_traceRegisterInfo { font-family: courier, monospace; } 4268 .DBG_traceRegisterInfo sub { font-size: 8px; line-height: 0; } 4269 4270 .DBG_startPcRequester { 4271 display: inline-flex; 4272 align-items: center; 4273 justify-content: flex-end; 4274 gap: 4px; 4275 white-space: nowrap; 4276 } 4277 4278 .DBG_startPcRequester[hidden] { 4279 display: none !important; 4280 } 4281 4282 .DBG_startPcRequester span { 4283 color: rgba(0,0,0,0.72); 4284 font-family: arial, sans-serif; 4285 font-size: 11px; 4286 font-weight: 700; 4287 } 4288 4289 .DBG_startPcInput { 4290 width: 64px; 4291 min-height: 20px; 4292 font: 12px/1.2 courier, monospace; 4293 text-transform: uppercase; 4294 box-sizing: border-box; 4295 } 4296 4297 .DBG_paneBody { 4298 height: 595px; 4299 min-height: 595px; 4300 padding: 0px; 4301 position: relative; 4302 border-radius: 10px; 4303 border: 1px solid rgba(0,0,0,0.12); 4304 box-sizing: border-box; 4305 overflow: hidden; 4306 } 4307 4308 .DBG_textPane { 4309 display: block; 4310 width: 100%; 4311 height: 100%; 4312 margin: 0px; 4313 padding: 0px 30px; 4314 border: 0px; 4315 outline: none; 4316 resize: none; 4317 color: #000000; 4318 background: transparent; 4319 font-family: courier, monospace; 4320 font-size: 12px; 4321 font-weight: 500; 4322 line-height: 21px; 4323 box-sizing: border-box; 4324 tab-size: 4; 4325 position: relative; 4326 z-index: 1; 4327 white-space: pre; 4328 overflow: auto; 4329 user-select: text; 4330 -moz-user-select: text; 4331 -webkit-user-select: text;
4332 -ms-user-select: text; 4333 -webkit-user-drag: none; 4334 caret-color: transparent; 4335 font-variant-ligatures: none; 4336 } 4337 4338 .DBG_textPane::selection { 4339 background: rgba(80, 145, 230, 0.34); 4340 } 4341 4342 .DBG_listingPcOverlay 4343 { 4344 position: absolute; 4345 left: 0px; 4346 right: 0px; 4347 height: 21px; 4348 background: rgba(255, 214, 64, 0.42); 4349 pointer-events: none; 4350 z-index: 0; 4351 } 4352 4353 .DBG_listingLineCurrent { 4354 display: block; 4355 margin: 0px -30px; 4356 padding: 0px 30px; 4357 background: rgba(255, 214, 64, 0.42); 4358 } 4359 4360 .DBG_breakpoint { 4361 color: #9a0000; 4362 font-weight: 700; 4363 } 4364 4365 .DBG_traceLineCurrent { 4366 display: block; 4367 margin: 0px -30px; 4368 padding: 0px 30px; 4369 background: rgba(140, 190, 220, 0.33); 4370 } 4371 4372 4373 /* 4374 * Debugger dark mode is applied dynamically by DBG_applyTheme(). 4375 * Do not style #tab3 itself: the background around the panes must stay unchanged. 4376 */ 4377 4378 .DBG_symbolBox { 4379 width: 370px; 4380 max-width: 370px; 4381 height: auto; 4382 min-height: 63px; 4383 padding: 0px 6px; 4384 text-align: left; 4385 box-sizing: border-box; 4386 overflow: visible; 4387 } 4388 4389 .DBG_symbolLabelWrap { 4390 display: flex; 4391 flex-wrap: wrap; 4392 align-items: flex-start; 4393 gap: 2px 3px; 4394 width: 100%; 4395 min-width: 0; 4396 max-width: 100%; 4397 margin-top: 2px; 4398 } 4399 4400 .DBG_symbolLabelWrap .appbut.label { 4401 float: none !important; 4402 display: inline-flex; 4403 align-items: center; 4404 gap: 3px; 4405 width: auto !important; 4406 min-width: 0; 4407 height: auto; 4408 min-height: 16px; 4409 line-height: 14px; 4410 margin: 0px; 4411 border-radius: 3px; 4412 background: rgba(128,128,128,0.08); 4413 border: 1px solid black; 4414 color: #000000; 4415 font-family: arial; 4416 font-size: 11px; 4417 padding: 0px 2px; 4418 box-sizing: border-box; 4419 white-space: nowrap; 4420 cursor: default; 4421 } 4422 4423 .DBG_symbolLabelWrap .appbut.label:hover { 4424 background: rgba(255,255,255,0.75); 4425 } 4426 4427 .DBG_symbolLabelWrap .DBG_symbolRemove { 4428 float: none !important; 4429 display: inline-flex; 4430 align-items: center; 4431 justify-content: center; 4432 width: 14px !important; 4433 min-width: 14px; 4434 height: 14px; 4435 min-height: 14px; 4436 line-height: 12px; 4437 margin: 0px; 4438 padding: 0px; 4439 border-radius: 3px; 4440 font-size: 10px; 4441 cursor: pointer; 4442 } 4443 4444 .DBG_symbolOverlapBack { 4445 position: fixed; 4446 left: 0px; 4447 top: 0px; 4448 right: 0px; 4449 bottom: 0px; 4450 z-index: 1000; 4451 display: flex; 4452 align-items: center; 4453 justify-content: center; 4454 background: rgba(0,0,0,0.28); 4455 } 4456 4457 .DBG_symbolOverlapBack[hidden] { 4458 display: none !important; 4459 } 4460 4461 .DBG_symbolOverlapBox { 4462 width: 520px; 4463 max-width: calc(100vw - 40px); 4464 padding: 10px; 4465 border: 1px solid rgba(0,0,0,0.42); 4466 border-radius: 8px; 4467 background: #f4f4f4; 4468 color: #000000; 4469 box-shadow: 0px 4px 18px rgba(0,0,0,0.35); 4470 font-family: arial, sans-serif; 4471 font-size: 12px; 4472 } 4473 4474 .DBG_symbolOverlapBox strong { 4475 display: block; 4476 margin-bottom: 6px; 4477 font-size: 13px; 4478 } 4479 4480 .DBG_symbolOverlapBox pre { 4481 max-height: 220px; 4482 margin: 6px 0px; 4483 padding: 6px; 4484 overflow: auto; 4485 border: 1px solid rgba(0,0,0,0.20); 4486 border-radius: 4px; 4487 background: #ffffff; 4488 color: #000000; 4489 font-family: courier, monospace; 4490 font-size: 11px; 4491 white-space: pre-wrap; 4492 } 4493 4494 .DBG_symbolOverlapButtons { 4495 display: flex; 4496 justify-content: flex-end; 4497 gap: 4px; 4498 margin-top: 8px; 4499 } 4500 4501 .DBG_footerNote { 4502 margin: 8px 0px 0px 0px; 4503 padding: 6px 9px; 4504 border-radius: 8px; 4505 background: rgba(244,244,244,0.74); 4506 color: rgba(0,0,0,0.72); 4507 font-size: 11px; 4508 } 4509 </style> 4510
4511<script> 4512 var DBG_oPANE = null; 4513 var DBG_running = false; 4514 var DBG_RAM_SIZE = 0x10000; 4515 var DBG_RAM = new Uint8Array(DBG_RAM_SIZE); 4516 var DBG_currentByteCodeText = ""; 4517 var DBG_debuggerStarted = false; 4518 var DBG_debuggerStartPC = 0x0000; 4519 var DBG_listingRows = []; 4520 var DBG_listingLines = []; 4521 var DBG_listingRowByPc = new Int32Array(DBG_RAM_SIZE); 4522 var DBG_listingRowByByteAddress = new Int32Array(DBG_RAM_SIZE); 4523 var DBG_listingCurrentPC = null; 4524 var DBG_listingScrollBehavior = "auto"; 4525 var DBG_defaultListingColumns = {adr:0,code:6,lin:15,num:23,lbl:30,ins:40,opr:45,com:61}; 4526 var DBG_listingColumnPresets = { 4527 "default":"{adr:0,code:6,lin:15,lbl:21,ins:30,opr:35,com:51}", 4528 "wide":"{adr:0,code:6,lin:17,lbl:24,ins:34,opr:40,com:60}", 4529 "compact":"{adr:0,code:6,lbl:15,ins:24,opr:29,com:45}" 4530 }; 4531 var DBG_darkMode = false; 4532 var DBG_symbolTables = []; 4533 var DBG_symbolTablesById = {}; 4534 var DBG_symbolAddressIndex = new Array(DBG_RAM_SIZE); 4535 var DBG_pendingSymbolImport = null; 4536 var DBG_nextSymbolTableId = 1; 4537 var DBG_traceCPU = null; 4538 var DBG_traceHW = null; 4539 var DBG_traceStarted = false; 4540 var DBG_traceStepCount = 0; 4541 var DBG_traceHistory = null; 4542 var DBG_traceLines = []; 4543 var DBG_traceCurrentWrites = null; 4544 var DBG_SELF_LOOP_THRESHOLD = 8; 4545 var DBG_runTimer = null; 4546 var DBG_runInstructionCount = 0; 4547 var DBG_runLastStatusStep = 0; 4548 var DBG_defaultTraceColumns = {stp:0,pc:5,byt:10,opc:19,rA:32,rX:35,rY:38,rSP:41,rP:44,cyc:47}; 4549 var DBG_debugSessionFileName = "sample.DEB"; 4550 4551 DBG_listingRowByPc.fill(-1); 4552 DBG_listingRowByByteAddress.fill(-1); 4553 4554 4555 function DBGUI() 4556 { 4557 /* 4558 * Debugger GUI helper container. 4559 * 4560 * Keep the debugger GUI local, but keep CPU-specific metadata owned by 4561 * the CPU implementation. Opcode names, addressing modes, instruction 4562 * lengths and cycle counts come from Cpu6502.getConfig(). 4563 */ 4564 4565 this.getConfig = function() 4566 { 4567 var cpu = 4568 (typeof oEMU !== "undefined" && 4569 oEMU && 4570 oEMU.component && 4571 oEMU.component.CPU) ? oEMU.component.CPU["6502"] : null; 4572 4573 if(cpu && typeof cpu.getConfig == "function") 4574 return cpu.getConfig(); 4575 4576 if(typeof console !== "undefined" && console.warn) 4577 console.warn("DBGUI: Cpu6502.getConfig() is unavailable; opcode metadata cannot be resolved."); 4578 4579 return {opctab:[],instrlen:[],cycle_count:[]}; 4580 }; 4581 4582 this.getOpcodeTable = function() 4583 { 4584 return this.getConfig().opctab || []; 4585 }; 4586 4587 /* 4588 * Compatibility property for older DBGUI call sites. 4589 * Source of truth remains Cpu6502.getConfig().opctab. 4590 */ 4591 this.opctab = this.getOpcodeTable(); 4592 4593 this.resetProcessor = function() 4594 { 4595 /* 4596 * Placeholder for future debugger GUI reset actions. 4597 * The current CPU trace reset is driven by DBG_startCpuTraceAtPC(). 4598 */ 4599 return true; 4600 }; 4601 4602 this.getOpcodeEntry = function(opcode) 4603 { 4604 return this.getOpcodeTable()[opcode & 0xff] || ['???','imp']; 4605 }; 4606 4607 this.instructionAddressMode = function(opcode) 4608 { 4609 var entry = this.getOpcodeEntry(opcode); 4610 return entry && entry[1] ? entry[1] : 'imp'; 4611 }; 4612 4613 this.instructionLength = function(opcode) 4614 { 4615 switch(this.instructionAddressMode(opcode)) 4616 { 4617 case 'imm': 4618 case 'zpg': 4619 case 'zpx': 4620 case 'zpy': 4621 case 'inx': 4622 case 'iny': 4623 case 'rel': 4624 return 2; 4625 4626 case 'abs': 4627 case 'abx': 4628 case 'aby': 4629 case 'ind': 4630 return 3; 4631 4632 case 'acc': 4633 case 'imp': 4634 default: 4635 return 1; 4636 } 4637 }; 4638 4639 this.disassemble = function(arg) 4640 { 4641 arg = arg || {}; 4642 4643 if(typeof DASM !== 'function') 4644 return {adr_lst:'0000', opcode_lst:'', mnemonic:'???'}; 4645 4646 var dasm = new DASM(); 4647 4648 if(typeof arg.sym_search === 'function') dasm.sym_search = arg.sym_search; 4649 else dasm.sym_search = function(op, adm) { return op; }; 4650 4651 if(typeof oCOM !== 'undefined' && oCOM && typeof oCOM.getHexByte === 'function') 4652 dasm.getHexByte = oCOM.getHexByte; 4653 4654 return dasm.disassemble({ 4655 code_arr: arg.code_arr || [0,0,0], 4656 pc: typeof arg.pc === 'number' ? (arg.pc & 0xffff) : 0, 4657 opctab: this.getOpcodeTable() 4658 }); 4659 }; 4660 } 4661 4662 var oDBGUI = new DBGUI(); 4663 4664 4665 4666 4667 function DBG_el(id) { 4668 return document.getElementById(id); 4669 } 4670 4671 function DBG_hexByte(v) { 4672 if (typeof oCOM !== "undefined" && oCOM && typeof oCOM.getHexByte === "function") return oCOM.getHexByte(v & 0xff); 4673 return ("0" + ((v & 0xff).toString(16).toUpperCase())).slice(-2); 4674 } 4675 4676 function DBG_hexWord(v) { 4677 if (typeof oCOM !== "undefined" && oCOM && typeof oCOM.getHexWord === "function") return oCOM.getHexWord(v & 0xffff); 4678 return ("000" + ((v & 0xffff).toString(16).toUpperCase())).slice(-4); 4679 } 4680 4681 function DBG_decodeDASMText(text) { 4682 return String(text || "") 4683 .replace(/<[^>]*>/g, "") 4684 .replace(/ /g, " ") 4685 .replace(/</g, "<") 4686 .replace(/>/g, ">") 4687 .replace(/&/g, "&") 4688 .replace(/\s+/g, " ") 4689 .trim(); 4690 } 4691 4692 function DBG_parseListingColumns(text) { 4693 var cfg = Object.assign({}, DBG_defaultListingColumns); 4694 String(text || "").replace(/([a-zA-Z_][\w]*)\s*:\s*(-?\d+)/g, function(_, key, val) { 4695 if (Object.prototype.hasOwnProperty.call(cfg, key)) cfg[key] = parseInt(val, 10) || 0; 4696 return ""; 4697 }); 4698 return cfg; 4699 } 4700 4701 function DBG_listingColumnsValue() { 4702 var input = DBG_el("DBG_listingColumnsInput"); 4703 return input ? input.value : "{adr:0,code:6,lin:15,lbl:21,ins:30,opr:35,com:51}"; 4704 } 4705 4706 function DBG_applyListingColumnPreset(name) { 4707 var input = DBG_el("DBG_listingColumnsInput"); 4708 var spec = DBG_listingColumnPresets[name]; 4709 if (!input || !spec) return false;
4710 4711 input.value = spec; 4712 DBG_disassembleRAM(); 4713 return true; 4714 } 4715 4716 function DBG_cropListingField(text, width) { 4717 text = String(text == null ? "" : text); 4718 if (width == null || width <= 0 || text.length <= width) return text; 4719 if (width <= 1) return text.slice(0, width); 4720 return text.slice(0, width - 1) + "â¦"; 4721 } 4722 4723 function DBG_formatListingParts(parts) { 4724 var columns = DBG_parseListingColumns(DBG_listingColumnsValue()); 4725 var order = ["adr","code","lin","lbl","ins","opr","com"]; 4726 if (typeof oCOM !== "undefined" && oCOM && typeof oCOM.renderTextTableRows === "function") { 4727 return oCOM.renderTextTableRows([parts], columns, { 4728 order: order, 4729 defaults: DBG_defaultListingColumns, 4730 trimRight: true, 4731 singleColumnRaw: true, 4732 ellipsis: "â¦" 4733 })[0]; 4734 } 4735 4736 var line = ""; 4737 for (var i = 0; i < order.length; i++) { 4738 var key = order[i]; 4739 if (!parts[key]) continue; 4740 var col = columns[key] | 0; 4741 var width; 4742 for (var j = i + 1; j < order.length; j++) { 4743 if ((columns[order[j]] | 0) > col) { width = (columns[order[j]] | 0) - col; break; } 4744 } 4745 var text = DBG_cropListingField(parts[key], width); 4746 if (line.length < col) line = line.padEnd(col, " "); 4747 else if (line.length > col) line += " "; 4748 line += text; 4749 } 4750 return line.replace(/\s+$/, ""); 4751 } 4752 4753 function DBG_countByteCodeText(text) { 4754 return DBG_parseByteCodeText(text).records.length; 4755 } 4756 4757 function DBG_parseByteCodeText(text) { 4758 var lines = String(text || "").split(/\r\n|\r|\n/); 4759 var records = []; 4760 var errors = []; 4761 var currentAddr = null; 4762 4763 for (var lineNo = 0; lineNo < lines.length; lineNo++) { 4764 var raw = lines[lineNo]; 4765 var clean = raw.replace(/;.*/, "").replace(/\/\/.*$/, "").trim(); 4766 if (!clean) continue; 4767 4768 var addrMatch = clean.match(/^\$?([0-9A-Fa-f]{4})\s*:?[\s,]*(.*)$/); 4769 var rest = clean; 4770 4771 if (addrMatch) { 4772 currentAddr = parseInt(addrMatch[1], 16) & 0xffff; 4773 rest = addrMatch[2] || ""; 4774 } 4775 4776 var bytes = rest.match(/\b[0-9A-Fa-f]{2}\b/g); 4777 if (!bytes || !bytes.length) continue; 4778 4779 if (currentAddr === null) { 4780 errors.push("Line " + (lineNo + 1) + ": byte data has no address prefix."); 4781 continue; 4782 } 4783 4784 for (var i = 0; i < bytes.length; i++) { 4785 records.push({pc: currentAddr, val: parseInt(bytes[i], 16) & 0xff, line: lineNo + 1}); 4786 currentAddr = (currentAddr + 1) & 0xffff; 4787 } 4788 } 4789 4790 return {records: records, errors: errors}; 4791 } 4792 4793 function DBG_updateByteCodeInfo(byteCount, extraText) {
4794 var info = DBG_el("DBG_byteCodeInfo"); 4795 if (info) info.textContent = (byteCount || 0) + " bytes" + (extraText ? " / " + extraText : ""); 4796 } 4797 4798 function DBG_syncByteCodeFromBoard() { 4799 var board = DBG_el("DBG_boardText"); 4800 DBG_currentByteCodeText = board ? board.value : ""; 4801 var parsed = DBG_parseByteCodeText(DBG_currentByteCodeText); 4802 DBG_updateByteCodeInfo(parsed.records.length, parsed.errors.length ? parsed.errors.length + " parse warning(s)" : ""); 4803 return parsed; 4804 } 4805 4806 function DBG_clearBoard() { 4807 DBG_currentByteCodeText = ""; 4808 var board = DBG_el("DBG_boardText"); 4809 if (board) board.value = ""; 4810 DBG_updateByteCodeInfo(0); 4811 } 4812 4813 4814 function DBG_statusText(text) { 4815 var status = DBG_el("DBG_status"); 4816 if (status) status.textContent = text; 4817 } 4818 4819 function DBG_parseHexAddress(text) { 4820 text = String(text == null ? "" : text).trim(); 4821 text = text.replace(/^\$/,"").replace(/^0x/i,""); 4822 if (!/^[0-9A-Fa-f]{1,4}$/.test(text)) return null; 4823 var value = parseInt(text,16); 4824 if (isNaN(value) || value < 0 || value > 0xffff) return null; 4825 return value & 0xffff; 4826 } 4827 4828 function DBG_startPcHost(hostId) { 4829 return DBG_el(hostId || "DBG_traceHeaderActions"); 4830 } 4831 4832 function DBG_showStartPcRequester(hostId) { 4833 var host = DBG_startPcHost(hostId); 4834 if (!host) return; 4835 4836 var requester = host.querySelector(".DBG_startPcRequester"); 4837 var input = host.querySelector(".DBG_startPcInput"); 4838 if (!requester || !input) return; 4839 4840 host.classList.add("DBG_requestActive"); 4841 requester.hidden = false; 4842 input.value = "$" + DBG_hexWord(DBG_debuggerStartPC); 4843 4844 DBG_statusText("Enter hexadecimal start address for the debugger."); 4845 4846 window.setTimeout(function(){ 4847 input.focus(); 4848 input.select(); 4849 },0); 4850 4851 DBG_equalizePaneHeaders(); 4852 } 4853 4854 function DBG_hideStartPcRequester(hostId) { 4855 var host = DBG_startPcHost(hostId); 4856 if (!host) return; 4857 4858 var requester = host.querySelector(".DBG_startPcRequester"); 4859 var input = host.querySelector(".DBG_startPcInput"); 4860 4861 host.classList.remove("DBG_requestActive"); 4862 if (requester) requester.hidden = true; 4863 if (input) input.value = ""; 4864 4865 DBG_equalizePaneHeaders(); 4866 } 4867 4868 function DBG_prepareDebuggerStartPC(pc,opts) { 4869 /* 4870 * Preset the debugger start PC without immediately starting execution. 4871 * 4872 * This is used by tab2's "to debugger" transfer. After import, the 4873 * Listing is positioned at the assembler origin and the next click on 4874 * "Step one instruction forward" starts from that same address. 4875 */ 4876 pc = Number(pc); 4877 if (isNaN(pc)) return false; 4878 4879 pc = pc & 0xffff; 4880 opts = opts || {}; 4881 4882 DBG_debuggerStartPC = pc; 4883 DBG_debuggerStarted = false; 4884 4885 if (opts.clearTrace !== false) { 4886 DBG_traceCPU = null; 4887 DBG_traceHW = null; 4888 DBG_traceStarted = false; 4889 DBG_traceStepCount = 0; 4890 DBG_resetTraceHistory(); 4891 DBG_traceLines = []; 4892 DBG_renderTraceView(); 4893 } 4894 4895 var pcDisplay = DBG_el("dispPC"); 4896 if (pcDisplay) pcDisplay.textContent = DBG_hexWord(pc); 4897 4898 var inputs = document.querySelectorAll(".DBG_startPcInput"); 4899 for (var i = 0; i < inputs.length; i++) 4900 inputs[i].value = "$" + DBG_hexWord(pc); 4901 4902 return true; 4903 } 4904 4905 function DBG_prepareDebuggerStartPC(pc,opts) { 4906 /* 4907 * Preset the debugger start PC without immediately starting execution. 4908 * 4909 * This is used by tab2's "to debugger" transfer. After import, the 4910 * Listing is positioned at the assembler origin and the next click on 4911 * "Step one instruction forward" starts from that same address. 4912 */ 4913 pc = Number(pc); 4914 if (isNaN(pc)) return false; 4915 4916 pc = pc & 0xffff; 4917 opts = opts || {}; 4918 4919 DBG_debuggerStartPC = pc; 4920 DBG_debuggerStarted = false; 4921 4922 if (opts.clearTrace !== false) { 4923 DBG_traceCPU = null; 4924 DBG_traceHW = null; 4925 DBG_traceStarted = false;
4926 DBG_traceStepCount = 0; 4927 DBG_traceLines = []; 4928 DBG_renderTraceView(); 4929 } 4930 4931 var pcDisplay = DBG_el("dispPC"); 4932 if (pcDisplay) pcDisplay.textContent = DBG_hexWord(pc); 4933 4934 var inputs = document.querySelectorAll(".DBG_startPcInput"); 4935 for (var i = 0; i < inputs.length; i++) 4936 inputs[i].value = "$" + DBG_hexWord(pc); 4937 4938 return true; 4939 } 4940 4941 function DBG_startDebuggerAtPC(hostId) { 4942 var host = DBG_startPcHost(hostId); 4943 if (!host) return; 4944 4945 var input = host.querySelector(".DBG_startPcInput"); 4946 var pc = DBG_parseHexAddress(input ? input.value : ""); 4947 4948 if (pc === null) { 4949 DBG_statusText("Invalid start address. Use hexadecimal 0000-FFFF."); 4950 if (input) { 4951 input.focus(); 4952 input.select(); 4953 } 4954 return; 4955 } 4956 4957 DBG_debuggerStartPC = pc & 0xffff; 4958 DBG_debuggerStarted = true; 4959 DBG_startCpuTraceAtPC(DBG_debuggerStartPC); 4960 4961 var pcDisplay = DBG_el("dispPC"); 4962 if (pcDisplay) pcDisplay.textContent = DBG_hexWord(DBG_debuggerStartPC); 4963 4964 var listed = DBG_scrollListingToPC(DBG_debuggerStartPC,{behavior:DBG_listingScrollBehavior}); 4965 4966 DBG_hideStartPcRequester(hostId); 4967 DBG_statusText( 4968 "Debugger start address set to $" + DBG_hexWord(DBG_debuggerStartPC) + (listed ? " / listing positioned." : " / listing row not found.") 4969 ); 4970 } 4971 4972 function DBG_startPcKeyDown(evt,hostId) { 4973 evt = evt || window.event; 4974 if (evt.key === "Enter") { evt.preventDefault(); DBG_startDebuggerAtPC(hostId); } 4975 else if (evt.key === "Escape") { evt.preventDefault(); DBG_hideStartPcRequester(hostId); } 4976 } 4977 4978 4979 function DBG_rangeListNormalize(scope) { 4980 var ranges = []; 4981 for (var i = 0; i < (scope || []).length; i++) { 4982 var r = scope[i] || []; 4983 if (r.length < 2) continue; 4984 var a = Number(r[0]); 4985 var b = Number(r[1]); 4986 if (isNaN(a) || isNaN(b)) continue; 4987 a = a & 0xffff; 4988 b = b & 0xffff; 4989 if (b < a) { var t = a; a = b; b = t; } 4990 ranges.push([a,b]); 4991 } 4992 4993 ranges.sort(function(x,y){ return x[0] - y[0] || x[1] - y[1]; }); 4994 4995 var out = []; 4996 for (var j = 0; j < ranges.length; j++) { 4997 var cur = ranges[j]; 4998 if (!out.length || cur[0] > out[out.length - 1][1] + 1) { 4999 out.push(cur.slice()); 5000 } else if (cur[1] > out[out.length - 1][1]) { 5001 out[out.length - 1][1] = cur[1]; 5002 } 5003 } 5004 return out; 5005 } 5006 5007 function DBG_rangeListSubtract(baseRanges, cutRanges) { 5008 var base = DBG_rangeListNormalize(baseRanges); 5009 var cuts = DBG_rangeListNormalize(cutRanges); 5010 var out = []; 5011 5012 for (var i = 0; i < base.length; i++) { 5013 var fragments = [base[i].slice()]; 5014 5015 for (var j = 0; j < cuts.length && fragments.length; j++) { 5016 var cut = cuts[j]; 5017 var next = []; 5018 5019 for (var k = 0; k < fragments.length; k++) { 5020 var f = fragments[k]; 5021 5022 if (cut[1] < f[0] || cut[0] > f[1]) { 5023 next.push(f); 5024 continue; 5025 } 5026 5027 if (cut[0] > f[0]) next.push([f[0], cut[0] - 1]); 5028 if (cut[1] < f[1]) next.push([cut[1] + 1, f[1]]); 5029 } 5030 5031 fragments = next; 5032 } 5033 5034 for (var n = 0; n < fragments.length; n++) out.push(fragments[n]); 5035 } 5036 5037 return DBG_rangeListNormalize(out); 5038 } 5039 5040 function DBG_rangeListHex(ranges) { 5041 var out = []; 5042 for (var i = 0; i < (ranges || []).length; i++) 5043 out.push("$" + DBG_hexWord(ranges[i][0]) + "-$" + DBG_hexWord(ranges[i][1])); 5044 return out; 5045 } 5046 5047 function DBG_symbolSourceName(table) { 5048 var s = table && table.source ? String(table.source) : ""; 5049 if (!s && table && table.filename) s = String(table.filename); 5050 return s || "symbol table"; 5051 } 5052 5053 function DBG_normalizeSymbolTable(raw, fileName) { 5054 if (!raw || typeof raw !== "object") throw new Error("Invalid JSON symbol table."); 5055 if (raw.format && raw.format !== "RetroAppleJS-ASM-symbols") 5056 throw new Error("Unsupported symbol table format: " + raw.format); 5057 5058 var scope = DBG_rangeListNormalize(raw.scope || []); 5059 if (!scope.length) throw new Error("Symbol table has no usable scope."); 5060 5061 var table = { 5062 id: "symtab_" + (DBG_nextSymbolTableId++), 5063 source: DBG_symbolSourceName(raw) || fileName || "symbol table", 5064 filename: fileName || "", 5065 format: raw.format || "", 5066 version: raw.version || 0, 5067 scopeMode: raw.scopeMode || "instruction-pc", 5068 scope: scope, 5069 effectiveScope: scope.slice(), 5070 raw: raw, 5071 symbols: Array.isArray(raw.symbols) ? raw.symbols : [], 5072 byValue: {1:{}, 2:{}}, 5073 labelsByAddress: {}, 5074 commentsByAddress: {} 5075 }; 5076 5077 for (var i = 0; i < table.symbols.length; i++) { 5078 var sym = table.symbols[i] || {};
5079 if (typeof sym.value !== "number") continue; 5080 5081 var value = sym.value & 0xffff; 5082 5083 /* 5084 * Comment entries use a reduced metadata set. They must not enter 5085 * byValue, otherwise COMMENT_FD42 could be substituted as an operand 5086 * symbol. Instruction/data comments are indexed by address and are 5087 * applied only after their opcode bytes match RAM at disassembly time. 5088 */ 5089 if (sym.type === "comment") { 5090 if ((sym.targetType === "instruction" || sym.targetType === "data") && 5091 typeof sym.comment === "string" && 5092 sym.comment && 5093 Array.isArray(sym.opcode) && 5094 sym.opcode.length) { 5095 if (!table.commentsByAddress[value]) table.commentsByAddress[value] = []; 5096 table.commentsByAddress[value].push(sym); 5097 } 5098 continue; 5099 } 5100 5101 var bytes = sym.bytes === 1 ? 1 : 2; 5102 5103 if (!table.byValue[bytes][value]) table.byValue[bytes][value] = []; 5104 table.byValue[bytes][value].push(sym); 5105 5106 if (sym.type === "label") { 5107 if (!table.labelsByAddress[value]) table.labelsByAddress[value] = []; 5108 table.labelsByAddress[value].push(sym); 5109 } 5110 } 5111 5112 return table; 5113 } 5114 5115 function DBG_rebuildSymbolIndex() { 5116 DBG_symbolTablesById = {}; 5117 DBG_symbolAddressIndex = new Array(DBG_RAM_SIZE); 5118 5119 for (var i = 0; i < DBG_symbolTables.length; i++) { 5120 var table = DBG_symbolTables[i]; 5121 DBG_symbolTablesById[table.id] = table; 5122 5123 var ranges = table.effectiveScope || []; 5124 for (var r = 0; r < ranges.length; r++) { 5125 for (var a = ranges[r][0]; a <= ranges[r][1]; a++) { 5126 var pc = a & 0xffff; 5127 if (!DBG_symbolAddressIndex[pc]) DBG_symbolAddressIndex[pc] = []; 5128 if (DBG_symbolAddressIndex[pc].indexOf(table.id) < 0) 5129 DBG_symbolAddressIndex[pc].push(table.id); 5130 } 5131 } 5132 } 5133 } 5134 5135 function DBG_getSymbolTablesAt(pc) { 5136 var ids = DBG_symbolAddressIndex[pc & 0xffff] || []; 5137 var out = []; 5138 for (var i = 0; i < ids.length; i++) { 5139 var table = DBG_symbolTablesById[ids[i]]; 5140 if (table) out.push(table); 5141 } 5142 return out; 5143 } 5144 5145 function DBG_findSymbolScopeOverlaps(scope) { 5146 var ranges = DBG_rangeListNormalize(scope); 5147 var overlaps = []; 5148 var active = null; 5149 5150 function flush() { 5151 if (active) { 5152 overlaps.push({ 5153 from: active.from, 5154 to: active.to, 5155 tableIds: Object.keys(active.ids) 5156 }); 5157 active = null; 5158 } 5159 } 5160 5161 for (var r = 0; r < ranges.length; r++) { 5162 for (var a = ranges[r][0]; a <= ranges[r][1]; a++) { 5163 var ids = DBG_symbolAddressIndex[a & 0xffff] || []; 5164 if (!ids.length) { flush(); continue; } 5165 5166 var key = ids.slice().sort().join("|"); 5167 if (!active || active.key !== key || active.to + 1 !== a) { 5168 flush(); 5169 active = {from:a, to:a, key:key, ids:{}}; 5170 for (var i = 0; i < ids.length; i++) active.ids[ids[i]] = true; 5171 } else { 5172 active.to = a; 5173 for (var j = 0; j < ids.length; j++) active.ids[ids[j]] = true; 5174 } 5175 } 5176 flush(); 5177 } 5178 5179 return overlaps; 5180 } 5181 5182 function DBG_formatSymbolOverlapReport(incoming, overlaps) { 5183 var lines = []; 5184 lines.push("New table: " + DBG_symbolSourceName(incoming)); 5185 lines.push("New scope: " + DBG_rangeListHex(incoming.scope).join(", ")); 5186 lines.push(""); 5187 lines.push("Overlapping ranges:"); 5188 5189 for (var i = 0; i < overlaps.length; i++) { 5190 var ov = overlaps[i]; 5191 var names = []; 5192 for (var j = 0; j < ov.tableIds.length; j++) { 5193 var table = DBG_symbolTablesById[ov.tableIds[j]]; 5194 if (table) names.push(DBG_symbolSourceName(table)); 5195 } 5196 lines.push(" $" + DBG_hexWord(ov.from) + "-$" + DBG_hexWord(ov.to) + " -> " + names.join(", ")); 5197 } 5198 5199 lines.push(""); 5200 lines.push("Cancel: do not import this table."); 5201 lines.push("Override: new table replaces existing tables in the overlapping address ranges."); 5202 lines.push("Merge: import only the non-overlapping part of the new table."); 5203 5204 return lines.join("\n"); 5205 } 5206 5207 function DBG_showSymbolOverlapPopup(incoming, overlaps) { 5208 DBG_pendingSymbolImport = {table: incoming, overlaps: overlaps}; 5209 5210 var text = DBG_el("DBG_symbolOverlapText"); 5211 if (text) text.textContent = DBG_formatSymbolOverlapReport(incoming, overlaps); 5212 5213 var popup = DBG_el("DBG_symbolOverlapPopup"); 5214 if (popup) popup.hidden = false;
5215 } 5216 5217 function DBG_resolveSymbolOverlap(choice) { 5218 var pending = DBG_pendingSymbolImport; 5219 DBG_pendingSymbolImport = null; 5220 5221 var popup = DBG_el("DBG_symbolOverlapPopup"); 5222 if (popup) popup.hidden = true; 5223 5224 if (!pending || choice === "cancel") { 5225 DBG_statusText("Symbol table import cancelled."); 5226 return; 5227 } 5228 5229 if (choice === "override") { 5230 for (var i = 0; i < DBG_symbolTables.length; i++) { 5231 DBG_symbolTables[i].effectiveScope = 5232 DBG_rangeListSubtract(DBG_symbolTables[i].effectiveScope, pending.table.scope); 5233 } 5234 DBG_symbolTables = DBG_symbolTables.filter(function(t){ 5235 return t.effectiveScope && t.effectiveScope.length; 5236 }); 5237 pending.table.effectiveScope = pending.table.scope.slice(); 5238 DBG_addSymbolTable(pending.table); 5239 return; 5240 } 5241 5242 if (choice === "merge") { 5243 var occupied = []; 5244 for (var j = 0; j < DBG_symbolTables.length; j++) 5245 occupied = occupied.concat(DBG_symbolTables[j].effectiveScope || []); 5246 5247 pending.table.effectiveScope = DBG_rangeListSubtract(pending.table.scope, occupied); 5248 5249 if (!pending.table.effectiveScope.length) { 5250 DBG_statusText("Symbol table merge skipped: no non-overlapping scoped addresses."); 5251 return; 5252 } 5253 5254 DBG_addSymbolTable(pending.table); 5255 } 5256 } 5257 5258 function DBG_addSymbolTable(table, options) { 5259 options = options || {}; 5260 5261 DBG_symbolTables.push(table); 5262 DBG_rebuildSymbolIndex(); 5263 DBG_renderSymbolLabels(); 5264 if(options.disassemble !== false) 5265 DBG_disassembleRAM(); 5266 5267 if(options.status !== false) 5268 { 5269 DBG_statusText( 5270 "Loaded symbol table: " + DBG_symbolSourceName(table) + 5271 " / " + table.symbols.length + " symbol(s) / active scope " + 5272 DBG_rangeListHex(table.effectiveScope).join(", ") 5273 ); 5274 } 5275 } 5276 5277 function DBG_removeSymbolTable(id) { 5278 DBG_symbolTables = DBG_symbolTables.filter(function(t){ return t.id !== id; }); 5279 DBG_rebuildSymbolIndex(); 5280 DBG_renderSymbolLabels(); 5281 DBG_disassembleRAM(); 5282 DBG_statusText("Removed symbol table."); 5283 } 5284 5285 function DBG_renderSymbolLabels() { 5286 var wrap = DBG_el("DBG_symbolLabelWrap"); 5287 if (!wrap) return; 5288 5289 wrap.replaceChildren(); 5290 5291 for (var i = 0; i < DBG_symbolTables.length; i++) { 5292 var table = DBG_symbolTables[i]; 5293 5294 var chip = document.createElement("div"); 5295 chip.className = "appbut label"; 5296 chip.title = DBG_rangeListHex(table.effectiveScope).join(", "); 5297 chip.appendChild(document.createTextNode(DBG_symbolSourceName(table) + " ")); 5298 5299 var x = document.createElement("div"); 5300 x.className = "appbut skinny DBG_symbolRemove"; 5301 x.title = "Remove symbol table"; 5302 x.textContent = "x"; 5303 x.setAttribute("data-symbol-id", table.id); 5304 x.onclick = function(e) { 5305 e.stopPropagation(); 5306 DBG_removeSymbolTable(this.getAttribute("data-symbol-id")); 5307 }; 5308 5309 chip.appendChild(x); 5310 wrap.appendChild(chip); 5311 } 5312 } 5313 5314 async function DBG_onSymbolFileChange(evt) { 5315 var input = evt && evt.target ? evt.target : DBG_el("DBG_sym_fileInput"); 5316 var file = input && input.files && input.files[0] ? input.files[0] : null; 5317 if (!file) return; 5318 5319 try { 5320 var raw = JSON.parse(await file.text()); 5321 var table = DBG_normalizeSymbolTable(raw, file.name); 5322 var overlaps = DBG_findSymbolScopeOverlaps(table.scope); 5323 DBG_updateDebugSessionFileNameFromSource(file.name); 5324 5325 if (overlaps.length) DBG_showSymbolOverlapPopup(table, overlaps); 5326 else DBG_addSymbolTable(table); 5327 } catch (err) { 5328 DBG_statusText("Symbol table import failed: " + (err && err.message ? err.message : String(err))); 5329 if (typeof console !== "undefined" && console.error) console.error(err); 5330 } finally { 5331 if (input) input.value = ""; 5332 } 5333 } 5334 5335 function DBG_receiveSymbolTable(raw, fileName, options) { 5336 /* 5337 * In-memory companion to DBG_onSymbolFileChange(). 5338 *
5339 * Used by tab2's "to debugger" action so byte-code transfer can also 5340 * update the debugger with labels, EQU values and scoped comments from 5341 * the assembler parser without a manual export/import round-trip. 5342 */ 5343 options = options || {}; 5344 5345 try { 5346 var table = DBG_normalizeSymbolTable(raw, fileName || ""); 5347 DBG_updateDebugSessionFileNameFromSource(fileName || table.source || ""); 5348 var overlaps = DBG_findSymbolScopeOverlaps(table.scope); 5349 var mode = options.overlap || "ask"; 5350 5351 if(overlaps.length && mode == "override") 5352 { 5353 for(var i = 0; i < DBG_symbolTables.length; i++) 5354 { 5355 DBG_symbolTables[i].effectiveScope = 5356 DBG_rangeListSubtract(DBG_symbolTables[i].effectiveScope, table.scope); 5357 } 5358 5359 DBG_symbolTables = DBG_symbolTables.filter(function(t){ 5360 return t.effectiveScope && t.effectiveScope.length; 5361 }); 5362 5363 table.effectiveScope = table.scope.slice(); 5364 DBG_addSymbolTable(table, options); 5365 return true; 5366 } 5367 5368 if(overlaps.length && mode == "merge") 5369 { 5370 var occupied = []; 5371 for(var j = 0; j < DBG_symbolTables.length; j++) 5372 occupied = occupied.concat(DBG_symbolTables[j].effectiveScope || []); 5373 5374 table.effectiveScope = DBG_rangeListSubtract(table.scope, occupied); 5375 5376 if(!table.effectiveScope.length) 5377 { 5378 if(options.status !== false) 5379 DBG_statusText("Symbol table merge skipped: no non-overlapping scoped addresses."); 5380 return false; 5381 } 5382 5383 DBG_addSymbolTable(table, options); 5384 return true; 5385 } 5386 5387 if(overlaps.length) 5388 { 5389 DBG_showSymbolOverlapPopup(table, overlaps); 5390 return false; 5391 } 5392 5393 DBG_addSymbolTable(table, options); 5394 return true; 5395 } catch(err) { 5396 DBG_statusText("Symbol table import failed: " + (err && err.message ? err.message : String(err))); 5397 if(typeof console !== "undefined" && console.error) console.error(err); 5398 return false; 5399 } 5400 } 5401 5402 function DBG_symbolNameForValue(instrPc, value, bytes) { 5403 var tables = DBG_getSymbolTablesAt(instrPc); 5404 value = value & (bytes === 1 ? 0xff : 0xffff); 5405 bytes = bytes === 1 ? 1 : 2; 5406 5407 for (var i = 0; i < tables.length; i++) { 5408 var list = tables[i].byValue[bytes][value] || []; 5409 if (list.length) return list[0].name; 5410 } 5411 5412 return ""; 5413 } 5414 5415 function DBG_labelForAddress(instrPc) { 5416 var tables = DBG_getSymbolTablesAt(instrPc); 5417 var value = instrPc & 0xffff; 5418 5419 for (var i = 0; i < tables.length; i++) { 5420 var list = tables[i].labelsByAddress[value] || []; 5421 if (list.length) return list[0].name; 5422 } 5423 5424 return ""; 5425 } 5426 5427 function DBG_applySymbolsToOperand(operand, instrPc) { 5428 return String(operand || "").replace(/\$([0-9A-Fa-f]{2,4})\b/g, function(match, hex) { 5429 var bytes = hex.length <= 2 ? 1 : 2; 5430 var value = parseInt(hex,16); 5431 var name = DBG_symbolNameForValue(instrPc,value,bytes); 5432 return name || match; 5433 }); 5434 } 5435 5436 5437 function DBG_opcodeMatchesAt(pcAddr, opcode) { 5438 if (!Array.isArray(opcode) || !opcode.length) return false; 5439 5440 pcAddr = pcAddr & 0xffff; 5441 for (var i = 0; i < opcode.length; i++) { 5442 var b = Number(opcode[i]); 5443 if (isNaN(b)) return false; 5444 if ((DBG_RAM[(pcAddr + i) & 0xffff] & 0xff) !== (b & 0xff)) return false; 5445 } 5446 5447 return true; 5448 } 5449 5450 function DBG_commentForInstruction(instrPc) { 5451 var tables = DBG_getSymbolTablesAt(instrPc); 5452 var value = instrPc & 0xffff; 5453 5454 for (var i = 0; i < tables.length; i++) { 5455 var table = tables[i] || {}; 5456 var list = table.commentsByAddress && table.commentsByAddress[value] ? table.commentsByAddress[value] : []; 5457 5458 for (var j = 0; j < list.length; j++) { 5459 var sym = list[j] || {}; 5460 if (sym.targetType !== "instruction" && sym.targetType !== "data") continue; 5461 if (!DBG_opcodeMatchesAt(value, sym.opcode)) continue; 5462 return String(sym.comment || ""); 5463 } 5464 } 5465 5466 return ""; 5467 } 5468 5469 function DBG_formatDisassemblyComment(comment) { 5470 comment = String(comment || "").trim(); 5471 if (!comment) return ""; 5472 return /^;
5472/.test(comment) ? comment : "; " + comment; 5473 } 5474 5475 function DBG_instructionAddressMode(opcode) { 5476 return oDBGUI.instructionAddressMode(opcode); 5477 } 5478 5479 function DBG_instructionLength(opcode) { 5480 return oDBGUI.instructionLength(opcode); 5481 } 5482 5483 function DBG_instructionTarget(pcAddr, mode) { 5484 pcAddr = pcAddr & 0xffff; 5485 5486 if (mode === "rel") { 5487 var off = DBG_RAM[(pcAddr + 1) & 0xffff] & 0xff; 5488 if (off & 0x80) off -= 0x100; 5489 return (pcAddr + 2 + off) & 0xffff; 5490 } 5491 5492 if (mode === "abs") { 5493 return (DBG_RAM[(pcAddr + 1) & 0xffff] | (DBG_RAM[(pcAddr + 2) & 0xffff] << 8)) & 0xffff; 5494 } 5495 5496 return null; 5497 } 5498 5499 function DBG_listingLineAssembler() { 5500 if (typeof ASM !== "function") return null; 5501 5502 var asm = new ASM({listingColumns: DBG_listingColumnsValue()}); 5503 if (typeof asm.setListingColumns === "function") 5504 asm.setListingColumns(DBG_listingColumnsValue()); 5505 5506 return asm; 5507 } 5508 5509 function DBG_applyDisassemblyJumpLines(rows) { 5510 var asm = DBG_listingLineAssembler(); 5511 if (asm && typeof asm.applyListingLineColumn === "function") 5512 asm.applyListingLineColumn(rows || []); 5513 return rows || []; 5514 } 5515 5516 function DBG_formatDisassemblyRow(row) { 5517 row = row || {}; 5518 return DBG_formatListingParts({ 5519 adr: DBG_hexWord(row.pc).toLowerCase() + ":", 5520 code: (row.byteHex || []).join(" ").toLowerCase(), 5521 lin: row.lin || "", 5522 lbl: row.label || "", 5523 ins: row.ins || "", 5524 opr: row.opr || "", 5525 com: row.com || "" 5526 }); 5527 } 5528 5529 5530 function DBG_disassembleInstruction(pcAddr) { 5531 var pcv = pcAddr & 0xffff; 5532 var opcode = DBG_RAM[pcv] & 0xff; 5533 var mode = DBG_instructionAddressMode(opcode); 5534 var len = DBG_instructionLength(opcode); 5535 var ret = oDBGUI.disassemble({ 5536 code_arr: [DBG_RAM[pcv], DBG_RAM[(pcv + 1) & 0xffff], DBG_RAM[(pcv + 2) & 0xffff]], 5537 pc: pcv, 5538 sym_search: function(op, adm) { return op; } 5539 }); 5540 5541 var mnemonicText = DBG_decodeDASMText(ret.mnemonic); 5542 var ins = mnemonicText; 5543 var opr = ""; 5544 var split = mnemonicText.match(/^([^\s]+)(?:\s+(.*))?$/); 5545 if (split) { 5546 ins = split[1] || ""; 5547 opr = split[2] || ""; 5548 } 5549 5550 opr = DBG_applySymbolsToOperand(opr, pcv); 5551 var label = DBG_labelForAddress(pcv); 5552 5553 var bytes = []; 5554 var byteHex = []; 5555 for (var i = 0; i < len; i++) { 5556 var b = DBG_RAM[(pcv + i) & 0xffff] & 0xff;
5557 bytes.push(b); 5558 byteHex.push(DBG_hexByte(b)); 5559 } 5560 var comment = DBG_commentForInstruction(pcv); 5561 var row = { 5562 pc: pcv, 5563 bytes: bytes, 5564 byteHex: byteHex, 5565 mnemonic: ins, 5566 addrMode: mode, 5567 val: DBG_instructionTarget(pcv, mode), 5568 lin: "", 5569 label: label, 5570 ins: ins, 5571 opr: opr, 5572 com: DBG_formatDisassemblyComment(comment) 5573 }; 5574 5575 return { 5576 len: len, 5577 row: row, 5578 line: DBG_formatDisassemblyRow(row) 5579 }; 5580 } 5581 5582 function DBG_resetListingIndex() { 5583 DBG_listingRows = []; 5584 DBG_listingLines = []; 5585 DBG_listingRowByPc.fill(-1); 5586 DBG_listingRowByByteAddress.fill(-1); 5587 } 5588 5589 function DBG_indexDisassemblyRows(rows,lines) { 5590 DBG_resetListingIndex(); 5591 DBG_listingRows = rows || []; 5592 DBG_listingLines = lines || []; 5593 5594 for (var i = 0; i < DBG_listingRows.length; i++) { 5595 var row = DBG_listingRows[i] || {}; 5596 if (typeof row.pc !== "number") continue; 5597 5598 var pc = row.pc & 0xffff; 5599 var len = row.bytes && row.bytes.length ? row.bytes.length : 1; 5600 5601 DBG_listingRowByPc[pc] = i; 5602 for (var b = 0; b < len; b++) 5603 DBG_listingRowByByteAddress[(pc + b) & 0xffff] = i; 5604 } 5605 } 5606 5607 function DBG_listingRowIndexForPC(pc) { 5608 pc = pc & 0xffff; 5609 5610 var rowIndex = DBG_listingRowByPc[pc]; 5611 if (rowIndex >= 0) return rowIndex; 5612 5613 /* 5614 * Fallback: if the selected address is inside a disassembled instruction 5615 * byte sequence, scroll to that containing row. This keeps navigation 5616 * useful even before the future virtual tracer can re-decode from any 5617 * arbitrary PC alignment. 5618 */ 5619 rowIndex = DBG_listingRowByByteAddress[pc]; 5620 return rowIndex >= 0 ? rowIndex : -1; 5621 } 5622 5623 function DBG_listingPaneBody() { 5624 return DBG_el("debugfield"); 5625 } 5626 5627 function DBG_listingLineHeight() { 5628 var pre = DBG_listingPaneBody(); 5629 if (!pre || !window.getComputedStyle) return 21; 5630 5631 var lh = parseFloat(window.getComputedStyle(pre).lineHeight); 5632 return isFinite(lh) && lh > 0 ? lh : 21; 5633 } 5634 5635 function DBG_positionListingPcOverlay(rowIndex) { 5636 var overlay = DBG_el("DBG_listingPcOverlay"); 5637 if (!overlay || rowIndex == null || rowIndex < 0) return; 5638 5639 var lineH = DBG_listingLineHeight(); 5640 var body = DBG_listingPaneBody(); 5641 var scrollTop = body ? body.scrollTop : 0; 5642 overlay.style.top = Math.round((rowIndex * lineH) - scrollTop) + "px"; 5643 overlay.style.height = Math.ceil(lineH) + "px"; 5644 overlay.hidden = false; 5645 } 5646 5647 function DBG_hideListingPcOverlay() { 5648 var overlay = DBG_el("DBG_listingPcOverlay"); 5649 if (overlay) overlay.hidden = true; 5650 } 5651 5652 function DBG_scrollListingToPC(pc,opts) { 5653 pc = pc & 0xffff; 5654 5655 /* 5656 * Full 64K disassembly is acceptable before tracing starts. During 5657 * tracing, this function should only consume the existing row index; 5658 * the later virtual scroller/tracer will update a small visible window. 5659 */ 5660 if (!DBG_listingRows.length) DBG_disassembleRAM(); 5661 5662 var rowIndex = DBG_listingRowIndexForPC(pc); 5663 if (rowIndex < 0) { 5664 DBG_hideListingPcOverlay(); 5665 return false; 5666 } 5667 5668 var body = DBG_listingPaneBody(); 5669 if (!body) return false; 5670 5671 var lineH = DBG_listingLineHeight(); 5672 var rowTop = Math.max(0, Math.round(rowIndex * lineH)); 5673 var targetTop = Math.max(0, Math.round(rowTop - ((body.clientHeight - lineH) / 2))); 5674 var behavior = opts && opts.behavior ? opts.behavior : DBG_listingScrollBehavior; 5675 var doScroll = !opts || opts.scroll !== false; 5676 5677 if (doScroll) { 5678 if (typeof body.scrollTo === "function") 5679 body.scrollTo({top:targetTop,behavior:behavior}); 5680 else 5681 body.scrollTop = targetTop; 5682 } 5683 5684 DBG_listingCurrentPC = pc; 5685 DBG_positionListingPcOverlay(rowIndex); 5686 return true; 5687 } 5688 5689 function DBG_updateListingPC(pc) { 5690 return DBG_scrollListingToPC(pc,{ 5691 behavior: DBG_listingScrollBehavior, 5692 scroll: DBG_scrollListingEnabled() 5693 }); 5694 } 5695 5696 function DBG_disassembleRAM() { 5697 var rows = []; 5698 var lines = []; 5699 var pcAddr = 0; 5700 while (pcAddr < DBG_RAM_SIZE) { 5701 var d = DBG_disassembleInstruction(pcAddr); 5702 if (d && d.row) rows.push(d.row); 5703 pcAddr += Math.max(1, d.len || 1); 5704 } 5705 5706 DBG_applyDisassemblyJumpLines(rows); 5707 for (var i = 0; i < rows.length; i++) 5708 lines.push(DBG_formatDisassemblyRow(rows[i])); 5709 5710 DBG_indexDisassemblyRows(rows,lines); 5711 5712 var debugfield = DBG_el("debugfield"); 5713 if (debugfield) debugfield.value = lines.join("\n"); 5714 5715 if (DBG_listingCurrentPC != null) DBG_scrollListingToPC(DBG_listingCurrentPC,{behavior:"auto"}); 5716 else DBG_hideListingPcOverlay(); 5717 5718 var info = DBG_el("DBG_listingInfo"); 5719 if (info) info.textContent = lines.length + " lines / 64K"; 5720 5721 return lines.length; 5722 } 5723 5724 function DBG_traceColumnsValue() { 5725 var input = DBG_el("DBG_traceColumnsInput"); 5726 return input ? input.value : "{stp:0,pc:5,byt:10,opc:19,rA:32,rX:35,rY:38,rSP:41,rP:44,cyc:47}"; 5727 5728 } 5729 5730 function DBG_parseTraceColumns(text) { 5731 var cfg = Object.assign({}, DBG_defaultTraceColumns); 5732 String(text || "").replace(/([a-zA-Z_][\w]*)\s*:\s*(-?\d+)/g, function(_, key, val) { 5733 if (Object.prototype.hasOwnProperty.call(cfg, key)) cfg[key] = parseInt(val, 10) || 0; 5734 return ""; 5735 }); 5736 return cfg; 5737 } 5738 5739 function DBG_formatTraceParts(parts) { 5740 var columns = DBG_parseTraceColumns(DBG_traceColumnsValue()); 5741 var order = ["stp","pc","byt","opc","rA","rX","rY","rSP","rP","cyc"]; 5742 var line = ""; 5743 5744 for (var i = 0; i < order.length; i++) { 5745 var key = order[i]; 5746 if (parts[key] == null || parts[key] === "") continue; 5747 var col = columns[key] | 0; 5748 var width; 5749 5750 for (var j = i + 1; j < order.length; j++) { 5751 if ((columns[order[j]] | 0) > col) { width = (columns[order[j]] | 0) - col; break; } 5752 } 5753 5754 var text = DBG_cropListingField(String(parts[key]), width); 5755 if (line.length < col) line = line.padEnd(col, " "); 5756 else if (line.length > col) line += " ";
5757 line += text; 5758 } 5759 5760 return line.replace(/\s+$/, ""); 5761 } 5762 5763 function DBG_escapeHTML(text) { 5764 return String(text == null ? "" : text) 5765 .replace(/&/g,"&") 5766 .replace(/</g,"<") 5767 .replace(/>/g,">") 5768 .replace(/\"/g,""") 5769 .replace(/'/g,"'"); 5770 } 5771 5772 function DBG_createCpuTraceHw() { 5773 var hw = { 5774 nmi_signal: false, 5775 irq_signal: false, 5776 RD: [], 5777 WR: [], 5778 lineDecode: function(addr) { return ((addr & 0xffff) >> 12) & 0x0f; } 5779 }; 5780 5781 for (var i = 0; i < 16; i++) { 5782 hw.RD[i] = function(addr) { return DBG_RAM[addr & 0xffff] & 0xff; }; 5783 hw.WR[i] = function(addr,d8) { 5784 var adr = addr & 0xffff; 5785 var oldVal = DBG_RAM[adr] & 0xff; 5786 var newVal = d8 & 0xff; 5787 if(DBG_traceCurrentWrites) DBG_traceCurrentWrites.push({addr:adr,oldVal:oldVal,newVal:newVal}); 5788 DBG_RAM[adr] = newVal; 5789 return 0x00; 5790 }; 5791 } 5792 5793 return hw; 5794 } 5795 5796 function DBG_armSelfLoopTrap() 5797 { 5798 if(DBG_traceCPU && typeof DBG_traceCPU.setSelfLoopTrap === "function") 5799 DBG_traceCPU.setSelfLoopTrap(true,DBG_SELF_LOOP_THRESHOLD); 5800 } 5801 5802 function DBG_getSelfLoopTrap() 5803 { 5804 return DBG_traceCPU && typeof DBG_traceCPU.getSelfLoopTrap === "function" 5805 ? DBG_traceCPU.getSelfLoopTrap() 5806 : null; 5807 } 5808 5809 function DBG_selfLoopTrapText(hit) 5810 { 5811 if(!hit) return ""; 5812
5813 var decoded = DBG_traceInstructionTextAt(hit.pc & 0xffff); 5814 var msg = "SELF-LOOP TRAP at $" + DBG_hexWord(hit.pc) 5815 + " after " + hit.repeats + " consecutive instructions"; 5816 5817 if(decoded && decoded.op) msg += " / " + decoded.op; 5818 5819 msg += " / A=$" + DBG_hexByte(hit.a) 5820 + " X=$" + DBG_hexByte(hit.x) 5821 + " Y=$" + DBG_hexByte(hit.y) 5822 + " P=$" + DBG_hexByte(hit.p) 5823 + " SP=$" + DBG_hexByte(hit.sp); 5824 5825 return msg; 5826 } 5827 5828 function DBG_settingChecked(id,defaultValue) { 5829 var el = DBG_el(id); 5830 if(!el) return !!defaultValue; 5831 return !!el.checked; 5832 } 5833 5834 function DBG_safeDebugBaseName(name) { 5835 name = String(name || "").trim(); 5836 name = name.replace(/[\\/:*?\"<>|]+/g,"_"); 5837 name = name.replace(/\.[^.]*$/,""); 5838 name = name.replace(/[^A-Za-z0-9_.-]+/g,"_"); 5839 return name || "sample"; 5840 } 5841 5842 function DBG_defaultDebugSessionFileName(sourceName) { 5843 return DBG_safeDebugBaseName(sourceName || "sample") + ".DEB"; 5844 } 5845 5846 function DBG_safeDebugSessionFileName(name) { 5847 name = String(name || "").trim(); 5848 name = name.replace(/[\\/:*?\"<>|]+/g,"_"); 5849 name = name.replace(/\.[^.]*$/,""); 5850 name = name.replace(/[^A-Za-z0-9_.-]+/g,"_"); 5851 return (name || DBG_safeDebugBaseName(DBG_debugSessionFileName)) + ".DEB"; 5852 } 5853 5854 function DBG_updateDebugSessionFileNameUI() { 5855 var input = DBG_el("DBG_debugSessionFileNameInput"); 5856 if(input && input.value !== DBG_debugSessionFileName) 5857 input.value = DBG_debugSessionFileName; 5858 } 5859 5860 function DBG_setDebugSessionFileName(name) { 5861 DBG_debugSessionFileName = DBG_safeDebugSessionFileName(name || DBG_debugSessionFileName); 5862 DBG_updateDebugSessionFileNameUI(); 5863 return DBG_debugSessionFileName; 5864 } 5865 5866 function DBG_updateDebugSessionFileNameFromSource(sourceName) { 5867 if(!sourceName) return DBG_debugSessionFileName; 5868 DBG_debugSessionFileName = DBG_defaultDebugSessionFileName(sourceName); 5869 DBG_updateDebugSessionFileNameUI(); 5870 return DBG_debugSessionFileName; 5871 } 5872 5873 function DBG_getDebugSessionFileName() { 5874 var input = DBG_el("DBG_debugSessionFileNameInput"); 5875 return DBG_setDebugSessionFileName(input ? input.value : DBG_debugSessionFileName); 5876 } 5877 5878 function DBG_downloadText(filename,text,mime) { 5879 filename = filename || "debug.DEB"; 5880 text = text || ""; 5881 mime = mime || "application/json;charset=utf-8"; 5882 5883 var blob = new Blob([text],{type:mime}); 5884 var url = URL.createObjectURL(blob); 5885 var a = document.createElement("a"); 5886 5887 a.href = url; 5888 a.download = filename; 5889 a.style.display = "none"; 5890 5891 document.body.appendChild(a); 5892 a.click(); 5893 document.body.removeChild(a); 5894 5895 URL.revokeObjectURL(url); 5896 } 5897 5898 function DBG_elementValue(id,defaultValue) { 5899 var el = DBG_el(id); 5900 return el ? el.value : defaultValue; 5901 } 5902 5903 function DBG_setElementValue(id,value) { 5904 var el = DBG_el(id); 5905 if(el && value != null) el.value = String(value); 5906 } 5907 5908 function DBG_setElementChecked(id,value) { 5909 var el = DBG_el(id); 5910 if(el && value != null) el.checked = !!value; 5911 } 5912 5913 function DBG_collectDebugSettings() { 5914 return { 5915 followPC: DBG_settingChecked("DBG_followPcInput",true), 5916 autoScrollTrace: DBG_settingChecked("DBG_autoScrollTraceInput",true), 5917 listingColumns: DBG_listingColumnsValue(), 5918 traceDepth: DBG_traceDepthLimit(), 5919 traceColumns: DBG_traceColumnsValue(), 5920 showCycles: DBG_showCyclesEnabled(), 5921 showPcHistogram: DBG_settingChecked("DBG_showPcHistogramInput",false), 5922 runSpeed: DBG_elementValue("DBG_runSpeedInput","1"), 5923 stopAfter: DBG_elementValue("DBG_stopAfterInput",""), 5924 defaultWatchSize: DBG_elementValue("DBG_defaultWatchSizeInput","byte"), 5925 defaultWatchFormat: DBG_elementValue("DBG_defaultWatchFormatInput","hex") 5926 }; 5927 } 5928 5929 function DBG_applyDebugSettings(settings) { 5930 settings = settings || {}; 5931 5932 DBG_setElementChecked("DBG_followPcInput",settings.followPC); 5933 DBG_setElementChecked("DBG_autoScrollTraceInput",settings.autoScrollTrace); 5934 DBG_setElementValue("DBG_listingColumnsInput",settings.listingColumns); 5935 DBG_setElementValue("DBG_traceDepthInput",settings.traceDepth); 5936 DBG_setElementValue("DBG_traceColumnsInput",settings.traceColumns); 5937 DBG_setElementChecked("DBG_showCyclesInput",settings.showCycles); 5938 DBG_setElementChecked("DBG_showPcHistogramInput",settings.showPcHistogram); 5939 DBG_setElementValue("DBG_runSpeedInput",settings.runSpeed); 5940 DBG_setElementValue("DBG_stopAfterInput",settings.stopAfter); 5941 DBG_setElementValue("DBG_defaultWatchSizeInput",settings.defaultWatchSize); 5942 DBG_setElementValue("DBG_defaultWatchFormatInput",settings.defaultWatchFormat); 5943
5944 DBG_stopAfterInstructionLimit(); 5945 DBG_applyTraceDepthLimit(); 5946 DBG_rebuildTraceLines(); 5947 DBG_renderTraceView(); 5948 DBG_updateRegisterDisplays(); 5949 } 5950 5951 function DBG_symbolTableSessionObject(table) { 5952 table = table || {}; 5953 return { 5954 source: table.source || "", 5955 filename: table.filename || "", 5956 effectiveScope: table.effectiveScope || table.scope || [], 5957 raw: table.raw || { 5958 format: "RetroAppleJS-ASM-symbols", 5959 version: table.version || 2, 5960 source: table.source || table.filename || "symbol table", 5961 scopeMode: table.scopeMode || "instruction-pc", 5962 scope: table.scope || [], 5963 symbols: table.symbols || [] 5964 } 5965 }; 5966 } 5967 5968 function DBG_collectDebugSession() { 5969 var board = DBG_el("DBG_boardText"); 5970 if(board) DBG_currentByteCodeText = board.value || ""; 5971 5972 return { 5973 format: "RetroAppleJS-DBG-session", 5974 version: 1, 5975 filename: DBG_getDebugSessionFileName(), 5976 startPC: DBG_debuggerStartPC & 0xffff, 5977 startPCHex: "$" + DBG_hexWord(DBG_debuggerStartPC), 5978 byteCode: DBG_currentByteCodeText || "", 5979 symbolTables: DBG_symbolTables.map(DBG_symbolTableSessionObject), 5980 settings: DBG_collectDebugSettings() 5981 }; 5982 } 5983 5984 function DBG_exportDebugSession() { 5985 var session = DBG_collectDebugSession(); 5986 DBG_downloadText( 5987 session.filename, 5988 JSON.stringify(session,null,2), 5989 "application/json;charset=utf-8" 5990 ); 5991 DBG_statusText("Exported debugger session as " + session.filename + "."); 5992 } 5993 5994 function DBG_restoreDebugSymbolTables(list) { 5995 DBG_symbolTables = []; 5996 DBG_pendingSymbolImport = null; 5997 5998 list = Array.isArray(list) ? list : []; 5999 for(var i=0;i<list.length;i++) { 6000 var entry = list[i] || {}; 6001 var raw = entry.raw || entry; 6002 try { 6003 var table = DBG_normalizeSymbolTable(raw,entry.filename || raw.source || ""); 6004 var effective = DBG_rangeListNormalize(entry.effectiveScope || []); 6005 table.effectiveScope = effective.length ? effective : table.scope.slice(); 6006 DBG_symbolTables.push(table); 6007 } catch(err) { 6008 if(typeof console !== "undefined" && console.warn) 6009 console.warn("Skipped symbol table from .DEB session:",err); 6010 } 6011 } 6012 6013 DBG_rebuildSymbolIndex(); 6014 DBG_renderSymbolLabels(); 6015 DBG_disassembleRAM(); 6016 } 6017 6018 function DBG_applyDebugSession(session,fileName) { 6019 if(!session || typeof session !== "object") throw new Error("Invalid debugger session file."); 6020 if(session.format && session.format !== "RetroAppleJS-DBG-session") 6021 throw new Error("Unsupported debugger session format: " + session.format); 6022 6023 if(DBG_running) DBG_stopContinuousRun("Continuous run stopped before loading debugger session."); 6024 6025 DBG_applyDebugSettings(session.settings || {}); 6026 6027 DBG_currentByteCodeText = String(session.byteCode || ""); 6028 var board = DBG_el("DBG_boardText"); 6029 if(board) board.value = DBG_currentByteCodeText; 6030 6031 DBG_importBoardByteCode(); 6032 DBG_restoreDebugSymbolTables(session.symbolTables || session.symbols || []); 6033 6034 var startPC = Number(session.startPC); 6035 if(isNaN(startPC)) startPC = 0x0000; 6036 DBG_prepareDebuggerStartPC(startPC,{clearTrace:true}); 6037 DBG_scrollListingToPC(startPC,{behavior:"auto"}); 6038 6039 DBG_setDebugSessionFileName(fileName || session.filename || DBG_debugSessionFileName); 6040 DBG_statusText("Loaded debugger session " + DBG_debugSessionFileName + "."); 6041 return true; 6042 } 6043 6044 async function DBG_onDebugSessionFileChange(evt) { 6045 var input = evt && evt.target ? evt.target : DBG_el("DBG_debugSessionFileInput"); 6046 var file = input && input.files && input.files[0] ? input.files[0] : null; 6047 if(!file) return; 6048 6049 try { 6050 var session = JSON.parse(await file.text()); 6051 DBG_applyDebugSession(session,file.name); 6052 } catch(err) { 6053 DBG_statusText("Debugger session import failed: " + (err && err.message ? err.message :
6053String(err))); 6054 if(typeof console !== "undefined" && console.error) console.error(err); 6055 } finally { 6056 if(input) input.value = ""; 6057 } 6058 } 6059 6060 function DBG_showCyclesEnabled() { 6061 return DBG_settingChecked("DBG_showCyclesInput",true); 6062 } 6063 6064 function DBG_scrollListingEnabled() { 6065 return DBG_settingChecked("DBG_followPcInput",true); 6066 } 6067 6068 function DBG_scrollCpuTraceEnabled() { 6069 return DBG_settingChecked("DBG_autoScrollTraceInput",true); 6070 } 6071 6072 function DBG_traceDepthLimit() { 6073 var input = DBG_el("DBG_traceDepthInput"); 6074 var n = input ? parseInt(input.value,10) : 2048; 6075 if(isNaN(n) || n < 1) n = 2048; 6076 if(input && String(input.value) !== String(n)) input.value = String(n); 6077 return n; 6078 } 6079 6080 function DBG_resetTraceHistory(records) 6081 { 6082 DBG_traceHistory = (DBG_traceHistory || oCOM.buffer(DBG_traceDepthLimit())) 6083 .resize(DBG_traceDepthLimit()) 6084 .fromArray(records || []); 6085 return DBG_traceHistory; 6086 } 6087 6088 6089 function DBG_applyTraceDepthLimit() 6090 { 6091 if(DBG_traceHistory) DBG_traceHistory.resize(DBG_traceDepthLimit()); 6092 else DBG_resetTraceHistory(); 6093 DBG_rebuildTraceLines(); 6094 } 6095 6096 function DBG_cloneTraceState(state) { 6097 if(!state) return null; 6098 return { 6099 pc: state.pc & 0xffff, 6100 a: state.a & 0xff, 6101 x: state.x & 0xff, 6102 y: state.y & 0xff, 6103 p: state.p & 0xff, 6104 sp: state.sp & 0xff, 6105 cycle_delay: state.cycle_delay | 0 6106 }; 6107 } 6108 6109 function DBG_traceStateLoadString(state) { 6110 state = state || {}; 6111 return [ 6112 DBG_hexByte(state.a || 0), 6113 DBG_hexByte(state.x || 0), 6114 DBG_hexByte(state.y || 0), 6115 DBG_hexByte(state.sp == null ? 0xff : state.sp), 6116 DBG_hexByte(state.p == null ? 0x24 : state.p), 6117 DBG_hexWord(state.pc || 0) 6118 ].join(","); 6119 } 6120 6121 function DBG_drawWatchField(grid,x,y,label,value,changed,width) 6122 { 6123 var text = String(label == null ? "" : label) + "·" + String(value == null ? "" : value); 6124 // âªâ¢ âµ â â ⯠â · â 6125 var inner = Math.max(1,Number(width) || text.length || 1); 6126 6127 var outer = inner + 2; 6128 /* 6129 * Stable 3-line block: 6130 * y = optional top box line 6131 * y + 1 = label/value 6132 * y + 2 = optional bottom box line 6133 * 6134 * Unchanged values still reserve the same grid area, but no box is drawn. 6135 */ 6136 if(changed) grid.box(x,y,x+outer-1,y+2,{kind:grid.BOX_SINGLE}); 6137 grid.cell(x + 1,y + 1,text); 6138 return outer; 6139 } 6140 6141 function DBG_renderWatchView(state,compareState) 6142 { 6143 var watch = DBG_el("DBG_watchView"); 6144 if(!watch) return; 6145 6146 state = state || {}; 6147 compareState = compareState || null; 6148 6149 var highlight = !!compareState; 6150 var cpc = compareState ? (compareState.pc & 0xffff) : (state.pc & 0xffff); 6151 var ca = compareState ? (compareState.a & 0xff) : (state.a & 0xff); 6152 var cx = compareState ? (compareState.x & 0xff) : (state.x & 0xff); 6153 var cy = compareState ? (compareState.y & 0xff) : (state.y & 0xff); 6154 var cp = compareState ? (compareState.p & 0xff) : (state.p & 0xff); 6155 var csp = compareState ? (compareState.sp & 0xff) : (state.sp & 0xff); 6156 6157 var grid = oCOM.textGrid(80,6); 6158 var x = 0; 6159 6160 x += DBG_drawWatchField(grid,x,0,"PC",DBG_hexWord(state.pc),highlight && ((state.pc & 0xffff) !== cpc),7); 6161 x += DBG_drawWatchField(grid,x,0,"a", DBG_hexByte(state.a), highlight && ((state.a & 0xff) !== ca),4); 6162 x += DBG_drawWatchField(grid,x,0,"x", DBG_hexByte(state.x), highlight && ((state.x & 0xff) !== cx),4); 6163 x += DBG_drawWatchField(grid,x,0,"y", DBG_hexByte(state.y), highlight && ((state.y & 0xff) !== cy),4); 6164 x += DBG_drawWatchField(grid,x,0,"SP",DBG_hexByte(state.sp),highlight && ((state.sp & 0xff) !== csp),5); 6165 6166 var names = ["N","V","B","D","I","Z","C"]; 6167 var bits = [ 7, 6, 4, 3, 2, 1, 0]; 6168 x = 0; 6169 6170 for(var i=0;i<bits.length;i++) 6171 { 6172 var bit = (state.p >> bits[i]) & 1; 6173 var cbit = (cp >> bits[i]) & 1; 6174 x += DBG_drawWatchField(grid,x,2,names[i],String(bit),highlight && bit !== cbit,3); 6175 } 6176 6177 watch.value = grid.toString(); 6178 } 6179 6180 function DBG_traceLineFromRecord(record) { 6181 var before = record.before || {}; 6182 var decoded = record.decoded || {}; 6183 var parts = { 6184 stp: ("0000" + record.step).slice(-4), 6185 pc: DBG_hexWord(record.pc), 6186 byt: decoded.bytes || "", 6187 opc: decoded.op || "", 6188 rA: DBG_hexByte(before.a || 0), 6189 rX: DBG_hexByte(before.x || 0), 6190 rY: DBG_hexByte(before.y || 0), 6191 rSP: DBG_hexByte(before.sp == null ? 0xff : before.sp), 6192 rP: DBG_hexByte(before.p || 0) 6193 }; 6194 6195 if(record.selfLoopTrap) 6196 parts.opc = "[LOOP] " + parts.opc; 6197 6198 if(DBG_showCyclesEnabled()) parts.cyc = String(record.cycles || 0); 6199 return DBG_formatTraceParts(parts); 6200 } 6201 6202 function DBG_rebuildTraceLines() { 6203 DBG_traceLines = []; 6204 if(DBG_traceHistory)
6205 DBG_traceHistory.forEach(function(record){ DBG_traceLines.push(DBG_traceLineFromRecord(record)); }); 6206 6207 } 6208 6209 function DBG_restoreTraceBefore(record) { 6210 if(!record || !DBG_traceCPU) return false; 6211 6212 var writes = record.writes || []; 6213 for(var i=writes.length-1;i>=0;i--) 6214 DBG_RAM[writes[i].addr & 0xffff] = writes[i].oldVal & 0xff; 6215 6216 DBG_traceCPU.load(DBG_traceStateLoadString(record.before)); 6217 return true; 6218 } 6219 6220 function DBG_updateRegisterDisplays(state,compareState) { 6221 state = state || (DBG_traceCPU && DBG_traceCPU.watch ? DBG_traceCPU.watch() : { 6222 pc:DBG_debuggerStartPC || 0x0000, 6223 a:0,x:0,y:0,p:0x24,sp:0xff, 6224 cycle_delay:0 6225 }); 6226 6227 var pc = state.pc & 0xffff; 6228 var a = state.a & 0xff; 6229 var x = state.x & 0xff; 6230 var y = state.y & 0xff; 6231 var p = state.p & 0xff; 6232 var sp = state.sp & 0xff; 6233 6234 DBG_renderWatchView({pc:pc,a:a,x:x,y:y,p:p,sp:sp},compareState); 6235 6236 var info = DBG_el("DBG_traceRegisterInfo"); 6237 if (info) { 6238 info.innerHTML = oCOM.formatCpuRegistersHTML( 6239 {pc:pc,a:a,x:x,y:y,sp:sp,p:p}, 6240 {includePC:false} 6241 ); 6242 } 6243 } 6244 6245 function DBG_setTraceActive(active) { 6246 /* 6247 * Trace mode does not hide Listing header action buttons. 6248 * DBG_requestActive is the only header action/requester switch. 6249 */ 6250 } 6251 6252 function DBG_renderTraceView() { 6253 var trace = DBG_el("DBG_traceView"); 6254 if (!trace) return; 6255 6256 trace.value = DBG_traceLines.join("\n") + (DBG_traceLines.length ? "\n" : ""); 6257 if (DBG_scrollCpuTraceEnabled()) 6258 trace.scrollTop = trace.scrollHeight; 6259 } 6260 6261 function DBG_startCpuTraceAtPC(startPC) { 6262 if (typeof Cpu6502 !== "function") { 6263 DBG_statusText("Cpu6502() was not found. Check res/EMU_cpu6502.js."); 6264 return false; 6265 } 6266 6267 startPC = startPC & 0xffff; 6268 DBG_traceHW = DBG_createCpuTraceHw(); 6269 DBG_traceCPU = new Cpu6502(DBG_traceHW); 6270 DBG_traceCPU.load("00,00,00,ff,24," + DBG_hexWord(startPC)); 6271 DBG_armSelfLoopTrap(); 6272 DBG_traceStarted = true; 6273 DBG_traceStepCount = 0; 6274 DBG_resetTraceHistory(); 6275 DBG_traceLines = []; 6276 DBG_traceCurrentWrites = null; 6277 6278 DBG_renderTraceView(); 6279 DBG_updateRegisterDisplays(DBG_traceCPU.watch()); 6280 DBG_updateListingPC(startPC); 6281 return true; 6282 } 6283 6284 6285 /* 6286 * TEST BENCH CPU bridge. 6287 * 6288 * Uses the same isolated CPU and DBG_RAM as the normal debugger without 6289 * rendering one CPU TRACE row per instruction. Bulk algorithm tests can 6290 * therefore run headlessly and repaint the debugger only when finished. 6291 */ 6292 function DBG_testCpuState() 6293 { 6294 if(!DBG_traceCPU || typeof DBG_traceCPU.watch !== "function") 6295 return null; 6296 6297 var s = DBG_traceCPU.watch(); 6298 return { 6299 pc: s.pc & 0xffff, 6300 a: s.a & 0xff, 6301 x: s.x & 0xff, 6302 y: s.y & 0xff, 6303 sp: s.sp & 0xff, 6304 p: s.p & 0xff, 6305 cycle_delay: s.cycle_delay | 0, 6306 ic: Number(s.ic) || 0 6307 }; 6308 } 6309 6310 function DBG_testNowMs() 6311 { 6312 return window.performance && typeof window.performance.now === "function" 6313 ? window.performance.now() 6314 : Date.now(); 6315 } 6316 6317 function DBG_testCpuStateText(state) 6318 { 6319 if(!state) return "PC=?"; 6320 return "PC=$" + DBG_hexWord(state.pc) 6321 + " A=$" + DBG_hexByte(state.a) 6322 + " X=$" + DBG_hexByte(state.x) 6323 + " Y=$" + DBG_hexByte(state.y) 6324 + " SP=$" + DBG_hexByte(state.sp) 6325 + " P=$" + DBG_hexByte(state.p); 6326 } 6327 6328 function DBG_testCpuReadWord(address) 6329 { 6330 if(address == null) return null; 6331 address = Number(address) & 0xffff; 6332 return (DBG_RAM[address] | (DBG_RAM[(address + 1) & 0xffff] << 8)) & 0xffff; 6333 } 6334 6335 /* 6336 * Capture a compact memory window around a pointer. Seven bytes before 6337 * the current address plus the current byte and eight following bytes 6338 * gives a 16-byte diagnostic line without turning failures into a dump. 6339 */ 6340 function DBG_testCpuHexWindow(address) 6341 { 6342 if(address == null) return null; 6343 6344 address = Number(address) & 0xffff; 6345 var before = 7; 6346 var length = 16; 6347 var start = (address - before) & 0xffff; 6348 var bytes = []; 6349 6350 for(var i=0;i<length;i++) 6351 bytes.push(DBG_RAM[(start + i) & 0xffff] & 0xff); 6352 6353 return { 6354 address:address, 6355 start:start, 6356 currentIndex:before, 6357 bytes:bytes 6358 }; 6359 } 6360 6361 function DBG_testCpuHexWindowText(window) 6362 { 6363 if(!window || !Array.isArray(window.bytes)) return ""; 6364 6365 var bytes = window.bytes.map(function(value,index){ 6366 var text = DBG_hexByte(value);
6367 return index === window.currentIndex ? "[" + text + "]" : text; 6368 }); 6369 6370 return "$" + DBG_hexWord(window.start) + ": " + bytes.join(" "); 6371 } 6372 6373 6374 function DBG_testCpuMemoryContext(inputPointerAddress,outputPointerAddress) 6375 { 6376 var inputPointer = DBG_testCpuReadWord(inputPointerAddress); 6377 var outputPointer = DBG_testCpuReadWord(outputPointerAddress); 6378 6379 return { 6380 inputPointerAddress:inputPointerAddress == null ? null : (Number(inputPointerAddress) & 0xffff), 6381 outputPointerAddress:outputPointerAddress == null ? null : (Number(outputPointerAddress) & 0xffff), 6382 inputPointer:inputPointer, 6383 outputPointer:outputPointer, 6384 inputWindow:DBG_testCpuHexWindow(inputPointer), 6385 outputWindow:DBG_testCpuHexWindow(outputPointer) 6386 }; 6387 } 6388 6389 function DBG_testCpuContextText(result) 6390 { 6391 result = result || {}; 6392 var parts = []; 6393 6394 if(result.state) 6395 parts.push(DBG_testCpuStateText(result.state)); 6396 6397 var memory = result.memory || {}; 6398 if(memory.inputPointer != null || memory.outputPointer != null) 6399 { 6400 var ptr = "PTR"; 6401 if(memory.inputPointer != null) 6402 ptr += " in" 6403 + (memory.inputPointerAddress != null ? "[$" + DBG_hexWord(memory.inputPointerAddress) + "]" : "") 6404 + "=$" + DBG_hexWord(memory.inputPointer); 6405 if(memory.outputPointer != null) 6406 ptr += " out" 6407 + (memory.outputPointerAddress != null ? "[$" + DBG_hexWord(memory.outputPointerAddress) + "]" : "") 6408 + "=$" + DBG_hexWord(memory.outputPointer); 6409 parts.push(ptr); 6410 } 6411 6412 var pcs = Array.isArray(result.recentPCs) ? result.recentPCs.slice(-6) : []; 6413 if(pcs.length) 6414 parts.push("PCs=" + pcs.map(function(pc){ return "$" + DBG_hexWord(pc); }).join("â")); 6415 6416 var dump = []; 6417 if(memory.inputWindow) 6418 dump.push("IN " + DBG_testCpuHexWindowText(memory.inputWindow)); 6419 if(memory.outputWindow) 6420 dump.push("OUT " + DBG_testCpuHexWindowText(memory.outputWindow)); 6421 6422 var text = parts.length ? " / " + parts.join(" / ") : ""; 6423 if(dump.length) text += "\n " + dump.join("\n "); 6424 return text; 6425 } 6426 6427 function DBG_testCpuDiagnostic(result) 6428 { 6429 result = result || {}; 6430 var elapsed = Number(result.elapsedMs) || 0; 6431 var suffix = result.reason === "stop-pc" ? "" : DBG_testCpuContextText(result); 6432 6433 switch(result.reason) 6434 { 6435 case "stop-pc": 6436 return "returned to $" + DBG_hexWord(result.stopPC) 6437 + " / " + result.instructions + " ins" 6438 + " / " + result.cycles + " cyc" 6439 + " / " + elapsed.toFixed(1) + " ms"; 6440 6441 case "timeout": 6442 return "timeout after " + elapsed.toFixed(1) + " ms" 6443 + " / " + result.instructions + " ins" + suffix; 6444 6445 case "instruction-limit": 6446 return "instruction limit " + result.maxInstructions 6447 + " reached / " + result.instructions + " ins" + suffix; 6448 6449 case "self-loop": 6450 return "self-loop at $" + DBG_hexWord( 6451 result.loopTrap && result.loopTrap.pc != null 6452 ? result.loopTrap.pc 6453 : (result.state ? result.state.pc : 0) 6454 ) 6455 + (result.loopTrap && result.loopTrap.repeats 6456 ? " x" + result.loopTrap.repeats 6457 : "") 6458 + " / " + result.instructions + " ins" + suffix; 6459 6460 case "pc-loop": 6461 var pcs = result.pcLoop && Array.isArray(result.pcLoop.pcs) 6462 ? result.pcLoop.pcs.map(function(pc){ return "$" + DBG_hexWord(pc); }).join("â") 6463 : ""; 6464 return "PC loop period " + (result.pcLoop ? result.pcLoop.period : "?") 6465 + " x" + (result.pcLoop ? result.pcLoop.repeats : "?") 6466 + (pcs ? " [" + pcs + "]" : "") 6467 + " / " + result.instructions + " ins" + suffix; 6468 6469 case "cpu-error": 6470 return "CPU error" + (result.error ? ": " + result.error : "") + suffix; 6471 6472 case "instruction-did-not-complete": 6473 return "instruction did not complete" + suffix; 6474 6475 case "cpu-state-unavailable": 6476 return "CPU state unavailable"; 6477 6478 case "not-started": 6479 return "CPU not started"; 6480 } 6481 6482 return String(result.reason || "unknown failure") + suffix; 6483 } 6484 6485 function DBG_testCpuStart(startPC) 6486 { 6487 startPC = Number(startPC) & 0xffff; 6488 6489 if(DBG_running) 6490 DBG_stopContinuousRun("Continuous run stopped for TEST BENCH."); 6491 6492 if(!DBG_startCpuTraceAtPC(startPC)) 6493 return null; 6494 6495 return DBG_testCpuState(); 6496 } 6497 6498 /* Execute one complete 6502 instruction, without rendering a trace row. */ 6499 function DBG_testCpuStepInstruction(options) 6500 { 6501 options = options || {}; 6502 6503 if(!DBG_traceCPU) 6504 return {ok:false,reason:"not-started"}; 6505 6506 var before = DBG_testCpuState(); 6507 var after = before; 6508 var cycles = 0; 6509 var captureWrites = !!options.captureWrites; 6510 6511 DBG_traceCurrentWrites = captureWrites ? [] : null; 6512 6513 try 6514 { 6515 do 6516 { 6517 DBG_traceCPU.cycle(); 6518 cycles++; 6519 after = DBG_testCpuState(); 6520 } 6521 while(after && after.cycle_delay > 0 && cycles < 64); 6522 } 6523 catch(err) 6524 { 6525 var failedWrites = DBG_traceCurrentWrites 6526 ? DBG_traceCurrentWrites.slice() 6527 : []; 6528 6529 DBG_traceCurrentWrites = null; 6530 6531 return { 6532 ok:false, 6533 reason:"cpu-error", 6534 error:String(err), 6535 before:before, 6536 after:DBG_testCpuState(), 6537 cycles:cycles, 6538 writes:failedWrites 6539 }; 6540 } 6541 6542 var writes = DBG_traceCurrentWrites 6543 ? DBG_traceCurrentWrites.slice() 6544 : []; 6545 6546 DBG_traceCurrentWrites = null; 6547 6548 if(!after || after.cycle_delay > 0) 6549 { 6550 return { 6551 ok:false, 6552 reason:"instruction-did-not-complete", 6553 before:before, 6554 after:after, 6555 cycles:cycles, 6556 writes:writes 6557 }; 6558 } 6559 6560 return { 6561 ok:true, 6562 reason:"step", 6563 before:before, 6564 after:after, 6565 cycles:cycles, 6566 writes:writes 6567 }; 6568 } 6569 6570 /* 6571 * Detect a short repeating PC cycle such as AâBâAâB. Same-PC loops are 6572 * already handled by Cpu6502's self-loop trap. The deliberately high 6573 * repeat limit avoids mistaking normal decode/copy loops for hangs. 6574 */ 6575 function DBG_testPcLoopDetector(maxPeriod,repeatLimit) 6576 { 6577 maxPeriod = Math.max(2,Math.min(32,Number(maxPeriod) | 0 || 8)); 6578 repeatLimit = Math.max(2,Number(repeatLimit) | 0 || 1024); 6579 6580 var recent = []; 6581 var streak = new Array(maxPeriod + 1).fill(0); 6582 6583 return function(pc) 6584 { 6585 recent.push(pc & 0xffff); 6586 if(recent.length > maxPeriod * 2) recent.shift(); 6587 6588 for(var period=2;period<=maxPeriod;period++) 6589 { 6590 if(recent.length < period * 2) continue; 6591 6592 var n = recent.length; 6593 var same = true; 6594 for(var i=0;i<period;i++) 6595 {
6596 if(recent[n-1-i] !== recent[n-1-period-i]) 6597 { 6598 same = false; 6599 break; 6600 } 6601 } 6602 6603 streak[period] = same ? streak[period] + 1 : 0; 6604 6605 if(streak[period] >= period * (repeatLimit - 1)) 6606 { 6607 return { 6608 period:period, 6609 repeats:Math.floor(streak[period] / period) + 1, 6610 pcs:recent.slice(n-period) 6611 }; 6612 } 6613 } 6614 6615 return null; 6616 }; 6617 } 6618 6619 /* 6620 * Run until stopPC is reached at a clean instruction boundary. 6621 * The instruction at stopPC is NOT executed. 6622 */ 6623 function DBG_testCpuRunUntil(stopPC,maxInstructions,options) 6624 { 6625 options = options || {}; 6626 stopPC = Number(stopPC) & 0xffff; 6627 6628 maxInstructions = Number(maxInstructions); 6629 if(!isFinite(maxInstructions) || maxInstructions < 1) 6630 maxInstructions = 1000000; 6631 maxInstructions = Math.floor(maxInstructions); 6632 6633 var timeoutMs = options.timeoutMs == null ? 5000 : Number(options.timeoutMs); 6634 if(!isFinite(timeoutMs) || timeoutMs < 0) timeoutMs = 5000; 6635 6636 var inputPointerAddress = options.inputPointerAddress == null 6637 ? null 6638 : (Number(options.inputPointerAddress) & 0xffff); 6639 var outputPointerAddress = options.outputPointerAddress == null 6640 ? null 6641 : (Number(options.outputPointerAddress) & 0xffff); 6642 6643 var detectPcLoops = options.detectPcLoops !== false; 6644 var pcLoopMaxPeriod = options.pcLoopMaxPeriod == null ? 8 : options.pcLoopMaxPeriod; 6645 var pcLoopRepeatLimit = options.pcLoopRepeatLimit == null ? 1024 : options.pcLoopRepeatLimit; 6646 var detectPcLoop = detectPcLoops 6647 ? DBG_testPcLoopDetector(pcLoopMaxPeriod,pcLoopRepeatLimit) 6648 : null; 6649 6650 var captureWrites = !!options.captureWrites; 6651 var allWrites = []; 6652 var instructions = 0; 6653 var cycles = 0; 6654 var reason = "not-started"; 6655 var error = null; 6656 var loopTrap = null; 6657 var pcLoop = null; 6658 var recentPCs = []; 6659 var startedAt = DBG_testNowMs(); 6660 6661 if(!DBG_traceCPU) 6662 { 6663 var notStarted = { 6664 ok:false, 6665 reason:reason, 6666 stopPC:stopPC, 6667 maxInstructions:maxInstructions, 6668 timeoutMs:timeoutMs, 6669 instructions:0, 6670 cycles:0, 6671 elapsedMs:0, 6672 state:null, 6673 loopTrap:null, 6674 pcLoop:null, 6675 recentPCs:[], 6676 memory:DBG_testCpuMemoryContext(inputPointerAddress,outputPointerAddress), 6677 writes:[] 6678 }; 6679 notStarted.diagnostic = DBG_testCpuDiagnostic(notStarted); 6680 return notStarted; 6681 } 6682 6683 while(true) 6684 { 6685 var state = DBG_testCpuState(); 6686 6687 if(!state) 6688 { 6689 reason = "cpu-state-unavailable"; 6690 break; 6691 } 6692 6693 if(state.cycle_delay === 0 && state.pc === stopPC) 6694 { 6695 reason = "stop-pc"; 6696 break; 6697 } 6698 6699 if(instructions >= maxInstructions) 6700 { 6701 reason = "instruction-limit"; 6702 break; 6703 } 6704 6705 /* Amortize wall-clock checks so long suites do not pay for Date.now() 6706 * on every single instruction. timeoutMs=0 explicitly disables it. */ 6707 if(timeoutMs > 0 && (instructions & 0xff) === 0 && 6708 DBG_testNowMs() - startedAt >= timeoutMs) 6709 { 6710 reason = "timeout"; 6711 break; 6712 } 6713 6714 var step = DBG_testCpuStepInstruction({captureWrites:captureWrites}); 6715 6716 /* Keep the actual instruction addresses, not merely the following PCs. */ 6717 if(step.before) 6718 { 6719 recentPCs.push(step.before.pc & 0xffff); 6720 if(recentPCs.length > 8) recentPCs.shift(); 6721 } 6722 6723 if(!step.ok) 6724 { 6725 reason = step.reason; 6726 error = step.error || null; 6727 if(captureWrites && step.writes) 6728 Array.prototype.push.apply(allWrites,step.writes); 6729 break; 6730 } 6731 6732 instructions++; 6733 cycles += step.cycles; 6734 6735 if(captureWrites && step.writes.length) 6736 Array.prototype.push.apply(allWrites,step.writes); 6737 6738 loopTrap = DBG_getSelfLoopTrap(); 6739 if(loopTrap) 6740 { 6741 reason = "self-loop"; 6742 break; 6743 } 6744 6745 6746 if(detectPcLoop && step.after && step.after.pc !== stopPC) 6747 { 6748 pcLoop = detectPcLoop(step.after.pc); 6749 if(pcLoop) 6750 { 6751 reason = "pc-loop"; 6752 break; 6753 } 6754 } 6755 6756 } 6757 6758 var finalState = DBG_testCpuState(); 6759 var elapsedMs = Math.max(0,DBG_testNowMs() - startedAt); 6760 var ok = reason === "stop-pc"; 6761 6762 /* Headless while running; repaint once at the end. */ 6763 if(finalState) 6764 { 6765 DBG_updateRegisterDisplays(finalState); 6766 DBG_updateListingPC(finalState.pc & 0xffff); 6767 } 6768 6769 var result = { 6770 ok:ok, 6771 reason:reason, 6772 stopPC:stopPC, 6773 maxInstructions:maxInstructions, 6774 timeoutMs:timeoutMs, 6775 instructions:instructions, 6776 cycles:cycles, 6777 elapsedMs:elapsedMs, 6778 state:finalState, 6779 error:error, 6780 loopTrap:loopTrap, 6781 pcLoop:pcLoop, 6782 recentPCs:recentPCs.slice(), 6783 memory:DBG_testCpuMemoryContext(inputPointerAddress,outputPointerAddress), 6784 writes:captureWrites ? allWrites : [] 6785 }; 6786 result.diagnostic = DBG_testCpuDiagnostic(result); 6787 /* 6788 * If CPU execution returned normally but an algorithm assertion fails, 6789 * TB.report() still needs the same register/pointer/memory context. 6790 */ 6791 result.failureDiagnostic = result.ok 6792 ? result.diagnostic + DBG_testCpuContextText(result) 6793 : result.diagnostic; 6794 DBG_statusText("TEST BENCH: " + result.diagnostic); 6795 return result; 6796 } 6797 6798 /* Narrow public surface consumed by res/DBG_testbench.js. */ 6799 window.DBG_TEST_CPU = { 6800 start:DBG_testCpuStart, 6801 state:DBG_testCpuState, 6802 step:DBG_testCpuStepInstruction, 6803 runUntil:DBG_testCpuRunUntil, 6804 diagnostic:DBG_testCpuDiagnostic 6805 }; 6806
6807 function DBG_traceInstructionTextAt(pc) { 6808 var d = DBG_disassembleInstruction(pc); 6809 var row = d && d.row ? d.row : null; 6810 if (!row) return {bytes:"", op:""}; 6811 6812 return { 6813 bytes: (row.byteHex || []).join(" "), 6814 op: String(row.ins || "") + (row.opr ? " " + row.opr : "") 6815 }; 6816 } 6817 6818 function DBG_traceStepForward() { 6819 if (!DBG_traceStarted || !DBG_traceCPU) { 6820 DBG_startCpuTraceAtPC(DBG_debuggerStartPC || 0x0000); 6821 if (!DBG_traceCPU) return false; 6822 } 6823 6824 var before = DBG_cloneTraceState(DBG_traceCPU.watch()); 6825 var pc = before.pc & 0xffff; 6826 var decoded = DBG_traceInstructionTextAt(pc); 6827 var cycles = 0; 6828 var after; 6829 DBG_traceCurrentWrites = []; 6830 6831 do { 6832 DBG_traceCPU.cycle(); 6833 cycles++; 6834 after = DBG_cloneTraceState(DBG_traceCPU.watch()); 6835 } while (after && after.cycle_delay > 0 && cycles < 64); 6836 6837 var selfLoopTrap = DBG_getSelfLoopTrap(); 6838 6839 var writes = DBG_traceCurrentWrites || []; 6840 DBG_traceCurrentWrites = null; 6841 6842 DBG_traceStepCount++; 6843 6844 var record = { 6845 step: DBG_traceStepCount, 6846 before: before, 6847 after: after, 6848 pc: pc, 6849 decoded: decoded, 6850 cycles: cycles, 6851 writes: writes.slice(), 6852 selfLoopTrap: selfLoopTrap 6853 }; 6854 6855 if(!DBG_traceHistory) DBG_resetTraceHistory(); 6856 if(DBG_traceHistory.full()) DBG_traceLines.shift(); 6857 DBG_traceHistory.push(record); 6858 DBG_traceLines.push(DBG_traceLineFromRecord(record)); 6859 while(DBG_traceLines.length > DBG_traceHistory.length) 6860 DBG_traceLines.shift(); 6861 6862 DBG_renderTraceView(); 6863 DBG_updateRegisterDisplays(after,before); 6864 if (after) DBG_updateListingPC(after.pc & 0xffff); 6865 6866 if(selfLoopTrap && !DBG_running) 6867 DBG_statusText(DBG_selfLoopTrapText(selfLoopTrap)); 6868 6869 return true; 6870 } 6871 6872 function DBG_clearTrace() { 6873 /* 6874 * Clear only the trace log/ring buffer and restart trace numbering. 6875 * Do not reset the CPU and do not stop an active continuous run. 6876 */ 6877 DBG_traceStepCount = 0; 6878 DBG_resetTraceHistory(); 6879 DBG_traceLines = []; 6880 DBG_armSelfLoopTrap(); 6881 DBG_renderTraceView(); 6882 DBG_statusText("Trace cleared; next trace entry will be step 0001."); 6883 return true; 6884 } 6885 6886 function DBG_traceStepBackward() 6887 { 6888 if(DBG_running) 6889 DBG_stopContinuousRun("Continuous run stopped before stepping backward."); 6890 6891 if(!DBG_traceHistory || !DBG_traceHistory.length) { 6892 DBG_statusText("Trace history is empty; cannot step backward."); 6893 return false; 6894 } 6895 6896 if(!DBG_traceCPU) { 6897 DBG_statusText("Trace CPU is not active; cannot step backward."); 6898 return false; 6899 } 6900 6901 var record = DBG_traceHistory.pop(); 6902 if(!DBG_restoreTraceBefore(record)) return false; 6903 6904 DBG_traceStepCount = Math.max(0,DBG_traceStepCount - 1); 6905 DBG_rebuildTraceLines(); 6906 DBG_renderTraceView(); 6907 DBG_updateRegisterDisplays(record.before,record.after); 6908 DBG_updateListingPC(record.before.pc & 0xffff); 6909 DBG_statusText("Stepped backward to $" + DBG_hexWord(record.before.pc) + "."); 6910 return true; 6911 } 6912 6913 function DBG_runSpeedValue() { 6914 var input = DBG_el("DBG_runSpeedInput"); 6915 var value = input ? String(input.value || "").toLowerCase() : "1"; 6916 6917 if(value === "max") return {mode:"max",ips:0,stepsPerTick:250,delay:0,label:"Max"}; 6918 6919 var ips = parseInt(value,10); 6920 if(isNaN(ips) || ips < 1) ips = 1; 6921 6922 if(ips >= 1000) return {mode:"fixed",ips:1000,stepsPerTick:50,delay:50,label:"1000 IPS"}; 6923 if(ips >= 100) return {mode:"fixed",ips:100, stepsPerTick:5, delay:50,label:"100 IPS"}; 6924 if(ips >= 10) return {mode:"fixed",ips:10, stepsPerTick:1, delay:100,label:"10 IPS"}; 6925 6926 return {mode:"fixed",ips:1,stepsPerTick:1,delay:1000,label:"1 IPS"}; 6927 } 6928
6929 function DBG_stopAfterInstructionLimit() { 6930 var input = DBG_el("DBG_stopAfterInput"); 6931 if(!input) return 0; 6932 6933 var raw = String(input.value || "").trim(); 6934 if(raw === "") return 0; // empty means no run limit / infinity 6935 6936 var n = parseInt(raw,10); 6937 if(isNaN(n) || n < 1) { 6938 input.value = ""; 6939 return 0; 6940 } 6941 6942 if(String(input.value) !== String(n)) input.value = String(n); 6943 return n; 6944 } 6945 6946 function DBG_updateRunButton() { 6947 var btn = DBG_el("DBG_runToggle"); 6948 if(!btn) return; 6949 6950 btn.innerHTML = DBG_running ? '<i class="fa fa-pause"></i>' : '<i class="fa fa-running"></i>'; 6951 btn.title = DBG_running ? "Stop continuous run" : "Continuous run"; 6952 btn.classList.remove("DBG_changed"); 6953 } 6954 6955 function DBG_stopContinuousRun(reason) { 6956 DBG_running = false; 6957 6958 if(DBG_runTimer !== null) { 6959 window.clearTimeout(DBG_runTimer); 6960 DBG_runTimer = null; 6961 } 6962 6963 DBG_updateRunButton(); 6964 if(reason) DBG_statusText(reason); 6965 } 6966 6967 function DBG_scheduleRunLoop(delay) { 6968 if(DBG_runTimer !== null) window.clearTimeout(DBG_runTimer); 6969 DBG_runTimer = window.setTimeout(DBG_runLoop,Math.max(0,delay | 0)); 6970 } 6971 6972 function DBG_runLoop() { 6973 if(!DBG_running) return; 6974 6975 var speed = DBG_runSpeedValue(); 6976 var stopAfter = DBG_stopAfterInstructionLimit(); 6977 var stepsThisTick = Math.max(1,speed.stepsPerTick | 0); 6978 6979 for(var i=0;i<stepsThisTick;i++) { 6980 if(stopAfter > 0 && DBG_runInstructionCount >= stopAfter) { 6981 DBG_stopContinuousRun( 6982 "Continuous run stopped after " + DBG_runInstructionCount + " instruction(s)." 6983 ); 6984 return; 6985 } 6986 6987 if(!DBG_traceStepForward()) { 6988 DBG_stopContinuousRun("Continuous run stopped: step failed."); 6989 return; 6990 } 6991 6992 DBG_runInstructionCount++; 6993 6994 var selfLoopTrap = DBG_getSelfLoopTrap(); 6995 if(selfLoopTrap) { 6996 DBG_stopContinuousRun(DBG_selfLoopTrapText(selfLoopTrap)); 6997 return; 6998 } 6999 7000 if(stopAfter > 0 && DBG_runInstructionCount >= stopAfter) { 7001 DBG_stopContinuousRun( 7002 "Continuous run stopped after " + DBG_runInstructionCount + " instruction(s)." 7003 ); 7004 return; 7005 } 7006 } 7007 7008 if(DBG_traceStepCount !== DBG_runLastStatusStep) { 7009 DBG_armSelfLoopTrap(); 7010 DBG_runLastStatusStep = DBG_traceStepCount; 7011 DBG_statusText( 7012 "Continuous run: " + DBG_runInstructionCount + " instruction(s) / " + speed.label + 7013 (stopAfter > 0 ? " / stop at " + stopAfter : "") 7014 ); 7015 } 7016 7017 DBG_scheduleRunLoop(speed.delay); 7018 } 7019 7020 function DBG_startContinuousRun() { 7021 if(DBG_running) return; 7022 7023 if(!DBG_traceStarted || !DBG_traceCPU) { 7024 DBG_startCpuTraceAtPC(DBG_debuggerStartPC || 0x0000); 7025 if(!DBG_traceCPU) return; 7026 } 7027 7028 DBG_runInstructionCount = 0; 7029 DBG_runLastStatusStep = DBG_traceStepCount; 7030 DBG_running = true; 7031 DBG_updateRunButton(); 7032 7033 var speed = DBG_runSpeedValue(); 7034 var stopAfter = DBG_stopAfterInstructionLimit(); 7035 DBG_statusText( 7036 "Continuous run started at " + speed.label + 7037 (stopAfter > 0 ? " / stop after " + stopAfter + " instruction(s)." : ".") 7038 ); 7039 7040 DBG_scheduleRunLoop(0); 7041 } 7042 7043 function DBG_toggleContinuousRun() { 7044 if(DBG_running) DBG_stopContinuousRun("Continuous run stopped."); 7045 else DBG_startContinuousRun(); 7046 } 7047 7048 7049 function DBG_importBoardByteCode() { 7050 var parsed = DBG_syncByteCodeFromBoard(); 7051 if (!parsed.records.length) { 7052 DBG_stub("No byte code to import"); 7053 return false; 7054 } 7055 7056 for (var i = 0; i < parsed.records.length; i++) { 7057 DBG_RAM[parsed.records[i].pc & 0xffff] = parsed.records[i].val & 0xff; 7058 } 7059 7060 var lineCount = DBG_disassembleRAM(); 7061 DBG_updateByteCodeInfo(parsed.records.length, lineCount + " listing lines"); 7062 DBG_statusText("Imported " + parsed.records.length + " byte(s) and disassembled RAM."); 7063 7064 if (parsed.errors.length && typeof console !== "undefined" && console.warn) console.warn(parsed.errors.join("\n")); 7065 return true; 7066 } 7067 7068 function DBG_togglePanel(id) { 7069 var target = DBG_el(id); 7070 if (!target) return; 7071 7072 var other = id === "tab3.1" ? DBG_el("tab3.2") : DBG_el("tab3.1"); 7073 if (other) other.hidden = true; 7074 target.hidden = !target.hidden; 7075 DBG_equalizePaneHeaders(); 7076 } 7077 7078 function DBG_closePanels() { 7079 if (DBG_el("tab3.1")) DBG_el("tab3.1").hidden = true; 7080 if (DBG_el("tab3.2")) DBG_el("tab3.2").hidden = true; 7081 } 7082 7083 function DBG_registerPopupRules() { 7084 /* 7085 * Keep the debugger tab buttons consistent with tab1:
7086 * buttons call oCOM.POPUP.runRule(), rules toggle one overlay 7087 * and close the other one. 7088 * 7089 * Rule names follow the tab1 convention: 7090 * tab3.1 = Settings 7091 * tab3.2 = Tools 7092 * 7093 * The actual popup IDs remain tab3.1 / tab3.2, 7094 * because the v22 debugger code already uses those IDs internally. 7095 */ 7096 if (typeof oCOM === "undefined" || !oCOM || !oCOM.POPUP || typeof oCOM.POPUP.addRule !== "function") return; 7097 oCOM.POPUP.addRule("tab3.1", function(o) { o.toggle("tab3.1"); o.off("tab3.2"); DBG_equalizePaneHeaders(); }); 7098 oCOM.POPUP.addRule("tab3.2", function(o) { o.toggle("tab3.2"); o.off("tab3.1"); DBG_equalizePaneHeaders(); }); 7099 } 7100 7101 function DBG_applyPaneHeaderButtonTheme() { 7102 /* 7103 * Keep pane-header buttons readable in dark mode without adding more 7104 * dark-mode CSS override chains. These colors are UI state, so apply 7105 * and clear them when the theme changes. 7106 */ 7107 var buttons = document.querySelectorAll("#DBG_root .DBG_paneTitle .DBG_headerActions .appbut"); 7108 7109 for (var i = 0; i < buttons.length; i++) { 7110 var btn = buttons[i]; 7111 7112 if (DBG_darkMode) { 7113 btn.style.backgroundColor = "#2b3139"; 7114 btn.style.borderColor = "#59636f"; 7115 btn.style.color = "#e6edf3"; 7116 } else { 7117 btn.style.backgroundColor = ""; 7118 btn.style.borderColor = ""; 7119 btn.style.color = ""; 7120 } 7121 } 7122 } 7123 7124 function DBG_setStyles(selector, styles) 7125 { 7126 var nodes = document.querySelectorAll(selector); 7127 for(var i = 0; i < nodes.length; i++) 7128 for(var k in styles) 7129 nodes[i].style[k] = styles[k]; 7130 } 7131 7132 function DBG_clearStyles(selector, props) 7133 { 7134 var nodes = document.querySelectorAll(selector); 7135 for(var i = 0; i < nodes.length; i++) 7136 for(var p = 0; p < props.length; p++) 7137 nodes[i].style[props[p]] = ""; 7138 } 7139 7140 function DBG_applyTheme() 7141 { 7142 /* 7143 * Dynamic-only debugger theme. 7144 */ 7145 var tab = DBG_el("tab3"); 7146 if(tab) 7147 { 7148 tab.classList.remove("DBG_dark"); 7149 tab.style.backgroundColor = ""; 7150 tab.style.color = ""; 7151 } 7152 7153 var basicProps = ["backgroundColor","backgroundImage","background","borderColor","color","caretColor"]; 7154 7155 if(DBG_darkMode) 7156 { 7157 DBG_setStyles("#DBG_root .DBG_paneTitle", { 7158 background:"#111820", 7159 borderColor:"#30363d", 7160 color:"#e6edf3" 7161 }); 7162 DBG_setStyles("#DBG_root .DBG_paneTitleMain strong, #DBG_root .DBG_paneInfo", { 7163 color:"#e6edf3" 7164 }); 7165 DBG_setStyles("#DBG_root .DBG_paneBody", { 7166 backgroundColor:"#000000", 7167 backgroundImage:"none", 7168 borderColor:"#30363d", 7169 color:"#e6edf3", 7170 caretColor:"#e6edf3" 7171 }); 7172 DBG_setStyles("#DBG_root .DBG_textPane", { 7173 color:"#e6edf3" 7174 }); 7175 7176 DBG_setStyles("#DBG_root .DBG_listingLineCurrent, #DBG_root .DBG_traceLineCurrent", { 7177 color:"#000000" 7178 }); 7179 DBG_setStyles("#DBG_root .DBG_listingPcOverlay", { 7180 background:"rgba(255, 214, 64, 0.34)" 7181 }); 7182 DBG_setStyles("#DBG_root .DBG_breakpoint", { 7183 color:"#ff7a7a" 7184 }); 7185 } 7186 else 7187 { 7188 DBG_clearStyles("#DBG_root .DBG_paneTitle, #DBG_root .DBG_paneTitleMain strong, #DBG_root .DBG_paneInfo, #DBG_root .DBG_paneBody, #DBG_root .DBG_textPane, #DBG_root .DBG_listingLineCurrent, #DBG_root .DBG_traceLineCurrent, #DBG_root .DBG_listingPcOverlay, #DBG_root .DBG_breakpoint", basicProps); 7189 } 7190 } 7191 7192 function DBG_toggleRun(btn) { 7193 DBG_running = !DBG_running; 7194 if (!btn) btn = DBG_el("DBG_runToggle"); 7195 if (!btn) return; 7196 7197 if (DBG_running) { 7198 btn.innerHTML = '<i class="fa fa-snowflake"></i>'; 7199 btn.title = "Stop continuous run"; 7200 btn.setAttribute("aria-label", "Stop continuous run"); 7201 } else { 7202 btn.innerHTML = '<i class="fa fa-running"></i>'; 7203 btn.title = "Continuous run"; 7204 btn.setAttribute("aria-label", "Continuous run"); 7205 } 7206 } 7207 7208 function DBG_togglePane3(forceVisible) { 7209 var host = DBG_el("DBG_paneHost"); 7210 var btn = DBG_el("DBG_toggleWatchPaneButton"); 7211 if (!host) return; 7212 7213 var hide = host.classList.contains("DBG_hideWatch"); 7214 if (typeof forceVisible === "boolean") hide = forceVisible ? true : false;
7215 7216 if (hide) host.classList.remove("DBG_hideWatch"); 7217 else host.classList.add("DBG_hideWatch"); 7218 7219 if (btn) { 7220 var visible = !host.classList.contains("DBG_hideWatch"); 7221 btn.innerHTML = visible ? '<i class="fa fa-eye-slash"></i>' : '<i class="fa fa-eye"></i>'; 7222 btn.title = visible ? "Hide Memory Watch pane" : "Show Memory Watch pane"; 7223 } 7224 DBG_equalizePaneHeaders(); 7225 } 7226 7227 function DBG_toggleTheme() { 7228 DBG_darkMode = !DBG_darkMode; 7229 DBG_applyTheme(); 7230 DBG_applyPaneHeaderButtonTheme(); 7231 DBG_equalizePaneHeaders(); 7232 } 7233 7234 function DBG_stub(action) { 7235 var status = DBG_el("DBG_status"); 7236 if (status) status.textContent = "Layout prototype: " + action + " is not wired yet."; 7237 } 7238 7239 function DBG_equalizePaneHeaders() { 7240 if (DBG_oPANE && typeof DBG_oPANE.equalizePaneHeaderHeights === "function") { 7241 DBG_oPANE.equalizePaneHeaderHeights("#DBG_root .com-pane-title"); 7242 } 7243 } 7244 7245 function DBG_bindTextPaneEvents() { 7246 var listing = DBG_el("debugfield"); 7247 if(listing) 7248 listing.addEventListener("scroll",function(){ 7249 if(DBG_listingCurrentPC != null) DBG_positionListingPcOverlay(DBG_listingRowIndexForPC(DBG_listingCurrentPC)); 7250 }); 7251 } 7252 7253 function DBG_initPaneContainer() { 7254 if (typeof PANE === "function") { 7255 DBG_oPANE = new PANE(); 7256 } 7257 7258 var board = DBG_el("DBG_boardText"); 7259 if (board) DBG_syncByteCodeFromBoard(); 7260 7261 var symFile = DBG_el("DBG_sym_fileInput"); 7262 if (symFile) symFile.addEventListener("change", DBG_onSymbolFileChange); 7263 7264 var debFile = DBG_el("DBG_debugSessionFileInput"); 7265 if (debFile) debFile.addEventListener("change", DBG_onDebugSessionFileChange); 7266 DBG_bindTextPaneEvents(); 7267 7268 DBG_renderSymbolLabels(); 7269 DBG_updateDebugSessionFileNameUI(); 7270 DBG_applyTheme(); 7271 DBG_applyPaneHeaderButtonTheme(); 7272 DBG_equalizePaneHeaders(); 7273 DBG_updateRegisterDisplays(); 7274 7275 DBG_loadManualPane(); 7276 } 7277 7278 function DBG_loadManualPane() { 7279 /* 7280 * Keep debugger manual loading explicit, mirroring the tab2 manual pane 7281 * while using the debugger documentation block. 7282 */ 7283 var manual = DBG_el("DBG_manual"); 7284 if (!manual) return false; 7285 7286 var html = ""; 7287 if (typeof _DOCS !== "undefined" && _DOCS && _DOCS["DEBUGGER"]) { 7288 html = String(_DOCS["DEBUGGER"]) 7289 .replace(/https:\/\/github\.com\/RetroAppleJS\/RetroAppleJS\.github\.io\/raw\/main\//g, ""); 7290 } 7291 7292 manual.innerHTML = html; 7293 return true; 7294 } 7295 7296 if (typeof oCOM !== "undefined" && oCOM && typeof oCOM.addToEventStack === "function") { 7297 oCOM.addToEventStack("onload", DBG_initPaneContainer); 7298 } else { 7299 window.addEventListener("load", DBG_initPaneContainer); 7300 } 7301 7302 DBG_registerPopupRules(); 7303 </script>
7303 7304 7305 <div id="DBG_root"> 7306 7307 <!-- DEBUGGER TAB PANE --> 7308 <div style="height:30px;padding:0px 90px 0px 0px;"> 7309 <button class=appbut value="Settings" onclick="javascript:oCOM.POPUP.runRule('tab3.1');" title="Settings"><i class="fa fa fa-sliders-h"></i></button> 7310 <button class=appbut value="Tools" onclick="javascript:oCOM.POPUP.runRule('tab3.2');" title="Tools"><i class="fa fa-toolbox"></i></button> 7311 </div> 7312 7313 <!-- DEBUGGER SETTINGS --> 7314 <div class="popupHost" id="tab3.1" hidden> 7315 <div class="toolbox"> 7316 <div class="appbox" title="Debugger display"> 7317 LISTING<br> 7318 <label> 7319 Scroll Listing <input id="DBG_followPcInput" type="checkbox" checked> 7320 7321 Scroll CPU Trace <input id="DBG_autoScrollTraceInput" type="checkbox" checked> 7322 </label> 7323 <label> 7324 Columns 7325 <input id="DBG_listingColumnsInput" type="text" value="{adr:0,code:6,lin:15,lbl:21,ins:30,opr:35,com:51}" title="Listing columns: address, code bytes, jump lines, label, instruction, operand, comment" spellcheck="false" onchange="DBG_disassembleRAM()"> 7326 </label> 7327 <div class="DBG_listingPresetList" aria-label="Listing column presets"> 7328 <button type="button" class="DBG_listingPreset" onclick="DBG_applyListingColumnPreset('default')" title="Use default debugger listing columns"> 7329 <span>default</span> 7330 <i class="fa fa-table"></i> 7331 </button> 7332 <button type="button" class="DBG_listingPreset" onclick="DBG_applyListingColumnPreset('wide')" title="Use wider debugger listing columns"> 7333 <span>wide</span> 7334 <i class="fa fa-table"></i> 7335 </button> 7336 <button type="button" class="DBG_listingPreset" onclick="DBG_applyListingColumnPreset('compact')" title="Use compact debugger listing columns"> 7337 <span>compact</span> 7338 <i class="fa fa-table"></i> 7339 </button> 7340 </div> 7341 </div> 7342 </div> 7343 7344 <div class="toolbox"> 7345 <div class="appbox" title="Trace configuration"> 7346 CPU TRACE<br> 7347 <label>Buffer 7348 <input id="DBG_traceDepthInput" type="number" value="2048" min="1" step="128" title="Trace depth / ring buffer" onchange="DBG_applyTraceDepthLimit();DBG_renderTraceView();"> 7349 </label> 7350 <label> 7351 Columns 7352 <input id="DBG_traceColumnsInput" type="text" value="{stp:0,pc:5,byt:10,opc:19,rA:32,rX:35,rY:38,rSP:41,rP:44,cyc:47}" title="Trace columns: step, PC, bytes, opcode, A, X, Y, P, SP, cycles" spellcheck="false" onchange="DBG_rebuildTraceLines();DBG_renderTraceView()"> 7353 </label> 7354 <label>Show cycles <input id="DBG_showCyclesInput" type="checkbox" checked onchange="DBG_rebuildTraceLines();DBG_renderTraceView()"></label> 7355 <label>Show PC histogram <input id="DBG_showPcHistogramInput" type="checkbox" onchange="DBG_stub('Show PC histogram')"></label> 7356 </div> 7357 </div> 7358 7359 <div class="toolbox"> 7360 <div class="appbox" title="Run limits"> 7361 RUN LIMITS<br> 7362 <label> 7363 Continuous run speed<br> 7364 <select id="DBG_runSpeedInput" onchange="DBG_statusText('Continuous run speed set to ' + this.options[this.selectedIndex].text + '.')"> 7365 <option value="1">1 IPS</option> 7366 <option value="10">10 IPS</option> 7367 <option value="100">100 IPS</option> 7368 <option value="1000">1000 IPS</option> 7369 <option value="max">Max</option> 7370 </select> 7371 </label> 7372 <label>Stop after N instructions<br> 7373 <input id="DBG_stopAfterInput" type="number" value="" placeholder="â" min="1" step="100" onchange="DBG_stopAfterInstru
7373ctionLimit()"> 7374 </label> 7375 </div> 7376 </div> 7377 7378 <div class="toolbox"> 7379 <div class="appbox" title="Watch defaults"> 7380 WATCH DEFAULTS<br> 7381 <label> 7382 Default watch size<br> 7383 <select id="DBG_defaultWatchSizeInput" onchange="DBG_stub('Default watch size')"> 7384 <option>byte</option> 7385 <option>word</option> 7386 <option>bit</option> 7387 </select> 7388 </label> 7389 <label> 7390 Default watch format<br> 7391 <select id="DBG_defaultWatchFormatInput" onchange="DBG_stub('Default watch format')"> 7392 <option>hex</option> 7393 <option>decimal</option> 7394 <option>binary</option> 7395 <option>ASCII</option> 7396 </select> 7397 </label> 7398 </div> 7399 </div> 7400 </div> 7401 7402 <!-- DEBUGGER TOOLS --> 7403 <div class="popupHost" id="tab3.2" hidden> 7404 7405 <div class=toolbox> 7406 <form name="DBGboard" onsubmit="return false"> 7407 <textarea id="DBG_boardText" cols=40 rows=4 style="float:left" placeholder="Byte-code input" name="boardText" oninput="DBG_syncByteCodeFromBoard()"></textarea><br> 7408 <input title="Send byte code to debugger" id="DBG_ByteCodeImportButton" type="button" value="to debugger" onclick="DBG_importBoardByteCode()"> 7409 <input title="Clear byte code" type="button" value="Clear" onclick="javascript:DBG_clearBoard()"> 7410 <span id="DBG_byteCodeInfo" style="color:#000000;font-family:arial;font-size:11px;white-space:nowrap;">0 bytes</span> 7411 <input type="button" value="X" onclick="javascript:oCOM.POPUP.off('tab3.2')"> 7412 </form> 7413 </div> 7414 7415 <!-- DEBUGGER ACTION BUTTONS --> 7416 <div class="toolbox"> 7417 <button id="DBG_toggleWatchPaneButton" class="appbut" onclick="DBG_togglePane3();" title="Show/hide Memory Watch pane"> 7418 <i class="fa fa-eye-slash"></i> 7419 </button> 7420 <br> 7421 <button class="appbut" onclick="DBG_stub('Show memory map')" title="Show/hide Memory Map"> 7422 <i class="fa fa-th"></i> 7423 </button> 7424 </div> 7425 7426 <!-- SYMBOL FILE TOOLBOX --> 7427 <div class="toolbox"> 7428 <div class="appbox DBG_symbolBox"> 7429 <div class="appbut" style="color:black">add symbol table<input id="DBG_sym_fileInput" type="file" accept=".JSON,.json"></div> 7430 <div class="appbut" style="color:black">load session<input id="DBG_debugSessionFileInput" type="file" accept=".DEB,.deb,.JSON,.json"></div> 7431 <div id="DBG_symbolLabelWrap" class="DBG_symbolLabelWrap"></div> 7432 </div> 7433 </div> 7434 7435 7436 <!-- ALGORITHM TEST BENCH --> 7437 <div class="toolbox DBG_testbenchToolbox"> 7438 <div id="DBG_testbenchBox" class="appbox DBG_testbenchBox" title="Algorithm test bench"> 7439 <div class="DBG_testbenchHeader"> 7440 <span class="DBG_testbenchTitle">TEST BENCH</span> 7441 <span class="DBG_testbenchHeaderButtons"> 7442 <button id="DBG_testExampleButton" class="appbut skinny" type="button" title="Load example JavaScript"><i class="fa fa-lightbulb"></i> example</button> 7443 <button id="DBG_testRunButton" class="appbut skinny" type="button" title="Run script (Ctrl/Cmd+Enter)"><i class="fa fa-play"></i> run</button> 7444 </span> 7445 </div> 7446 7447 <div class="DBG_testbenchHarnessBar"> 7448 test harness <code>TB</code> 7449 · RAM <code>DBG_RAM</code> 7450 · helpers <code>TB.ram.*</code> 7451 <span id="DBG_testbenchStatus">initializing</span> 7452 </div> 7453 7454 <div class="DBG_testbenchGrid"> 7455 <section class="DBG_testbenchSection"> 7456 <div class="DBG_testbenchSectionHeader">
7457 <span>JAVASCRIPT</span> 7458 <span>Ctrl/Cmd + Enter = run</span> 7459 </div> 7460 <textarea id="DBG_testScript" spellcheck="false" wrap="off">// Test-bench JavaScript 7461print("DBG_RAM:", RAM.length, "bytes"); 7462print(TB.ram.dump(0x0000, 32));</textarea> 7463 </section> 7464 7465 <section class="DBG_testbenchSection"> 7466 <div class="DBG_testbenchSectionHeader"> 7467 <span>CONSOLE / REPL</span> 7468 <span class="DBG_testbenchConsoleHeaderRight"> 7469 <span>TB · RAM · ram · print()</span> 7470 <button id="DBG_testClearConsoleButton" class="appbut skinny DBG_testConsoleClear" type="button" title="Clear console"><i class="fa fa-trash-alt"></i> clear</button> 7471 </span> 7472 </div> 7473 <div id="DBG_testConsole" class="DBG_testConsole"></div> 7474 </section> 7475 </div> 7476 7477 <div class="DBG_testbenchRam"> 7478 <div class="DBG_testbenchRamRow"> 7479 <span class="DBG_testbenchRamTitle">RAM I/O</span> 7480 <label>address <input id="DBG_testRamAddress" type="text" value="$3000" spellcheck="false"></label> 7481 <label>length <input id="DBG_testRamLength" type="number" value="16" min="1" max="65536"></label> 7482 <input id="DBG_testRamData" type="text" value="00 01 02 03" spellcheck="false" title="Hex bytes; prefix with = for a trusted JavaScript array/expression"> 7483 <button id="DBG_testRamInjectButton" class="appbut skinny" type="button" title="Inject data into debugger RAM"><i class="fa fa-sign-in-alt"></i> inject</button> 7484 <button id="DBG_testRamReadButton" class="appbut skinny" type="button" title="Read debugger RAM"><i class="fa fa-search"></i> read</button> 7485 </div> 7486 </div> 7487 </div> 7488 </div> 7489 7490 7491 7492 </div> 7493 7494 <div id="DBG_symbolOverlapPopup" class="DBG_symbolOverlapBack" hidden> 7495 <div class="DBG_symbolOverlapBox"> 7496 <strong>Symbol table scope overlap</strong> 7497 <div>The new symbol table overlaps with already loaded scoped addresses.</div> 7498 <pre id="DBG_symbolOverlapText"></pre> 7499 <div class="DBG_symbolOverlapButtons"> 7500 <button class="appbut skinny" type="button" onclick="DBG_resolveSymbolOverlap('cancel')">Cancel</button> 7501 <button class="appbut skinny" type="button" onclick="DBG_resolveSymbolOverlap('override')">Override</button> 7502 <button class="appbut skinny" type="button" onclick="DBG_resolveSymbolOverlap('merge')">Merge</button> 7503 </div> 7504 </div> 7505 </div> 7506 7507 <div id="DBG_paneHost" class="DBG_paneHost"> 7508 <section id="DBG_listingPane" class="DBG_pane"> 7509 <div class="com-pane-title DBG_paneTitle"> 7510 <div class="DBG_paneTitleMain"> 7511 <strong>Listing</strong> 7512 <input id="DBG_debugSessionFileNameInput" 7513 type="text" 7514 value="sample.DEB" 7515 title="Debugger session filename" 7516 spellcheck="false" 7517 style="width:150px;max-width:22vw;border:1px solid rgba(0,0,0,0.25);border-radius:4px;background:#ffffff;color:#000000;padding:2px 5px;font:11px/1.2 courier,monospace;"> 7518 <button id="DBG_exportDebugSessionButton" 7519 class="appbut skinny" 7520 type="button" 7521 title="Download debugger session" 7522 onclick="DBG_exportDebugSession()"><i class="fa fa-cloud-download-alt"></i></button> 7523 <span id="DBG_listingInfo" class="DBG_paneInfo"></span> 7524 </div> 7525 <div id="DBG_listingHeaderActions" class="com-pane-header-default-widget DBG_headerActions"> 7526 7527 <button class="appbut skinny" data-dbg-action="step-back" onclick="DBG_traceStepBackward()" title="Step one instruction backward"><i class="fa fa-step-backward"></i></button> 7528 <button class="appbut skinny" data-dbg-action="step-forward" onclick="DBG_traceStepForward()" title="Step one instruction forward"><i class="fa fa-step-forward"></i></button> 7529 <button id="DBG_runToggle" class="appbut skinny" data-dbg-action="run" onclick="DBG_toggleContinuousRun()" title="Continuous run"><i class="fa fa-running"></i></button> 7530 <button class="appbut skinny" data-dbg-action="goto-pc" onclick="DBG_showStartPcRequester('DBG_listingHeaderActions')" title="Go to PC"><i class="fa fa-crosshairs"></i></button> 7531 <button class="appbut skinny" data-dbg-action="theme" onclick="DBG_toggleTheme()" title="Toggle dark/light mode"><i class="fa fa-adjust"></i></button> 7532
7533 <span class="DBG_startPcRequester" hidden> 7534 <span>PC</span> 7535 <input class="DBG_startPcInput" type="text" value="$0000" maxlength="6" spellcheck="false" onkeydown="DBG_startPcKeyDown(event,'DBG_listingHeaderActions')"> 7536 <button class="appbut skinny" type="button" onclick="DBG_startDebuggerAtPC('DBG_listingHeaderActions')" title="Start debugger at this PC"><i class="fa fa-play"></i></button> 7537 </span> 7538 7539 <!--button class="appbut skinny" data-dbg-action="find" onclick="DBG_stub('Find address / label / opcode')" title="Find address / label / opcode"><i class="fa fa-search"></i></button--> 7540 <!-- TODO: use the find & replace function here (triggered by ctrl/command + F), same pop-up behavior as in tab2 --> 7541 </div> 7542 </div> 7543 <div class="DBG_paneBody dotmatrix_bck"> 7544 <div id="DBG_listingPcOverlay" class="DBG_listingPcOverlay" hidden></div> 7545 <textarea id="debugfield" class="DBG_textPane" readonly spellcheck="false" wrap="off"></textarea> 7546 </div> 7547 </section> 7548 7549 <section id="DBG_tracePane" class="DBG_pane"> 7550 <div class="com-pane-title DBG_paneTitle"> 7551 <div class="DBG_paneTitleMain"> 7552 <strong>CPU Trace</strong> 7553 <span id="DBG_traceRegisterInfo" class="DBG_paneInfo DBG_traceRegisterInfo">A=00 X=00 Y=00 SP=FF SR=<sub>n</sub>0<sub>v</sub>0<sub>-</sub>1<sub>b</sub>0<sub>d</sub>0<sub>i</sub>1<sub>z</sub>0<sub>c</sub>0</span> 7554 </div> 7555 <div id="DBG_traceHeaderActions" class="com-pane-header-default-widget DBG_headerActions"> 7556 <button class="appbut skinny" data-dbg-action="clear-trace" onclick="DBG_clearTrace()" title="Clear trace"><i class="fa fa-trash-alt"></i></button> 7557 <!--button class="appbut skinny" data-dbg-action="autoscroll" onclick="DBG_stub('Auto-scroll trace')" title="Auto-scroll trace on/off"><i class="fa fa-arrow-down"></i></button--> 7558 <!-- TODO: always auto-scroll --> 7559 <!--button class="appbut skinny" data-dbg-action="filter-trace" onclick="DBG_stub('Filter trace')" title="Filter trace"><i class="fa fa-filter"></i></button--> 7560 7561 <button class="appbut skinny" data-dbg-action="break-on-change" onclick="DBG_stub('Break on change')" title="Add break rule"><i class="fa fa-bell"></i></button> 7562 <button class="appbut skinny" data-dbg-action="export-trace" onclick="DBG_stub('Export trace')" title="Export trace"><i class="fa fa-cloud-download-alt"></i></button> 7563 7564 <span class="DBG_startPcRequester" hidden> 7565 <span>PC</span> 7566 <input class="DBG_startPcInput" type="text" value="$0000" maxlength="6" spellcheck="false" onkeydown="DBG_startPcKeyDown(event,'DBG_traceHeaderActions')"> 7567 <button class="appbut skinny" type="button" onclick="DBG_startDebuggerAtPC('DBG_traceHeaderActions')" title="Start debugger at this PC"><i class="fa fa-play"></i></button> 7568 </span> 7569 7570 7571 </div> 7572 </div> 7573 <div class="DBG_paneBody dotmatrix_bck"> 7574 <textarea id="DBG_traceView" class="DBG_textPane" readonly spellcheck="false" wrap="off"></textarea> 7575 </div> 7576 </section> 7577 7578 <section id="DBG_watchPane" class="DBG_pane"> 7579 <div class="com-pane-title DBG_paneTitle"> 7580 <div class="DBG_paneTitleMain"> 7581 <strong>CPU Watch</strong> 7582 <span id="DBG_status" class="DBG_paneInfo"></span> 7583 </div> 7584 <div class="com-pane-header-default-widget DBG_headerActions"> 7585 <button class="appbut skinny" data-dbg-action="add-watch" onclick="DBG_stub('Add watch')" title="Add watch"><i class="fa fa-stethoscope"></i></button> 7586 <!--button class="appbut skinny" data-dbg-action="remove-watch" onclick="DBG_stub('Remove selected watcher')" title="Remove selected watcher"><i class="fa fa-minus-circle"></i></button--> 7587 <!-- TODO: WE WILL PUT A DELETE PICTOGRAM NEXT TO EACH MEMORY WATCH ITEM like <i class="fa fa-times-circle"></i> --> 7588 <!--button class="appbut skinny" data-dbg-action="load-save-watch-and-break" onclick="DBG_stub('Load/Save watch & break ')" title="Load/Save"><i class="fa fa-save"></i></button--> 7589 7590 </div> 7591 </div> 7592 <div class="DBG_paneBody dotmatrix_bck"> 7593 <textarea id="DBG_watchView" class="DBG_textPane" readonly spellcheck="false" wrap="off"></textarea> 7594 </div> 7595 </section> 7596 7597 </div> 7598 7599 <section id="DBG_manualPane" class="DBG_manualPane CRT_TV DOUBL_TV"> 7600 <div style="height:120px"></div> 7601 <div name="codefield" id="DBG_manual" class="DBG_manualField markdown_mon" style="border:1px solid black;width:625px;height:420px"></div> 7602 <div style="height:300px"></div> 7603 </section> 7604 7605 </div> 7606 </div> 7607 7608 </div> 7609 </td> 7610 </tr> 7611</table> 7612 7613</body> 7614<footer><small><a target=_blank href=https://github.com/RetroAppleJS/RetroAppleJS.github.io>GitHub</a> <a target=_blank href="tools/TOOLS_CATALOG.html">Tools</a> <a target=_blank href=https://raw.githubusercontent.com/RetroAppleJS/RetroAppleJS.github.io/main/LICENSE.md>
7614Copyright (c) 2022, Freddy Vandriessche</a> <a href="JavaScript:document.body.scrollTop=0;document.documentElement.scrollTop = 0;">Top</a> </small> <div id="footer" style="display: inline"></div></footer> 7615</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.