Sunday, November 12, 2023

Ham Radio Assembly Language for Browsers

<!-- HAL -->
<!DOCTYPE html>
<html>
<!-- Hi I am a comment -->
  <style >
body {background-color: gray;}
  td { overflow='clip';}
  textarea
  {background-color: green; color: white; font-size: 25px; padding = 10px}
  canvas {background-color=navy;}
  th { color:black}
  select { color:black;background-color : lime}
  table  {color: blue; text-align: center; border : 2px;}
  button {background-color : red }
  h1 {background-color : green; font-size : 24px; }
  ul {background-color : blue; color : white; border : 4px ; text-align : center}
  caption {background-color : yellow;font-size:20px;}
  p {background-color : yellow; color: black;font-size: 15px;
   text-align: center; vertical-align : top; width " 70"; hidden=true}
</style>
   <h1 align='center'>HAL</h1>
  <form id = 'dummy' >
    <button onclick ='ok()'>OK</button>
<button onclick ='cancel()'>Cancel</button>
<textarea cols=50 rows = 3 font-size = 14px>Javascript libraries use this part of the screen for user input. The dialog docking area.  Operators hide this window as desired.</textarea>


</form>
   <div align = left>
<div>
  <table >
        <caption>Simple Menu System V1.77</caption>
    <tbody >
    <tr>
      <td>
        <textarea id = 'selectedItem' cols=10 rows=1>Top</textarea>
      </td>
      <td>
        <button id ='menuNext' >Next</button>
        <button id ='menuDown' >Down</button>
        <button id ='menuUp' >Up</button>
      </td>
      <td width=200></td>

</tr>
</tbody>
</table>
</div>

</div>
<br>
<table id = 'Display1' align = left>
    <caption>Incoming Connection Reports</caption>
    <tbody class = 'Display' id = 'Displays' >
      <tr>
        <td ><textarea  class = 'Display' cols=15 id = "D1">  Frequ 10</textarea></td>
        <td><textarea  class = 'Display' cols=15 id = "D2">  Power 10</textarea>
        </td>

        
      </tr>
       <tr>
         <td id = 'connectTop'>
           <button  name = 'B11'  > B11</button><button  name = 'B12'  > B12</button><button  name = 'B13'> B13</button> <button name = 'B14'> B14</button>
       </td>
       <td id = 'connectBottom'>
         <button  name = 'B21'  > B21</button><button  name = 'B22'  > B22</button><button  name = 'B23'  > B23</button><button name = 'B24'>B24</button>
        </td>
     
       </tr>
    </tbody>

  </table>

<div >

<table >
  <colgroup>
    <col valign=top/>
    <col valign=top/>
  </colgroup>
  <caption>Ham Assembly Language</caption>
  <tbody >
    <tr>
      <th id= 'contexts'>
      <select name= "textMethods" onchange = "actionText(this)">
        <option  onclick = 'saveText()'>Save</option>
        <option  onclick = 'closeText()'>Close</option>
        <option  onclick = 'openText()'>Open</option>
        <option  onclick = 'createText()'>Create</option>
      </select>
    </th>
    <th>Log</th>
      <th id = 'sources' contenteditable=true>Sources</th>
    </tr>
      <tr id = 'textareas' valign=top>
           <td>
      <ul class = 'visible' id = 'macroList'></ul>
    </td>
      <td>
      <textarea class = 'visible' id = 'logger' cols="35" rows="25" border = 2  >Logger </textarea>
      </td>
      <td id = "textanchor"></td>
    <td>

  </td>
 
  </tr>
  </tbody>
  </table>

  </div>

  <ul  class = 'top' id = 'simpleMenu' hidden>
    <ul class = 'Debugger'>
    <li class = 'debug' onclick = 'DebugStep(INIT)'>Init</li>
    <li class = 'debug' onclick = 'DebugStep(TOKEN)'>Token</li>
    <li class = 'debug' onclick = 'DebugStep(LINE)'>Line</li>
  </ul>
  <ul  class= "usb">
      <li class = "method"  onclick ="USBConnect(current)">Connect</li>
      <li class = "method"  onclick = "openport()">open</li>
      <li class = "method"  onclick ="getports()">Ports</li>
      <li class = "method"  onclick ="getport()">Port</li>
      <li class = "method"  onclick ="readport()">Read</li>
      <li class = "method"  onclick ="writeport()">Write</li>
      <li class = "method"  onclick ="closeport()">Close</li>
      <li class = "method"  onclick ="infoport()">Info</li>
      <li class = "method"  onclick ="freeport()">Free</li>
    </ul>
    <li class = 'Exec' onclick ="ExecSource(current);">RUN</li>
   
    <ul class = 'Assembler'>
      <li class = 'list' onclick ="listLibrary()">Library</li>
      <li class = 'list' onclick ="listOpcodes()">Opcodes</li>
      <li class = 'list' onclick ="listSysOps();">Sysops</li>
      <li class = 'list' onclick ="listSymbols();">Symbols</li>
    </ul>


  </ul>

<div id = 'startcode'>


#log nice start;
  add r0 hope;
#log end;
hope sub r0 r2;
#log done it;
</div>

  <script>
//tokens
// tokens can have various types
// with precedances
//  / scoping rules
const NEWLINE = 32;
const LITERAL = 20;
const VAL = 19; const NUM = 18; const REG = 17;
const MUL = 16; const DIV = 15;
const ADD = 14; const SUB = 13;
const LP = 12; const RP = 11;
const COMMA = 10;
const MACRO = 9;
const OP = 8; const SEMI = 7;
const ALPHA = 6;
const SYSOP = 5
const DISCARD = 4;
const QUOTE = 3;

// return status
const OK = 101; const DONE =102; // normal or source done
const WAIT = 103; const ERROR = -3; // forward reference or syntax error

// symbol classes
const USER = 40; const HAL = 41; const CONST = 42;
const EMBED = 43; const MODULE = 44; const LIB = 45;
// step indicators
const TEST = 56; const INIT = 55; const INDEX = 54;
const LINE = 53; const TOKEN = 52; const OFF = 51;

// symbol,states
const NULL = -1; const UNDEF = -2;
const DEF = 202; const PREDEF = 203;
const LOG = document.getElementById('logger');
</script>
<script id='halinone'>
  
  //bigtable

// const USER,OP,SYSOP,EMBED,MODULE,LIB  / scoping rules
// All symbols in one table, sysops, opodes, user symbols
const Symbol = {str : "",expr : 0,type : 0,value : 0,handle : null}
var symtable = [
  {str : 'R0',expr : RegExp(/R0/,'i'),type : USER, value : 0,handle : 0},
  {str : 'R1',expr : RegExp(/R1/,'i'),type : USER, value : 1,handle : null},
  {str : 'R2',expr : RegExp(/R2/,'i'),type : USER, value : 2,handle : null},
  {str : 'R3',expr : RegExp(/R3/,'i'),type : USER, value : 3,handle : null},
  {str : 'define',  expr : RegExp(/define/), type : SYSOP, value : 0,handle :  function(){return define()}},
  {str : 'assign',  expr : RegExp(/assign/), type : SYSOP,value : 0,handle :  function(){return assign() }},
  {str : 'include', expr : RegExp(/include/),type : SYSOP,value : 0,handle :  function(){return include()}},
  {str : 'end',     expr : RegExp(/end/), type : SYSOP,value : 0,handle :  function(){return OK}},
  {str : 'log',     expr : RegExp(/log/), type : SYSOP,value : 0,handle :  function(){return log()}},
  {str : 'ifdef',   expr : RegExp(/ifdef/), type : SYSOP,value : 0,handle :  function(){return OK }},
  {str : 'else',    expr : RegExp(/else/) , type : SYSOP,value : 0,handle :
    function(){return OK }},
  {str : 'seg',     expr : RegExp(/seg/) , type : SYSOP,value : 0,handle :
    function(){return OK }},
  {str : 'add', expr : RegExp(/add/), type : OP, value : 0,handle : null},
  {str : 'sub', expr : RegExp(/sub/), type : OP, value : 0,handle : null},
  {str : 'mul' ,expr : RegExp(/mul/), type : OP, value : 0,handle : null},
  {str : 'div' ,expr : RegExp(/div/), type : OP, value : 0,handle : null},
  {str : 'shft',expr : RegExp(/shft/),type : OP, value : 0,handle : null},
  {str : 'and', expr : RegExp(/and/), type : OP, value : 0,handle : null},
  {str : 'or'  ,expr : RegExp(/or/),  type : OP, value : 0,handle : null},
  {str : 'xor' ,expr : RegExp(/xor/), type : OP, value : 0,handle : null},
  {str : 'addi',expr : RegExp(/addi/),type : OP, value : 0,handle : null},
  {str : 'subi',expr : RegExp(/subi/),type : OP, value : 0,handle : null},
  {str : 'ori', expr : RegExp(/ori/), type : OP, value : 0,handle : null},
  {str : 'xori',expr : RegExp(/xori/),type : OP, value : 0,handle : null},
  {str : 'clr' ,expr : RegExp(/clr/), type : OP, value : 0,handle : null},
  {str : 'comp',expr : RegExp(/comp/),type : OP, value : 0,handle : null},
  {str : 'jmp' ,expr : RegExp(/jmp/), type : OP, value : 0,handle : null},
  {str : 'call',expr : RegExp(/call/),type : OP, value : 0,handle : null},
  {str : 'bre' ,expr : RegExp(/br/) , type : OP, value : 0,handle : null},
  {str : 'brn' ,expr : RegExp(/brn/), type : OP, value : 0,handle : null},
  {str : 'br'  ,expr : RegExp(/brp/), type : OP, value : 0,handle : null},
  {str : 'ret' ,expr : RegExp(/ret/), type : OP, value : 0,handle : null},
  {str : 'btst',expr : RegExp(/btst/),type : OP, value : 0,handle : null},
  {str : 'bset',expr : RegExp(/bset/),type : OP, value : 0,handle : null},
  {str : 'ld'  ,expr : RegExp(/ld/),  type : OP, value : 0,handle : null},
  {str : 'ldm' ,expr : RegExp(/stm/), type : OP, value : 0,handle : null},
  {str : 'ldw' ,expr : RegExp(/ldw/), type : OP, value : 0,handle : null},
  {str : 'st'  ,expr : RegExp(/st/),  type : OP, value : 0,handle : null},
  {str : 'stm' ,expr : RegExp(/stm/), type : OP, value : 0,handle : null},
  {str : 'stw' ,expr : RegExp(/stw/), type : OP, value : 0,handle : null}
  ]
  var symLength = symtable.length;

 function listSymbols(type) {
   clearLog();
  strLog('\n  Symbols ');
  for(let i=0; i < symtable.length;i++)
    if(symtable[i] = type)
  strLog('\n: ' + i +' '  + symtable[i].str + ' '+symtable[i].expr +' '+symtable[i].value );
  }

function symValue(index) {
   if(index == false) index =0;
  if((index< 0) || (index > symtable.length)) return syntaxError('Bad index');
  return symtable[index].value;
}

function addSym(name,type,value) {
  var index = findSym(name)
  if(index > -1) {
    symtable[index].expr = RegExp(name);
    symtable[inde].type = type
    symtable[index].value = value;
    }
  else{
    var sym = Object(Symbol);
    sym.str =name;
    sym.expr = RegExp(name)
    sym.type = type;
    sym.value = value;
    symtable.push(sym);
  }
  return OK;
}
function findSym(name) {
    len = name.length;
    for(let i =0; i < symtable.length;i++) {
      if(len == symtable[i].str.length)
        if(symtable[i].expr.test(name)) {
        return i;
        }
    }
      return UNDEF;
}
// Drop entries from  user symbols
 function delSym() {
  do {
    if(nextToken() < 0) return syntaxError('No token')
    if(index = findSym(cc.slice) < 0)
      break;
    var type = cc.type;
    if(type == SEMI) break;
    symtable.splice(index,1);
  } while(type != SEMI);
  return endOfLine();
}


  // assembler found a symbol
  // a finite string of alpha numerics
  // that begins with an alpha
function symbol(opContext) {
  var index;
  if(arguments.length == 0) {// symbol defined
    console.log('New symbol');
    index=newSym(cc.slice(),cc.line())
    symtable[index].state = DEF;
    symtable[index].value = cc.line();
    return waitService(i);
  } else  { // this maybe a wait
    if( (index = findSym(cc.slice())) < 0){
      opContext.index =newSym(cc.slice());
      return waitRequest(opContext); // opcode context pushed onto wait stack
    } else {
      if(symtable[index].state == UNDEF) {
        opContext.index = index;
        return waitRequest(opContext); // opcode context pushed onto wait stace
      } else {
        cc.setIndex(symtable[index].value)
        cc.setType(VAL); //resolved
        return OK
        }
      }
    }
}
// text
 function clearLog() {
  LOG.textContent = ' ';
}
// send string to log
function strLog(str){
  LOG.textContent+=  str;
  }
 // send string to log
function strPrependLog(str){
  LOG.textContent =  str + LOG.textContent;
  }
function srcCharByIndex(node,index) {
  var ch = node.textContent.charAt(index);
  return ch
}
 function tokenText(str) {
  var s= String();
  s =  '|'+cc.slice()+'|';
  //s.replace(/\n/,'X')
  if(arguments > 1) s += str;
  strPrependLog(
  '\nTok ' + s + ' ' + ' t: ' + cc.type()+' s: '+cc.start()+ ' e: ' + cc.end()+'\n' );
}

// generic find a name among
// a list of html children
function findItemByName(items,name) {
  for(i=0;i < items.length;i++)
    if(items[i].name == name)
      return items[i];
  return null;
}
function findText(name) {
  items = document.getElementById('textanchor').children;
  return findItemByName(items,name);
}

function findListItem(name) {
  items = document.getElementsByClassName('openmacro');
  return findItemByName(items,name);
}
// when the user clicks on a macro list item
function setListEvent(node) {
  node.addEventListener('click', () => {
  showText(node.name);
  blinkListItems(node.name);
  //console.log('Set up ' +node.name)
  });
}
function newListItem(name) {
  var list = document.createElement('li')
  list.name = name;
  list.className = 'openmacro'
  list.textContent = name
  //console.log('NewItem '+ name);
  setListEvent(list);
  document.getElementById('macroList').appendChild(list);
  showText(name);
  }
function delListItem(name) {
  parent = document.getElementById('macroList');
  node = findName(name,parent.children);
  parents.removeChild(node);
}
////////////// Methods to manage textareas////////////////
function listTexts() {
  var items = document.getElementById('textanchor').children;
  for(let i =0; i < items.length;i++) {
    strLog(': '+ items[i].name);
  }
}
function saveText() {
  strPrependLog('Save\n');
  var nameNode =  document.getElementById('sources');
  var textNode = findTextByName(nameNode.textContent);
  if(textNode.hidden == false) {// The macro in focus
    localStorage.setItem(nameNode.name,textNode.textContent);
    nameSelector();
  }
  return;
}
// th element as editable input
function newText(name) {
  strPrependLog('New\n');
  if(name == null)
    name = document.findElementByID('sources').textContent;
  addText(name);
}
function openText(name) {
  strPrependLog('Open\n');
}
function closeText() { strPrependLog('Close\n');
  parent = document.getElementById('macroList');
  name = document.getElementById('sources').textContent;
  node = nodeFindByName(parent.chldren,name);
  parent.removeChild(node);
  parent = documemt.getElementById('textanchor');
  node = nodeFindByName(parent.chldren,name);
  parent.removeChild(node);
}
function showText(name) {
  items = document.getElementById('textanchor').children;
  for(i=0;i < items.length;i++) {
    if(items[i].name == name)
      items[i].hidden=false;
    else
      items[i].hidden=true;
    document.getElementById('sources').textContent = name;
  }
}
function createText(name) {
  var td = document.getElementById('textanchor');
  var text = document.createElement('textarea');
  text.hidden = false;
  text.cols =30
  text.rows = 15
  if(name == null)
  text.name = document.getElementById('sources').textContent;
  else
  text.name = name;
  text.className = 'invisible';
  td.appendChild(text);
  showText(text.name);
  return text;
  }
  function context() {
    this.hidden = true;
    this.nextSibling().hidden = false;
    //this.parent.onchange();
  }
function actionText(node) {
  var items = node.children;
  for(i=0;i < items.length;i++)
    if(items[i].selected) {
      items[i].onclick(items[i].value);
      return;}
  }

function blinkListItems(name) {
  items = document.getElementById('macroList').children;
  radioButtons(name,items);
}

function source(name) {
  getElementById('sources').textContent=name;
  showTextArea(name);
  console.log('arrived');
}

// Make a name list from local storage
function nameSelector() {
  var len = localStorage.length;
  parent = document.getElementById('sources');
  var  sel = document.createElement('select');
  sel.hidden = true;
  sel.onchange = 'source()'.
  parent.append(sel);
  for(let i = 0;i < len;i++) {
    name = localStorage.key(i);
    var opt = document.createElement('option');
    opt.textContent = name;
    sel.appendChild(opt)
  }
  opt = document.createElement('option');
  opt.textContent='rename';
  sel.appendChild(opt);
}


function listLibrary() {
  clearLog();
  var len = localStorage.length;
  strLog('\nLocal storage ' + len)
  for(let i =0;i < len;i++) {
    var name = localStorage.key(i);
    value = localStorage.getItem(name);
    strLog('\n '+i+': ' +name + ' '+ value);
    }
  }
  // sysops

  //Install a module from local storage
// put up a selector for it, add its own
// text area, and install the name
// on the sysop table
function installModule(name,str) {
    console.log('Install');
    if(arguments.length == 2)
      localStorage.setItem(name,str);
    var index =  findSym(name);  // is it installed?
    if((index > -1) && (index < builtinsLength))
        return syntaxError('Opcode  redefined i '+index+' '+builtinsLength+'|'+name+'|')
    if(index >= 0) {  // already in the opcode table
      node= findListItem(name);
      if(node==null) syntaxError('Library misorder')
      node = findText(name)
      if(node== 0) syntaxError('Set up wrong')
    } else { // new set up
      strPrependLog('\nNew set '+name);
      node = createText(name);
      console.log('Installed  '+node.name + ' '+name);
      addSym(name);
      newListItem(name);
    }
    node.textContent=localStorage.getItem(name);
    return OK;
}

// drop an entry in the sysop table
 function undef() {
   remove(delSys); }
// in user table
function remove() {
   remove(delSym); }

function define() {
  do {
    if(nextToken() < 0) return syntaxError('No token')
      newSysop(cc.slice());
    }while(cc.type() != SEMI)
}
// manipulate the user symbol table
function assign(){
  do {
    if(nextToken() < 0) return syntaxError('No token')
    type = cc.type();
    if(type() == SEMI) break;
    token1 = cc.slice();
    if(nextToken() > 0) {
      type = cc.type()
      if(type ==ARG)
        newSym(token1,args.pop());
      else if(type = NUM)
        newSym(token1,JSON.parse(cc.slice()));
    } else
    newSym(token1,UNDEF);
    strPrependLog('Define: '+ token1 + ' ' + symValue(findSym(token1))+'\n')
  } while(cc.type() != SEMI);
  //nextToken(); //pass the semi
  return endOfLine();
}

function log(ptr){
  var start,end;
  var eol = endOfLine(); // move the newline pointer
  //console.log('log ' + newline.lastIndex + ' '+ SRC.textContent.length);
  start = cc.end();
  if(newline.lastIndex !== 0)
    end = newline.lastIndex-1;
  else
    end = cc.node().textContent.length;
  strPrependLog('\n '+ cc.slice(start,end)+'\n');
  return eol;
}
  function newSysop(name) {
  var i=0;
  var symbol = new Object(opCode);
  i = findSym(name,optable)
  console.log('New sys  '+name + ' '+i)
  if(i < 0) {// New entry
    symbol.str = name;
    symbol.expr = RegExp(name);
    symbol.idx=optable.length+1;
    optable.push(symbol);
    opcodesLength = optable.length;
  }
  return OK;
}
function delsysop(name) {
  i = findSym(name,optable);
  if(i < 0) return OK;
  optable.splice(i,1);
  return OK;
}

function include(){
  var node;
    console.log('Include');
    do {
      r=nextToken()
      var type = cc.type();
      if(type == SEMI)
        break; // move the newline pointer
      if(r < 0) return syntaxError('No token');
      if(type != ALPHA) return syntaxError('Expected Name');
      var name = cc.slice();
      installsysop(name);
      strPrependLog('Add sysop '+ name+'\n')
    } while(type != SEMI);
  return OK;
}
// Bracket quoted HAL must be valid
// Should end with end; pseudo opcode
function macro() {
  var index = nextToken();
  if(index > -1) syntaxError('cannot redefine');
  token = cc.slice();
  nextToken();
  if(cc.type() != LB) syntaxError('No start');
  newOpcode(token,cc.end());
  while(true) {
    nextToken()
    if(cc.type() == RB)
      break;
    endOfLine(); // skip the line
  }
}
</script>


<script>
  
// sneak in a fake source on startup
function sneak() {
  var str = document.getElementById('startcode').textContent
  installModule('source',str);
}
sneak();
</script>
<script>
 // exec

//Define a context of some type  of token
// must include the node holding textContent,
// and a start, end, lastIndex
// scanned in a  textArea of node, possibly
//having a symbol index pointing into the symbol table
// .
const Context = { start : 0, end : 0, type : 0,
                  node : 0,index : 0,line:1,lastIndex:0}
var CS = Array();

const cc = {
  k : -1,
  version : function(){ strPrependLog('V1.0\n')},
  length : function(){ return CS[this.k].end - CS[this.k].start;},
  get : function() {return CS[this.k]},
  clear : function(){CS.length =0; this.k =-1;},
  incLine : function(){ CS[this.k].line++;return CS[this.k].line;},
  line : function(){ return CS[this.k].line;},
  push : function(x){ this.k++; return CS.push(x)},
  pop : function(){ this.k--; return CS.pop()},
  startChar : function(){ return CS[this.k].node.textContent.charAt(CS[this.k].start)},
  endChar   : function(){ return CS[this.k].node.textContent.charAt(CS[this.k].end)},
  indexChar : function(index) { return CS[this.k].node.textContent.charAt(index);},
  setStart : function(start) {CS[this.k].start =start;},
  incStart : function(start) {CS[this.k].start++;},
  setLastIndex : function() {CS[this.k].lastIndex =CS[this.k].end;},
  start : function(start) {return  CS[this.k].start;},
  setIndex : function(index) {CS[this.k].index =index;},
  setEnd : function(end) { CS[this.k].end =end;},
  end : function() {return CS[this.k].end;},
  node : function() {return CS[this.k].node;},
  setType : function(type) { CS[this.k].type =type;},
  type : function() {return CS[this.k].type;},
  text : function() { return CS[this.k.node.textContent];},
  slice : function(start,end){
    var s,e;
    var i = this.k;
    if(arguments.length > 0)  s = start; else s = CS[this.k].start;
    if(arguments.length > 1) e = end; else e =CS[this.k].end;
    return CS[this.k].node.textContent.slice(s,e);
  },
  twoChars : function(){
    var s = CS[this.k].end;  // aftr the newline
    return CS[this.k].node.textContent.slice(s,s+2);
  }
}

// Initialize the assembler
function initHal() {
  tokend.lastIndex =0;
  newline.lastIndex =0;//
  clearLog();
  resetRpn();
  //resetWaits();
  return OK;
}
function endOfLine() {
  newline.lastIndex = tokend.lastIndex;
  newline.exec(cc.node().textContent)
  if(newline.lastIndex !== 0) {
// Do not allow parser to chew past arguments
    tokend.lastIndex = newline.lastIndex;
   return OK;
  }else return DONE;
  }
  // everything needed to process textContent
function newContext(node) {
  var p = new Object(CONTEXT);
  var x =0;
  p.lastIndex = 0;
  p.line = 1;
  p.start = 0;
  p.end = 0;
  p.type = DISCARD;
  p.node = node;
  p.name = node.name;
  cc.push(p);
  // Newline required here
  if(cc.indexChar(0) != '\n') return syntaxError('No starting newline');
  strLog('New Context ' + node.name);
  return endOfLine();
}
function execLines(){
  var result=OK;
  var count = 0;
  while( (result == OK) || (result == WAIT))  {
    count++;
    result = firstColumn();
    if(result==DONE)
      return strPrependLog('\nSource done');
    }
    syntaxAlert('Assembly error ' + result)
  }
  
function ExecSource() {
  node = findText('source')
  if(node == null) return syntaxError('Nosource');
  initHal();
  console.log('CC ' +cc.k)
  newContext(node);
  execLines();
  return OK;
}



function listToken() {
  var result = nextToken();
  if(result == OK)
    tokenText();
  return result;
}
function listTokens() {
  var result=OK;
  var str;
  clearLog()
  strPrependLog('\n...All tokens....');
  cc.setStart(0);
  cc.setEnd(1);
  tokstart.lastIndex =0;
  tokend.lastIndex = 0;
  newline.lastIndex =0;
  //endOfLine(); // to starting line
  do result = listToken();  while(result == OK);
  strPrependLog('End of source');
  return result;
}
function DebugStep(as) {
  var result;
  switch(as) {
    case TEST:
      e = findText('source');
      if(e.hidden == 1) delete e.hidden;
      else e.hidden = 1;
      break;
    case TOKEN :
      if(listToken() == DONE) InitHal();
    break;+tokend.lastIndex
    case LINE :
      if(nextToken() != DONE);
        result = firstColumn();
      //console.log('col end |'+cc.slice()+'|'+tokend.lastIndex);
    break;
    case INDEX:
    break;
    case INIT:
      var node = findText('source');
      if(node == null) console.log('Nosource');
      initHal();
      newContext(node);
    break;
  }
}

// forward reference array
var waits = new Array();
function waitService(index) {
  var n = waits.length;
  console.log('Wait service len ' + n)
  for(let i = 0; i < n;i++)
    if(waits[i].index == index) {
      cc.push(waits[i]);
      console.log('Service '+tokenText())
      waits.splice(i,1);
      argsProcess();
      CS.pop();
      }
    return OK;
  }
function waitRequest(context,index) {
  console.log('Wait request');
  context.index =index;
  waits.push(context);
  return WAIT;
}
function opProcess(token){}
function funProcess(token) {
  var result = 0;
  var textNodes = document.getElementsByClassName('invisible');
  var el = findName(textNodes,optable[token.index].str);
  initHal();
  result = newContext(el);
  while(result != DONE) {
      result = firstColumn();
  }
  return OK;
}

 //
// Global and local  regx
const newline = RegExp(/\n/ ,'g'); // a regx to find end of line
const tokstart = RegExp(/\S|\n/, 'g');
const semi =  RegExp(/;/,'g');
const tokend =  RegExp(/\W/,'g'); // no alpha or numeric
const quote =  RegExp(/\"/,'g'); // open literal
const bracket =  RegExp(/\{\}/,'g'); // open literal
// local operators

const alum =  RegExp(/\w/); // alpha or numeric
const space =  RegExp(/\t| /); // valid separators
const nospace =  RegExp(/\S/); // drk
const operator = RegExp(/[;\*\\\+\-#\(\)]/);
const white = RegExp(/\s/);  //not words

const primes = [
{ str : 'register', expr :  RegExp(/r\d/), type : REG},
{ str : 'alpha',    expr :  RegExp(/[a-zA-Z]/),type : ALPHA},
{ str : 'number',   expr :  RegExp(/[0-9]/),type : NUM},
{ str : 'sysop',    expr :  RegExp(/#/),type : SYSOP},
{ str : 'muliply',  expr :  RegExp(/[\*]/),type : MUL},
{ str : 'divide',   expr:   RegExp(/[\\]/),type : DIV},
{ str : 'add',      expr :  RegExp(/[\+]/),type : ADD},
{ str : 'sub',      expr :  RegExp(/[\-]/),type : SUB},
{ str : 'comma',    expr :  RegExp(/\,/),type : COMMA},
{ str : 'semi',     expr :  RegExp(/;/),type : SEMI},
{ str : 'left',     expr :  RegExp(/\(/),type : LP},
{ str : 'right',    expr :  RegExp(/\)/),type : RP},
{ str : 'newline',  expr :  RegExp(/\n/),type : NEWLINE}
]
const primeLength = primes.length;

function regtest(regx,str){
  
  let x = regx.test(str);
  return x;
}
 // errors
function syntaxAlert(msg) {
    var l;
    alert(msg +' type '+cc.type()+' '+cc.start()+' '+cc.end()+' '+cc.slice()+'\n')
    return OK;
  }
  // errors
function syntaxError(msg) {
    var l;
    strPrependLog(msg +' '+cc.start()+' '+cc.end()+' '+cc.slice()+'\n')
    return ERROR;
  }
function typeGet(index,str){
  let x = primes[index].expr.test(str);
  if(x) return primes[index].type;
  else return ERROR;
}
function updateToken(index) {
  var type,ch;
    ch = cc.indexChar(index);

    for(i=0;i < primeLength;i++)
      if( (type = typeGet(i,ch)) >  -1){
        cc.setType(type);
        return  cc.type();
      }
    return syntaxError('No type |'+ch+'|' );
  }

//args process found a quote character
function literal() {
  cc.setStart(cc.end());
  quote.lastIndex = cc.start();
  quote.exec(cc.text());
  if( (quote.lastIndex ==0) || (quote.lastIndex >= newlne.lastIndex) )
    return syntaxError('missing quote');
  cc.setEnd(quote.lastIndex-1);
  rpn();
  return OK;
  }
  
// The function sets the start and end indices
// for a token of three classes:
//  numeric, alphanumeric and single char operators
// New lin3 is a valid operator, right associated.
// It changes assmbler state based on the next two chars.
  function nextToken() {
    SRC= cc.node();
    if(SRC ===null) syntaxError('No source');
    var ch,index,result;
    tokstart.lastIndex = tokend.lastIndex-1; // get end of last token
    if(regtest(space,SRC.textContent.charAt(tokstart.lastIndex) )){
      tokstart.exec(cc.node().textContent)
      if(tokstart.lastIndex == 0) return DONE;
      } else tokstart.lastIndex++;
    updateToken(tokstart.lastIndex-1);
    tokend.lastIndex = tokstart.lastIndex;  // get start of current token
    if(regtest(alum,SRC.textContent.charAt(tokend.lastIndex-1)) ){
      tokend.exec(SRC.textContent);
      if(tokend.lastIndex == 0) return syntaxError('No line terminator')
      } else tokend.lastIndex++;  //must be a single char
    cc.setStart(tokstart.lastIndex-1);
    cc.setEnd(tokend.lastIndex-1);
    return OK;
    }
//First argument always an opcode
// handle opcode and multiple arguments
// symbol argument, literals, end of line
function argsProcess() {
  opContext = cc.get(); // restart here on forward reference
  if(nextToken() == DONE) return DONE;
  if(cc.type() == NEWLINE) { //blank line
    return OK;
  }
  // opcode required here
  index = findSym(cc.slice());
  if( index< 0) return syntaxError('\nNo op ' + cc.slice())
  rpn(); // process opcode
  let type = cc.type();
  while((type !=  SEMI) && (type != NEWLINE )) {
    if(nextToken() == ERROR) return syntaxError('Or a bug'); // line done
    type = cc.type();
    if(type == QUOTE) { // literals shipped as is.
      literal(); // one literal per line
      break;
    } else if(type == ALPHA)
        if(symbol(opContext ) == WAIT ) {
          return endOfLine(); // fludh to end of line
        }
 // ready to activate rpn)
    if(type > OP )
     rpn();
  }
  return OK;
  //endOfLine();
}
//the currnt token is newline
// which triggers  first column call
 function  firstColumn() {
  var result;
  if(cc.type() != NEWLINE) return syntaxError('Col bug');
  var twochars = cc.twoChars();

  // arrive here once per line and never more
  cc.incLine();  // Every context keeps its own line counter
  if(white.test(twochars)) // if no column one work
    return argsProcess(); // pass it on
  console.log('after args |'+cc.slice()+'|'+tokend.lastIndex);
  nextToken(); // there is column one work
  if(cc.type() == SYSOP) {
    if(nextToken() < 0)return syntaxError('No sysop ');
      let index = findMacro(cc.slice());
      if(index < 0)
        return syntaxError('Undefined sysop ');
      else {
        //console.log('Sysop '+cc.slice())
        return mactable[index].handle();
      }
      }
    // source defines symbol
    else if(cc.type() == ALPHA) {
      // we just set the symbol to the current lineZ
      symbol();
      return argsProcess();
      }
    else
      return syntaxError('Illegal char ');
}
if ("serial" in navigator) {
  // The Web Serial API is supported.
}
var port;

async function getport() {
  console.log('get port')
  port = await navigator.serial.requestPort();
  if(port)
    var info = port.getInfo();
  else console.log('no port')
  console.log('info '+ info)
}
// Get all serial ports the user has previously granted the website access to.
async function getports() {
 ports = await navigator.serial.getPorts();
 console.log('Get ports ' + ports.length)
 console.log(ports[0])
}

async function closeport() {
  await port.close();
}

// Filter on devices with te Arduino Uno USB Vendor/Product IDs.
const filters = [
  { usbVendorId: 0x2341, usbProductId: 0x0043 },
  { usbVendorId: 0x2341, usbProductId: 0x0001 }
];

// Prompt user to select an Arduino Uno device.
async function getPort() {
port = await navigator.serial.requestPort([filters]);
if(port)
 var info = port.getInfo();
}

// Wait for the serial port to open.
async function openport() {
await port.open({ baudRate: 9600 });
}
//After the serial port connection is established, the readable and writable properties //from the SerialPort object return a ReadableStream and a WritableStream. Those will be //used to receive data from and send data to the serial device. Both use Uint8Array //instances for data transfer.

//When new data arrives from the serial device, port.readable.getReader().read() returns //two properties asynchronously: the value and a done boolean. If done is true, the serial //port has been closed or there is no more data coming in. Calling //port.readable.getReader() creates a reader and locks readable to it. While readable is //locked, the serial port can't be closed.

async function readport() {
reader = port.readable.getReader();
  console.log('read '+  reader);
// Listen to data coming from the serial device.
while (true) {

  var { value, done } = await reader.read();
  if (done)
  {
    console.log('Value ' + value.length)
    // Allow the serial port to be closed later.
    reader.releaseLock();
    break;
  }
  // value is a Uint8Array.
}
  console.log('Done '+value.length);
}
function freeport(){
  if(reader)
    reader.releaseLock();
  if(writer)
    writr.relaseLock();
}

async function writeport() {
  var writer = port.writable.getWriter();
  var data = new Uint8Array([104, 101, 108, 108, 111,010,013]); // hello
  await writer.write(data);
  console.log('Written ');
  // Allow the serial port to be closed later.
  writer.releaseLock();
}
function getreader() {
const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
reader = textDecoder.readable.getReader();
}

// menu
var iCount=-1;
    var current;
    var selected =  document.getElementById("selectedItem")
    var menuItems =  document.getElementById('simpleMenu').childNodes
    current= menuItems[0];
    parent=current.parentNode;
    
 function radioButtons(name,items){
  for(i=0;i < items.length;i++) {
    if(items[i].name == name)
      items[i].style.backgroundColor = "orange";
    else
      items[i].style.backgroundColor = "navy";
  }
}
   
    // set up the radio buttons
function setRadioEvent(element) {
  element.addEventListener('click', () => {
  element.style.backgroundColor = 'red'
  let items=element.parentNode.children;
    radioButtons(element.name,items);
    //for(i=0;i<items.length;i++)
      // if(items[i].name != element.name)
        // items[i].style.backgroundColor = "blue"
    });
  }

 
function setConnects(str) {
  var element = document.getElementById(str)
  var items = element.children
  for(i=0; i < items.length;i++) setRadioEvent(items[i]);
  }
   
button = document.getElementById("menuNext")
 button.addEventListener('click', () => {

        do{
        iCount++
        if(menuItems.length == iCount) iCount = 0;
        current =  menuItems[iCount];
          if(current.nodeName=="UL")
            selected.textContent= current.className;
          else if(current.nodeName=="LI")
            selected.textContent= current.textContent;
      } while(current.nodeType !=1)
  
      });
   
   
   
  button = document.getElementById("menuDown")
  button.addEventListener('click', () => {
    if(current.nodeName == "UL") {
        parent=current;
        menuItems = parent.childNodes;
        current =  menuItems[0];
        iCount=-1
        if(current.nodeName=="UL")
              selected.textContent= current.className
        }
        else {
          selected.textContent= current.textContent;
          current.onclick()
        }
   });

 button = document.getElementById("menuUp")
 button.addEventListener('click', () => {
    if(parent.nodeName =="UL") {
      current = parent
      parent = current.parentNode;
      menuItems = parent.childNodes;
      iCount=-1
      selected.textContent= current.className
    } else
    console.log('To level')

   });
      setConnects('connectTop')
      setConnects('connectBottom')
      
      // resolve
objectCode = new Uint8Array(100);
arg = new Array();
oper = new Array();
function resetRpn() {
  arg.length = 0;
  oper.length = 0;}

function operLast()  {
  return oper[oper.length-1]}
function argLast()  {
  return arg[arg.length-1]}
  
function OpProcess(index) {
  var result = 0;
  switch(optable[index].type) {
    case MODULE:
      var str = optable[index].str;
      var node = findTextarea(str);
      newContext(node);
      execLines();
      cc.pop();
    break
    case MACRO:
      var state = nextToken();
      while(state != DONE)
      executeLine();
    break
    case END:
     return DONE;
     break;
}
  if(index < builtinsLength) processOpcode(index)
  else { // must be a module
    var str = optable[index].str;
    var node = findText(str);
    newContext(node);
    execLines();
    CS.pop();
  }

  return OK;
}
function flushLiteral(addr) {
  start = cc.start();
  end = start + cc.length;
  node=cc.node();
  while(start < end) {
    objectCode[addr] = node.textContent[start];
    start++;addr++;
  }
}
function flushNum(n,addr) {
  var list = String('0123456789abcdef')
  var out = String(' ');
  bit = 1;
  while(bit < n) {
    var r = 0;
    var t = 1;
    for(j=0;j < 4;j++) { // for each bit
      if(n & bit)
        r += t;
      t *=2;
      bit *=2;
    }
    out = list[r] +out;
  }
  for(j=0;j < out.length;j++){
    objectCode[addr] = out[j];
    addr++;
  }
    return addr;
}
// numbers or operators
function flushExpression(arg,addr) {
  var len = expr.length;
  var x;
  var y;
  var z;
  console.log('flush');
  while(arg.length>2) {  // there should remain one resolved literal
    x = arg.pop();
    y = arg.pop();
    z = 0;
      switch(lastOper.type) {
        case MUL:
          z = (x*y);
        break;
        case DIV:
          z =(x/y);
          break;
        case ADD:
          z =(x+y);
          break;
        case SUB:
          z =(x-y)
          break;
      }
      arg.push(z);
      arg.splice(0,1)
    }
    flushNum(z);
    return OK;
  }
// make the source line shunted
// evaluate arg expressions
// separat arguments
var addr=0;
function rpn()
{
  strLog('\nrpn ' + cc.slice());
  var type = cc.type();
    switch(type) {
      case LITERAL:
        addr=flushLiteral(addr);
      break;
      case VAL:
        arg.push(cc.index());
        break;
      case NUM:
        x = JSON.parse(cc.slice());
        arg.push(x);
     break;
      case RP:
        while(oper.length > 0 )
          if (type = lastOper.type != LP)
            arg.push(oper.pop().type)
          else
            addr = flushExpression(addr);
          if(oper.length == 0)return syntaxError('no left parenth')
          oper.pop();
        break;
      case SEMI:  // The last argument
      if(oper.length==0) return(syntaxError('Semi-colon mismatch'));
        addr = flushExpression(addr);  // one last arg
        type = oper.pop().type;
        if((type != OP) && (type != MACRO))
          return(sytaxError('Line not closed'));
        // call the OP or MACRO process
        processOpcode(cc.index());
        break;
      break;
      case COMMA:
       addr = flushExpression(addr);
       break
      case ALPHA:
      case SYSOP:
        return syntaxError('Not prime');
      break;
      default:
        while( (oper.length != 0) &&  (operLast.type > type)  )
          arg.push(oper.pop().type);
    }
    return OK;
  }
</script>

  </html>