PageSourceSearch

https://calormen.com/jsbasic/index.js

js calormen.com collected 2026-10-02 04:37:15 UTC 8,254 bytes, 293 lines download raw bytes

1
2window.addEventListener('DOMContentLoaded', function() {
3
4  var $ = function(s) { return document.querySelector(s); };
5
6  $('#lb_files').selectedIndex = 0;
7
8  var frame = $('#frame');
9
10  var keyboard = frame;
11  if (/iPod|iPad|iPhone/.test(navigator.platform)) {
12    keyboard = document.createElement('input');
13    keyboard.type = 'text';
14    keyboard.style.width = keyboard.style.height = '1px';
15    keyboard.style.border = 'none';
16    keyboard.style.position = 'absolute';
17    keyboard.style.left = keyboard.style.top = '-100px';
18    frame.removeAttribute('tabIndex');
19    frame.addEventListener('click', function() { keyboard.focus(); });
20    frame.parentNode.insertBefore(keyboard, frame);
21  }
22
23  var tty = new TTY($('#screen'), keyboard);
24  (function() {
25    // Install output hook for bell
26    var b = new Bell(/^.*\/|/.exec(window.location)[0]);
27    var orig = tty.writeChar;
28    tty.writeChar = function index_writeChar(c) {
29      if (c.charCodeAt(0) === 7)
30        b.play();
31      else
32        orig(c);
33    };
34  }());
35  var dos = new DOS(tty);
36
37  var lores = new LoRes($('#lores'), 40, 48);
38  var hires = new HiRes($('#hires'), 280, 192);
39  var hires2 = new HiRes($('#hires2'), 280, 192);
40  var display = {
41    state: { graphics: false, full: true, page1: true, lores: true },
42    setState: function(state, value /* ... */) {
43      var args = Array.prototype.slice.call(arguments);
44      while (args.length) {
45        state = args.shift();
46        value = args.shift();
47        this.state[state] = value;
48      }
49
50      if (this.state.graphics) {
51        lores.show(this.state.lores);
52        hires.show(!this.state.lores && this.state.page1);
53        hires2.show(!this.state.lores && !this.state.page1);
54        tty.splitScreen(tty.getScreenSize().height - (this.state.full ? 0 : 4));
55      } else {
56        lores.show(false);
57        hires.show(false);
58        hires2.show(false);
59        tty.splitScreen(0);
60      }
61    },
62    getState: function() {
63      return Object.assign({}, this.state);
64    }
65  };
66  var pdl = [0, 0, 0, 0];
67
68  // Lexical highlighting, if available
69  var editor;
70  if (typeof CodeMirror === 'function') {
71    editor = new CodeMirror($('#editorframe'), {
72      mode: 'basic',
73      tabMode: 'default',
74      content: $('#source').value,
75      height: '100%'
76    });
77  } else {
78    editor = (function() {
79      var textArea = document.createElement('textarea');
80      $('#editorframe').appendChild(textArea);
81      textArea.style.width = '598px';
82      textArea.style.height = '384px';
83      return {
84        getValue: function() {
85          return textArea.value;
86        },
87        setValue: function(value) {
88          textArea.value = value;
89        },
90        setCursor: function(line, column) {
91          // TODO: Implement me!
92        }
93      };
94    }());
95  }
96
97  $('#btn_share').onclick = function(e) {
98    // Load up the hidden text area with the current source
99    $('#source').value = getSource();
100  };
101
102  function getSource() {
103    return editor.getValue();
104  }
105
106  function setSource(source) {
107    editor.setValue(source);
108  }
109
110  var program;
111  $('#btn_run').addEventListener('click', function(e) {
112    e.preventDefault();
113
114    dos.reset();
115    tty.reset();
116    tty.autoScroll = true;
117
118    try {
119      program = basic.compile(getSource());
120    } catch (e) {
121      if (e instanceof basic.ParseError) {
122        editor.setCursor({ line: e.line - 1, ch: e.column - 1 });
123        console.log(e.message +
124                    ' (source line:' + e.line + ', column:' + e.column + ')');
125      }
126      alert(e);
127      return;
128    }
129
130    stopped = false;
131    updateUI();
132    $('#btn_stop').focus();
133
134    program.init({
135      tty: tty,
136      hires: hires,
137      hires2: hires2,
138      lores: lores,
139      display: display,
140      paddle: function(n) { return pdl[n]; }
141    });
142    setTimeout(driver, 0);
143  });
144
145  $('#btn_stop').addEventListener('click', function(e) {
146    e.preventDefault();
147
148    tty.reset(); // cancel any blocking input
149    stopped = true;
150    updateUI();
151  });
152
153  $('#lb_files').addEventListener('change', function() {
154    var sel = $('#lb_files');
155    loadFile('samples/' + sel.value + ".txt", setSource);
156  });
157
158  var current_file_name;
159  $('#btn_save').addEventListener('click', function(e) {
160    e.preventDefault();
161    var a = document.createElement('a');
162    a.download = current_file_name || 'basic_program.txt';
163    a.href = 'data:text/plain;base64,' + window.btoa(getSource());
164    document.body.appendChild(a);
165    a.click();
166    a.parentElement.removeChild(a);
167  });
168  $('#btn_load').addEventListener('click', function(e) {
169    e.preventDefault();
170    var input = document.createElement('input');
171    input.type = 'file';
172    input.addEventListener('change', function(e) {
173      var file = e.target.files[0];
174      current_file_name = file.name;
175      var reader = new FileReader();
176      reader.addEventListener('load', function() {
177        setSource(reader.result);
178      });
179      reader.readAsText(file);
180    });
181    document.body.appendChild(input);
182    input.click();
183    input.parentElement.removeChild(input);
184  });
185
186  // Add a "printer" on demand
187  var printer = null;
188  var paper = $('#paper');
189  $('#show_paper').addEventListener('click', function(e) {
190    e.preventDefault();
191    document.body.classList.add('printout');
192    printer = new Printer(tty, paper);
193  });
194  $('#hide_paper').addEventListener('click', function(e) {
195    e.preventDefault();
196    document.body.classList.remove('printout');
197    printer.close();
198    printer = null;
199  });
200
201  // Mouse-as-Joystick
202  var wrapper = $('#screen-wrapper');
203  wrapper.addEventListener('mousemove', function(e) {
204    var rect = wrapper.getBoundingClientRect(),
205        x = e.clientX - rect.left, y = e.clientY - rect.top;
206    function clamp(n, min, max) { return n < min ? min : n > max ? max : n; }
207    pdl[0] = clamp(x / (rect.width - 1), 0, 1);
208    pdl[1] = clamp(y / (rect.height - 1), 0, 1);
209  });
210
211  var stopped = true;
212  function updateUI() {
213    var btnFocus = (document.activeElement === $("#btn_run") ||
214                    document.activeElement === $("#btn_stop"));
215    $("#btn_stop").disabled = stopped ? "disabled" : "";
216    $("#btn_run").disabled = stopped ? "" : "disabled";
217    $("#lb_files").disabled = stopped ? "" : "disabled";
218
219    if (btnFocus || stopped) {
220      $(stopped ? "#btn_run" : "#btn_stop").focus();
221    } else {
222      tty.focus();
223    }
224  }
225
226
227  // TODO: Expose a RESPONSIVE |---+--------| FAST slider in the UI
228  // Number of steps to execute before yielding execution
229  // (Use a prime to minimize risk of phasing with loops)
230  var NUM_SYNCHRONOUS_STEPS = 37;
231
232  function driver() {
233    var state = basic.STATE_RUNNING;
234    var statements = NUM_SYNCHRONOUS_STEPS;
235
236    while (!stopped && state === basic.STATE_RUNNING && statements > 0) {
237
238      try {
239        state = program.step(driver);
240      } catch (e) {
241        console.log(e);
242        alert(e.message ? e.message : e);
243        stopped = true;
244        updateUI();
245        return;
246      }
247
248      statements -= 1;
249    }
250
251    if (state === basic.STATE_STOPPED || stopped) {
252      stopped = true;
253      updateUI();
254    } else if (state === basic.STATE_BLOCKED) {
255      // Fall out
256    } else { // state === basic.STATE_RUNNING
257      setTimeout(driver, 0); // Keep going
258    }
259  }
260
261  function parseQueryParams() {
262    var params = {};
263    var query = document.location.search.substring(1);
264    query.split(/&/g).forEach(function(pair) {
265      pair = pair.replace(/\+/g, " ").split(/=/).map(decodeURIComponent);
266      params[pair[0]] = pair.length === 1 ? pair[0] : pair[1];
267    });
268    return params;
269  }
270
271  function loadFile(filename, callback) {
272    var req = new XMLHttpRequest();
273    var url = encodeURI(filename); // not encodeURIComponent, so paths can be specified
274    var async = true;
275    req.open("GET", url, async);
276    req.onreadystatechange = function() {
277      if (req.readyState === XMLHttpRequest.DONE) {
278        if (req.status === 200 || req.status === 0) {
279          callback(req.responseText);
280        }
281      }
282    };
283    req.send(null);
284  }
285
286  // load default
287  var params = parseQueryParams();
288  if ('source' in params) {
289    setSource(params.source);
290  } else {
291    loadFile('samples/sample.default.txt', setSource);
292  }
293});

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.