forthtranspiler
Version:
Forth to Javascript Transpiler
268 lines • 11.9 kB
JavaScript
/* jefvm.v3.js
--- Javascript easy Forth (jef or jeforth) virtual machine (vm)
--- minimalist Forth Implementation in Javascript
--- MIT license
2015/01/04 update ms for multi-task by samsuanchen@gmail.com
2014/10/08 interpretive for-next begin-again begin-until begin-while-repeat
2014/10/06 add ms as version 3 by samsuanchen@gmail.com
2014/10/06 add ?dup 0= 1-
if...then, if...else...then,
for...next, for aft...then next,
begin...again, begin..until, begin...while...repeat by samsuanchen@gmail.com
2014/09/26 add ip, data area, and return stack as version 2 by samsuanchen@gmail.com
2014/09/25 add data stack and number conversion as version 1 by samsuanchen@gmail.com
2014/09/22 simplifiy to have only code as version 0 by samsuanchen@gmail.com
2014/09/04 New Version For Espruino Hardware by yapcheahshen@gmail.com
2012/02/17 add example and modify kernel to be more friendly for education.
2011/12/23 initial version by yapcheahshen@gmail.com
equiv to http://tutor.ksana.tw/ksanavm lesson1~8
TODO: complete eForth core word set
interface to HTML5 canvas
port C.H.Ting flag demo for kids
this is merely a kick off, everyone is welcome to enhance it. */
function JeForthVM() {
var error = 0 ; // flag to abort source code interpreting
var words =[0]; // collection of all words (jeforth instructions) defined
var nameWord={ }; // use nameWord['swap'] to get the word swap
var ip = 0 ; // instruction pointer for running a high level colon definition word // v2
var cArea =[0]; // code area to hold high level colon definition // v2
var rStack =[ ]; // return stack to return from high level colon definition // v2
var dStack =[ ]; // data stack
this.base=10 ; // number conversion base // v1 // v1
this.uob=''; // user output buffer // 20141209 sam
this.clear=function(){ // clear data stack // v1
dStack=this.dStack=[]; // v1
};
var cr=this.cr=function(msg){
var t=this.tob||'';
this.tob='';
if(!msg) this.lastTob=t; // use cr(msg) to print out more system message
else t+=msg; // use cr() as normal jeforth word
this.uob+=(this.uob?'\n':'')+t; // 20141209 sam
console.log(t); // print t (fixed)
};
var intToString=function(t){ // auto converting integer according to this.base
return typeof(t)==='number' && t%1===0 && this.base!==10 ? t.toString(this.base) : t;
}
var type=this.type=function(msg){ // send msg to terminal output buffer
var a=msg||dStack.pop(), that=this; // pop from data stack if no msg
a= Array.isArray(a) ? a.map(function(t){
return intToString.apply(that,[t])
}).join(' ') : intToString.apply(that,[a]); // v1
this.tob+=a; // append t to terminal output buffer
};
function showErr(msg){var m=msg;
if(this.err) m='<'+this.err+'>'+m+'</'+this.err+'>'; cr.apply(this,[m]);
}
function showTst(msg){var m=msg;
if(this.tst) m='<'+this.tst+'>'+m+'</'+this.tst+'>'; cr.apply(this,[m]);
}
function showOk (msg){var m=msg;
if(this.ok ) m='<'+this.ok +'>'+m+'</'+this.ok +'>'; cr.apply(this,[m]);
}
function showInp(msg){var m=msg;
if(this.inp) m='<'+this.inp+'>'+m+'</'+this.inp+'>'; cr.apply(this,[m]);
}
function panic(msg){ // clear tob, show error msg, and abort
showErr(msg),error=msg,this.compiling=0;
}
function nextChar(){ // get a char from tib
return this.nTib<this.tib.length ? this.tib.charAt(this.nTib++) : ''; // get null if eoi
}
function nextToken(){ // get a token from tib
this.token=''; var c=nextChar.call(this);
while (c===' '||c==='\t'||c==='\r') c=nextChar.call(this); // skip white-space
while (c){
if(c===' '||c==='\t'||c==='\r'||c==='\n')break; // break if white-space
this.token+=c, c=nextChar.call(this); // pick up none-white-space
}
this.c=c;
return this.token;
}
function compile(v) { // compile v to code area // v2
var c= v===undefined ? this.cArea[this.ip++] : v; // v2
this.cArea.push(c); // v2
} // v2
function compileCode(name,v) { // compile named word to code area // v2
var n= name===undefined ? nextToken.call(this) : name; // v2
var w=this.nameWord[n]; // v2
compile.apply(this,[w]); // v2
if(v!==undefined)this.compile.apply(this,[v]); // v2
} // v2
function resumeCall() { // resume inner loop interpreting of compiled code // v3
while(this.ip && !this.waiting){ // v3
var w=this.cArea[this.ip]; // v3
this.ip++, execute.apply(this,[w]); // v3
} // v3
} // v3
function call(addr) { // interpret compiled code at addr of cArea // v2
this.rStack.push(this.ip), this.ip=addr; // v2
resumeCall.call(this); // v3
} // v2
function exit() { // return from colon definition // v2
this.ip=this.rStack.pop();// pop ip from return stack // v2
} // v2
function execute(w){ // execute or compile a word
var immediate=w.immediate, compiling=immediate?0:this.compiling; // v2
if(typeof w==='object'){
if(compiling){ // v2
compile.apply(this,[w]); // v2
} else { // v2
var x=w.xt, t=typeof x;
if(t==="function"){
x.call(this); // execute function x directly
} else if(t==="number"){ // v2
call.apply(this,[x]);
} else {
panic.apply(this,['error execute:\t'+w.name+' w.xt='+x+' ????']);// xt undefined
}
} // v2
} else {
panic.apply(this,['error execute:\t'+w+' ????']); // w is not a word
}
}
function extData(tkn){
}
function extQuotedStr(tkn){
var c=tkn.charAt(0);
if(c==='"'){ // v1
var t=this.tib.substr(0,this.nTib-1); // v1
var L=Math.max(t.lastIndexOf(' '),t.lastIndexOf('\n'),t.lastIndexOf('\t'))+1; // current " // v1
t=this.tib.substr(L+1); // rest tib // v1
var i=t.indexOf(c); // next " // v1
var p=t.charAt(i-1); // prev char // v1
var n=t.charAt(i+1); // next char // v1
if(i>=0 && p!=='\\' && (n===' '||n==='\t'||n==='\r'||n==='\n'||n==='')){ // v1
this.nTib=L+i+2, t=this.tib.substr(L+1,i); // v1
return t; // "abc" return string abc ( alow space ) // v1
} // v1
} // v1
if(c==="'" && c===tkn.charAt(tkn.length-1)){ // v1
return tkn.substr(1,tkn.length-2); // 'abc' return string abc no space // v1
}
return this.extData.apply(this,[tkn]);
}
function extNum(tkn){ var n;
if(tkn.charAt(0)==='$'){
n=parseInt(tkn.substr(1),16);
if('$'+n.toString(16)===tkn) return n; // hexa decimal integer number
}
if(this.base===10){
n=parseFloat(tkn);
if(n.toString()===tkn) return n; // decimal floating number
} else {
n=parseInt(tkn,this.base);
if(n.toString(this.base)===tkn) return n; // any based integer numbe
}
} // v1
function resumeExec(step,resumeDone){ // resume outer source code interpreting loop // v3
this.onDone=resumeDone;
this.waiting=this.steping||step; // v3
if(this.ip){ // v3
resumeCall.call(this); // resume inner compiled code interpreting // v3
} // v3
var tkn,n;
do{ this.token=tkn=nextToken.call(this); // get a token
if (tkn) { // break if no more
var w=nameWord[tkn]; // get word if token is already defined
if (w){
// if(!this.compiling)
// throw "compile-only word:"+tkn;
execute.apply(this,[w]); // execute or compile the word
}else{ n=extNum.apply(this,[tkn]); // v1
if(n===undefined)
n=extQuotedStr.apply(this,[tkn]); // v1
if(n===undefined)
n=this.extData.apply(this,[tkn]); // v1
if(n===undefined){ // v1
panic.apply(this,["? "+this.token+" undefined"]); return; // token undefined
} // v1
if(this.compiling) // v2
compileCode.apply(this,['doLit',n]); // v2
else // v2
dStack.push(n); // v1
}
}
} while(!this.waiting && this.nTib<this.tib.length);
if(!this.waiting && !this.compiling){
var ok=' ok';
if(this.ok)
ok=' <'+this.ok+'>'+ok+'</'+this.ok+'>'; // v3
cr.apply(this,[ok]);
}
if(resumeDone)
resumeDone();
var result=this.uob+this.tob;
this.uob=this.tob='';
return result;
}
var lastCmd='',tasks=[];
function exec(cmd,step){ // source code interpreting
if(!cmd) return // 20141216 sam
if(cmd!==lastCmd)
lastCmd=cmd, this.cmds.push(cmd), this.iCmd=this.cmds.length; // for tracing only
if(this.inp)
this.showInp.apply(this,[cmd]);
else
cr.apply(this,['source input '+this.cmds.length+':\n'+cmd]); // for tracing only
error=0, this.tib=cmd, this.nTib=0, this.tob=this.uob=''; // 20141209 sam
resumeExec.apply(this,[step]), this.error=error; // 20141209 sam // v3
return this.uob+this.tob; // return this.uob // 20141209 sam
}
function addWord(name,xt,immediate){ //
var id=words.length, w={name:name,xt:xt,id:id}; words.push(w), nameWord[name]=w;
if(immediate)w.immediate=1;
cr.apply(this,['defined '+id+': '+name+(typeof xt==='function'? ' as primitive' : '')]);
}
var endCode='end-code';
function code(){ // code <name> d( -- ) // low level definition as a new word
var i,t;
this.newName=nextToken.call(this);
t=this.tib.substr(this.nTib),i=t.indexOf(endCode),this.nTib+=i+endCode.length;
if(i<0){
panic("missing end-code for low level "+this.token+" definition");
return;
}
var txt='('+t.substr(0,i)+')';
var newXt=eval(txt);//eval(txt);
addWord.apply(this,[this.newName,newXt]);
}
function doLit(){ // doLit ( -- n ) // // v2
this.dStack.push(this.cArea[this.ip++]); // v2
}
this.cmds=[];
this.iCmd=-1;
this.showErr=showErr;
this.showTst=showTst;
this.showOk =showOk ;
this.showInp=showInp;
this.panic=panic ; // v2
this.nextToken=nextToken; // v2
this.compileCode=compileCode; // v2
this.execute=execute ; // v2
this.compile=compile ; // v2
this.nameWord=nameWord ; // v2
this.ip=ip ; // v2
this.cArea=cArea ; // v2
this.rStack=rStack ; // v2
this.dStack=dStack ; // v1
this.extData=extData ; // v3
this.rTimes = 0 ; // resume times // v3
this.waiting = 0 ; // flag of waiting mode // v3
this.compiling= 0 ; // flag of compiling mode // v2
this.resumeExec=resumeExec; // v3
this.tob ='' ; // initial terminal output buffer
this.tib ='' ; // initial terminal input buffer (source code)
this.nTib = 0 ; // offset of tib processed
this.exec =exec ;
this.words=words ;
this.code =code ;
this.doLit=doLit ;
this.exit =exit ;
this.addWord=addWord ;
}
if (typeof module!="undefined")
module.exports=JeForthVM;
else
window.vm=new JeForthVM();
// vm is now creaded and ready to use.