diff --git a/easycoder/easycoder-min.js b/easycoder/easycoder-min.js index e2ba2a1..c3699a1 100644 --- a/easycoder/easycoder-min.js +++ b/easycoder/easycoder-min.js @@ -1,123 +1,225 @@ var $jscomp$this=this,$jscomp=$jscomp||{};$jscomp.scope={};$jscomp.createTemplateTagFirstArg=function(a){return a.raw=a};$jscomp.createTemplateTagFirstArgWithRaw=function(a,b){a.raw=b;return a};$jscomp.arrayIteratorImpl=function(a){var b=0;return function(){return bc&&(c=Math.max(c+e,0));ca?-b:b}},"es6","es3");$jscomp.polyfill("String.prototype.endsWith",function(a){return a?a:function(a,c){var b=$jscomp.checkStringArgs(this,a,"endsWith");a+="";void 0===c&&(c=b.length);c=Math.max(0,Math.min(c|0,b.length));for(var e=a.length;0=e}},"es6","es3"); +$jscomp.polyfill("String.prototype.startsWith",function(a){return a?a:function(a,c){var b=$jscomp.checkStringArgs(this,a,"startsWith");a+="";var e=b.length,f=a.length;c=Math.max(0,Math.min(c|0,b.length));for(var k=0;k=f}},"es6","es3"); +$jscomp.polyfill("String.prototype.repeat",function(a){return a?a:function(a){var b=$jscomp.checkStringArgs(this,null,"repeat");if(0>a||1342177279>>=1)b+=b;return d}},"es6","es3");$jscomp.stringPadding=function(a,b){a=void 0!==a?String(a):" ";return 0=e}},"es6","es3");$jscomp.polyfill("Object.is",function(a){return a?a:function(a,c){return a===c?0!==a||1/a===1/c:a!==a&&c!==c}},"es6","es3"); -$jscomp.polyfill("Array.prototype.includes",function(a){return a?a:function(a,c){var b=this;b instanceof String&&(b=String(b));var e=b.length;c=c||0;for(0>c&&(c=Math.max(c+e,0));ca?-b:b}},"es6","es3");$jscomp.polyfill("String.prototype.repeat",function(a){return a?a:function(a){var b=$jscomp.checkStringArgs(this,null,"repeat");if(0>a||1342177279>>=1)b+=b;return d}},"es6","es3"); -$jscomp.stringPadding=function(a,b){a=void 0!==a?String(a):" ";return 0d?1:c=this.tokens.length?null:this.tokens[this.index]?this.tokens[this.index].token:null},nextToken:function(){this.next();return this.getToken()},tokenIs:function(a){return this.index>=this.tokens.length?!1:a===this.tokens[this.index].token}, -nextTokenIs:function(a){this.next();return this.tokenIs(a)},skip:function(a){if(this.index>=this.tokens.length)return null;this.next();this.tokenIs(a)&&this.next()},prev:function(){this.index--},getLino:function(){return this.index>=this.tokens.length?0:this.tokens[this.index].lino},getTarget:function(a){a=void 0===a?this.index:a;return this.tokens[a].token},getTargetPc:function(a){a=void 0===a?this.index:a;return this.symbols[this.getTarget(a)].pc},getCommandAt:function(a){return this.program[a]}, -isSymbol:function(a){a=void 0===a?!1:a;if(this.getTarget()in this.symbols)return!0;if(a)throw Error("Unknown symbol: '"+this.getTarget()+"'");return!1},nextIsSymbol:function(a){a=void 0===a?!1:a;this.next();return this.isSymbol(a)},getSymbol:function(a){if(this.isSymbol(void 0===a?!1:a))return this.symbols[this.getToken()]},getSymbolPc:function(a){return this.getSymbol(void 0===a?!1:a).pc},getSymbolRecord:function(){var a=this.program[this.getSymbolPc(!0)];a.used=!0;return a},getSymbols:function(){return this.symbols}, -getProgram:function(){return this.program},getPc:function(){return this.program.length},getValue:function(){return this.value.compile(this)},getNextValue:function(){this.next();return this.getValue()},getCondition:function(){return this.condition.compile(this)},constant:function(a,b){return this.value.constant(a,void 0===b?!1:b)},addCommand:function(a){this.program.push(Object.assign({},{pc:this.program.length},a))},addSymbol:function(a,b){this.symbols[a]={pc:b}},mark:function(){this.savedMark=this.index}, -rewind:function(){this.index=this.savedMark},rewindTo:function(a){this.index=a},completeHandler:function(){var a=this.getLino(),b=this.getPc();this.addCommand({domain:"core",keyword:"goto",lino:a,goto:0});this.compileOne();this.continue?(this.addCommand({domain:"core",keyword:"goto",lino:a,goto:this.getPc()+1}),this.continue=!1):this.addCommand({domain:"core",keyword:"stop",lino:a,next:0});this.getCommandAt(b).goto=this.getPc();return!0},compileVariable:function(a,b,c,d){c=void 0===c?!1:c;d=void 0=== -d?null:d;this.next();var e=this.getLino(),f=this.getTokens()[this.getIndex()];if(this.symbols[f.token])throw Error("Duplicate variable name '"+f.token+"'");var g=this.getPc();this.next();this.addSymbol(f.token,g);a={domain:a,keyword:b,lino:e,isSymbol:!0,used:!1,isVHolder:c,name:f.token,elements:1,index:0,value:[{}],element:[],extra:d};"dom"===d&&(a.element=[]);this.addCommand(a);return a},compileToken:function(){var a=this.getToken();if(a){this.mark();for(var b=$jscomp.makeIterator(Object.keys(this.domain)), -c=b.next();!c.done;c=b.next()){if((c=this.domain[c.value])&&(c=c.getHandler(a))&&c.compile(this))return;this.rewind()}console.log("No handler found");throw Error("I don't understand '"+a+"...'");}},compileOne:function(){var a=this.getToken();if(a){this.warnings=[];var b=this.program.length;if(a.endsWith(":")){a=a.substring(0,a.length-1);if(this.symbols[a])throw Error("Duplicate symbol: '"+a+"'");this.symbols[a]={pc:b};this.index++}else this.compileToken()}},compileFromHere:function(a){for(;this.index< -this.tokens.length;){var b=this.tokens[this.index].token;if("else"===b)return this.program;this.compileOne();if(-1=c.elements&&a.runtimeError(b.lino,"Array index "+d+" is out of range for '"+c.name+"'");c.index=d;c.imported&&(EasyCoder.symbols[c.exporter].getSymbolRecord(c.exportedName).index=d);return b.pc+1}},Load:{compile:function(a){var b=a.getLino();switch(a.nextToken()){case "plugin":var c=a.getNextValue();a.addCommand({domain:"core",keyword:"load",lino:b,name:c});return!0}return!1},run:function(a){var b= -a[a.pc],c=a.getValue(b.name);switch(b.keyword){case "load":if(a.checkPlugin(c))return b.pc+1;EasyCoder_Plugins.getLocalPlugin(a.getPluginsPath,c,a.getPlugin,a.addLocalPlugin,function(){a.run(b.pc+1)});return 0}}},Module:{compile:function(a){a.compileVariable("core","module");return!0},run:function(a){return a[a.pc].pc+1}},Multiply:{compile:function(a){var b=a.getLino();a.next();if(a.isSymbol()){var c=a.getSymbol();var d=a.getCommandAt(c.pc).name}c=a.getValue();a.tokenIs("by")&&a.next();var e=a.getValue(); -if(a.tokenIs("giving")){a.next();if(a.isSymbol())return d=a.getSymbol(),d=a.getCommandAt(d.pc).name,a.next(),a.addCommand({domain:"core",keyword:"multiply",lino:b,value1:c,value2:e,target:d}),!0;a.warning("core multiply: Expected value holder")}else return"undefined"===typeof d&&a.warning("core multiply: No target variable given"),a.addCommand({domain:"core",keyword:"multiply",lino:b,value2:e,target:d}),!0;return!1},run:function(a){var b=a[a.pc],c=b.value1,d=b.value2,e=a.getSymbolRecord(b.target); -if(e.isVHolder){var f=e.value[e.index];c?(a=a.getValue(c)*a.getValue(d),e.value[e.index]={type:"constant",numeric:!0,content:a}):(!f.numeric&&isNaN(f.content)&&a.nonNumericValueError(b,lino),a=parseInt(f.content)*parseInt(a.getValue(d)),e.value[e.index]={type:"constant",numeric:!0,content:a})}else a.variableDoesNotHoldAValueError(b.lino,e.name);return b.pc+1}},Negate:{compile:function(a){var b=a.getLino();a.next();if(a.isSymbol()){var c=a.getToken();a.next();a.addCommand({domain:"core",keyword:"negate", -lino:b,symbol:c});return!0}return!1},run:function(a){var b=a[a.pc],c=a.getSymbolRecord(b.symbol);c.isVHolder?c.value[c.index]={type:"constant",numeric:!0,content:-c.value[c.index].content}:a.variableDoesNotHoldAValueError(b.lino,c.name);return b.pc+1}},On:{compile:function(a){var b=a.getLino(),c=a.nextToken();switch(c){case "close":case "message":case "error":return a.next(),a.addCommand({domain:"core",keyword:"on",lino:b,action:c}),a.completeHandler()}return a.isSymbol()&&(c=a.getSymbolRecord(), -"callback"===c.keyword)?(a.next(),a.addCommand({domain:"core",keyword:"on",lino:b,action:c.name}),a.completeHandler()):!1},run:function(a){var b=a[a.pc],c=b.pc+2;switch(b.action){case "close":a.onClose=c;break;case "message":a.onMessage=c;break;case "error":a.onError=c;break;default:var d=a.getSymbolRecord(b.action);if(d)d.cb=c;else return a.runtimeError(b.lino,"Unknown action '"+b.action+"'"),0}return b.pc+1}},Print:{compile:function(a){var b=a.getLino();a.next();var c=a.getValue();a.addCommand({domain:"core", -keyword:"print",lino:b,value:c});return!0},run:function(a){var b=a[a.pc];a=a.getFormattedValue(b.value);console.log("-> "+a);return b.pc+1}},Put:{compile:function(a){var b=a.getLino(),c=a.getNextValue();if(a.tokenIs("into")){if(a.nextIsSymbol()){var d=a.getToken();a.next();a.addCommand({domain:"core",keyword:"put",lino:b,value:c,target:d});return!0}a.warning("core:put: No such variable: '"+a.getToken()+"'")}return!1},run:function(a){var b=a[a.pc],c=a.getSymbolRecord(b.target);c.isVHolder||a.variableDoesNotHoldAValueError(b.lino, -c.name);a=a.evaluate(b.value);c.value[c.index]={type:a.type,numeric:a.numeric,content:a.content};c.imported&&(c=EasyCoder.scripts[c.exporter].getSymbolRecord(c.exportedName),c.value[c.index]=a);return b.pc+1}},Replace:{compile:function(a){var b=a.getLino(),c=a.getNextValue();if(a.tokenIs("with")){var d=a.getNextValue();if(a.tokenIs("in")&&a.nextIsSymbol()){var e=a.getSymbolRecord();if(e.isVHolder)return a.next(),a.addCommand({domain:"core",keyword:"replace",lino:b,original:c,replacement:d,target:e.name}), -!0;throw Error("'"+e.name+"' does not hold a value");}}return!1},run:function(a){var b=a[a.pc],c=a.getValue(b.original),d=a.getValue(b.replacement),e=a.getSymbolRecord(b.target);a=a.getValue(e.value[e.index]).split(c).join(d);e.value[e.index]={type:"constant",numeric:!1,content:a};return b.pc+1}},Require:{compile:function(a){var b=a.getLino(),c=a.nextToken();if(["css","js"].includes(c)){var d=a.getNextValue();a.addCommand({domain:"core",keyword:"require",lino:b,type:c,url:d});return!0}throw Error("File type must be 'css' or 'js'"); -},run:function(a){var b=a[a.pc];a.require(b.type,a.getValue(b.url),function(){a.run(b.pc+1)});return 0}},Return:{compile:function(a){var b=a.getLino();a.next();a.addCommand({domain:"core",keyword:"return",lino:b});return!0},run:function(a){return a.stack.pop()}},Run:{compile:function(a){var b=a.getLino(),c=a.getNextValue(),d=[];if(a.tokenIs("with"))for(;;)if(a.nextIsSymbol(!0)){var e=a.getSymbolRecord();d.push(e.name);a.next();if(!a.tokenIs("and"))break}if(a.tokenIs("as")&&a.nextIsSymbol(!0)){var f= -a.getSymbolRecord();a.next();if("module"!==f.keyword)throw Error("'"+f.name+"' is not a module");f=f.name}var g=!1;a.tokenIs("nowait")&&(a.next(),g=!0);e=a.getPc();a.addCommand({domain:"core",keyword:"run",lino:b,script:c,imports:d,module:f,nowait:g,then:0});a.tokenIs("then")&&(b=a.getPc(),a.addCommand({domain:"core",keyword:"goto",goto:0}),a.getCommandAt(e).then=a.getPc(),a.next(),a.compileOne(!0),a.addCommand({domain:"core",keyword:"stop"}),a.getCommandAt(b).goto=a.getPc());return!0},run:function(a){a.nextPc= -a.pc+1;a.runScript(a);return 0}},Sanitize:{compile:function(a){var b=a.getLino();if(a.nextIsSymbol()){var c=a.getToken();a.next();a.addCommand({domain:"core",keyword:"sanitize",lino:b,name:c});return!0}return!1},run:function(a){var b=a[a.pc];a=a.getSymbolRecord(b.name);a=a.value[a.index];a.content=JSON.stringify(JSON.parse(a.content));return b.pc+1}},Script:{compile:function(a){var b=a.getProgram();b.script=a.nextToken();a.script=b.script;if(EasyCoder.scripts[b.script])throw delete a.script,Error("Script '"+ -b.script+"' is already running.");EasyCoder.scripts[b.script]=b;a.next();return!0},run:function(a){return a[a.pc].pc+1}},Send:{compile:function(a){var b=a.getLino(),c="";a.nextTokenIs("to")||(c=a.getValue());if(a.tokenIs("to")){if(a.nextTokenIs("parent"))var d="parent";else if(a.isSymbol){d=a.getSymbolRecord();if("module"!==d.keyword)throw Error("'"+d.name+"' is not a module");d=d.name}a.next();a.addCommand({domain:"core",keyword:"send",lino:b,message:c,recipient:d})}return!0},run:function(a){var b= -a[a.pc],c=a.getValue(b.message);"parent"===b.recipient?a.parent&&(a=EasyCoder.scripts[a.parent],a.onMessage&&(a.message=c,a.run(a.onMessage))):(a=a.getSymbolRecord(b.recipient),a.program&&(a=EasyCoder.scripts[a.program],a.message=c,a.run(a.onMessage)));return b.pc+1}},Set:{compile:function(a){var b=a.getLino();if(a.nextIsSymbol()){var c=a.getSymbolRecord();if(!c.isVHolder)return!1;if(a.nextTokenIs("to")){var d=a.nextToken();if(["array","object"].includes(d))return a.next(),a.addCommand({domain:"core", -keyword:"set",lino:b,request:"setVarTo",target:c.name,type:d}),!0;for(d=[];;){a.mark();try{d.push(a.getValue())}catch(f){a.rewind();break}}a.addCommand({domain:"core",keyword:"set",lino:b,request:"setArray",target:c.name,value:d});return!0}a.addCommand({domain:"core",keyword:"set",lino:b,request:"setBoolean",target:c.name});return!0}switch(a.getToken()){case "ready":return a.next(),a.addCommand({domain:"core",keyword:"set",lino:b,request:"setReady"}),!0;case "element":c=a.getNextValue();if(a.tokenIs("of")&& -a.nextIsSymbol()&&(d=a.getSymbolRecord(),"variable"===d.keyword&&a.nextTokenIs("to"))){var e=a.getNextValue();a.addCommand({domain:"core",keyword:"set",lino:b,request:"setElement",target:d.name,index:c,value:e});return!0}break;case "property":c=a.getNextValue();if(a.tokenIs("of")&&a.nextIsSymbol()&&(d=a.getSymbolRecord(),"variable"===d.keyword&&a.nextTokenIs("to")))return e=a.getNextValue(),a.addCommand({domain:"core",keyword:"set",lino:b,request:"setProperty",target:d.name,name:c,value:e}),!0;break; -case "arg":if(c=a.getNextValue(),a.tokenIs("of")&&a.nextIsSymbol()&&(d=a.getSymbolRecord(),a.nextTokenIs("to")))return e=a.getNextValue(),a.addCommand({domain:"core",keyword:"set",lino:b,request:"setArg",target:d.name,name:c,value:e}),!0}a.tokenIs("the")&&a.next();switch(a.getToken()){case "elements":a.next();if(a.tokenIs("of")){a.next();if(!a.isSymbol())throw Error("Unknown variable '"+a.getToken()+"'");c=a.getToken();a.next();if(a.tokenIs("to"))return a.next(),d=a.getValue(),a.addCommand({domain:"core", -keyword:"set",lino:b,request:"setElements",symbol:c,value:d}),!0}break;case "encoding":if(a.nextTokenIs("to"))return c=a.getNextValue(),a.addCommand({domain:"core",keyword:"set",request:"encoding",lino:b,encoding:c}),!0;a.addWarning("Unknown encoding option");break;case "payload":if(a.nextTokenIs("of")&&a.nextIsSymbol()&&(c=a.getSymbolRecord(),"callback"===c.keyword&&a.nextTokenIs("to")))return d=a.getNextValue(),a.addCommand({domain:"core",keyword:"set",request:"setPayload",lino:b,callback:c.name, -payload:d}),!0}return!1},run:function(a){var b=a[a.pc];switch(b.request){case "setBoolean":var c=a.getSymbolRecord(b.target);c.isVHolder?(c.value[c.index]={type:"boolean",content:!0},b.numeric=!1):a.variableDoesNotHoldAValueError(b.lino,c.name);break;case "setReady":if(c=EasyCoder.scripts[a.parent])c.run(c.nextPc),c.nextPc=0,a.unblocked=!0;break;case "setArray":c=a.getSymbolRecord(b.target);c.elements=b.value.length;c.value=b.value;break;case "encoding":a.encoding=a.getValue(b.encoding);break;case "setElements":c= -a.getSymbolRecord(b.symbol);var d=c.elements;c.elements=a.getValue(b.value);c.index=0;if(c.elements>d)for(a=d;a"};case "uuid":return{type:"constant",numeric:!1,content:"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(a){var b=16*Math.random()|0;return("x"==a?b:b&3|8).toString(16)})};case "encode":return{type:"constant",numeric:!1, -content:a.encode(a.getValue(b.value))};case "decode":return{type:"constant",numeric:!1,content:a.decode(a.getValue(b.value))};case "reverse":return{type:"constant",numeric:!1,content:a.getValue(b.value).split("").reverse().join("")};case "lowercase":return{type:"constant",numeric:!1,content:a.getValue(b.value).toLowerCase()};case "hash":a=a.getValue(b.value);b=0;if(0===a.length)return b;for(c=0;c=c:0c;case "not":return!a.getValue(b.value); -case "moduleRunning":return c=a.getSymbolRecord(b.name),EasyCoder.scripts.hasOwnProperty(c.program)?(c=EasyCoder.scripts[c.program],b.sense?c.running:!c.running):!b.sense;case "includes":return c=JSON.parse(a.getValue(b.value1)),b=a.getValue(b.value2),c.includes(b)}return!1}}},EasyCoder={name:"EasyCoder_Main",domain:{core:EasyCoder_Core},elementId:0,runtimeError:function(a,b){this.lino=a;this.reportError({message:"Line "+(0<=a?a:"")+": "+b},this.program);this.program&&(this.program.aborted=!0)},nonNumericValueError:function(a){this.runtimeError(a, +var EasyCoder_Core={name:"EasyCoder_Core",Add:{compile:function(a){var b=a.getLino();a.next();var c=a.getValue();if(a.tokenIs("to"))if(a.next(),a.isSymbol()){var d=a.getSymbol();if(a.getCommandAt(d.pc).isVHolder){if("giving"===a.peek()){d=a.getValue();a.next();var e=a.getToken();a.next();a.addCommand({domain:"core",keyword:"add",lino:b,value1:c,value2:d,target:e})}else d=a.getToken(),a.next(),a.addCommand({domain:"core",keyword:"add",lino:b,value1:c,target:d});return!0}a.warning("core 'add': Expected value holder")}else{d= +a.getValue();if(a.tokenIs("giving"))return a.next(),e=a.getToken(),a.next(),a.addCommand({domain:"core",keyword:"add",lino:b,value1:c,value2:d,target:e}),!0;a.warning("core 'add'': Expected \"giving\"")}return!1},run:function(a){var b=a[a.pc],c=b.value1,d=b.value2,e=a.getSymbolRecord(b.target);if(e.isVHolder){var f=e.value[e.index];d?(a=a.getValue(d)+a.getValue(c),e.value[e.index]={type:"constant",numeric:!0,content:a}):(!f.numeric&&isNaN(f.content)&&a.nonNumericValueError(b.lino),a=parseInt(f.content)+ +parseInt(a.getValue(c)),e.value[e.index]={type:"constant",numeric:!0,content:a})}else a.variableDoesNotHoldAValueError(b.lino,e.name);return b.pc+1}},Alias:{compile:function(a){var b=a.getLino();a.next();if(a.isSymbol()){var c=a.getToken();a.next();if(a.tokenIs("to")&&(a.next(),a.isSymbol())){var d=a.getSymbolRecord();d.used=!0;a.next();a.addCommand({domain:"core",keyword:"alias",lino:b,alias:c,symbol:d.name});return!0}}return!1},run:function(a){var b=a[a.pc],c=a.symbols[b.alias].pc,d=a[c],e=a.getSymbolRecord(b.symbol); +a[c]={pc:d.pc,domain:e.domain,keyword:e.keyword,lino:d.lino,name:d.name,alias:b.symbol};return b.pc+1}},Append:{compile:function(a){var b=a.getLino(),c=a.getNextValue();if(a.tokenIs("to")&&a.nextIsSymbol()){var d=a.getSymbolRecord();if(d.isVHolder)return a.next(),a.addCommand({domain:"core",keyword:"append",lino:b,value:c,select:d.name}),!0}return!1},run:function(a){var b=a[a.pc],c=a.getSymbolRecord(b.select);try{var d=a.getValue(b.value),e=["{","["].includes(d[0])?JSON.parse(d):d,f=c.value[c.index], +k=f.content;k=k?JSON.parse(k):[];k.push(e);f.content=JSON.stringify(k);return b.pc+1}catch(g){return a.runtimeError(b.lino,"JSON: Unable to parse value"),!1}}},Begin:{compile:function(a){a.next();a.compileFromHere(["end"]);return!0},run:function(a){return a[a.pc].pc+1}},Callback:{compile:function(a){a.compileVariable("core","callback");return!0},run:function(a){return a[a.pc].pc+1}},Clear:{compile:function(a){var b=a.getLino();a.next();if(a.isSymbol()){var c=a.getSymbolRecord();if(c.isVHolder)return c= +a.getToken(),a.next(),a.addCommand({domain:"core",keyword:"clear",lino:b,symbol:c}),!0;a.warning("'Variable '"+c.name+"' does not hold a value")}return!1},run:function(a){var b=a[a.pc],c=a.getSymbolRecord(b.symbol);c.isVHolder?(a.domain[c.domain].value.put(c,{type:"boolean",content:!1}),b.numeric=!1):a.variableDoesNotHoldAValueError(b.lino,c.name);return b.pc+1}},Close:{compile:function(a){var b=a.getLino();if(a.nextIsSymbol()){var c=a.getSymbolRecord();if("module"===c.keyword)return a.next(),a.addCommand({domain:"core", +keyword:"close",lino:b,module:c.name}),!0}return!1},run:function(a){var b=a[a.pc];a=a.getSymbolRecord(b.module);a=EasyCoder.scripts[a.program];a.run(a.onClose);return b.pc+1}},Continue:{compile:function(a){a.next();return a.continue=!0}},Debug:{compile:function(a){var b=a.getLino();if(a.nextTokenIs("program")){a.next();if(["item","pc"].includes(a.getToken())){var c=a.getNextValue();a.addCommand({domain:"core",keyword:"debug",lino:b,item:c});return!0}a.addCommand({domain:"core",keyword:"debug",lino:b, +item:"program"});return!0}return a.tokenIs("symbols")?(a.next(),a.addCommand({domain:"core",keyword:"debug",lino:b,item:"symbols"}),!0):a.tokenIs("symbol")?(c=a.nextToken(),a.next(),a.addCommand({domain:"core",keyword:"debug",lino:b,item:"symbol",name:c}),!0):a.tokenIs("step")?(a.next(),a.addCommand({domain:"core",keyword:"debug",lino:b,item:"step"}),!0):!1},run:function(a){var b=a[a.pc],c=b.item;switch(c){case "symbols":console.log("Symbols: "+JSON.stringify(a.symbols,null,2));break;case "symbol":a= +a.getSymbolRecord(b.name);c=a.exporter.script;delete a.exporter;console.log("Symbol: "+JSON.stringify(a,null,2));a.exporter.script=c;break;case "step":a.debugStep=!0;break;case "program":console.log("Debug program: "+JSON.stringify(a,null,2));break;default:0<=c.content&&console.log("Debug item "+c.content+": "+JSON.stringify(a[c.content],null,2))}return b.pc+1}},Decode:{compile:function(a){var b=a.getLino();if(a.nextIsSymbol()){var c=a.getToken();a.next();a.addCommand({domain:"core",keyword:"decode", +lino:b,symbol:c});return!0}return!1},run:function(a){var b=a[a.pc],c=a.getSymbolRecord(b.symbol);if(c.isVHolder){var d=a.getValue(c.value[c.index]);c.value[c.index]={type:"constant",numeric:!1,content:a.decode(d)};b.numeric=!1}else a.variableDoesNotHoldAValueError(b.lino,c.name);return b.pc+1}},Divide:{compile:function(a){var b=a.getLino();if(a.nextIsSymbol()){var c=a.getSymbol();var d=a.getCommandAt(c.pc).name}c=a.getValue();a.tokenIs("by")&&a.next();var e=a.getValue();if(a.tokenIs("giving")){a.next(); +if(a.isSymbol())return d=a.getSymbol(),d=a.getCommandAt(d.pc).name,a.next(),a.addCommand({domain:"core",keyword:"divide",lino:b,value1:c,value2:e,target:d}),!0;a.warning("core 'divide'': Expected value holder")}else return"undefined"===typeof d&&a.warning("core 'divide': No target variable given"),a.addCommand({domain:"core",keyword:"divide",lino:b,value2:e,target:d}),!0;return!1},run:function(a){var b=a[a.pc],c=b.value1,d=b.value2,e=a.getSymbolRecord(b.target);if(e.isVHolder){var f=e.value[e.index]; +c?(a=a.getValue(c)/a.getValue(d),e.value[e.index]={type:"constant",numeric:!0,content:Math.trunc(a)}):(!f.numeric&&isNaN(f.content)&&a.nonNumericValueError(b,lino),a=parseInt(f.content)/parseInt(a.getValue(d)),e.value[e.index]={type:"constant",numeric:!0,content:Math.trunc(a)})}else a.variableDoesNotHoldAValueError(b.lino,e.name);return b.pc+1}},Dummy:{compile:function(a){var b=a.getLino();a.next();a.addCommand({domain:"core",keyword:"dummy",lino:b});return!0},run:function(a){return a[a.pc].pc+1}}, +Encode:{compile:function(a){var b=a.getLino();a.next();if(a.isSymbol()){var c=a.getToken();a.next();a.addCommand({domain:"core",keyword:"encode",lino:b,symbol:c});return!0}return!1},run:function(a){var b=a[a.pc],c=a.getSymbolRecord(b.symbol);if(c.isVHolder){var d=a.getValue(c.value[c.index]);c.value[c.index]={type:"constant",numeric:!1,content:a.encode(d)};b.numeric=!1}else a.variableDoesNotHoldAValueError(b.lino,c.name);return b.pc+1}},End:{compile:function(a){a.next();return!0},run:function(){return 0}}, +Exit:{compile:function(a){a.next();a.addCommand({domain:"core",keyword:"exit"});return!0},run:function(a){var b=EasyCoder.scripts[a.parent],c=a.unblocked;a.exit();!c&&b&&(b.run(b.nextPc),b.nextPc=0);return 0}},Filter:{compile:function(a){var b=a.getLino();if(a.nextIsSymbol()){var c=a.getSymbolRecord();if(a.nextTokenIs("with")){var d=a.nextToken();a.next();a.addCommand({domain:"core",keyword:"filter",lino:b,array:c.name,func:d});return!0}}return!1},run:function(a){var b=a[a.pc],c=a.getSymbolRecord(b.array), +d=c.value[c.index].content,e=a.getSymbolRecord(b.func).pc;try{var f=JSON.parse(d).filter(function(b){c.a=b;a.run(e);return c.v});c.value[c.index].content=JSON.stringify(f)}catch(k){a.runtimeError(b.lino,"Can't parse this array")}return b.pc+1}},Fork:{compile:function(a){var b=a.getLino();a.next();a.nextTokenIs("to")&&a.next();var c=a.getToken();a.next();a.addCommand({domain:"core",keyword:"fork",lino:b,label:c});return!0},run:function(a){var b=a[a.pc];try{a.run(a.symbols[b.label].pc)}catch(c){console.log(c.message), +alert(c.message)}return b.pc+1}},Go:{compile:function(a){var b=a.getLino();a.nextTokenIs("to")&&a.next();var c=a.getToken();a.next();a.addCommand({domain:"core",keyword:"go",lino:b,label:c});return!0},run:function(a){var b=a[a.pc];if(b.label){if(a.verifySymbol(b.label)){var c=a.symbols[b.label];if(c)return c.pc}a.runtimeError(b.lino,"Unknown symbol '"+b.label+"'");return 0}return b.goto}},Gosub:{compile:function(a){var b=a.getLino();a.nextTokenIs("to")&&a.next();var c=a.getToken();a.next();a.addCommand({domain:"core", +keyword:"gosub",lino:b,label:c});return!0},run:function(a){var b=a[a.pc];if(a.verifySymbol(b.label))return a.stack.push(a.pc+1),a.symbols[b.label].pc;a.runtimeError(b.lino,"Unknown symbol '"+b.label+"'");return 0}},If:{compile:function(a){var b=a.getLino();a.next();var c=a.condition.compile(a),d=a.getPc();a.addCommand({domain:"core",keyword:"if",lino:b,condition:c});a.compileOne();if(!a.getToken())return a.getCommandAt(d).else=a.getPc(),!0;a.tokenIs("else")?(c=a.getPc(),a.addCommand({domain:"core", +keyword:"goto",lino:b,goto:0}),a.getCommandAt(d).else=a.getPc(),a.next(),a.compileOne(!0),a.getCommandAt(c).goto=a.getPc()):a.getCommandAt(d).else=a.getPc();return!0},run:function(a){var b=a[a.pc];return a.condition.test(a,b.condition)?b.pc+1:b.else}},Import:{compile:function(a){var b=a.imports,c=EasyCoder.scripts[b.caller],d=a.getProgram();if(b.length){b=$jscomp.makeIterator(b);for(var e=b.next();!e.done;e=b.next()){e=c.getSymbolRecord(e.value);var f=a.nextToken(),k=e.keyword;if(f===k){if(f=a.compileVariable(e.domain, +k,!0),f=d[a.getSymbols()[f.name].pc],f.element=e.element,f.exporter=e.exporter?e.exporter:c.script,f.exportedName=e.name,f.extra=e.extra,f.isVHolder=e.isVHolder,e.program&&(f.program=e.program.script),f.imported=!0,!a.tokenIs("and"))break}else throw Error("Mismatched import variable type for '"+e.name+"'");}if(a.tokenIs("and"))throw Error("Imports do not match exports");}else a.next();return!0},run:function(a){return a[a.pc].pc+1}},Index:{compile:function(a){var b=a.getLino();if(a.nextIsSymbol(!0)){var c= +a.getToken();if(a.nextTokenIs("to")){var d=a.getNextValue();a.addCommand({domain:"core",keyword:"index",lino:b,symbol:c,value:d});return!0}}return!1},run:function(a){var b=a[a.pc],c=a.getSymbolRecord(b.symbol),d=a.getValue(b.value);d>=c.elements&&a.runtimeError(b.lino,"Array index "+d+" is out of range for '"+c.name+"'");c.index=d;c.imported&&(EasyCoder.symbols[c.exporter].getSymbolRecord(c.exportedName).index=d);return b.pc+1}},Module:{compile:function(a){a.compileVariable("core","module");return!0}, +run:function(a){return a[a.pc].pc+1}},Multiply:{compile:function(a){var b=a.getLino();a.next();if(a.isSymbol()){var c=a.getSymbol();var d=a.getCommandAt(c.pc).name}c=a.getValue();a.tokenIs("by")&&a.next();var e=a.getValue();if(a.tokenIs("giving")){a.next();if(a.isSymbol())return d=a.getSymbol(),d=a.getCommandAt(d.pc).name,a.next(),a.addCommand({domain:"core",keyword:"multiply",lino:b,value1:c,value2:e,target:d}),!0;a.warning("core multiply: Expected value holder")}else return"undefined"===typeof d&& +a.warning("core multiply: No target variable given"),a.addCommand({domain:"core",keyword:"multiply",lino:b,value2:e,target:d}),!0;return!1},run:function(a){var b=a[a.pc],c=b.value1,d=b.value2,e=a.getSymbolRecord(b.target);if(e.isVHolder){var f=e.value[e.index];c?(a=a.getValue(c)*a.getValue(d),e.value[e.index]={type:"constant",numeric:!0,content:a}):(!f.numeric&&isNaN(f.content)&&a.nonNumericValueError(b,lino),a=parseInt(f.content)*parseInt(a.getValue(d)),e.value[e.index]={type:"constant",numeric:!0, +content:a})}else a.variableDoesNotHoldAValueError(b.lino,e.name);return b.pc+1}},Negate:{compile:function(a){var b=a.getLino();a.next();if(a.isSymbol()){var c=a.getToken();a.next();a.addCommand({domain:"core",keyword:"negate",lino:b,symbol:c});return!0}return!1},run:function(a){var b=a[a.pc],c=a.getSymbolRecord(b.symbol);c.isVHolder?c.value[c.index]={type:"constant",numeric:!0,content:-c.value[c.index].content}:a.variableDoesNotHoldAValueError(b.lino,c.name);return b.pc+1}},On:{compile:function(a){var b= +a.getLino(),c=a.nextToken();switch(c){case "close":case "message":case "error":return a.next(),a.addCommand({domain:"core",keyword:"on",lino:b,action:c}),a.completeHandler()}return a.isSymbol()&&(c=a.getSymbolRecord(),"callback"===c.keyword)?(a.next(),a.addCommand({domain:"core",keyword:"on",lino:b,action:c.name}),a.completeHandler()):!1},run:function(a){var b=a[a.pc],c=b.pc+2;switch(b.action){case "close":a.onClose=c;break;case "message":a.onMessage=c;break;case "error":a.onError=c;break;default:var d= +a.getSymbolRecord(b.action);if(d)d.cb=c;else return a.runtimeError(b.lino,"Unknown action '"+b.action+"'"),0}return b.pc+1}},Print:{compile:function(a){var b=a.getLino();a.next();var c=a.getValue();a.addCommand({domain:"core",keyword:"print",lino:b,value:c});return!0},run:function(a){var b=a[a.pc];a=a.getFormattedValue(b.value);console.log("-> "+a);return b.pc+1}},Put:{compile:function(a){var b=a.getLino(),c=a.getNextValue();if(a.tokenIs("into")){if(a.nextIsSymbol()){var d=a.getToken();a.next();a.addCommand({domain:"core", +keyword:"put",lino:b,value:c,target:d});return!0}a.warning("core:put: No such variable: '"+a.getToken()+"'")}return!1},run:function(a){var b=a[a.pc],c=a.getSymbolRecord(b.target);c.isVHolder||a.variableDoesNotHoldAValueError(b.lino,c.name);a=a.evaluate(b.value);c.value[c.index]={type:a.type,numeric:a.numeric,content:a.content};c.imported&&(c=EasyCoder.scripts[c.exporter].getSymbolRecord(c.exportedName),c.value[c.index]=a);return b.pc+1}},Replace:{compile:function(a){var b=a.getLino(),c=a.getNextValue(); +if(a.tokenIs("with")){var d=a.getNextValue();if(a.tokenIs("in")&&a.nextIsSymbol()){var e=a.getSymbolRecord();if(e.isVHolder)return a.next(),a.addCommand({domain:"core",keyword:"replace",lino:b,original:c,replacement:d,target:e.name}),!0;throw Error("'"+e.name+"' does not hold a value");}}return!1},run:function(a){var b=a[a.pc],c=a.getValue(b.original),d=a.getValue(b.replacement),e=a.getSymbolRecord(b.target);a=a.getValue(e.value[e.index]).split(c).join(d);e.value[e.index]={type:"constant",numeric:!1, +content:a};return b.pc+1}},Require:{compile:function(a){var b=a.getLino(),c=a.nextToken();if(["css","js"].includes(c)){var d=a.getNextValue();a.addCommand({domain:"core",keyword:"require",lino:b,type:c,url:d});return!0}throw Error("File type must be 'css' or 'js'");},run:function(a){var b=a[a.pc];a.require(b.type,a.getValue(b.url),function(){a.run(b.pc+1)});return 0}},Return:{compile:function(a){var b=a.getLino();a.next();a.addCommand({domain:"core",keyword:"return",lino:b});return!0},run:function(a){return a.stack.pop()}}, +Run:{compile:function(a){var b=a.getLino(),c=a.getNextValue(),d=[];if(a.tokenIs("with"))for(;;)if(a.nextIsSymbol(!0)){var e=a.getSymbolRecord();d.push(e.name);a.next();if(!a.tokenIs("and"))break}if(a.tokenIs("as")&&a.nextIsSymbol(!0)){var f=a.getSymbolRecord();a.next();if("module"!==f.keyword)throw Error("'"+f.name+"' is not a module");f=f.name}var k=!1;a.tokenIs("nowait")&&(a.next(),k=!0);e=a.getPc();a.addCommand({domain:"core",keyword:"run",lino:b,script:c,imports:d,module:f,nowait:k,then:0});a.tokenIs("then")&& +(b=a.getPc(),a.addCommand({domain:"core",keyword:"goto",goto:0}),a.getCommandAt(e).then=a.getPc(),a.next(),a.compileOne(!0),a.addCommand({domain:"core",keyword:"stop"}),a.getCommandAt(b).goto=a.getPc());return!0},run:function(a){a.nextPc=a.pc+1;a.runScript(a);return 0}},Sanitize:{compile:function(a){var b=a.getLino();if(a.nextIsSymbol()){var c=a.getToken();a.next();a.addCommand({domain:"core",keyword:"sanitize",lino:b,name:c});return!0}return!1},run:function(a){var b=a[a.pc];a=a.getSymbolRecord(b.name); +a=a.value[a.index];a.content=JSON.stringify(JSON.parse(a.content));return b.pc+1}},Script:{compile:function(a){var b=a.getProgram();b.script=a.nextToken();a.script=b.script;if(EasyCoder.scripts[b.script])throw delete a.script,Error("Script '"+b.script+"' is already running.");EasyCoder.scripts[b.script]=b;a.next();return!0},run:function(a){return a[a.pc].pc+1}},Send:{compile:function(a){var b=a.getLino(),c="";a.nextTokenIs("to")||(c=a.getValue());if(a.tokenIs("to")){if(a.nextTokenIs("parent"))var d= +"parent";else if(a.isSymbol){d=a.getSymbolRecord();if("module"!==d.keyword)throw Error("'"+d.name+"' is not a module");d=d.name}a.next();a.addCommand({domain:"core",keyword:"send",lino:b,message:c,recipient:d})}return!0},run:function(a){var b=a[a.pc],c=a.getValue(b.message);"parent"===b.recipient?a.parent&&(a=EasyCoder.scripts[a.parent],a.onMessage&&(a.message=c,a.run(a.onMessage))):(a=a.getSymbolRecord(b.recipient),a.program&&(a=EasyCoder.scripts[a.program],a.message=c,a.run(a.onMessage)));return b.pc+ +1}},Set:{compile:function(a){var b=a.getLino();if(a.nextIsSymbol()){var c=a.getSymbolRecord();if(!c.isVHolder)return!1;if(a.nextTokenIs("to")){var d=a.nextToken();if(["array","object"].includes(d))return a.next(),a.addCommand({domain:"core",keyword:"set",lino:b,request:"setVarTo",target:c.name,type:d}),!0;for(d=[];;){a.mark();try{d.push(a.getValue())}catch(f){a.rewind();break}}a.addCommand({domain:"core",keyword:"set",lino:b,request:"setArray",target:c.name,value:d});return!0}a.addCommand({domain:"core", +keyword:"set",lino:b,request:"setBoolean",target:c.name});return!0}switch(a.getToken()){case "ready":return a.next(),a.addCommand({domain:"core",keyword:"set",lino:b,request:"setReady"}),!0;case "element":c=a.getNextValue();if(a.tokenIs("of")&&a.nextIsSymbol()&&(d=a.getSymbolRecord(),"variable"===d.keyword&&a.nextTokenIs("to"))){var e=a.getNextValue();a.addCommand({domain:"core",keyword:"set",lino:b,request:"setElement",target:d.name,index:c,value:e});return!0}break;case "property":c=a.getNextValue(); +if(a.tokenIs("of")&&a.nextIsSymbol()&&(d=a.getSymbolRecord(),"variable"===d.keyword&&a.nextTokenIs("to")))return e=a.getNextValue(),a.addCommand({domain:"core",keyword:"set",lino:b,request:"setProperty",target:d.name,name:c,value:e}),!0;break;case "arg":if(c=a.getNextValue(),a.tokenIs("of")&&a.nextIsSymbol()&&(d=a.getSymbolRecord(),a.nextTokenIs("to")))return e=a.getNextValue(),a.addCommand({domain:"core",keyword:"set",lino:b,request:"setArg",target:d.name,name:c,value:e}),!0}a.tokenIs("the")&&a.next(); +switch(a.getToken()){case "elements":a.next();if(a.tokenIs("of")){a.next();if(!a.isSymbol())throw Error("Unknown variable '"+a.getToken()+"'");c=a.getToken();a.next();if(a.tokenIs("to"))return a.next(),d=a.getValue(),a.addCommand({domain:"core",keyword:"set",lino:b,request:"setElements",symbol:c,value:d}),!0}break;case "encoding":if(a.nextTokenIs("to"))return c=a.getNextValue(),a.addCommand({domain:"core",keyword:"set",request:"encoding",lino:b,encoding:c}),!0;a.addWarning("Unknown encoding option"); +break;case "payload":if(a.nextTokenIs("of")&&a.nextIsSymbol()&&(c=a.getSymbolRecord(),"callback"===c.keyword&&a.nextTokenIs("to")))return d=a.getNextValue(),a.addCommand({domain:"core",keyword:"set",request:"setPayload",lino:b,callback:c.name,payload:d}),!0}return!1},run:function(a){var b=a[a.pc];switch(b.request){case "setBoolean":var c=a.getSymbolRecord(b.target);c.isVHolder?(c.value[c.index]={type:"boolean",content:!0},b.numeric=!1):a.variableDoesNotHoldAValueError(b.lino,c.name);break;case "setReady":if(c= +EasyCoder.scripts[a.parent])c.run(c.nextPc),c.nextPc=0,a.unblocked=!0;break;case "setArray":c=a.getSymbolRecord(b.target);c.elements=b.value.length;c.value=b.value;break;case "encoding":a.encoding=a.getValue(b.encoding);break;case "setElements":c=a.getSymbolRecord(b.symbol);var d=c.elements;c.elements=a.getValue(b.value);c.index=0;if(c.elements>d)for(a=d;a"};case "uuid":return{type:"constant",numeric:!1,content:"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, +function(a){var b=16*Math.random()|0;return("x"==a?b:b&3|8).toString(16)})};case "encode":return{type:"constant",numeric:!1,content:a.encode(a.getValue(b.value))};case "decode":return{type:"constant",numeric:!1,content:a.decode(a.getValue(b.value))};case "reverse":return{type:"constant",numeric:!1,content:a.getValue(b.value).split("").reverse().join("")};case "lowercase":return{type:"constant",numeric:!1,content:a.getValue(b.value).toLowerCase()};case "hash":a=a.getValue(b.value);b=0;if(0===a.length)return b; +for(c=0;c=c:0c;case "not":return!a.getValue(b.value);case "moduleRunning":return c=a.getSymbolRecord(b.name),EasyCoder.scripts.hasOwnProperty(c.program)?(c=EasyCoder.scripts[c.program],b.sense?c.running:!c.running):!b.sense;case "includes":return c=JSON.parse(a.getValue(b.value1)),b=a.getValue(b.value2),c.includes(b)}return!1}}},EasyCoder_Browser={name:"EasyCoder_Browser",A:{compile:function(a){a.compileVariable("browser", +"a",!1,"dom");return!0},run:function(a){return a[a.pc].pc+1}},Alert:{compile:function(a){var b=a.getLino(),c=a.getNextValue();a.addCommand({domain:"browser",keyword:"alert",lino:b,value:c});return!0},run:function(a){var b=a[a.pc];a=a.getFormattedValue(b.value);alert(a);return b.pc+1}},Attach:{compile:function(a){var b=a.getLino();a.next();if(a.isSymbol()){var c=a.getSymbolRecord(),d=c.keyword;switch(d){case "a":case "blockquote":case "button":case "canvas":case "div":case "fieldset":case "file":case "form":case "h1":case "h2":case "h3":case "h4":case "h5":case "h6":case "image":case "img":case "input":case "label":case "legend":case "li":case "option":case "p":case "pre":case "select":case "span":case "table":case "td":case "text":case "textarea":case "tr":case "ul":a.next(); +if(a.tokenIs("to")){if(a.nextTokenIs("body"))if("div"===d)cssId="body",a.next();else throw Error("Body variable must be a div");else cssId=a.getValue();var e=0;a.tokenIs("or")&&(a.next(),e=a.getPc()+1,a.completeHandler());a.addCommand({domain:"browser",keyword:"attach",lino:b,type:d,symbol:c.name,cssId:cssId,onError:e});return!0}break;default:return a.addWarning("type '"+c.keyword+"' not recognized in browser 'attach'"),!1}}a.addWarning("Unrecognised syntax in 'attach'");return!1},run:function(a){var b= +a[a.pc],c=null,d=null;"body"===b.cssId?d=document.body:(c=a.value.evaluate(a,b.cssId).content,d=document.getElementById(c));if(!d)return b.onError?a.run(b.onError):a.runtimeError(b.lino,"No such element: '"+c+"'"),0;var e=a.getSymbolRecord(b.symbol);e.element[e.index]=d;e.value[e.index]={type:"constant",numeric:!1,content:c};"popup"===b.type&&(a.popups.push(d.id),window.onclick=function(b){a.popups.includes(b.target.id)&&(b.target.style.display="none")});return b.pc+1}},Audioclip:{compile:function(a){a.compileVariable("browser", +"audioclip");return!0},run:function(a){return a[a.pc].pc+1}},BLOCKQUOTE:{compile:function(a){a.compileVariable("browser","blockquote",!1,"dom");return!0},run:function(a){return a[a.pc].pc+1}},BUTTON:{compile:function(a){a.compileVariable("browser","button",!1,"dom");return!0},run:function(a){return a[a.pc].pc+1}},CANVAS:{compile:function(a){a.compileVariable("browser","canvas",!1,"dom");return!0},run:function(a){return a[a.pc].pc+1}},Clear:{compile:function(a){var b=a.getLino();if(a.nextTokenIs("body"))return a.next(), +a.addCommand({domain:"browser",keyword:"clear",lino:b,name:null}),!0;if(a.isSymbol()){var c=a.getSymbolRecord();if("dom"===c.extra)return a.next(),a.addCommand({domain:"browser",keyword:"clear",lino:b,name:c.name}),!0}return!1},run:function(a){var b=a[a.pc];if(b.name){a=a.getSymbolRecord(b.name);var c=a.element[a.index];switch(a.keyword){case "input":case "textarea":c.value="";break;default:c.innerHTML=""}}else document.body.innerHTML="";return b.pc+1}},Convert:{compile:function(a){var b=a.getLino(); +if(a.nextTokenIs("whitespace")&&a.nextTokenIs("in")&&a.nextIsSymbol()){var c=a.getSymbolRecord();if(c.isVHolder&&a.nextTokenIs("to")){var d=a.nextToken();a.next();a.addCommand({domain:"browser",keyword:"convert",lino:b,name:c.name,mode:d});return!0}}return!1},run:function(a){var b=a[a.pc];a=a.getSymbolRecord(b.name);var c=a.value[a.index].content;switch(b.mode){case "print":c=c.split("%0a").join("\n").split("%0A").join("\n").split("%0d").join("").split("$0D").join("");break;case "html":c=c.split("%0a").join("
").split("%0A").join("
").split("%0d").join("").split("$0D").join("")}a.value[a.index].content= +c;return b.pc+1}},Create:{compile:function(a){var b=a.getLino();if(a.nextIsSymbol()){var c=a.getSymbolRecord(),d=c.keyword;if("audioclip"===d)return a.nextTokenIs("from")?(d=a.getNextValue(),a.addCommand({domain:"browser",keyword:"create",type:"audioclip",name:c.name,lino:b,value:d}),!0):!1;if("a blockquote button canvas div fieldset file form h1 h2 h3 h4 h5 h6 hr image img input label legend li option p pre progress select span table tr td text textarea ul".split(" ").includes(d))if(a.nextTokenIs("in")){if(a.nextTokenIs("body"))return a.next(), +a.addCommand({domain:"browser",keyword:"create",lino:b,name:c.name,parent:"body"}),!0;if(a.isSymbol())return d=a.getSymbolRecord(),a.next(),a.addCommand({domain:"browser",keyword:"create",lino:b,name:c.name,parent:d.name}),!0}else return(d=a.imports)&&0b&&a.onSwipeRight&&a.run(a.onSwipeRight)))},!1);break;case "pick":d=a.getSymbolRecord(b.symbol);document.pickRecord=d;d.element.forEach(function(c, +d){document.pickIndex=d;c.pickIndex=d;c.mouseDownPc=b.pc+2;"ontouchstart"in c?(c.addEventListener("touchstart",function(b){var c=b.targetTouches[0].target;document.pickX=b.touches[0].clientX;document.pickY=b.touches[0].clientY;c.blur();setTimeout(function(){document.pickRecord.index=c.pickIndex;a.run(c.mouseDownPc)},1)},!1),c.addEventListener("touchmove",function(b){document.dragX=b.touches[0].clientX;document.dragY=b.touches[0].clientY;setTimeout(function(){a.run(document.mouseMovePc)},1);return!1}, +!1),c.addEventListener("touchend",function(){setTimeout(function(){a.run(document.mouseUpPc)},1);return!1})):c.onmousedown=function(b){b=b?b:window.event;b.stopPropagation();if(0
', +b.style.display="none";a.tracing=!0}a.stop=!1}return a.pc+1}},UL:{compile:function(a){a.compileVariable("browser","ul",!1,"dom");return!0},run:function(a){return a[a.pc].pc+1}},Upload:{compile:function(a){var b=a.getLino();if(a.nextIsSymbol()){var c=a.getToken();if(a.nextTokenIs("to")){var d=a.getNextValue();if(a.tokenIs("with")&&a.nextIsSymbol()){var e=a.getToken();if(a.nextTokenIs("and")&&a.nextIsSymbol()){var f=a.getToken();a.next();a.addCommand({domain:"browser",keyword:"upload",lino:b,file:c, +path:d,progress:e,status:f});return!0}}}}return!1},run:function(a){var b=a[a.pc],c=a.getSymbolRecord(b.file),d=a.getValue(b.path),e=a.getSymbolRecord(b.progress),f=a.getSymbolRecord(b.status),k=e.element[e.index],g=f.element[f.index],h=function(a){g&&(g.innerHTML=a)};if(e=c.element[c.index].files[0])c=new FormData,c.append("source",e),c.append("path",d),e=new XMLHttpRequest,e.upload.addEventListener("progress",function(a){a=Math.round(a.loaded/a.total*100);k&&(k.value=a);h(Math.round(a)+"%...")}, +!1),e.addEventListener("load",function(a){a=a.target.responseText;k&&(k.value=0);h("");console.log(a)},!1),e.addEventListener("error",function(){h("Upload failed");console.log("Upload failed")},!1),e.addEventListener("abort",function(){h("Upload aborted");console.log("Upload aborted")},!1),e.onreadystatechange=function(){if(4===this.readyState){var b=a.ajaxCommand,c=this.status;switch(c){case 200:a.run(b.pc+1);break;case 0:break;default:try{a.runtimeError(b.lino,"Error "+c)}catch(p){a.reportError(p, +a)}}}},a.ajaxCommand=b,b=d.startsWith("http")?d:window.location.origin+"//"+d,e.open("POST",b),e.send(c);return 0}},getHandler:function(a){switch(a){case "a":return EasyCoder_Browser.A;case "alert":return EasyCoder_Browser.Alert;case "attach":return EasyCoder_Browser.Attach;case "audioclip":return EasyCoder_Browser.Audioclip;case "blockquote":return EasyCoder_Browser.BLOCKQUOTE;case "button":return EasyCoder_Browser.BUTTON;case "canvas":return EasyCoder_Browser.CANVAS;case "clear":return EasyCoder_Browser.Clear; +case "convert":return EasyCoder_Browser.Convert;case "create":return EasyCoder_Browser.Create;case "disable":return EasyCoder_Browser.Disable;case "div":return EasyCoder_Browser.DIV;case "enable":return EasyCoder_Browser.Enable;case "fieldset":return EasyCoder_Browser.FIELDSET;case "file":return EasyCoder_Browser.FILE;case "focus":return EasyCoder_Browser.Focus;case "form":return EasyCoder_Browser.FORM;case "fullscreen":return EasyCoder_Browser.FullScreen;case "get":return EasyCoder_Browser.Get;case "h1":return EasyCoder_Browser.H1; +case "h2":return EasyCoder_Browser.H2;case "h3":return EasyCoder_Browser.H3;case "h4":return EasyCoder_Browser.H4;case "h5":return EasyCoder_Browser.H5;case "h6":return EasyCoder_Browser.H6;case "highlight":return EasyCoder_Browser.Highlight;case "history":return EasyCoder_Browser.History;case "hr":return EasyCoder_Browser.HR;case "image":return EasyCoder_Browser.IMAGE;case "img":return EasyCoder_Browser.IMG;case "input":return EasyCoder_Browser.INPUT;case "label":return EasyCoder_Browser.LABEL;case "legend":return EasyCoder_Browser.LEGEND; +case "li":return EasyCoder_Browser.LI;case "location":return EasyCoder_Browser.Location;case "mail":return EasyCoder_Browser.Mail;case "on":return EasyCoder_Browser.On;case "option":return EasyCoder_Browser.OPTION;case "p":return EasyCoder_Browser.P;case "play":return EasyCoder_Browser.Play;case "pre":return EasyCoder_Browser.PRE;case "progress":return EasyCoder_Browser.PROGRESS;case "put":return EasyCoder_Browser.Put;case "remove":return EasyCoder_Browser.Remove;case "request":return EasyCoder_Browser.Request; +case "select":return EasyCoder_Browser.SELECT;case "scroll":return EasyCoder_Browser.Scroll;case "section":return EasyCoder_Browser.SECTION;case "set":return EasyCoder_Browser.Set;case "span":return EasyCoder_Browser.SPAN;case "table":return EasyCoder_Browser.TABLE;case "tr":return EasyCoder_Browser.TR;case "td":return EasyCoder_Browser.TD;case "textarea":return EasyCoder_Browser.TEXTAREA;case "trace":return EasyCoder_Browser.Trace;case "ul":return EasyCoder_Browser.UL;case "upload":return EasyCoder_Browser.Upload; +default:return null}},run:function(a){var b=a[a.pc],c=EasyCoder_Browser.getHandler(b.keyword);c||a.runtimeError(b.lino,"Unknown keyword '"+b.keyword+"' in 'browser' package");return c.run(a)},value:{compile:function(a){if(a.isSymbol()){var b=a.getSymbolRecord();if(a.nextTokenIs("exists"))return"dom"===b.extra?(a.next(),{domain:"browser",type:"exists",value:b.name}):null;switch(b.keyword){case "file":case "input":case "select":case "textarea":return{domain:"browser",type:b.keyword,value:b.name}}return null}a.tokenIs("the")&& +a.next();var c=!1;a.tokenIs("offset")&&(c=!0,a.next());b=a.getToken();switch(b){case "mobile":case "portrait":case "landscape":case "br":case "location":case "key":case "hostname":return a.next(),{domain:"browser",type:b};case "content":case "text":if(a.nextTokenIs("of")){if(a.nextIsSymbol())return b=a.getSymbolRecord(),a.next(),{domain:"browser",type:"contentOf",symbol:b.name};throw Error("'"+a.getToken()+"' is not a symbol");}break;case "selected":b=a.nextToken();if(["index","item"].includes(b)&& +["in","of"].includes(a.nextToken())&&a.nextIsSymbol()&&(c=a.getSymbolRecord(),["ul","ol","select"].includes(c.keyword)))return a.next(),{domain:"browser",type:"selected",symbol:c.name,arg:b};break;case "color":return a.next(),a=a.getValue(),{domain:"browser",type:b,value:a};case "attribute":c=a.getNextValue();if(a.tokenIs("of")&&(a.next(),a.isSymbol()&&(b=a.getSymbolRecord(),"dom"===b.extra)))return a.next(),{domain:"browser",type:"attributeOf",attribute:c,symbol:b.name};break;case "style":c=a.getNextValue(); +if(a.tokenIs("of")&&a.nextIsSymbol()){var d=a.getSymbolRecord();if("dom"===d.extra)return a.next(),{domain:"browser",type:b,style:c,target:d.name}}break;case "confirm":return b=a.getNextValue(),{domain:"browser",type:"confirm",text:b};case "prompt":return b=a.getNextValue(),c=null,a.tokenIs("with")&&(c=a.getNextValue()),{domain:"browser",type:"prompt",text:b,pre:c};case "screen":c=a.nextToken();if(["width","height"].includes(c))return a.next(),{domain:"browser",type:b,attribute:c};break;case "top":case "bottom":case "left":case "right":case "width":case "height":return EasyCoder_Browser.value.getCoord(a, +b,c);case "scroll":if(a.nextTokenIs("position"))return a.next(),{domain:"browser",type:"scrollPosition"};break;case "document":if(a.nextTokenIs("path"))return a.next(),{domain:"browser",type:"docPath"};break;case "storage":if(a.nextTokenIs("keys"))return a.next(),{domain:"browser",type:"storageKeys"};break;case "parent":switch(a.nextToken()){case "name":return a.next(),{domain:"browser",type:"varName"};case "index":return a.next(),{domain:"browser",type:"varIndex"}}break;case "history":if(a.nextTokenIs("state"))return a.next(), +{domain:"browser",type:"historyState"};break;case "pick":case "drag":if(a.nextTokenIs("position"))return a.next(),{domain:"browser",type:b+"Position"}}return null},getCoord:function(a,b,c){if(a.nextTokenIs("of")){if(a.nextTokenIs("window"))return a.next(),{domain:"browser",type:b,symbol:"window",offset:c};if(a.isSymbol()){var d=a.getSymbolRecord();if("dom"===d.extra)return a.next(),{domain:"browser",type:b,symbol:d.name,offset:c}}}return null},get:function(a,b){switch(b.type){case "file":case "input":case "select":case "textarea":var c= +a.getSymbolRecord(b.value);var d=c.element[c.index];return{type:"constant",numeric:!1,content:d.value};case "exists":return c=a.getSymbolRecord(b.value),{domain:"browser",type:"boolean",content:"undefined"!==typeof c.element[c.index]};case "mobile":return{domain:"browser",type:"boolean",content:"undefined"!==typeof window.orientation||-1!==navigator.userAgent.indexOf("IEMobile")};case "portrait":return{domain:"browser",type:"boolean",content:document.documentElement.clientWidth=document.documentElement.clientHeight};case "br":return{type:"constant",numeric:!1,content:decodeURIComponent("%3Cbr%20%2F%3E")};case "attributeOf":return c=a.getSymbolRecord(b.symbol),b=a.getValue(b.attribute),d=c.element[c.index],0===b.indexOf("data-")?a.getSimpleValue(d.dataset[b.substr(5)]):a.getSimpleValue(d[b]);case "style":return c=a.getSymbolRecord(b.target),b=a.getValue(b.style),d=c.element[c.index], +a.getSimpleValue(d.style[b]);case "confirm":return{type:"boolean",content:window.confirm(a.getValue(b.text))};case "prompt":return d=a.getValue(b.text),b=a.getValue(b.pre),{type:"constant",numeric:!1,content:b?window.prompt(d,b):window.prompt(d)};case "contentOf":c=a.getSymbolRecord(b.symbol);d=c.element[c.index];switch(c.keyword){case "input":case "textarea":b=d.value;break;case "pre":b=d.innerHTML;break;default:b=d.innerHTML.split("\n").join("")}return{type:"constant",numeric:!1,content:b};case "selected":return c= +a.getSymbolRecord(b.symbol),d=c.element[c.index],a=d.selectedIndex,d=0<=a?d.options[a].text:"",b="index"===b.arg?a:d,{type:"constant",numeric:!1,content:b};case "top":if("window"==b.symbol)return{type:"constant",numeric:!0,content:window.screenY};c=a.getSymbolRecord(b.symbol);a=c.element[c.index];b=Math.round(b.offset?a.offsetTop:a.getBoundingClientRect().top);return{type:"constant",numeric:!0,content:b};case "bottom":if("window"==b.symbol)return{type:"constant",numeric:!0,content:window.screenY+ +window.innerHeight};c=a.getSymbolRecord(b.symbol);b=Math.round(c.element[c.index].getBoundingClientRect().bottom);return{type:"constant",numeric:!0,content:b};case "left":if("window"==b.symbol)return{type:"constant",numeric:!0,content:window.screenLeft};c=a.getSymbolRecord(b.symbol);a=c.element[c.index];b=Math.round(b.offset?a.offsetLeft:a.getBoundingClientRect().left);return{type:"constant",numeric:!0,content:b};case "right":if("window"==b.symbol)return{type:"constant",numeric:!0,content:window.screenX+ +window.innerWidth};c=a.getSymbolRecord(b.symbol);b=Math.round(c.element[c.index].getBoundingClientRect().right);return{type:"constant",numeric:!0,content:b};case "width":if("window"==b.symbol)return{type:"constant",numeric:!0,content:window.innerWidth};c=a.getSymbolRecord(b.symbol);b=Math.round(c.element[c.index].getBoundingClientRect().width);return{type:"constant",numeric:!0,content:b};case "height":if("window"==b.symbol)return{type:"constant",numeric:!0,content:window.innerHeight};c=a.getSymbolRecord(b.symbol); +b=Math.round(c.element[c.index].getBoundingClientRect().height);return{type:"constant",numeric:!0,content:b};case "color":return{type:"constant",numeric:!1,content:"#"+a.value.evaluate(a,b.value).content.toString(16).padStart(6,"0")};case "docPath":return{type:"constant",numeric:!1,content:a.docPath};case "storageKeys":return{type:"constant",numeric:!1,content:JSON.stringify(Object.keys(localStorage))};case "location":return{type:"constant",numeric:!1,content:window.location.href};case "historyState":return{type:"constant", +numeric:!1,content:window.history.state};case "scrollPosition":return{type:"constant",numeric:!0,content:scrollPosition};case "varName":return{type:"constant",numeric:!1,content:a.varName};case "varIndex":return{type:"constant",numeric:!0,content:a.varIndex};case "key":return{type:"constant",numeric:!1,content:a.key};case "hostname":return{type:"constant",numeric:!1,content:location.hostname};case "screen":return{type:"constant",numeric:!0,content:screen[b.attribute]};case "pickPosition":return{type:"constant", +numeric:!1,content:JSON.stringify({x:document.pickX,y:document.pickY})};case "dragPosition":return{type:"constant",numeric:!1,content:JSON.stringify({x:document.dragX,y:document.dragY})}}}},condition:{compile:function(a){if(a.tokenIs("confirm"))return{domain:"browser",type:"confirm",value:a.getNextValue()};if(a.tokenIs("element")&&a.nextIsSymbol()){var b=a.getSymbolRecord();if("dom"===b.extra){var c=a.nextToken();if("has"===c){if(a.nextTokenIs("the")&&a.next(),a.tokenIs("focus"))return a.next(),{domain:"browser", +type:"focus",element:b.name}}else if("contains"===c)return a=a.getNextValue(),{domain:"browser",type:"contains",element:b.name,position:a}}}return null},test:function(a,b){switch(b.type){case "confirm":return confirm(a.getValue(b.value));case "focus":var c=a.getSymbolRecord(b.element);return c.element[c.index]===document.activeElement;case "contains":c=a.getSymbolRecord(b.element);var d=c.element[c.index].getBoundingClientRect();c=Math.round(d.left);var e=Math.round(d.right),f=Math.round(d.top);d= +Math.round(d.bottom);b=JSON.parse(a.getValue(b.position));a=b.x;b=b.y;return a>=c&&a<=e&&b>=f&&b<=d?!0:!1}}},setStyles:function(a,b){a=document.getElementById(a);b=b.split(";");b=$jscomp.makeIterator(b);for(var c=b.next();!c.done;c=b.next())c=c.value.split(":"),a.setAttribute(c[0],c[1])}},scrollPosition=0;window.addEventListener("scroll",function(){scrollPosition=this.scrollY}); +window.onpopstate=function(a){window.EasyCoder.timestamp=Date.now();(a=JSON.parse(a.state))&&a.script&&((a=window.EasyCoder.scripts[a.script])?a.onBrowserBack&&a.run(a.onBrowserBack):console.log("No script property in window state object"))}; +var EasyCoder_Json={name:"EasyCoder_JSON",Json:{compile:function(a){var b=a.getLino(),c=a.nextToken();switch(c){case "set":a.next();if(a.isSymbol())if(c=a.getSymbolRecord(),"variable"===c.keyword){if(a.nextTokenIs("to")){var d=a.nextToken();if('["array","object"]'.includes(d))return a.next(),a.addCommand({domain:"json",keyword:"json",lino:b,request:"setVariable",target:c.name,type:d}),!0}}else if("select"===c.keyword&&a.nextTokenIs("from")&&(a.next(),a.isSymbol()&&(d=a.getSymbolRecord(),"variable"=== +d.keyword))){var e=null;a.nextTokenIs("as")&&(e=a.getNextValue());a.addCommand({domain:"json",keyword:"json",lino:b,request:"setList",target:c.name,source:d.name,display:e});return!0}break;case "sort":case "shuffle":case "format":if(a.nextIsSymbol()&&(d=a.getSymbolRecord(),"variable"===d.keyword))return a.next(),a.addCommand({domain:"json",keyword:"json",lino:b,request:c,target:d.name}),!0;break;case "parse":if(a.nextTokenIs("url")&&(d=a.getNextValue(),a.tokenIs("as")&&a.nextIsSymbol()&&(e=a.getSymbolRecord(), +"variable"===e.keyword)))return a.next(),a.addCommand({domain:"json",keyword:"json",lino:b,request:c,source:d,target:e.name}),!0;break;case "delete":d=a.nextToken();if(["property","element"].includes(d)&&(e=a.getNextValue(),["from","of"].includes(a.getToken())&&a.nextIsSymbol())){var f=a.getSymbolRecord();if("variable"===f.keyword)return a.next(),a.addCommand({domain:"json",keyword:"json",lino:b,request:c,what:d,value:e,target:f.name}),!0}break;case "rename":d=a.getNextValue();if(a.tokenIs("to")&& +(e=a.getNextValue(),a.tokenIs("in")&&a.nextIsSymbol()&&(f=a.getSymbolRecord(),"variable"===f.keyword)))return a.next(),a.addCommand({domain:"json",keyword:"json",lino:b,request:c,oldName:d,newName:e,target:f.name}),!0;break;case "add":d=a.getNextValue();if(a.tokenIs("to")&&a.nextIsSymbol()&&(e=a.getSymbolRecord(),"variable"===e.keyword))return a.next(),a.addCommand({domain:"json",keyword:"json",lino:b,request:c,item:d,target:e.name}),!0;break;case "split":d=a.getNextValue();e="\n";a.tokenIs("on")&& +(e=a.getNextValue());if(["giving","into"].includes(a.getToken())&&a.nextIsSymbol()&&(f=a.getSymbolRecord(),"variable"===f.keyword))return a.next(),a.addCommand({domain:"json",keyword:"json",lino:b,request:c,item:d,on:e,target:f.name}),!0;break;case "replace":if(a.nextTokenIs("element")&&(d=a.getNextValue(),a.tokenIs("of")&&a.nextIsSymbol()&&(e=a.getSymbolRecord(),"variable"===e.keyword&&["by","with"].includes(a.nextToken()))))return f=a.getNextValue(),a.addCommand({domain:"json",keyword:"json",lino:b, +request:c,target:e.name,index:d,value:f}),!0}a.addWarning("Unrecognised json command syntax");return!1},run:function(a){var b=a[a.pc];switch(b.request){case "setVariable":var c=a.getSymbolRecord(b.target);var d="array"===b.type?"[]":"{}";c.value[c.index]={type:"constant",numeric:!1,content:d};break;case "setList":c=a.getSymbolRecord(b.source);c=a.getValue(c.value[c.index]);var e="";try{e=JSON.parse(c)}catch(l){return a.runtimeError(b.lino,"Can't parse JSON"),0}c=a.getSymbolRecord(b.target);var f= +c.element[c.index];f.options.length=0;var k=a.getValue(b.display);e.forEach(function(b){var c=k?a.decode(b[k]):null,d=document.createElement("option");d.innerHTML=c?c:b;b=c?JSON.stringify(b):b;d.value=b;f.appendChild(d)});f.selectedIndex=-1;break;case "sort":c=a.getSymbolRecord(b.target);d=(e=a.getValue(c.value[c.index]))?JSON.stringify(JSON.parse(e).sort()):null;c.value[c.index]={type:"constant",numeric:!1,content:d};break;case "shuffle":c=a.getSymbolRecord(b.target);e=JSON.parse(a.getValue(c.value[c.index])); +for(d=e.length-1;0e.length-1&&a.runtimeError(b.lino,"Index out of range"),e[d]=g, +c.value[c.index].content=JSON.stringify(e)}return b.pc+1}},getHandler:function(a){switch(a){case "json":return EasyCoder_Json.Json;default:return null}},run:function(a){var b=a[a.pc],c=EasyCoder_Json.getHandler(b.keyword);c||a.runtimeError(b.lino,"Unknown keyword '"+b.keyword+"' in 'json' package");return c.run(a)},value:{compile:function(a){a.tokenIs("the")&&a.next();if(a.tokenIs("json")){var b=a.nextToken();if(["size","count","keys"].includes(b)){if(a.skip("of"),a.isSymbol()){var c=a.getSymbolRecord(); +a.next();if(c.isVHolder)return{domain:"json",type:b,name:c.name}}}else if("index"===b&&a.nextTokenIs("of")&&(c=a.getNextValue(),a.tokenIs("in")))return a=a.getNextValue(),{domain:"json",type:b,item:c,list:a}}return null},get:function(a,b){switch(b.type){case "size":case "count":b=a.getSymbolRecord(b.name);a=a.getValue(b.value[b.index]);try{var c=JSON.parse(a)}catch(e){c=[]}return{type:"constant",numeric:!0,content:c?c.length:0};case "keys":return b=a.getSymbolRecord(b.name),c=(a=a.getValue(b.value[b.index]))? +JSON.stringify(Object.keys(JSON.parse(a)).sort()):"[]",{type:"constant",numeric:!1,content:c};case "index":var d=a.getValue(b.item);c=JSON.parse(a.getValue(b.list)).findIndex(function(a){return a===d});return{type:"constant",numeric:!0,content:c}}}},condition:{compile:function(){},test:function(){}}},EasyCoder_Rest={name:"EasyCoder_Rest",Rest:{compile:function(a){var b=a.getLino();switch(a.nextToken()){case "get":if(a.nextIsSymbol(!0)){var c=a.getSymbolRecord();if("variable"===c.keyword&&a.nextTokenIs("from")){var d= +a.getNextValue(),e=a.getPc();a.addCommand({domain:"rest",keyword:"rest",lino:b,request:"get",target:c.name,url:d,onError:null});a.tokenIs("or")&&(a.next(),a.getCommandAt(e).onError=a.getPc()+1,a.completeHandler());return!0}}break;case "post":c=null;if(a.nextTokenIs("to"))a.next();else if(c=a.getValue(),a.tokenIs("to"))a.next();else break;d=a.getValue();if(!d)throw Error(command.lino,"No URL present");e=null;if(a.tokenIs("giving")&&a.nextIsSymbol())if(e=a.getSymbolRecord(),e.isVHolder)e=e.name,a.next(); +else throw Error("'"+e.name+"' cannot hold a value");a.addCommand({domain:"rest",keyword:"rest",lino:b,request:"post",value:c,url:d,target:e,onError:a.getPc()+2});onError=null;a.tokenIs("or")&&(a.next(),a.completeHandler());return!0}return!1},createCORSRequest:function(a,b){var c=new XMLHttpRequest;"withCredentials"in c?c.open(a,b,!0):"undefined"!=typeof XDomainRequest?(c=new XDomainRequest,c.open(a,b)):c=null;return c},run:function(a){var b=a[a.pc],c=a.getValue(b.url);c=c.startsWith("http")?c:"/"=== +c[0]?c.substr(1):window.location.origin+"/"+c;var d=EasyCoder_Rest.Rest.createCORSRequest(b.request,c);if(d){d.script=a.script;d.pc=a.pc;d.onload=function(){var c=EasyCoder.scripts[d.script],e=c[d.pc];if(200<=d.status&&400>d.status){var g=d.responseText.trim();if(e.target){var h=a.getSymbolRecord(b.target);h.value[h.index]={type:"constant",numeric:!1,content:g};h.used=!0}c.run(e.pc+1)}else g=d.status+" "+d.statusText,e.onError?(c.errorMessage="Exception trapped: "+g,c.run(e.onError)):c.runtimeError(e.lino, +"Error: "+g)};d.onerror=function(){b.onError?(a.errorMessage=this.responseText,a.run(b.onError)):a.runtimeError(b.lino,this.responseText)};switch(b.request){case "get":d.send();break;case "post":var e=a.getValue(b.value);console.log("POST to "+c);d.setRequestHeader("Content-type","application/json; charset=UTF-8");d.send(e)}return 0}a.runtimeError(b.lino,"CORS not supported")}},getHandler:function(a){switch(a){case "rest":return EasyCoder_Rest.Rest;default:return null}},run:function(a){var b=a[a.pc], +c=EasyCoder_Rest.getHandler(b.keyword);c||a.runtimeError(b.lino,"Unknown keyword '"+b.keyword+"' in 'rest' package");return c.run(a)},value:{compile:function(){return null},get:function(){return null}},condition:{compile:function(){},test:function(){}}},EasyCoder_Compare=function(a,b,c){b=a.value.evaluate(a,b);a=a.value.evaluate(a,c);c=b.content;var d=a.content;c&&b.numeric?a.numeric||(d=""===d||"-"===d||"undefined"===typeof d?0:parseInt(d)):(d&&a.numeric&&(d=d.toString()),"undefined"===typeof c&& +(c=""),"undefined"===typeof d&&(d=""));return c>d?1:c')}l+="
"}g.innerHTML=h+" "+l;g.style.display="block";b.$jscomp$loop$prop$run$143=document.getElementById("easycoder-run-button");b.$jscomp$loop$prop$step$144=document.getElementById("easycoder-step-button");b.$jscomp$loop$prop$run$143.onclick= +function(b){return function(){b.$jscomp$loop$prop$run$143.blur();a.tracing=!1;document.getElementById("easycoder-tracer-content").style.display="none";try{EasyCoder_Run.run(a,a.resume)}catch(q){var c="Error in run handler: "+q.message;console.log(c);alert(c)}}}(b);b.$jscomp$loop$prop$step$144.onclick=function(b){return function(){console.log("step");b.$jscomp$loop$prop$step$144.blur();a.tracing=!0;document.getElementById("easycoder-tracer-content").style.display="block";try{a.run(a.resume)}catch(q){var c= +"Error in step handler: "+q.message;console.log(c);alert(c)}}}(b)}a.resume=a.pc;a.pc=0}break}b={$jscomp$loop$prop$run$143:b.$jscomp$loop$prop$run$143,$jscomp$loop$prop$step$144:b.$jscomp$loop$prop$step$144}}},exit:function(a){a.onExit&&a.run(a.onExit);var b=a.parent,c=a.afterExit;delete EasyCoder.scripts[a.script];a.module&&delete a.module.program;Object.keys(a).forEach(function(b){delete a[b]});b&&c&&EasyCoder.scripts[b].run(c)}},EasyCoder_Compiler={name:"EasyCoder_Compiler",getTokens:function(){return this.tokens}, +addWarning:function(a){this.warnings.push(a)},warning:function(a){this.addWarning(a)},unrecognisedSymbol:function(a){this.addWarning("Unrecognised symbol '"+a+"'")},getWarnings:function(){return this.warnings},getIndex:function(){return this.index},next:function(a){this.index+=void 0===a?1:a},peek:function(){return this.tokens[this.index+1].token},more:function(){return this.index=this.tokens.length?null:this.tokens[this.index]?this.tokens[this.index].token: +null},nextToken:function(){this.next();return this.getToken()},tokenIs:function(a){return this.index>=this.tokens.length?!1:a===this.tokens[this.index].token},nextTokenIs:function(a){this.next();return this.tokenIs(a)},skip:function(a){if(this.index>=this.tokens.length)return null;this.next();this.tokenIs(a)&&this.next()},prev:function(){this.index--},getLino:function(){return this.index>=this.tokens.length?0:this.tokens[this.index].lino},getTarget:function(a){a=void 0===a?this.index:a;return this.tokens[a].token}, +getTargetPc:function(a){a=void 0===a?this.index:a;return this.symbols[this.getTarget(a)].pc},getCommandAt:function(a){return this.program[a]},isSymbol:function(a){a=void 0===a?!1:a;if(this.getTarget()in this.symbols)return!0;if(a)throw Error("Unknown symbol: '"+this.getTarget()+"'");return!1},nextIsSymbol:function(a){a=void 0===a?!1:a;this.next();return this.isSymbol(a)},getSymbol:function(a){if(this.isSymbol(void 0===a?!1:a))return this.symbols[this.getToken()]},getSymbolPc:function(a){return this.getSymbol(void 0=== +a?!1:a).pc},getSymbolRecord:function(){var a=this.program[this.getSymbolPc(!0)];a.used=!0;return a},getSymbols:function(){return this.symbols},getProgram:function(){return this.program},getPc:function(){return this.program.length},getValue:function(){return this.value.compile(this)},getNextValue:function(){this.next();return this.getValue()},getCondition:function(){return this.condition.compile(this)},constant:function(a,b){return this.value.constant(a,void 0===b?!1:b)},addCommand:function(a){this.program.push(Object.assign({}, +{pc:this.program.length},a))},addSymbol:function(a,b){this.symbols[a]={pc:b}},mark:function(){this.savedMark=this.index},rewind:function(){this.index=this.savedMark},rewindTo:function(a){this.index=a},completeHandler:function(){var a=this.getLino(),b=this.getPc();this.addCommand({domain:"core",keyword:"goto",lino:a,goto:0});this.compileOne();this.continue?(this.addCommand({domain:"core",keyword:"goto",lino:a,goto:this.getPc()+1}),this.continue=!1):this.addCommand({domain:"core",keyword:"stop",lino:a, +next:0});this.getCommandAt(b).goto=this.getPc();return!0},compileVariable:function(a,b,c,d){c=void 0===c?!1:c;d=void 0===d?null:d;this.next();var e=this.getLino(),f=this.getTokens()[this.getIndex()];if(this.symbols[f.token])throw Error("Duplicate variable name '"+f.token+"'");var k=this.getPc();this.next();this.addSymbol(f.token,k);a={domain:a,keyword:b,lino:e,isSymbol:!0,used:!1,isVHolder:c,name:f.token,elements:1,index:0,value:[{}],element:[],extra:d};"dom"===d&&(a.element=[]);this.addCommand(a); +return a},compileToken:function(){var a=this.getToken();if(a){this.mark();for(var b=$jscomp.makeIterator(Object.keys(this.domain)),c=b.next();!c.done;c=b.next()){if((c=this.domain[c.value])&&(c=c.getHandler(a))&&c.compile(this))return;this.rewind()}console.log("No handler found");throw Error("I don't understand '"+a+"...'");}},compileOne:function(){var a=this.getToken();if(a){this.warnings=[];var b=this.program.length;if(a.endsWith(":")){a=a.substring(0,a.length-1);if(this.symbols[a])throw Error("Duplicate symbol: '"+ +a+"'");this.symbols[a]={pc:b};this.index++}else this.compileToken()}},compileFromHere:function(a){for(;this.indexe?0:e;e')}l+="
"}k.innerHTML=h+" "+l;k.style.display="block";b.$jscomp$loop$prop$run$66=document.getElementById("easycoder-run-button"); -b.$jscomp$loop$prop$step$67=document.getElementById("easycoder-step-button");b.$jscomp$loop$prop$run$66.onclick=function(b){return function(){b.$jscomp$loop$prop$run$66.blur();a.tracing=!1;document.getElementById("easycoder-tracer-content").style.display="none";try{EasyCoder_Run.run(a,a.resume)}catch(p){var c="Error in run handler: "+p.message;console.log(c);alert(c)}}}(b);b.$jscomp$loop$prop$step$67.onclick=function(b){return function(){console.log("step");b.$jscomp$loop$prop$step$67.blur();a.tracing= -!0;document.getElementById("easycoder-tracer-content").style.display="block";try{a.run(a.resume)}catch(p){var c="Error in step handler: "+p.message;console.log(c);alert(c)}}}(b)}a.resume=a.pc;a.pc=0}break}b={$jscomp$loop$prop$run$66:b.$jscomp$loop$prop$run$66,$jscomp$loop$prop$step$67:b.$jscomp$loop$prop$step$67}}},exit:function(a){a.onExit&&a.run(a.onExit);var b=a.parent,c=a.afterExit;delete EasyCoder.scripts[a.script];a.module&&delete a.module.program;Object.keys(a).forEach(function(b){delete a[b]}); -b&&c&&EasyCoder.scripts[b].run(c)}},EasyCoder_Value={name:"EasyCoder_Value",getItem:function(a){var b=a.getToken();if(!b)return null;if("true"===b)return a.next(),{type:"boolean",content:!0};if("false"===b)return a.next(),{type:"boolean",content:!1};if("`"===b.charAt(0))return a.next(),{type:"constant",numeric:!1,content:b.substring(1,b.length-1)};if(b.charAt(0).match(/[0-9-]/)){var c=eval(b);if(Number.isInteger(c))return a.next(),{type:"constant",numeric:!0,content:c};throw Error("'"+b+"' is not an integer"); -}b=a.getIndex();c=$jscomp.makeIterator(Object.keys(a.domain));for(var d=c.next();!d.done;d=c.next())if(d=d.value,a.rewindTo(b),d=a.domain[d].value.compile(a))return d;return null},compile:function(a){var b=a.getToken(),c=EasyCoder_Value.getItem(a);if(!c)throw Error("Undefined value: '"+b+"'");if("cat"===a.getToken()){for(b={type:"cat",numeric:!1,parts:[c]};a.tokenIs("cat");)a.next(),b.parts.push(a.value.getItem(a));return b}return c},doValue:function(a,b){if("undefined"===typeof b.type)return a.runtimeError(a[a.pc].lino, -"Undefined value (variable not initialized?)"),null;switch(b.type){case "cat":return{type:"constant",numeric:!1,content:b.parts.reduce(function(b,c){c=EasyCoder_Value.doValue(a,c);return b+(c?c.content:"")},"")};case "boolean":case "constant":return b;case "symbol":var c=a.getSymbolRecord(b.name);if(c.isVHolder){if(b=c.value[c.index]){c=b.content;if(null===c||"undefined"===typeof c)b.content=b.numeric?0:"";return b}return null}return a.domain[c.domain].value.get(a,b)}return a.domain[b.domain].value.get(a, -b)},constant:function(a,b){return{type:"constant",numeric:b,content:a}},evaluate:function(a,b){if(!b)return{type:"constant",numeric:!1,content:""};var c=EasyCoder_Value.doValue(a,b);if(c)return c;a.runtimeError(a[a.pc].lino,"Can't decode value: "+b)},getValue:function(a,b){return EasyCoder_Value.evaluate(a,b).content},encode:function(a,b){if(a)switch(b){case "ec":return a.replace(/\n/g,"%0a").replace(/\r/g,"%0d").replace(/"/g,"~dq~").replace(/'/g,"~sq~").replace(/\\/g,"~bs~");case "url":return encodeURIComponent(a.replace(/\s/g, -"+"));case "sanitize":return a.normalize("NFD").replace(/[\u0300-\u036f]/g,"")}return a},decode:function(a,b){if(a)switch(b){case "ec":return a.replace(/%0a/g,"\n").replace(/%0d/g,"\r").replace(/~dq~/g,'"').replace(/~sq~/g,"'").replace(/~bs~/g,"\\");case "url":return decodeURIComponent(a).replace(/\+/g," ")}return a}};EasyCoder.version="2.6.1";EasyCoder.timestamp=Date.now();console.log("EasyCoder loaded; waiting for page"); -function EasyCoder_Startup(){console.log(Date.now()-EasyCoder.timestamp+" ms: Page loaded; reset timer & start EasyCoder");EasyCoder.timestamp=Date.now();EasyCoder.scripts={};window.EasyCoder=EasyCoder;var a=document.getElementById("easycoder-script");if(a){a.style.display="none";try{EasyCoder.start(a.innerText)}catch(b){EasyCoder.reportError(b)}}}window.onload=EasyCoder_Startup; +"{}"}return a.content},getSimpleValue:function(a){return!0===a||!1===a?{type:"boolean",content:a}:{type:"constant",numeric:Number.isInteger(a),content:a}},run:function(a){a&&(this.program=this,EasyCoder_Run.run(this,a))},exit:function(){EasyCoder_Run.exit(this)},register:function(a){$jscomp$this.program=a},require:function(a,b,c){var d="";"/"==b[0]&&(d=window.location+"/");var e=document.createElement("css"===a?"link":"script");switch(a){case "css":e.type="text/css";e.href=""+d+b;e.rel="stylesheet"; +break;case "js":e.type="text/javascript";e.src=""+d+b;break;default:return}e.onload=function(){console.log(Date.now()-EasyCoder.timestamp+" ms: Library "+d+b+" loaded");c()};document.head.appendChild(e)},isUndefined:function(a){return"undefined"===typeof a},isJsonString:function(a){try{JSON.parse(a)}catch(b){return!1}return!0},runScript:function(a){var b=a[a.pc],c=a.getValue(b.script),d=b.imports;d.caller=a.script;var e=b.module?a.getSymbolRecord(b.module):null;try{EasyCoder.tokeniseAndCompile(c.split("\n"), +d,e,this.script,b.then)}catch(f){EasyCoder.reportError(f,a,a.source);a.onError?a.run(a.onError):(a=EasyCoder.scripts[a.parent])&&a.onError&&a.run(a.onError);return}b.nowait&&EasyCoder.run(a.nextPc)},close:function(){},compileScript:function(a,b,c,d){var e=a.tokens;this.compiling=!0;var f=EasyCoder_Compiler;this.compiler=f;f.value=EasyCoder_Value;f.condition=EasyCoder_Condition;f.domain=this.domain;f.imports=b;f.continue=!1;b=EasyCoder_Compiler.compile(e);this.compiling=!1;b.EasyCoder=this;b.value= +EasyCoder_Value;b.condition=EasyCoder_Condition;b.compare=EasyCoder_Compare;b.source=a;b.run=this.run;b.exit=this.exit;b.runScript=this.runScript;b.evaluate=this.evaluate;b.getValue=this.getValue;b.getFormattedValue=this.getFormattedValue;b.getSimpleValue=this.getSimpleValue;b.encode=this.encode;b.decode=this.decode;b.domain=this.domain;b.require=this.require;b.isUndefined=this.isUndefined;b.isJsonString=this.isJsonString;b.getSymbolRecord=this.getSymbolRecord;b.verifySymbol=this.verifySymbol;b.runtimeError= +this.runtimeError;b.nonNumericValueError=this.nonNumericValueError;b.variableDoesNotHoldAValueError=this.variableDoesNotHoldAValueError;b.reportError=this.reportError;b.register=this.register;b.symbols=f.getSymbols();b.unblocked=!1;b.encoding="ec";b.popups=[];b.stack=[];b.queue=[0];b.module=c;b.parent=d;c&&(c.program=b.script);return b},tokeniseFile:function(a){var b=[],c=[],d=0;a.forEach(function(a,f){b.push({lino:f+1,line:a});for(var e=a.length,g="",h=!0,l=0;l { +const EasyCoder_Core = { - const val1 = program.value.evaluate(program, value1); - const val2 = program.value.evaluate(program, value2); - var v1 = val1.content; - var v2 = val2.content; - if (v1 && val1.numeric) { - if (!val2.numeric) { - v2 = (v2 === `` || v2 === `-` || typeof v2 === `undefined`) ? 0 : parseInt(v2); - } - } else { - if (v2 && val2.numeric) { - v2 = v2.toString(); - } - if (typeof v1 === `undefined`) { - v1 = ``; - } - if (typeof v2 === `undefined`) { - v2 = ``; - } - } - if (v1 > v2) { - return 1; - } - if (v1 < v2) { - return -1; - } - return 0; -}; -// eslint-disable-next-line no-unused-vars -const EasyCoder_Compiler = { + name: `EasyCoder_Core`, - name: `EasyCoder_Compiler`, + Add: { - getTokens: function() { - return this.tokens; - }, + compile: compiler => { + const lino = compiler.getLino(); + compiler.next(); + // Get the (first) value + const value1 = compiler.getValue(); + if (compiler.tokenIs(`to`)) { + compiler.next(); + // Check if a value holder is next + if (compiler.isSymbol()) { + const symbol = compiler.getSymbol(); + const variable = compiler.getCommandAt(symbol.pc); + if (variable.isVHolder) { + if (compiler.peek() === `giving`) { + // This variable must be treated as a second value + const value2 = compiler.getValue(); + compiler.next(); + const target = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `add`, + lino, + value1, + value2, + target + }); + } else { + // Here the variable is the target. + const target = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `add`, + lino, + value1, + target + }); + } + return true; + } + compiler.warning(`core 'add': Expected value holder`); + } else { + // Here we have 2 values so 'giving' must come next + const value2 = compiler.getValue(); + if (compiler.tokenIs(`giving`)) { + compiler.next(); + const target = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `add`, + lino, + value1, + value2, + target + }); + return true; + } + compiler.warning(`core 'add'': Expected "giving"`); + } + } + return false; + }, - addWarning: function(message) { - this.warnings.push(message); - }, + // runtime - warning: function(message) { - this.addWarning(message); + run: program => { + const command = program[program.pc]; + const value1 = command.value1; + const value2 = command.value2; + const target = program.getSymbolRecord(command.target); + if (target.isVHolder) { + const value = target.value[target.index]; + if (value2) { + const result = program.getValue(value2) + + program.getValue(value1); + target.value[target.index] = { + type: `constant`, + numeric: true, + content: result + }; + } else { + if (!value.numeric && isNaN(value.content)) { + program.nonNumericValueError(command.lino); + } + const result = parseInt(value.content) + parseInt(program.getValue(value1)); + target.value[target.index] = { + type: `constant`, + numeric: true, + content: result + }; + } + } else { + program.variableDoesNotHoldAValueError(command.lino, target.name); + } + return command.pc + 1; + } }, - unrecognisedSymbol: function(item) { - this.addWarning(`Unrecognised symbol '${item}'`); - }, + Alias: { - getWarnings: function() { - return this.warnings; - }, + compile: compiler => { + const lino = compiler.getLino(); + compiler.next(); + if (compiler.isSymbol()) { + const alias = compiler.getToken(); + compiler.next(); + if (compiler.tokenIs(`to`)) { + compiler.next(); + if (compiler.isSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + symbolRecord.used = true; + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `alias`, + lino, + alias, + symbol: symbolRecord.name + }); + return true; + } + } + } + return false; + }, - getIndex: function() { - return this.index; + run: program => { + const command = program[program.pc]; + const aliasPc = program.symbols[command.alias].pc; + const aliasRecord = program[aliasPc]; + const symbolRecord = program.getSymbolRecord(command.symbol); + program[aliasPc] = { + pc: aliasRecord.pc, + domain: symbolRecord.domain, + keyword: symbolRecord.keyword, + lino: aliasRecord.lino, + name: aliasRecord.name, + alias: command.symbol + }; + return command.pc + 1; + } }, - next: function(step = 1) { - this.index = this.index + step; - }, + Append: { - peek: function() { - return this.tokens[this.index + 1].token; - }, + compile: compiler => { + const lino = compiler.getLino(); + const value = compiler.getNextValue(); + if (compiler.tokenIs(`to`)) { + if (compiler.nextIsSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + if (symbolRecord.isVHolder) { + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `append`, + lino, + value, + select: symbolRecord.name + }); + return true; + } + } + } + return false; + }, - more: function() { - return this.index < this.tokens.length; + run: program => { + const command = program[program.pc]; + const array = program.getSymbolRecord(command.select); + try { + const v = program.getValue(command.value); + const value = [`{`, `[`].includes(v[0]) ? JSON.parse(v) : v; + const item = array.value[array.index]; + let a = item.content; + if (a) { + a = JSON.parse(a); + } else { + a = []; + } + a.push(value); + item.content = JSON.stringify(a); + return command.pc + 1; + } catch (err) { + program.runtimeError(command.lino, `JSON: Unable to parse value`); + return false; + } + } }, - getToken: function() { - if (this.index >= this.tokens.length) { - return null; + Begin: { + + compile: compiler => { + compiler.next(); + compiler.compileFromHere([`end`]); + return true; + }, + + run: program => { + return program[program.pc].pc + 1; } - const item = this.tokens[this.index]; - return item ? this.tokens[this.index].token : null; }, - nextToken: function() { - this.next(); - return this.getToken(); - }, - - tokenIs: function(token) { - if (this.index >= this.tokens.length) { - return false; - } - return token === this.tokens[this.index].token; - }, + Callback: { - nextTokenIs: function(token) { - this.next(); - return this.tokenIs(token); - }, + compile: compiler => { + compiler.compileVariable(`core`, `callback`); + return true; + }, - skip: function(token) { - if (this.index >= this.tokens.length) { - return null; - } - this.next(); - if (this.tokenIs(token)) { - this.next(); + run: program => { + return program[program.pc].pc + 1; } }, - prev: function() { - this.index--; - }, + Clear: { - getLino: function() { - if (this.index >= this.tokens.length) { - return 0; - } - return this.tokens[this.index].lino; - }, + compile: compiler => { + const lino = compiler.getLino(); + compiler.next(); + if (compiler.isSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + if (symbolRecord.isVHolder) { + const symbol = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `clear`, + lino, + symbol + }); + return true; + } + compiler.warning(`'Variable '${symbolRecord.name}' does not hold a value`); + } + return false; + }, - getTarget: function(index = this.index) { - return this.tokens[index].token; + run: program => { + const command = program[program.pc]; + const symbol = program.getSymbolRecord(command.symbol); + if (symbol.isVHolder) { + const handler = program.domain[symbol.domain]; + handler.value.put(symbol, { + type: `boolean`, + content: false + }); + command.numeric = false; + } else { + program.variableDoesNotHoldAValueError(command.lino, symbol.name); + } + return command.pc + 1; + } }, - getTargetPc: function(index = this.index) { - return this.symbols[this.getTarget(index)].pc; - }, + Close: { - getCommandAt: function(pc) { - return this.program[pc]; - }, + compile: compiler => { + const lino = compiler.getLino(); + if (compiler.nextIsSymbol()) { + const moduleRecord = compiler.getSymbolRecord(); + if (moduleRecord.keyword === `module`) { + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `close`, + lino, + module: moduleRecord.name + }); + return true; + } + } + return false; + }, - isSymbol: function(required = false) { - const isSymbol = this.getTarget() in this.symbols; - if (isSymbol) return true; - if (required) { - throw new Error(`Unknown symbol: '${this.getTarget()}'`); + run: program => { + const command = program[program.pc]; + const moduleRecord = program.getSymbolRecord(command.module); + const p = EasyCoder.scripts[moduleRecord.program]; + p.run(p.onClose); + return command.pc + 1; } - return false; }, - nextIsSymbol: function(required = false) { - this.next(); - return this.isSymbol(required); - }, + Continue: { - getSymbol: function(required = false) { - if (this.isSymbol(required)) { - return this.symbols[this.getToken()]; + compile: compiler => { + compiler.next(); + compiler.continue = true; + return true; } }, - getSymbolPc: function(required = false) { - return this.getSymbol(required).pc; - }, + Debug: { - getSymbolRecord: function() { - const record = this.program[this.getSymbolPc(true)]; - record.used = true; - return record; - }, + compile: compiler => { + const lino = compiler.getLino(); + if (compiler.nextTokenIs(`program`)) { + compiler.next(); + if ([`item`, `pc`].includes(compiler.getToken())) { + const item = compiler.getNextValue(); + compiler.addCommand({ + domain: `core`, + keyword: `debug`, + lino, + item + }); + return true; + } + compiler.addCommand({ + domain: `core`, + keyword: `debug`, + lino, + item: `program` + }); + return true; + } else if (compiler.tokenIs(`symbols`)) { + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `debug`, + lino, + item: `symbols` + }); + return true; + } else if (compiler.tokenIs(`symbol`)) { + const name = compiler.nextToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `debug`, + lino, + item: `symbol`, + name + }); + return true; + } else if (compiler.tokenIs(`step`)) { + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `debug`, + lino, + item: `step` + }); + return true; + } + return false; + }, - getSymbols: function() { - return this.symbols; + run: program => { + const command = program[program.pc]; + const item = command.item; + switch (item) { + case `symbols`: + console.log(`Symbols: ${JSON.stringify(program.symbols, null, 2)}`); + break; + case `symbol`: + const record = program.getSymbolRecord(command.name); + const exporter = record.exporter.script; + delete record.exporter; + console.log(`Symbol: ${JSON.stringify(record, null, 2)}`); + record.exporter.script = exporter; + break; + case `step`: + program.debugStep = true; + break; + case `program`: + console.log(`Debug program: ${JSON.stringify(program, null, 2)}`); + break; + default: + if (item.content >= 0) { + console.log(`Debug item ${item.content}: ${JSON.stringify(program[item.content], null, 2)}`); + } + break; + } + return command.pc + 1; + } }, - getProgram: function() { - return this.program; - }, + Decode: { - getPc: function() { - return this.program.length; - }, + compile: compiler => { + const lino = compiler.getLino(); + if (compiler.nextIsSymbol()) { + const symbol = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `decode`, + lino, + symbol + }); + return true; + } + return false; + }, - getValue: function() { - return this.value.compile(this); + run: program => { + const command = program[program.pc]; + const target = program.getSymbolRecord(command.symbol); + if (target.isVHolder) { + const content = program.getValue(target.value[target.index]); + target.value[target.index] = { + type: `constant`, + numeric: false, + content: program.decode(content) + }; + command.numeric = false; + } else { + program.variableDoesNotHoldAValueError(command.lino, target.name); + } + return command.pc + 1; + } }, - getNextValue: function() { - this.next(); - return this.getValue(); - }, + Divide: { - getCondition: function() { - return this.condition.compile(this); - }, + compile: compiler => { + const lino = compiler.getLino(); + var target; + if (compiler.nextIsSymbol()) { + // It may be the target + const symbol = compiler.getSymbol(); + target = compiler.getCommandAt(symbol.pc).name; + } + // Get the value even if we have a target + const value1 = compiler.getValue(); + if (compiler.tokenIs(`by`)) { + compiler.next(); + } + // The next item is always a value + const value2 = compiler.getValue(); + // If we now have 'giving' then the target follows + if (compiler.tokenIs(`giving`)) { + compiler.next(); + // Get the target + if (compiler.isSymbol()) { + const symbol = compiler.getSymbol(); + target = compiler.getCommandAt(symbol.pc).name; + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `divide`, + lino, + value1, + value2, + target + }); + return true; + } + compiler.warning(`core 'divide'': Expected value holder`); + } else { + // Here we should already have the target. + if (typeof target === `undefined`) { + compiler.warning(`core 'divide': No target variable given`); + } + compiler.addCommand({ + domain: `core`, + keyword: `divide`, + lino, + value2, + target + }); + return true; + } + return false; + }, - constant: function(content, numeric = false) { - return this.value.constant(content, numeric); + run: program => { + const command = program[program.pc]; + const value1 = command.value1; + const value2 = command.value2; + const target = program.getSymbolRecord(command.target); + if (target.isVHolder) { + const value = target.value[target.index]; + if (value1) { + const result = program.getValue(value1) / program.getValue(value2); + target.value[target.index] = { + type: `constant`, + numeric: true, + content: Math.trunc(result) + }; + } else { + if (!value.numeric && isNaN(value.content)) { + program.nonNumericValueError(command, lino); + } + const result = parseInt(value.content) / parseInt(program.getValue(value2)); + target.value[target.index] = { + type: `constant`, + numeric: true, + content: Math.trunc(result) + }; + } + } else { + program.variableDoesNotHoldAValueError(command.lino, target.name); + } + return command.pc + 1; + } }, - addCommand: function(item) { - const pc = this.program.length; - this.program.push({ - pc, - ...item - }); - }, + Dummy: { - addSymbol: function(name, pc) { - this.symbols[name] = { - pc - }; - }, + compile: compiler => { + const lino = compiler.getLino(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `dummy`, + lino + }); + return true; + }, - mark: function() { - this.savedMark = this.index; + run: program => { + return program[program.pc].pc + 1; + } }, - rewind: function() { - this.index = this.savedMark; - }, + Encode: { - rewindTo: function(index) { - this.index = index; + compile: compiler => { + const lino = compiler.getLino(); + compiler.next(); + if (compiler.isSymbol()) { + const symbol = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `encode`, + lino, + symbol + }); + return true; + } + return false; + }, + + run: program => { + const command = program[program.pc]; + const target = program.getSymbolRecord(command.symbol); + if (target.isVHolder) { + const content = program.getValue(target.value[target.index]); + target.value[target.index] = { + type: `constant`, + numeric: false, + content: program.encode(content) + }; + command.numeric = false; + } else { + program.variableDoesNotHoldAValueError(command.lino, target.name); + } + return command.pc + 1; + } }, - completeHandler: function() { - const lino = this.getLino(); - // Add a 'goto' to skip the action - const goto = this.getPc(); - this.addCommand({ - domain: `core`, - keyword: `goto`, - lino, - goto: 0 - }); - // Add the action - this.compileOne(); - // If `continue` is set - if (this.continue) { - this.addCommand({ - domain: `core`, - keyword: `goto`, - lino, - goto: this.getPc() + 1 - }); - this.continue = false; + End: { + + compile: compiler => { + compiler.next(); + return true; + }, + + run: () => { + return 0; } - // else add a 'stop' - else { - this.addCommand({ + }, + + Exit: { + + compile: compiler => { + compiler.next(); + compiler.addCommand({ domain: `core`, - keyword: `stop`, - lino, - next: 0 + keyword: `exit` }); - } - // Fixup the 'goto' - this.getCommandAt(goto).goto = this.getPc(); - return true; - }, + return true; + }, - compileVariable: function(domain, keyword, isVHolder = false, extra = null) { - this.next(); - const lino = this.getLino(); - const item = this.getTokens()[this.getIndex()]; - if (this.symbols[item.token]) { - throw new Error(`Duplicate variable name '${item.token}'`); - } - const pc = this.getPc(); - this.next(); - this.addSymbol(item.token, pc); - const command = { - domain, - keyword, - lino, - isSymbol: true, - used: false, - isVHolder, - name: item.token, - elements: 1, - index: 0, - value: [{}], - element: [], - extra - }; - if (extra === `dom`) { - command.element = []; + run: program => { + let parent = EasyCoder.scripts[program.parent]; + let unblocked = program.unblocked; + program.exit(); + if (!unblocked && parent) { + parent.run(parent.nextPc); + parent.nextPc = 0; + } + return 0; } - this.addCommand(command); - return command; }, - compileToken: function() { - // Try each domain in turn until one can handle the command - const token = this.getToken(); - if (!token) { - return; - } - // console.log(`Compile ${token}`); - this.mark(); - for (const domainName of Object.keys(this.domain)) { - // console.log(`Try domain ${domainName} for token ${token}`); - const domain = this.domain[domainName]; - if (domain) { - const handler = domain.getHandler(token); - if (handler) { - if (handler.compile(this)) { - return; - } + Filter: { + + compile: compiler => { + const lino = compiler.getLino(); + if (compiler.nextIsSymbol()) { + const arrayRecord = compiler.getSymbolRecord(); + if (compiler.nextTokenIs(`with`)) { + const func = compiler.nextToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `filter`, + lino, + array: arrayRecord.name, + func + }); + return true; } } - this.rewind(); - } - console.log(`No handler found`); - throw new Error(`I don't understand '${token}...'`); - }, + return false; + }, - compileOne: function() { - const keyword = this.getToken(); - if (!keyword) { - return; - } - // console.log(`Compile keyword '${keyword}'`); - this.warnings = []; - const pc = this.program.length; - // First check for a label - if (keyword.endsWith(`:`)) { - const name = keyword.substring(0, keyword.length - 1); - if (this.symbols[name]) { - throw new Error(`Duplicate symbol: '${name}'`); + run: program => { + const command = program[program.pc]; + const variable = program.getSymbolRecord(command.array); + const value = variable.value[variable.index].content; + const func = program.getSymbolRecord(command.func).pc; + try { + const array = JSON.parse(value); + const result = array.filter(function (a) { + variable.a = a; + program.run(func); + return variable.v; + }); + variable.value[variable.index].content = JSON.stringify(result); + } catch (err) { + program.runtimeError(command.lino, `Can't parse this array`); } - this.symbols[name] = { - pc - }; - this.index++; - } else { - this.compileToken(); + return command.pc + 1; } }, - compileFromHere: function(stopOn) { - while (this.index < this.tokens.length) { - const token = this.tokens[this.index]; - const keyword = token.token; - if (keyword === `else`) { - return this.program; - } - this.compileOne(); - if (stopOn.indexOf(keyword) > -1) { - break; - } - } - }, + Fork: { - compile: function(tokens) { - this.tokens = tokens; - this.index = 0; - this.program = []; - this.program.symbols = {}; - this.symbols = this.program.symbols; - this.warnings = []; - this.compileFromHere([]); - this.addCommand({ - domain: `core`, - keyword: `exit`, - lino: this.getLino(), - next: 0 - }); - // console.log('Symbols: ' + JSON.stringify(this.symbols, null, 2)); - for (const symbol in this.symbols) { - const record = this.program[this.symbols[symbol].pc]; - if (record.isSymbol && !record.used && !record.exporter) { - console.log(`Symbol '${record.name}' has not been used.`); + compile: compiler => { + const lino = compiler.getLino(); + compiler.next(); + if (compiler.nextTokenIs(`to`)) { + compiler.next(); } - } - return this.program; - } -}; -// eslint-disable-next-line no-unused-vars -const EasyCoder_Condition = { - - name: `EasyCoder_Condition`, + const label = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `fork`, + lino, + label + }); + return true; + }, - compile: (compiler) => { - // See if any of the domains can handle it - compiler.mark(); - for (const domainName of Object.keys(compiler.domain)) { - // console.log(`Try domain '${domainName}' for condition`); - const domain = compiler.domain[domainName]; - const code = domain.condition.compile(compiler); - if (code) { - return { - domain: name, - ...code - }; + run: program => { + const command = program[program.pc]; + try { + program.run(program.symbols[command.label].pc); + } catch (err) { + console.log(err.message); + alert(err.message); } - compiler.rewind(); + return command.pc + 1; } }, - // runtime - - test: (program, condition) => { - const handler = program.domain[condition.domain]; - return handler.condition.test(program, condition); - } -}; -const EasyCoder_Core = { - - name: `EasyCoder_Core`, - - Add: { + Go: { compile: compiler => { const lino = compiler.getLino(); - compiler.next(); - // Get the (first) value - const value1 = compiler.getValue(); - if (compiler.tokenIs(`to`)) { + if (compiler.nextTokenIs(`to`)) { compiler.next(); - // Check if a value holder is next - if (compiler.isSymbol()) { - const symbol = compiler.getSymbol(); - const variable = compiler.getCommandAt(symbol.pc); - if (variable.isVHolder) { - if (compiler.peek() === `giving`) { - // This variable must be treated as a second value - const value2 = compiler.getValue(); - compiler.next(); - const target = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `add`, - lino, - value1, - value2, - target - }); - } else { - // Here the variable is the target. - const target = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `add`, - lino, - value1, - target - }); - } - return true; - } - compiler.warning(`core 'add': Expected value holder`); - } else { - // Here we have 2 values so 'giving' must come next - const value2 = compiler.getValue(); - if (compiler.tokenIs(`giving`)) { - compiler.next(); - const target = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `add`, - lino, - value1, - value2, - target - }); - return true; - } - compiler.warning(`core 'add'': Expected "giving"`); - } } - return false; + const label = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `go`, + lino, + label + }); + return true; }, - // runtime - run: program => { const command = program[program.pc]; - const value1 = command.value1; - const value2 = command.value2; - const target = program.getSymbolRecord(command.target); - if (target.isVHolder) { - const value = target.value[target.index]; - if (value2) { - const result = program.getValue(value2) + - program.getValue(value1); - target.value[target.index] = { - type: `constant`, - numeric: true, - content: result - }; - } else { - if (!value.numeric && isNaN(value.content)) { - program.nonNumericValueError(command.lino); + if (command.label) { + if (program.verifySymbol(command.label)) { + const pc = program.symbols[command.label]; + if (pc) { + return pc.pc; } - const result = parseInt(value.content) + parseInt(program.getValue(value1)); - target.value[target.index] = { - type: `constant`, - numeric: true, - content: result - }; } - } else { - program.variableDoesNotHoldAValueError(command.lino, target.name); + program.runtimeError(command.lino, `Unknown symbol '${command.label}'`); + return 0; } - return command.pc + 1; + return command.goto; } }, - Alias: { + Gosub: { compile: compiler => { const lino = compiler.getLino(); - compiler.next(); - if (compiler.isSymbol()) { - const alias = compiler.getToken(); + if (compiler.nextTokenIs(`to`)) { compiler.next(); - if (compiler.tokenIs(`to`)) { - compiler.next(); - if (compiler.isSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - symbolRecord.used = true; - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `alias`, - lino, - alias, - symbol: symbolRecord.name - }); - return true; - } - } } - return false; + const label = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `gosub`, + lino, + label + }); + return true; }, run: program => { const command = program[program.pc]; - const aliasPc = program.symbols[command.alias].pc; - const aliasRecord = program[aliasPc]; - const symbolRecord = program.getSymbolRecord(command.symbol); - program[aliasPc] = { - pc: aliasRecord.pc, - domain: symbolRecord.domain, - keyword: symbolRecord.keyword, - lino: aliasRecord.lino, - name: aliasRecord.name, - alias: command.symbol - }; - return command.pc + 1; + if (program.verifySymbol(command.label)) { + program.stack.push(program.pc + 1); + return program.symbols[command.label].pc; + } + program.runtimeError(command.lino, `Unknown symbol '${command.label}'`); + return 0; } }, - Append: { + If: { compile: compiler => { const lino = compiler.getLino(); - const value = compiler.getNextValue(); - if (compiler.tokenIs(`to`)) { - if (compiler.nextIsSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.isVHolder) { - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `append`, - lino, - value, - select: symbolRecord.name - }); - return true; - } - } + compiler.next(); + const condition = compiler.condition.compile(compiler); + const pc = compiler.getPc(); + compiler.addCommand({ + domain: `core`, + keyword: `if`, + lino, + condition + }); + // Get the 'then' code + compiler.compileOne(); + if (!compiler.getToken()) { + compiler.getCommandAt(pc).else = compiler.getPc(); + return true; } - return false; + if (compiler.tokenIs(`else`)) { + const goto = compiler.getPc(); + // Add a 'goto' to skip the 'else' + compiler.addCommand({ + domain: `core`, + keyword: `goto`, + lino, + goto: 0 + }); + // Fixup the link to the 'else' branch + compiler.getCommandAt(pc).else = compiler.getPc(); + // Process the 'else' branch + compiler.next(); + // Add the 'else' branch + compiler.compileOne(true); + // Fixup the 'goto' + compiler.getCommandAt(goto).goto = compiler.getPc(); + } else { + // We're at the next command + compiler.getCommandAt(pc).else = compiler.getPc(); + } + return true; }, run: program => { const command = program[program.pc]; - const array = program.getSymbolRecord(command.select); - try { - const v = program.getValue(command.value); - const value = [`{`, `[`].includes(v[0]) ? JSON.parse(v) : v; - const item = array.value[array.index]; - let a = item.content; - if (a) { - a = JSON.parse(a); - } else { - a = []; - } - a.push(value); - item.content = JSON.stringify(a); + const condition = command.condition; + const test = program.condition.test(program, condition); + if (test) { return command.pc + 1; - } catch (err) { - program.runtimeError(command.lino, `JSON: Unable to parse value`); - return false; } + return command.else; } }, - Begin: { - - compile: compiler => { - compiler.next(); - compiler.compileFromHere([`end`]); - return true; - }, - - run: program => { - return program[program.pc].pc + 1; - } - }, - - Callback: { + Import: { compile: compiler => { - compiler.compileVariable(`core`, `callback`); + const imports = compiler.imports; + let caller = EasyCoder.scripts[imports.caller]; + const program = compiler.getProgram(); + if (imports.length) { + for (const name of imports) { + let symbolRecord = caller.getSymbolRecord(name); + const thisType = compiler.nextToken(); + const exportedType = symbolRecord.keyword; + if (thisType === exportedType) { + const command = compiler.compileVariable(symbolRecord.domain, exportedType, true); + const newRecord = program[compiler.getSymbols()[command.name].pc]; + newRecord.element = symbolRecord.element; + newRecord.exporter = symbolRecord.exporter ? symbolRecord.exporter : caller.script; + newRecord.exportedName = symbolRecord.name; + newRecord.extra = symbolRecord.extra; + newRecord.isVHolder = symbolRecord.isVHolder; + if (symbolRecord.program) { + newRecord.program = symbolRecord.program.script; + } + newRecord.imported = true; + if (!compiler.tokenIs(`and`)) { + break; + } + } else { + throw new Error(`Mismatched import variable type for '${symbolRecord.name}'`); + } + } + if (compiler.tokenIs(`and`)) { + throw new Error(`Imports do not match exports`); + } + } else { + compiler.next(); + } return true; }, run: program => { - return program[program.pc].pc + 1; + const command = program[program.pc]; + return command.pc + 1; } }, - Clear: { + Index: { compile: compiler => { const lino = compiler.getLino(); - compiler.next(); - if (compiler.isSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.isVHolder) { - const symbol = compiler.getToken(); - compiler.next(); + // get the variable + if (compiler.nextIsSymbol(true)) { + const symbol = compiler.getToken(); + if (compiler.nextTokenIs(`to`)) { + // get the value + const value = compiler.getNextValue(); compiler.addCommand({ domain: `core`, - keyword: `clear`, + keyword: `index`, lino, - symbol + symbol, + value }); return true; } - compiler.warning(`'Variable '${symbolRecord.name}' does not hold a value`); } return false; }, @@ -642,186 +856,39 @@ const EasyCoder_Core = { run: program => { const command = program[program.pc]; const symbol = program.getSymbolRecord(command.symbol); - if (symbol.isVHolder) { - const handler = program.domain[symbol.domain]; - handler.value.put(symbol, { - type: `boolean`, - content: false - }); - command.numeric = false; - } else { - program.variableDoesNotHoldAValueError(command.lino, symbol.name); + const index = program.getValue(command.value); + if (index >= symbol.elements) { + program.runtimeError(command.lino, + `Array index ${index} is out of range for '${symbol.name}'`); + } + symbol.index = index; + if (symbol.imported) { + const exporterRecord = EasyCoder.symbols[symbol.exporter].getSymbolRecord(symbol.exportedName); + exporterRecord.index = index; } return command.pc + 1; } }, - Close: { + Module: { compile: compiler => { - const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const moduleRecord = compiler.getSymbolRecord(); - if (moduleRecord.keyword === `module`) { - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `close`, - lino, - module: moduleRecord.name - }); - return true; - } - } - return false; + compiler.compileVariable(`core`, `module`); + return true; }, run: program => { - const command = program[program.pc]; - const moduleRecord = program.getSymbolRecord(command.module); - const p = EasyCoder.scripts[moduleRecord.program]; - p.run(p.onClose); - return command.pc + 1; - } - }, - - Continue: { - - compile: compiler => { - compiler.next(); - compiler.continue = true; - return true; + return program[program.pc].pc + 1; } }, - Debug: { - - compile: compiler => { - const lino = compiler.getLino(); - if (compiler.nextTokenIs(`program`)) { - compiler.next(); - if ([`item`, `pc`].includes(compiler.getToken())) { - const item = compiler.getNextValue(); - compiler.addCommand({ - domain: `core`, - keyword: `debug`, - lino, - item - }); - return true; - } - compiler.addCommand({ - domain: `core`, - keyword: `debug`, - lino, - item: `program` - }); - return true; - } else if (compiler.tokenIs(`symbols`)) { - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `debug`, - lino, - item: `symbols` - }); - return true; - } else if (compiler.tokenIs(`symbol`)) { - const name = compiler.nextToken(); - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `debug`, - lino, - item: `symbol`, - name - }); - return true; - } else if (compiler.tokenIs(`step`)) { - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `debug`, - lino, - item: `step` - }); - return true; - } - return false; - }, - - run: program => { - const command = program[program.pc]; - const item = command.item; - switch (item) { - case `symbols`: - console.log(`Symbols: ${JSON.stringify(program.symbols, null, 2)}`); - break; - case `symbol`: - const record = program.getSymbolRecord(command.name); - const exporter = record.exporter.script; - delete record.exporter; - console.log(`Symbol: ${JSON.stringify(record, null, 2)}`); - record.exporter.script = exporter; - break; - case `step`: - program.debugStep = true; - break; - case `program`: - console.log(`Debug program: ${JSON.stringify(program, null, 2)}`); - break; - default: - if (item.content >= 0) { - console.log(`Debug item ${item.content}: ${JSON.stringify(program[item.content], null, 2)}`); - } - break; - } - return command.pc + 1; - } - }, - - Decode: { - - compile: compiler => { - const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const symbol = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `decode`, - lino, - symbol - }); - return true; - } - return false; - }, - - run: program => { - const command = program[program.pc]; - const target = program.getSymbolRecord(command.symbol); - if (target.isVHolder) { - const content = program.getValue(target.value[target.index]); - target.value[target.index] = { - type: `constant`, - numeric: false, - content: program.decode(content) - }; - command.numeric = false; - } else { - program.variableDoesNotHoldAValueError(command.lino, target.name); - } - return command.pc + 1; - } - }, - - Divide: { + Multiply: { compile: compiler => { const lino = compiler.getLino(); + compiler.next(); var target; - if (compiler.nextIsSymbol()) { + if (compiler.isSymbol()) { // It may be the target const symbol = compiler.getSymbol(); target = compiler.getCommandAt(symbol.pc).name; @@ -843,7 +910,7 @@ const EasyCoder_Core = { compiler.next(); compiler.addCommand({ domain: `core`, - keyword: `divide`, + keyword: `multiply`, lino, value1, value2, @@ -851,15 +918,15 @@ const EasyCoder_Core = { }); return true; } - compiler.warning(`core 'divide'': Expected value holder`); + compiler.warning(`core multiply: Expected value holder`); } else { // Here we should already have the target. if (typeof target === `undefined`) { - compiler.warning(`core 'divide': No target variable given`); + compiler.warning(`core multiply: No target variable given`); } compiler.addCommand({ domain: `core`, - keyword: `divide`, + keyword: `multiply`, lino, value2, target @@ -877,21 +944,22 @@ const EasyCoder_Core = { if (target.isVHolder) { const value = target.value[target.index]; if (value1) { - const result = program.getValue(value1) / program.getValue(value2); + const result = program.getValue(value1) * + program.getValue(value2); target.value[target.index] = { type: `constant`, numeric: true, - content: Math.trunc(result) + content: result }; } else { if (!value.numeric && isNaN(value.content)) { program.nonNumericValueError(command, lino); } - const result = parseInt(value.content) / parseInt(program.getValue(value2)); + const result = parseInt(value.content) * parseInt(program.getValue(value2)); target.value[target.index] = { type: `constant`, numeric: true, - content: Math.trunc(result) + content: result }; } } else { @@ -901,25 +969,7 @@ const EasyCoder_Core = { } }, - Dummy: { - - compile: compiler => { - const lino = compiler.getLino(); - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `dummy`, - lino - }); - return true; - }, - - run: program => { - return program[program.pc].pc + 1; - } - }, - - Encode: { + Negate: { compile: compiler => { const lino = compiler.getLino(); @@ -929,7 +979,7 @@ const EasyCoder_Core = { compiler.next(); compiler.addCommand({ domain: `core`, - keyword: `encode`, + keyword: `negate`, lino, symbol }); @@ -940,343 +990,338 @@ const EasyCoder_Core = { run: program => { const command = program[program.pc]; - const target = program.getSymbolRecord(command.symbol); - if (target.isVHolder) { - const content = program.getValue(target.value[target.index]); - target.value[target.index] = { + const symbol = program.getSymbolRecord(command.symbol); + if (symbol.isVHolder) { + symbol.value[symbol.index] = { type: `constant`, - numeric: false, - content: program.encode(content) + numeric: true, + content: -symbol.value[symbol.index].content }; - command.numeric = false; } else { - program.variableDoesNotHoldAValueError(command.lino, target.name); + program.variableDoesNotHoldAValueError(command.lino, symbol.name); } return command.pc + 1; } }, - End: { + On: { compile: compiler => { - compiler.next(); - return true; + const lino = compiler.getLino(); + const action = compiler.nextToken(); + switch (action) { + case `close`: + case `message`: + case `error`: + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `on`, + lino, + action + }); + return compiler.completeHandler(); + } + if (compiler.isSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + if (symbolRecord.keyword === `callback`) { + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `on`, + lino, + action: symbolRecord.name + }); + return compiler.completeHandler(); + } + } + return false; }, - run: () => { - return 0; + run: program => { + const command = program[program.pc]; + const cb = command.pc + 2; + switch (command.action) { + case `close`: + program.onClose = cb; + break; + case `message`: + program.onMessage = cb; + break; + case `error`: + program.onError = cb; + break; + default: + const callbacklRecord = program.getSymbolRecord(command.action); + if (callbacklRecord) { + callbacklRecord.cb = cb; + } else { + program.runtimeError(command.lino, `Unknown action '${command.action}'`); + return 0; + } + } + return command.pc + 1; } }, - Exit: { + Print: { compile: compiler => { + const lino = compiler.getLino(); compiler.next(); + const value = compiler.getValue(); compiler.addCommand({ domain: `core`, - keyword: `exit` + keyword: `print`, + lino, + value }); return true; }, run: program => { - let parent = EasyCoder.scripts[program.parent]; - let unblocked = program.unblocked; - program.exit(); - if (!unblocked && parent) { - parent.run(parent.nextPc); - parent.nextPc = 0; - } - return 0; + const command = program[program.pc]; + const value = program.getFormattedValue(command.value); + console.log(`-> ` + value); + return command.pc + 1; } }, - Filter: { + Put: { compile: compiler => { const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const arrayRecord = compiler.getSymbolRecord(); - if (compiler.nextTokenIs(`with`)) { - const func = compiler.nextToken(); + // Get the value + const value = compiler.getNextValue(); + if (compiler.tokenIs(`into`)) { + if (compiler.nextIsSymbol()) { + const target = compiler.getToken(); compiler.next(); compiler.addCommand({ domain: `core`, - keyword: `filter`, + keyword: `put`, lino, - array: arrayRecord.name, - func + value, + target }); return true; } + compiler.warning(`core:put: No such variable: '${compiler.getToken()}'`); } return false; }, + // runtime + run: program => { const command = program[program.pc]; - const variable = program.getSymbolRecord(command.array); - const value = variable.value[variable.index].content; - const func = program.getSymbolRecord(command.func).pc; - try { - const array = JSON.parse(value); - const result = array.filter(function (a) { - variable.a = a; - program.run(func); - return variable.v; - }); - variable.value[variable.index].content = JSON.stringify(result); - } catch (err) { - program.runtimeError(command.lino, `Can't parse this array`); + const target = program.getSymbolRecord(command.target); + if (!target.isVHolder) { + program.variableDoesNotHoldAValueError(command.lino, target.name); + } + const value = program.evaluate(command.value); + // target.value[target.index] = value; + target.value[target.index] = { + type: value.type, + numeric: value.numeric, + content: value.content + }; + if (target.imported) { + const exporterRecord = EasyCoder.scripts[target.exporter].getSymbolRecord(target.exportedName); + exporterRecord.value[exporterRecord.index] = value; } return command.pc + 1; } }, - Fork: { + Replace: { compile: compiler => { const lino = compiler.getLino(); - compiler.next(); - if (compiler.nextTokenIs(`to`)) { - compiler.next(); + const original = compiler.getNextValue(); + if (compiler.tokenIs(`with`)) { + const replacement = compiler.getNextValue(); + if (compiler.tokenIs(`in`)) { + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.isVHolder) { + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `replace`, + lino, + original, + replacement, + target: targetRecord.name + }); + return true; + } else { + throw new Error(`'${targetRecord.name}' does not hold a value`); + } + } + } } - const label = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `fork`, - lino, - label - }); - return true; + return false; }, + // runtime + run: program => { const command = program[program.pc]; - try { - program.run(program.symbols[command.label].pc); - } catch (err) { - console.log(err.message); - alert(err.message); - } + const original = program.getValue(command.original); + const replacement = program.getValue(command.replacement); + const target = program.getSymbolRecord(command.target); + const value = program.getValue(target.value[target.index]); + const content = value.split(original).join(replacement); + target.value[target.index] = { + type: `constant`, + numeric: false, + content + }; return command.pc + 1; } }, - Go: { + Require: { compile: compiler => { const lino = compiler.getLino(); - if (compiler.nextTokenIs(`to`)) { - compiler.next(); + const type = compiler.nextToken(); + if ([`css`, `js`].includes(type)) { + const url = compiler.getNextValue(); + compiler.addCommand({ + domain: `core`, + keyword: `require`, + lino, + type, + url + }); + return true; } - const label = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `go`, - lino, - label - }); - return true; + throw new Error(`File type must be 'css' or 'js'`); }, + // runtime + run: program => { const command = program[program.pc]; - if (command.label) { - if (program.verifySymbol(command.label)) { - const pc = program.symbols[command.label]; - if (pc) { - return pc.pc; - } - } - program.runtimeError(command.lino, `Unknown symbol '${command.label}'`); - return 0; - } - return command.goto; + program.require(command.type, program.getValue(command.url), + function () { + program.run(command.pc + 1); + }); + return 0; } }, - Gosub: { + Return: { compile: compiler => { const lino = compiler.getLino(); - if (compiler.nextTokenIs(`to`)) { - compiler.next(); - } - const label = compiler.getToken(); compiler.next(); compiler.addCommand({ domain: `core`, - keyword: `gosub`, - lino, - label + keyword: `return`, + lino }); return true; }, + // runtime + run: program => { - const command = program[program.pc]; - if (program.verifySymbol(command.label)) { - program.stack.push(program.pc + 1); - return program.symbols[command.label].pc; - } - program.runtimeError(command.lino, `Unknown symbol '${command.label}'`); - return 0; + return program.stack.pop(); } }, - If: { + Run: { compile: compiler => { const lino = compiler.getLino(); - compiler.next(); - const condition = compiler.condition.compile(compiler); + const script = compiler.getNextValue(); + const imports = []; + if (compiler.tokenIs(`with`)) { + while (true) { + if (compiler.nextIsSymbol(true)) { + const symbolRecord = compiler.getSymbolRecord(); + imports.push(symbolRecord.name); + compiler.next(); + if (!compiler.tokenIs(`and`)) { + break; + } + } + } + } + let module; + if (compiler.tokenIs(`as`)) { + if (compiler.nextIsSymbol(true)) { + const moduleRecord = compiler.getSymbolRecord(); + // moduleRecord.program = program.script; + compiler.next(); + if (moduleRecord.keyword !== `module`) { + throw new Error(`'${moduleRecord.name}' is not a module`); + } + module = moduleRecord.name; + } + } + let nowait = false; + if (compiler.tokenIs(`nowait`)) { + compiler.next(); + nowait = true; + } const pc = compiler.getPc(); compiler.addCommand({ domain: `core`, - keyword: `if`, + keyword: `run`, lino, - condition + script, + imports, + module, + nowait, + then: 0 }); - // Get the 'then' code - compiler.compileOne(); - if (!compiler.getToken()) { - compiler.getCommandAt(pc).else = compiler.getPc(); - return true; - } - if (compiler.tokenIs(`else`)) { + // Get the 'then' code, if any + if (compiler.tokenIs(`then`)) { const goto = compiler.getPc(); - // Add a 'goto' to skip the 'else' + // Add a 'goto' to skip the 'then' compiler.addCommand({ domain: `core`, keyword: `goto`, - lino, goto: 0 }); - // Fixup the link to the 'else' branch - compiler.getCommandAt(pc).else = compiler.getPc(); - // Process the 'else' branch + // Fixup the link to the 'then' branch + compiler.getCommandAt(pc).then = compiler.getPc(); + // Process the 'then' branch compiler.next(); - // Add the 'else' branch compiler.compileOne(true); + compiler.addCommand({ + domain: `core`, + keyword: `stop` + }); // Fixup the 'goto' compiler.getCommandAt(goto).goto = compiler.getPc(); - } else { - // We're at the next command - compiler.getCommandAt(pc).else = compiler.getPc(); } return true; }, + // runtime + run: program => { - const command = program[program.pc]; - const condition = command.condition; - const test = program.condition.test(program, condition); - if (test) { - return command.pc + 1; - } - return command.else; + program.nextPc = program.pc + 1; + program.runScript(program); + return 0; } }, - Import: { - - compile: compiler => { - const imports = compiler.imports; - let caller = EasyCoder.scripts[imports.caller]; - const program = compiler.getProgram(); - if (imports.length) { - for (const name of imports) { - let symbolRecord = caller.getSymbolRecord(name); - const thisType = compiler.nextToken(); - const exportedType = symbolRecord.keyword; - if (thisType === exportedType) { - const command = compiler.compileVariable(symbolRecord.domain, exportedType, true); - const newRecord = program[compiler.getSymbols()[command.name].pc]; - newRecord.element = symbolRecord.element; - newRecord.exporter = symbolRecord.exporter ? symbolRecord.exporter : caller.script; - newRecord.exportedName = symbolRecord.name; - newRecord.extra = symbolRecord.extra; - newRecord.isVHolder = symbolRecord.isVHolder; - if (symbolRecord.program) { - newRecord.program = symbolRecord.program.script; - } - newRecord.imported = true; - if (!compiler.tokenIs(`and`)) { - break; - } - } else { - throw new Error(`Mismatched import variable type for '${symbolRecord.name}'`); - } - } - if (compiler.tokenIs(`and`)) { - throw new Error(`Imports do not match exports`); - } - } else { - compiler.next(); - } - return true; - }, - - run: program => { - const command = program[program.pc]; - return command.pc + 1; - } - }, - - Index: { - - compile: compiler => { - const lino = compiler.getLino(); - // get the variable - if (compiler.nextIsSymbol(true)) { - const symbol = compiler.getToken(); - if (compiler.nextTokenIs(`to`)) { - // get the value - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `core`, - keyword: `index`, - lino, - symbol, - value - }); - return true; - } - } - return false; - }, - - run: program => { - const command = program[program.pc]; - const symbol = program.getSymbolRecord(command.symbol); - const index = program.getValue(command.value); - if (index >= symbol.elements) { - program.runtimeError(command.lino, - `Array index ${index} is out of range for '${symbol.name}'`); - } - symbol.index = index; - if (symbol.imported) { - const exporterRecord = EasyCoder.symbols[symbol.exporter].getSymbolRecord(symbol.exportedName); - exporterRecord.index = index; - } - return command.pc + 1; - } - }, - - Load: { + Sanitize: { compile: compiler => { const lino = compiler.getLino(); - const type = compiler.nextToken(); - switch (type) { - case `plugin`: - const name = compiler.getNextValue(); + if (compiler.nextIsSymbol()) { + const name = compiler.getToken(); + compiler.next(); compiler.addCommand({ domain: `core`, - keyword: `load`, + keyword: `sanitize`, lino, name }); @@ -1287,29 +1332,25 @@ const EasyCoder_Core = { run: program => { const command = program[program.pc]; - const name = program.getValue(command.name); - switch (command.keyword) { - case `load`: - if (program.checkPlugin(name)) { - return command.pc + 1; - } - EasyCoder_Plugins.getLocalPlugin( - program.getPluginsPath, - name, - program.getPlugin, - program.addLocalPlugin, - function () { - program.run(command.pc + 1); - }); - return 0; - } + const symbolRecord = program.getSymbolRecord(command.name); + const value = symbolRecord.value[symbolRecord.index]; + value.content = JSON.stringify(JSON.parse(value.content)); + return command.pc + 1; } }, - Module: { + Script: { compile: compiler => { - compiler.compileVariable(`core`, `module`); + const program = compiler.getProgram(); + program.script = compiler.nextToken(); + compiler.script = program.script; + if (EasyCoder.scripts[program.script]) { + delete compiler.script; + throw new Error(`Script '${program.script}' is already running.`); + } + EasyCoder.scripts[program.script] = program; + compiler.next(); return true; }, @@ -1318,448 +1359,613 @@ const EasyCoder_Core = { } }, - Multiply: { + Send: { compile: compiler => { const lino = compiler.getLino(); - compiler.next(); - var target; - if (compiler.isSymbol()) { - // It may be the target - const symbol = compiler.getSymbol(); - target = compiler.getCommandAt(symbol.pc).name; - } - // Get the value even if we have a target - const value1 = compiler.getValue(); - if (compiler.tokenIs(`by`)) { - compiler.next(); + let message = ``; + if (!compiler.nextTokenIs(`to`)) { + message = compiler.getValue(); } - // The next item is always a value - const value2 = compiler.getValue(); - // If we now have 'giving' then the target follows - if (compiler.tokenIs(`giving`)) { - compiler.next(); - // Get the target - if (compiler.isSymbol()) { - const symbol = compiler.getSymbol(); - target = compiler.getCommandAt(symbol.pc).name; - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `multiply`, - lino, - value1, - value2, - target - }); - return true; - } - compiler.warning(`core multiply: Expected value holder`); - } else { - // Here we should already have the target. - if (typeof target === `undefined`) { - compiler.warning(`core multiply: No target variable given`); + if (compiler.tokenIs(`to`)) { + var recipient; + if (compiler.nextTokenIs(`parent`)) { + recipient = `parent`; + } else if (compiler.isSymbol) { + const moduleRecord = compiler.getSymbolRecord(); + if (moduleRecord.keyword !== `module`) { + throw new Error(`'${moduleRecord.name}' is not a module`); + } + recipient = moduleRecord.name; } + compiler.next(); compiler.addCommand({ domain: `core`, - keyword: `multiply`, + keyword: `send`, lino, - value2, - target + message, + recipient }); - return true; } - return false; + return true; }, run: program => { const command = program[program.pc]; - const value1 = command.value1; - const value2 = command.value2; - const target = program.getSymbolRecord(command.target); - if (target.isVHolder) { - const value = target.value[target.index]; - if (value1) { - const result = program.getValue(value1) * - program.getValue(value2); - target.value[target.index] = { - type: `constant`, - numeric: true, - content: result - }; - } else { - if (!value.numeric && isNaN(value.content)) { - program.nonNumericValueError(command, lino); + const message = program.getValue(command.message); + if (command.recipient === `parent`) { + if (program.parent) { + const parent = EasyCoder.scripts[program.parent]; + const onMessage = parent.onMessage; + if (onMessage) { + parent.message = message; + parent.run(parent.onMessage); } - const result = parseInt(value.content) * parseInt(program.getValue(value2)); - target.value[target.index] = { - type: `constant`, - numeric: true, - content: result - }; } } else { - program.variableDoesNotHoldAValueError(command.lino, target.name); + const recipient = program.getSymbolRecord(command.recipient); + if (recipient.program) { + let rprog = EasyCoder.scripts[recipient.program]; + rprog.message = message; + rprog.run(rprog.onMessage); + } } return command.pc + 1; } }, - Negate: { + Set: { compile: compiler => { + let name; const lino = compiler.getLino(); - compiler.next(); - if (compiler.isSymbol()) { - const symbol = compiler.getToken(); - compiler.next(); + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (!targetRecord.isVHolder) { + return false; + } + if (compiler.nextTokenIs(`to`)) { + const token = compiler.nextToken(); + if ([`array`, `object`].includes(token)) { + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `set`, + lino, + request: `setVarTo`, + target: targetRecord.name, + type: token + }); + return true; + } + const value = []; + while (true) { + compiler.mark(); + try { + value.push(compiler.getValue()); + } catch (err) { + compiler.rewind(); + break; + } + } + compiler.addCommand({ + domain: `core`, + keyword: `set`, + lino, + request: `setArray`, + target: targetRecord.name, + value + }); + return true; + } compiler.addCommand({ domain: `core`, - keyword: `negate`, + keyword: `set`, lino, - symbol + request: `setBoolean`, + target: targetRecord.name }); return true; } - return false; - }, - - run: program => { - const command = program[program.pc]; - const symbol = program.getSymbolRecord(command.symbol); - if (symbol.isVHolder) { - symbol.value[symbol.index] = { - type: `constant`, - numeric: true, - content: -symbol.value[symbol.index].content - }; - } else { - program.variableDoesNotHoldAValueError(command.lino, symbol.name); - } - return command.pc + 1; - } - }, - - On: { - - compile: compiler => { - const lino = compiler.getLino(); - const action = compiler.nextToken(); - switch (action) { - case `close`: - case `message`: - case `error`: + switch (compiler.getToken()) { + case `ready`: compiler.next(); compiler.addCommand({ domain: `core`, - keyword: `on`, + keyword: `set`, lino, - action + request: `setReady` }); - return compiler.completeHandler(); - } - if (compiler.isSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.keyword === `callback`) { - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `on`, - lino, - action: symbolRecord.name - }); - return compiler.completeHandler(); + return true; + case `element`: + const index = compiler.getNextValue(); + if (compiler.tokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.keyword === `variable`) { + if (compiler.nextTokenIs(`to`)) { + const value = compiler.getNextValue(); + compiler.addCommand({ + domain: `core`, + keyword: `set`, + lino, + request: `setElement`, + target: targetRecord.name, + index, + value + }); + return true; + } + } + } } - } - return false; - }, - - run: program => { - const command = program[program.pc]; - const cb = command.pc + 2; - switch (command.action) { - case `close`: - program.onClose = cb; - break; - case `message`: - program.onMessage = cb; break; - case `error`: - program.onError = cb; + case `property`: + name = compiler.getNextValue(); + if (compiler.tokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.keyword === `variable`) { + if (compiler.nextTokenIs(`to`)) { + const value = compiler.getNextValue(); + compiler.addCommand({ + domain: `core`, + keyword: `set`, + lino, + request: `setProperty`, + target: targetRecord.name, + name, + value + }); + return true; + } + } + } + } break; - default: - const callbacklRecord = program.getSymbolRecord(command.action); - if (callbacklRecord) { - callbacklRecord.cb = cb; - } else { - program.runtimeError(command.lino, `Unknown action '${command.action}'`); - return 0; + case `arg`: + name = compiler.getNextValue(); + if (compiler.tokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (compiler.nextTokenIs(`to`)) { + const value = compiler.getNextValue(); + compiler.addCommand({ + domain: `core`, + keyword: `set`, + lino, + request: `setArg`, + target: targetRecord.name, + name, + value + }); + return true; + } + } } } - return command.pc + 1; - } - }, - - Print: { - - compile: compiler => { - const lino = compiler.getLino(); - compiler.next(); - const value = compiler.getValue(); - compiler.addCommand({ - domain: `core`, - keyword: `print`, - lino, - value - }); - return true; - }, - - run: program => { - const command = program[program.pc]; - const value = program.getFormattedValue(command.value); - console.log(`-> ` + value); - return command.pc + 1; - } - }, - - Put: { - - compile: compiler => { - const lino = compiler.getLino(); - // Get the value - const value = compiler.getNextValue(); - if (compiler.tokenIs(`into`)) { - if (compiler.nextIsSymbol()) { - const target = compiler.getToken(); + if (compiler.tokenIs(`the`)) { + compiler.next(); + } + switch (compiler.getToken()) { + case `elements`: + compiler.next(); + if (compiler.tokenIs(`of`)) { + compiler.next(); + if (!compiler.isSymbol()) { + throw new Error(`Unknown variable '${compiler.getToken()}'`); + } + const symbol = compiler.getToken(); compiler.next(); + if (compiler.tokenIs(`to`)) { + compiler.next(); + // get the value + const value = compiler.getValue(); + compiler.addCommand({ + domain: `core`, + keyword: `set`, + lino, + request: `setElements`, + symbol, + value + }); + return true; + } + } + break; + case `encoding`: + if (compiler.nextTokenIs(`to`)) { + const encoding = compiler.getNextValue(); compiler.addCommand({ domain: `core`, - keyword: `put`, + keyword: `set`, + request: `encoding`, lino, - value, - target + encoding }); return true; } - compiler.warning(`core:put: No such variable: '${compiler.getToken()}'`); + compiler.addWarning(`Unknown encoding option`); + break; + case `payload`: + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const callbackRecord = compiler.getSymbolRecord(); + if (callbackRecord.keyword === `callback`) { + if (compiler.nextTokenIs(`to`)) { + const payload = compiler.getNextValue(); + compiler.addCommand({ + domain: `core`, + keyword: `set`, + request: `setPayload`, + lino, + callback: callbackRecord.name, + payload + }); + return true; + } + } + } + } } return false; }, - // runtime - run: program => { + let targetRecord; const command = program[program.pc]; - const target = program.getSymbolRecord(command.target); - if (!target.isVHolder) { - program.variableDoesNotHoldAValueError(command.lino, target.name); - } - const value = program.evaluate(command.value); - // target.value[target.index] = value; - target.value[target.index] = { - type: value.type, - numeric: value.numeric, - content: value.content - }; - if (target.imported) { - const exporterRecord = EasyCoder.scripts[target.exporter].getSymbolRecord(target.exportedName); - exporterRecord.value[exporterRecord.index] = value; - } - return command.pc + 1; - } - }, - - Replace: { - - compile: compiler => { - const lino = compiler.getLino(); - const original = compiler.getNextValue(); - if (compiler.tokenIs(`with`)) { - const replacement = compiler.getNextValue(); - if (compiler.tokenIs(`in`)) { - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.isVHolder) { - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `replace`, - lino, - original, - replacement, - target: targetRecord.name - }); - return true; - } else { - throw new Error(`'${targetRecord.name}' does not hold a value`); - } + switch (command.request) { + case `setBoolean`: + const target = program.getSymbolRecord(command.target); + if (target.isVHolder) { + target.value[target.index] = { + type: `boolean`, + content: true + }; + command.numeric = false; + } else { + program.variableDoesNotHoldAValueError(command.lino, target.name); + } + break; + case `setReady`: + let parent = EasyCoder.scripts[program.parent]; + if (parent) { + parent.run(parent.nextPc); + parent.nextPc = 0; + program.unblocked = true; + } + break; + case `setArray`: + targetRecord = program.getSymbolRecord(command.target); + targetRecord.elements = command.value.length; + targetRecord.value = command.value; + break; + case `encoding`: + program.encoding = program.getValue(command.encoding); + break; + case `setElements`: + const symbol = program.getSymbolRecord(command.symbol); + const oldCount = symbol.elements; + symbol.elements = program.getValue(command.value); + symbol.index = 0; + if (symbol.elements > oldCount) { + for (var n = oldCount; n < symbol.elements; n++) { + symbol.value.push({}); + symbol.element.push(null); + } + } else { + symbol.value = symbol.value.slice(0, symbol.elements); + symbol.element = symbol.element.slice(0, symbol.elements); + } + break; + case `setElement`: + targetRecord = program.getSymbolRecord(command.target); + const index = program.getValue(command.index); + const elements = JSON.parse(program.getValue(targetRecord.value[targetRecord.index])); + const value = program.getValue(command.value); + elements[index] = JSON.parse(value); + targetRecord.value[targetRecord.index].content = JSON.stringify(elements); + break; + case `setProperty`: + // This is the name of the property + const itemName = program.getValue(command.name); + // This is the value of the property + let itemValue = program.getValue(command.value); + if (program.isJsonString(itemValue)) { + itemValue = JSON.parse(itemValue); + } + targetRecord = program.getSymbolRecord(command.target); + let targetValue = targetRecord.value[targetRecord.index]; + // Get the existing JSON + if (!targetValue.numeric) { + let content = targetValue.content; + if (content === ``) { + content = {}; + } + else if (program.isJsonString(content)) { + content = JSON.parse(content); } + // Set the property + content[itemName] = itemValue; + // Put it back + content = JSON.stringify(content); + targetRecord.value[targetRecord.index] = { + type: `constant`, + numeric: false, + content + }; + } + break; + case `setPayload`: + program.getSymbolRecord(command.callback).payload = program.getValue(command.payload); + break; + case `setArg`: + const name = program.getValue(command.name); + targetRecord = program.getSymbolRecord(command.target); + targetRecord[name] = program.getValue(command.value); + break; + case `setVarTo`: + targetRecord = program.getSymbolRecord(command.target); + targetRecord.value[targetRecord.index] = { + type: `constant`, + numeric: false, + content: command.type === `array` ? `[]` : `{}` + }; + break; + default: + break; + } + return command.pc + 1; + } + }, + + Sort: { + + compile: compiler => { + const lino = compiler.getLino(); + if (compiler.nextIsSymbol()) { + const arrayRecord = compiler.getSymbolRecord(); + if (compiler.nextTokenIs(`with`)) { + const func = compiler.nextToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `sort`, + lino, + array: arrayRecord.name, + func + }); + return true; } } return false; }, - // runtime - run: program => { const command = program[program.pc]; - const original = program.getValue(command.original); - const replacement = program.getValue(command.replacement); - const target = program.getSymbolRecord(command.target); - const value = program.getValue(target.value[target.index]); - const content = value.split(original).join(replacement); - target.value[target.index] = { - type: `constant`, - numeric: false, - content - }; + const variable = program.getSymbolRecord(command.array); + const value = variable.value[variable.index].content; + const func = program.getSymbolRecord(command.func).pc; + try { + const array = JSON.parse(value); + array.sort(function (a, b) { + variable.a = a; + variable.b = b; + program.run(func); + return variable.v; + }); + variable.value[variable.index].content = JSON.stringify(array); + } catch (err) { + program.runtimeError(command.lino, `Can't parse this array`); + } return command.pc + 1; } }, - Require: { + Split: { compile: compiler => { const lino = compiler.getLino(); - const type = compiler.nextToken(); - if ([`css`, `js`].includes(type)) { - const url = compiler.getNextValue(); - compiler.addCommand({ - domain: `core`, - keyword: `require`, - lino, - type, - url - }); - return true; + item = compiler.getNextValue(); + let on = `\n`; + if (compiler.tokenIs(`on`)) { + on = compiler.getNextValue(); } - throw new Error(`File type must be 'css' or 'js'`); + if ([`giving`, `into`].includes(compiler.getToken())) { + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.keyword === `variable`) { + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `split`, + lino, + item, + on, + target: targetRecord.name + }); + return true; + } + } + } + return false; }, - // runtime - run: program => { - const command = program[program.pc]; - program.require(command.type, program.getValue(command.url), - function () { - program.run(command.pc + 1); - }); - return 0; + let command = program[program.pc]; + let content = program.getValue(command.item); + let on = program.getValue(command.on); + content = content.split(on); + let elements = content.length; + targetRecord = program.getSymbolRecord(command.target); + targetRecord.elements = elements; + for (let n = 0; n < elements; n++) { + targetRecord.value[n] = { + type: `constant`, + numeric: false, + content: content[n] + }; + } + targetRecord.index = 0; + return command.pc + 1; } }, - Return: { + Stop: { compile: compiler => { const lino = compiler.getLino(); compiler.next(); + if (compiler.more() && compiler.isSymbol() && !compiler.getToken().endsWith(`:`)) { + const symbolRecord = compiler.getSymbolRecord(); + if (symbolRecord.keyword === `module`) { + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `stop`, + lino, + name: symbolRecord.name + }); + return true; + } else { + return false; + } + } compiler.addCommand({ domain: `core`, - keyword: `return`, - lino + keyword: `stop`, + lino, + next: 0 }); return true; }, - // runtime - run: program => { - return program.stack.pop(); + const command = program[program.pc]; + if (command.name) { + const symbolRecord = program.getSymbolRecord(command.name); + EasyCoder.scripts[symbolRecord.program].exit(); + symbolRecord.program = null; + } else { + return 0; + } + return command.pc + 1; } }, - Run: { + Take: { compile: compiler => { const lino = compiler.getLino(); - const script = compiler.getNextValue(); - const imports = []; - if (compiler.tokenIs(`with`)) { - while (true) { - if (compiler.nextIsSymbol(true)) { - const symbolRecord = compiler.getSymbolRecord(); - imports.push(symbolRecord.name); - compiler.next(); - if (!compiler.tokenIs(`and`)) { - break; + compiler.next(); + // Get the (first) value + const value1 = compiler.getValue(); + if (compiler.tokenIs(`from`)) { + compiler.next(); + if (compiler.isSymbol()) { + const symbol = compiler.getSymbol(); + const variable = compiler.getCommandAt(symbol.pc); + if (variable.isVHolder) { + if (compiler.peek() === `giving`) { + // This variable must be treated as a second value + const value2 = compiler.getValue(); + compiler.next(); + const target = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `take`, + lino, + value1, + value2, + target + }); + } else { + // Here the variable is the target. + const target = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `take`, + lino, + value1, + target + }); } + return true; + } else { + compiler.warning(`core 'take'': Expected value holder`); } - } - } - let module; - if (compiler.tokenIs(`as`)) { - if (compiler.nextIsSymbol(true)) { - const moduleRecord = compiler.getSymbolRecord(); - // moduleRecord.program = program.script; - compiler.next(); - if (moduleRecord.keyword !== `module`) { - throw new Error(`'${moduleRecord.name}' is not a module`); + } else { + // Here we have 2 values so 'giving' must come next + const value2 = compiler.getValue(); + if (compiler.tokenIs(`giving`)) { + compiler.next(); + const target = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `core`, + keyword: `take`, + lino, + value1, + value2, + target + }); + return true; + } else { + compiler.warning(`core 'take'': Expected "giving"`); } - module = moduleRecord.name; } } - let nowait = false; - if (compiler.tokenIs(`nowait`)) { - compiler.next(); - nowait = true; - } - const pc = compiler.getPc(); - compiler.addCommand({ - domain: `core`, - keyword: `run`, - lino, - script, - imports, - module, - nowait, - then: 0 - }); - // Get the 'then' code, if any - if (compiler.tokenIs(`then`)) { - const goto = compiler.getPc(); - // Add a 'goto' to skip the 'then' - compiler.addCommand({ - domain: `core`, - keyword: `goto`, - goto: 0 - }); - // Fixup the link to the 'then' branch - compiler.getCommandAt(pc).then = compiler.getPc(); - // Process the 'then' branch - compiler.next(); - compiler.compileOne(true); - compiler.addCommand({ - domain: `core`, - keyword: `stop` - }); - // Fixup the 'goto' - compiler.getCommandAt(goto).goto = compiler.getPc(); - } - return true; + return false; }, - // runtime - run: program => { - program.nextPc = program.pc + 1; - program.runScript(program); - return 0; + const command = program[program.pc]; + const value1 = command.value1; + const value2 = command.value2; + const target = program.getSymbolRecord(command.target); + if (target.isVHolder) { + const value = target.value[target.index]; + if (value2) { + const result = program.getValue(value2) - + program.getValue(value1); + target.value[target.index] = { + type: `constant`, + numeric: true, + content: result + }; + } else { + if (!value.numeric && isNaN(value.content)) { + program.nonNumericValueError(command.lino); + } + const result = parseInt(program.getValue(value)) - parseInt(program.getValue(value1)); + target.value[target.index] = { + type: `constant`, + numeric: true, + content: result + }; + } + } else { + program.variableDoesNotHoldAValueError(command.lino, target.name); + } + return command.pc + 1; } }, - Sanitize: { + Toggle: { compile: compiler => { const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const name = compiler.getToken(); + compiler.next(); + if (compiler.isSymbol()) { + const symbolPc = compiler.getSymbolPc(); compiler.next(); compiler.addCommand({ domain: `core`, - keyword: `sanitize`, + keyword: `toggle`, lino, - name + symbol: symbolPc }); return true; } @@ -1768,25 +1974,25 @@ const EasyCoder_Core = { run: program => { const command = program[program.pc]; - const symbolRecord = program.getSymbolRecord(command.name); - const value = symbolRecord.value[symbolRecord.index]; - value.content = JSON.stringify(JSON.parse(value.content)); + const symbol = program[command.symbol]; + if (symbol.isVHolder) { + const handler = program.domain[symbol.domain]; + const content = handler.value.get(program, symbol.value[symbol.index]).content; + handler.value.put(symbol, { + type: `boolean`, + content: !content + }); + } else { + program.variableDoesNotHoldAValueError(command.lino, symbol.name); + } return command.pc + 1; } }, - Script: { + Variable: { compile: compiler => { - const program = compiler.getProgram(); - program.script = compiler.nextToken(); - compiler.script = program.script; - if (EasyCoder.scripts[program.script]) { - delete compiler.script; - throw new Error(`Script '${program.script}' is already running.`); - } - EasyCoder.scripts[program.script] = program; - compiler.next(); + compiler.compileVariable(`core`, `variable`, true); return true; }, @@ -1795,2566 +2001,6292 @@ const EasyCoder_Core = { } }, - Send: { + Wait: { compile: compiler => { const lino = compiler.getLino(); - let message = ``; - if (!compiler.nextTokenIs(`to`)) { - message = compiler.getValue(); - } - if (compiler.tokenIs(`to`)) { - var recipient; - if (compiler.nextTokenIs(`parent`)) { - recipient = `parent`; - } else if (compiler.isSymbol) { - const moduleRecord = compiler.getSymbolRecord(); - if (moduleRecord.keyword !== `module`) { - throw new Error(`'${moduleRecord.name}' is not a module`); - } - recipient = moduleRecord.name; - } + compiler.next(); + const value = compiler.getValue(compiler); + const scale = compiler.getToken(); + let multiplier = 1000; + switch (scale) { + case `milli`: + case `millis`: compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `send`, - lino, - message, - recipient - }); + multiplier = 1; + break; + case `tick`: + case `ticks`: + compiler.next(); + multiplier = 10; + break; + case `second`: + case `seconds`: + compiler.next(); + multiplier = 1000; + break; + case `minute`: + case `minutes`: + compiler.next(); + multiplier = 60000; + break; } + compiler.addCommand({ + domain: `core`, + keyword: `wait`, + lino, + value, + multiplier + }); return true; }, run: program => { const command = program[program.pc]; - const message = program.getValue(command.message); - if (command.recipient === `parent`) { - if (program.parent) { - const parent = EasyCoder.scripts[program.parent]; - const onMessage = parent.onMessage; - if (onMessage) { - parent.message = message; - parent.run(parent.onMessage); - } + const value = program.getValue(command.value); + setTimeout(function () { + if (program.run) { + program.run(command.pc + 1); } - } else { - const recipient = program.getSymbolRecord(command.recipient); - if (recipient.program) { - let rprog = EasyCoder.scripts[recipient.program]; - rprog.message = message; - rprog.run(rprog.onMessage); - } - } - return command.pc + 1; + }, value * command.multiplier); + return 0; } }, - Set: { + While: { compile: compiler => { - let name; const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (!targetRecord.isVHolder) { - return false; - } - if (compiler.nextTokenIs(`to`)) { - const token = compiler.nextToken(); - if ([`array`, `object`].includes(token)) { - compiler.next(); - compiler.addCommand({ + compiler.next(); + const condition = compiler.getCondition(); + const pc = compiler.getPc(); + compiler.addCommand({ + domain: `core`, + keyword: `while`, + lino, + condition + }); + // Skip when test fails + const skip = compiler.getPc(); + compiler.addCommand({ + domain: `core`, + keyword: `goto`, + goto: 0 + }); + // Do the body + compiler.compileOne(); + // Repeat the test + compiler.addCommand({ + domain: `core`, + keyword: `goto`, + goto: pc + }); + // Fixup the 'goto' on completion + compiler.getCommandAt(skip).goto = compiler.getPc(); + return true; + }, + + run: program => { + const command = program[program.pc]; + const condition = command.condition; + const test = program.condition.test(program, condition); + if (test) { + return program.pc + 2; + } + return program.pc + 1; + } + }, + + getHandler: (name) => { + switch (name) { + case `add`: + return EasyCoder_Core.Add; + case `alias`: + return EasyCoder_Core.Alias; + case `append`: + return EasyCoder_Core.Append; + case `begin`: + return EasyCoder_Core.Begin; + case `callback`: + return EasyCoder_Core.Callback; + case `clear`: + return EasyCoder_Core.Clear; + case `close`: + return EasyCoder_Core.Close; + case `continue`: + return EasyCoder_Core.Continue; + case `debug`: + return EasyCoder_Core.Debug; + case `decode`: + return EasyCoder_Core.Decode; + case `divide`: + return EasyCoder_Core.Divide; + case `dummy`: + return EasyCoder_Core.Dummy; + case `encode`: + return EasyCoder_Core.Encode; + case `end`: + return EasyCoder_Core.End; + case `exit`: + return EasyCoder_Core.Exit; + case `filter`: + return EasyCoder_Core.Filter; + case `fork`: + return EasyCoder_Core.Fork; + case `go`: + case `goto`: + return EasyCoder_Core.Go; + case `gosub`: + return EasyCoder_Core.Gosub; + case `if`: + return EasyCoder_Core.If; + case `import`: + return EasyCoder_Core.Import; + case `index`: + return EasyCoder_Core.Index; + case `module`: + return EasyCoder_Core.Module; + case `multiply`: + return EasyCoder_Core.Multiply; + case `negate`: + return EasyCoder_Core.Negate; + case `on`: + return EasyCoder_Core.On; + case `print`: + return EasyCoder_Core.Print; + case `put`: + return EasyCoder_Core.Put; + case `replace`: + return EasyCoder_Core.Replace; + case `require`: + return EasyCoder_Core.Require; + case `return`: + return EasyCoder_Core.Return; + case `run`: + return EasyCoder_Core.Run; + case `sanitize`: + return EasyCoder_Core.Sanitize; + case `script`: + return EasyCoder_Core.Script; + case `send`: + return EasyCoder_Core.Send; + case `set`: + return EasyCoder_Core.Set; + case `sort`: + return EasyCoder_Core.Sort; + case `split`: + return EasyCoder_Core.Split; + case `stop`: + return EasyCoder_Core.Stop; + case `take`: + return EasyCoder_Core.Take; + case `toggle`: + return EasyCoder_Core.Toggle; + case `variable`: + return EasyCoder_Core.Variable; + case `wait`: + return EasyCoder_Core.Wait; + case `while`: + return EasyCoder_Core.While; + default: + return false; + } + }, + + run: program => { + // Look up the appropriate handler and call it + // If it's not there throw an error + const command = program[program.pc]; + const handler = EasyCoder_Core.getHandler(command.keyword); + if (!handler) { + program.runtimeError(command.lino, + `Unknown keyword '${command.keyword}' in 'core' package`); + } + return handler.run(program); + }, + + isNegate: (compiler) => { + const token = compiler.getToken(); + if (token === `not`) { + compiler.next(); + return true; + } + return false; + }, + + value: { + + compile: compiler => { + if (compiler.isSymbol()) { + const name = compiler.getToken(); + const symbolRecord = compiler.getSymbolRecord(); + switch (symbolRecord.keyword) { + case `module`: + compiler.next(); + return { + domain: `core`, + type: `module`, + name + }; + case `variable`: + const type = compiler.nextToken(); + if ([`format`, `modulo`].includes(type)) { + const value = compiler.getNextValue(); + return { domain: `core`, - keyword: `set`, - lino, - request: `setVarTo`, - target: targetRecord.name, - type: token - }); - return true; - } - const value = []; - while (true) { - compiler.mark(); - try { - value.push(compiler.getValue()); - } catch (err) { - compiler.rewind(); - break; - } + type, + name, + value + }; } - compiler.addCommand({ + return { domain: `core`, - keyword: `set`, - lino, - request: `setArray`, - target: targetRecord.name, - value - }); - return true; + type: `symbol`, + name + }; } - compiler.addCommand({ + return null; + } + + var token = compiler.getToken(); + if (token === `true`) { + compiler.next(); + return { domain: `core`, - keyword: `set`, - lino, - request: `setBoolean`, - target: targetRecord.name - }); - return true; + type: `boolean`, + content: true + }; } - switch (compiler.getToken()) { - case `ready`: + if (token === `false`) { compiler.next(); - compiler.addCommand({ + return { domain: `core`, - keyword: `set`, - lino, - request: `setReady` - }); - return true; - case `element`: - const index = compiler.getNextValue(); + type: `boolean`, + content: false + }; + } + if (token === `random`) { + compiler.next(); + const range = compiler.getValue(); + return { + domain: `core`, + type: `random`, + range + }; + } + if (token === `cos`) { + compiler.next(); + const angle_c = compiler.getValue(); + compiler.skip(`radius`); + const radius_c = compiler.getValue(); + return { + domain: `core`, + type: `cos`, + angle_c, + radius_c + }; + } + if (token === `sin`) { + compiler.next(); + const angle_s = compiler.getValue(); + compiler.skip(`radius`); + const radius_s = compiler.getValue(); + return { + domain: `core`, + type: `sin`, + angle_s, + radius_s + }; + } + if (token === `tan`) { + compiler.next(); + const angle_t = compiler.getValue(); + compiler.skip(`radius`); + const radius_t = compiler.getValue(); + return { + domain: `core`, + type: `tan`, + angle_t, + radius_t + }; + } + if ([`now`, `today`, `newline`, `break`, `empty`, `uuid`].includes(token)) { + compiler.next(); + return { + domain: `core`, + type: token + }; + } + if (token === `date`) { + const value = compiler.getNextValue(); + return { + domain: `core`, + type: `date`, + value + }; + } + if ([`encode`, `decode`, `lowercase`, `hash`, `reverse`].includes(token)) { + compiler.next(); + const value = compiler.getValue(); + return { + domain: `core`, + type: token, + value + }; + } + if (token === `element`) { + const element = compiler.getNextValue(); if (compiler.tokenIs(`of`)) { if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.keyword === `variable`) { - if (compiler.nextTokenIs(`to`)) { - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `core`, - keyword: `set`, - lino, - request: `setElement`, - target: targetRecord.name, - index, - value - }); - return true; - } + const symbolRecord = compiler.getSymbolRecord(); + compiler.next(); + if (symbolRecord.keyword === `variable`) { + return { + domain: `core`, + type: `element`, + element, + symbol: symbolRecord.name + }; } } } - break; - case `property`: - name = compiler.getNextValue(); + return null; + } + if (token === `property`) { + const property = compiler.getNextValue(); if (compiler.tokenIs(`of`)) { if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.keyword === `variable`) { - if (compiler.nextTokenIs(`to`)) { - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `core`, - keyword: `set`, - lino, - request: `setProperty`, - target: targetRecord.name, - name, - value - }); - return true; - } + const symbolRecord = compiler.getSymbolRecord(); + compiler.next(); + if (symbolRecord.keyword === `variable`) { + return { + domain: `core`, + type: `property`, + property, + symbol: symbolRecord.name + }; } } } - break; - case `arg`: - name = compiler.getNextValue(); + return null; + } + if (token === `arg`) { + const value = compiler.getNextValue(); if (compiler.tokenIs(`of`)) { if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (compiler.nextTokenIs(`to`)) { - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `core`, - keyword: `set`, - lino, - request: `setArg`, - target: targetRecord.name, - name, - value - }); - return true; - } + const target = compiler.getSymbolRecord(); + compiler.next(); + return { + domain: `core`, + type: `arg`, + value, + target: target.name + }; } } } + if ([`character`, `char`].includes(token)) { + let index = compiler.getNextValue(); + if (compiler.tokenIs(`of`)) { + let value = compiler.getNextValue(); + return { + domain: `core`, + type: `char`, + index, + value + }; + } + } if (compiler.tokenIs(`the`)) { compiler.next(); } - switch (compiler.getToken()) { + const type = compiler.getToken(); + switch (type) { case `elements`: - compiler.next(); - if (compiler.tokenIs(`of`)) { - compiler.next(); - if (!compiler.isSymbol()) { - throw new Error(`Unknown variable '${compiler.getToken()}'`); - } - const symbol = compiler.getToken(); - compiler.next(); - if (compiler.tokenIs(`to`)) { + if ([`of`, `in`].includes(compiler.nextToken())) { + if (compiler.nextIsSymbol()) { + const name = compiler.getToken(); compiler.next(); - // get the value - const value = compiler.getValue(); - compiler.addCommand({ + return { domain: `core`, - keyword: `set`, - lino, - request: `setElements`, - symbol, - value - }); - return true; + type, + name + }; } } break; - case `encoding`: - if (compiler.nextTokenIs(`to`)) { - const encoding = compiler.getNextValue(); - compiler.addCommand({ + case `index`: + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + if (compiler.peek() === `in`) { + const value1 = compiler.getValue(); + const value2 = compiler.getNextValue(); + return { + domain: `core`, + type: `indexOf`, + value1, + value2 + }; + } else { + const name = compiler.getToken(); + compiler.next(); + return { + domain: `core`, + type, + name + }; + } + } else { + const value1 = compiler.getValue(); + if (compiler.tokenIs(`in`)) { + const value2 = compiler.getNextValue(); + return { + domain: `core`, + type: `indexOf`, + value1, + value2 + }; + } + } + } + break; + case `value`: + if (compiler.nextTokenIs(`of`)) { + compiler.next(); + const value = compiler.getValue(); + return { domain: `core`, - keyword: `set`, - request: `encoding`, - lino, - encoding - }); - return true; + type: `valueOf`, + value + }; } - compiler.addWarning(`Unknown encoding option`); break; - case `payload`: + case `length`: if (compiler.nextTokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const callbackRecord = compiler.getSymbolRecord(); - if (callbackRecord.keyword === `callback`) { - if (compiler.nextTokenIs(`to`)) { - const payload = compiler.getNextValue(); - compiler.addCommand({ - domain: `core`, - keyword: `set`, - request: `setPayload`, - lino, - callback: callbackRecord.name, - payload - }); - return true; - } - } - } - } - } - return false; - }, - - run: program => { - let targetRecord; - const command = program[program.pc]; - switch (command.request) { - case `setBoolean`: - const target = program.getSymbolRecord(command.target); - if (target.isVHolder) { - target.value[target.index] = { - type: `boolean`, - content: true + compiler.next(); + const value = compiler.getValue(); + return { + domain: `core`, + type: `lengthOf`, + value }; - command.numeric = false; - } else { - program.variableDoesNotHoldAValueError(command.lino, target.name); - } - break; - case `setReady`: - let parent = EasyCoder.scripts[program.parent]; - if (parent) { - parent.run(parent.nextPc); - parent.nextPc = 0; - program.unblocked = true; } break; - case `setArray`: - targetRecord = program.getSymbolRecord(command.target); - targetRecord.elements = command.value.length; - targetRecord.value = command.value; - break; - case `encoding`: - program.encoding = program.getValue(command.encoding); - break; - case `setElements`: - const symbol = program.getSymbolRecord(command.symbol); - const oldCount = symbol.elements; - symbol.elements = program.getValue(command.value); - symbol.index = 0; - if (symbol.elements > oldCount) { - for (var n = oldCount; n < symbol.elements; n++) { - symbol.value.push({}); - symbol.element.push(null); + case `left`: + case `right`: + try { + const count = compiler.getNextValue(); + if (compiler.tokenIs(`of`)) { + const value = compiler.getNextValue(); + return { + domain: `core`, + type, + count, + value + }; } - } else { - symbol.value = symbol.value.slice(0, symbol.elements); - symbol.element = symbol.element.slice(0, symbol.elements); + } catch (err) { + return null; } break; - case `setElement`: - targetRecord = program.getSymbolRecord(command.target); - const index = program.getValue(command.index); - const elements = JSON.parse(program.getValue(targetRecord.value[targetRecord.index])); - const value = program.getValue(command.value); - elements[index] = JSON.parse(value); - targetRecord.value[targetRecord.index].content = JSON.stringify(elements); + case `from`: + const from = compiler.getNextValue(); + const to = compiler.tokenIs(`to`) ? compiler.getNextValue() : null; + if (compiler.tokenIs(`of`)) { + const value = compiler.getNextValue(); + return { + domain: `core`, + type, + from, + to, + value + }; + } break; - case `setProperty`: - // This is the name of the property - const itemName = program.getValue(command.name); - // This is the value of the property - let itemValue = program.getValue(command.value); - if (program.isJsonString(itemValue)) { - itemValue = JSON.parse(itemValue); + case `position`: + let nocase = false; + if (compiler.nextTokenIs(`nocase`)) { + nocase = true; + compiler.next(); } - targetRecord = program.getSymbolRecord(command.target); - let targetValue = targetRecord.value[targetRecord.index]; - // Get the existing JSON - if (!targetValue.numeric) { - let content = targetValue.content; - if (content === ``) { - content = {}; + if (compiler.tokenIs(`of`)) { + var last = false; + if (compiler.nextTokenIs(`the`)) { + if (compiler.nextTokenIs(`last`)) { + compiler.next(); + last = true; + } } - else if (program.isJsonString(content)) { - content = JSON.parse(content); + const needle = compiler.getValue(); + if (compiler.tokenIs(`in`)) { + const haystack = compiler.getNextValue(); + return { + domain: `core`, + type: `position`, + needle, + haystack, + last, + nocase + }; } - // Set the property - content[itemName] = itemValue; - // Put it back - content = JSON.stringify(content); - targetRecord.value[targetRecord.index] = { - type: `constant`, - numeric: false, - content - }; } break; - case `setPayload`: - program.getSymbolRecord(command.callback).payload = program.getValue(command.payload); - break; - case `setArg`: - const name = program.getValue(command.name); - targetRecord = program.getSymbolRecord(command.target); - targetRecord[name] = program.getValue(command.value); + case `payload`: + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const callbackRecord = compiler.getSymbolRecord(); + if (callbackRecord.keyword === `callback`) { + compiler.next(); + return { + domain: `core`, + type: `payload`, + callback: callbackRecord.name + }; + } + } + } break; - case `setVarTo`: - targetRecord = program.getSymbolRecord(command.target); - targetRecord.value[targetRecord.index] = { - type: `constant`, - numeric: false, - content: command.type === `array` ? `[]` : `{}` + case `message`: + case `error`: + case `millisecond`: + compiler.next(); + return { + domain: `core`, + type }; - break; - default: - break; - } - return command.pc + 1; - } - }, - Sort: { - - compile: compiler => { - const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const arrayRecord = compiler.getSymbolRecord(); - if (compiler.nextTokenIs(`with`)) { - const func = compiler.nextToken(); - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `sort`, - lino, - array: arrayRecord.name, - func - }); - return true; - } } - return false; + return null; }, - run: program => { - const command = program[program.pc]; - const variable = program.getSymbolRecord(command.array); - const value = variable.value[variable.index].content; - const func = program.getSymbolRecord(command.func).pc; - try { - const array = JSON.parse(value); - array.sort(function (a, b) { - variable.a = a; - variable.b = b; - program.run(func); - return variable.v; - }); - variable.value[variable.index].content = JSON.stringify(array); - } catch (err) { - program.runtimeError(command.lino, `Can't parse this array`); - } - return command.pc + 1; - } - }, - - Split: { - - compile: compiler => { - const lino = compiler.getLino(); - item = compiler.getNextValue(); - let on = `\n`; - if (compiler.tokenIs(`on`)) { - on = compiler.getNextValue(); - } - if ([`giving`, `into`].includes(compiler.getToken())) { - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.keyword === `variable`) { - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `split`, - lino, - item, - on, - target: targetRecord.name - }); - return true; + get: (program, value) => { + switch (value.type) { + case `boolean`: + return { + type: `boolean`, + numeric: false, + content: value.content + }; + case `elements`: + return { + type: `constant`, + numeric: true, + content: program.getSymbolRecord(value.name).elements + }; + case `index`: + return { + type: `constant`, + numeric: true, + content: program.getSymbolRecord(value.name).index + }; + case `random`: + const range = program.evaluate(value.range); + return { + type: `constant`, + numeric: true, + content: Math.floor((Math.random() * range.content)) + }; + case `cos`: + const angle_c = program.getValue(value.angle_c); + const radius_c = program.getValue(value.radius_c); + return { + type: `constant`, + numeric: true, + content: parseInt(Math.cos(parseFloat(angle_c) * 0.01745329) * radius_c, 10) + }; + case `sin`: + const angle_s = program.getValue(value.angle_s); + const radius_s = program.getValue(value.radius_s); + return { + type: `constant`, + numeric: true, + content: parseInt(Math.sin(parseFloat(angle_s) * 0.01745329) * radius_s, 10) + }; + case `tan`: + const angle_t = program.getValue(value.angle_t); + const radius_t = program.getValue(value.radius_t); + return { + type: `constant`, + numeric: true, + content: parseInt(Math.tan(parseFloat(angle_t) * 0.01745329) * radius_t, 10) + }; + case `valueOf`: + const v = parseInt(program.getValue(value.value)); + return { + type: `constant`, + numeric: true, + content: v ? v : 0 + }; + case `lengthOf`: + return { + type: `constant`, + numeric: true, + content: program.getValue(value.value).length + }; + case `left`: + return { + type: `constant`, + numeric: false, + content: program.getValue(value.value).substr(0, program.getValue(value.count)) + }; + case `right`: + const str = program.getValue(value.value); + return { + type: `constant`, + numeric: false, + content: str.substr(str.length - program.getValue(value.count)) + }; + case `from`: + const from = program.getValue(value.from); + const to = value.to ? program.getValue(value.to) : null; + const fstr = program.getValue(value.value); + return { + type: `constant`, + numeric: false, + content: to ? fstr.substr(from, to) : fstr.substr(from) + }; + case `position`: + let needle = program.getValue(value.needle); + let haystack = program.getValue(value.haystack); + if (value.nocase) { + needle = needle.toLowerCase(); + haystack = haystack.toLowerCase(); + } + return { + type: `constant`, + numeric: true, + content: value.last ? haystack.lastIndexOf(needle) : haystack.indexOf(needle) + }; + case `payload`: + return { + type: `constant`, + numeric: false, + content: program.getSymbolRecord(value.callback).payload + }; + case `modulo`: + const symbolRecord = program.getSymbolRecord(value.name); + const modval = program.evaluate(value.value); + return { + type: `constant`, + numeric: true, + content: symbolRecord.value[symbolRecord.index].content % modval.content + }; + case `format`: + const fmtRecord = program.getSymbolRecord(value.name); + const fmtValue = program.getValue(fmtRecord.value[fmtRecord.index]) * 1000; + try { + const spec = JSON.parse(program.getValue(value.value)); + switch (spec.mode) { + case `time`: + + return { + type: `constant`, + numeric: true, + content: new Date(fmtValue).toLocaleTimeString(spec.locale, spec.options) + }; + case `date`: + default: + const date = new Date(fmtValue); + const content = (spec.format === `iso`) + ? `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()}` + : date.toLocaleDateString(spec.locale, spec.options); + return { + type: `constant`, + numeric: true, + content + }; + } + } catch (err) { + program.runtimeError(program[program.pc].lino, `Can't parse ${value.value}`); + return null; + } + case `empty`: + return { + type: `constant`, + numeric: false, + content: `` + }; + case `now`: + return { + type: `constant`, + numeric: true, + content: Math.floor(Date.now() / 1000) + }; + case `millisecond`: + return { + type: `constant`, + numeric: true, + content: Math.floor(Date.now()) + }; + case `today`: + const date = new Date(); + date.setHours(0, 0, 0, 0); + return { + type: `constant`, + numeric: true, + content: Math.floor(date.getTime() / 1000) + }; + case `date`: + content = Date.parse(program.getValue(value.value)) / 1000; + if (isNaN(content)) { + program.runtimeError(program[program.pc].lino, `Invalid date format; expecting 'yyyy-mm-dd'`); + return null; + } + return { + type: `constant`, + numeric: true, + content + }; + case `newline`: + return { + type: `constant`, + numeric: false, + content: `\n` + }; + case `break`: + return { + type: `constant`, + numeric: false, + content: `
` + }; + case `uuid`: + return { + type: `constant`, + numeric: false, + content: `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function(c) { + var r = Math.random() * 16 | 0, v = c == `x` ? r : (r & 0x3 | 0x8); + return v.toString(16); + }) + }; + case `encode`: + return { + type: `constant`, + numeric: false, + content: program.encode(program.getValue(value.value)) + }; + case `decode`: + return { + type: `constant`, + numeric: false, + content: program.decode(program.getValue(value.value)) + }; + case `reverse`: + return { + type: `constant`, + numeric: false, + content: program.getValue(value.value).split(``).reverse().join(``) + }; + case `lowercase`: + return { + type: `constant`, + numeric: false, + content: program.getValue(value.value).toLowerCase() + }; + case `hash`: + const hashval = program.getValue(value.value); + let hash = 0; + if (hashval.length === 0) return hash; + for (let i = 0; i < hashval.length; i++) { + const chr = hashval.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + // hash |= 0; // Convert to 32bit integer + } + return { + type: `constant`, + numeric: true, + content: hash + }; + case `element`: + const element = program.getValue(value.element); + const elementRecord = program.getSymbolRecord(value.symbol); + var elementContent = ``; + try { + elementContent = JSON.parse(program.getValue(elementRecord.value[elementRecord.index]))[element]; + } catch (err) { + program.runtimeError(program[program.pc].lino, `Can't parse JSON`); + return null; + } + return { + type: `constant`, + numeric: false, + content: typeof elementContent === `object` ? + JSON.stringify(elementContent) : elementContent + }; + case `property`: + const property = program.getValue(value.property); + const propertyRecord = program.getSymbolRecord(value.symbol); + let propertyContent = program.getValue(propertyRecord.value[propertyRecord.index]); + var content = ``; + if (property && propertyContent) { + if (typeof propertyContent === `object`) { + content = propertyContent[property]; + } else if ([`{`, `]`].includes(propertyContent.charAt(0))) { + try { + content = JSON.parse(propertyContent)[property]; + } catch (err) { + console.log(`Can't parse '${propertyContent}': ${err.message}`); + } + } + } + return { + type: `constant`, + numeric: !Array.isArray(content) && !isNaN(content), + content: typeof content === `object` ? JSON.stringify(content) : content + }; + case `module`: + const module = program.getSymbolRecord(value.name); + return { + type: `boolean`, + numeric: false, + content: module.program + }; + case `message`: + content = program.message; + return { + type: `constant`, + numeric: false, + content + }; + case `error`: + content = program.errorMessage; + return { + type: `constant`, + numeric: false, + content + }; + case `indexOf`: + const value1 = program.getValue(value.value1); + const value2 = program.getValue(value.value2); + try { + content = JSON.parse(value2).indexOf(value1); + return { + type: `constant`, + numeric: true, + content + }; + } catch (err) { + program.runtimeError(program[program.pc].lino, `Can't parse ${value2}`); + } + break; + case `arg`: + const name = program.getValue(value.value); + const target = program.getSymbolRecord(value.target); + content = target[name]; + return { + type: `constant`, + numeric: !isNaN(content), + content + }; + case `char`: + let index = program.getValue(value.index); + let string = program.getValue(value.value); + return { + type: `constant`, + numeric: false, + content: string[index] + }; + } + return null; + }, + + put: (symbol, value) => { + symbol.value[symbol.index] = value; + } + }, + + condition: { + + compile: compiler => { + if (compiler.isSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + if (symbolRecord.keyword === `module`) { + if (compiler.nextTokenIs(`is`)) { + let sense = true; + if (compiler.nextTokenIs(`not`)) { + compiler.next(); + sense = false; + } + if (compiler.tokenIs(`running`)) { + compiler.next(); + return { + domain: `core`, + type: `moduleRunning`, + name: symbolRecord.name, + sense + }; + } + } + return null; + } + } + if (compiler.tokenIs(`not`)) { + const value = compiler.getNextValue(); + return { + domain: `core`, + type: `not`, + value + }; + } + try { + const value1 = compiler.getValue(); + const token = compiler.getToken(); + if (token === `includes`) { + const value2 = compiler.getNextValue(); + return { + domain: `core`, + type: `includes`, + value1, + value2 + }; + } + if (token === `is`) { + compiler.next(); + const negate = EasyCoder_Core.isNegate(compiler); + const test = compiler.getToken(); + switch (test) { + case `numeric`: + compiler.next(); + return { + domain: `core`, + type: `numeric`, + value1, + negate + }; + case `even`: + compiler.next(); + return { + domain: `core`, + type: `even`, + value1 + }; + case `odd`: + compiler.next(); + return { + domain: `core`, + type: `odd`, + value1 + }; + case `greater`: + compiler.next(); + if (compiler.tokenIs(`than`)) { + compiler.next(); + const value2 = compiler.getValue(); + return { + domain: `core`, + type: `greater`, + value1, + value2, + negate + }; + } + return null; + case `less`: + compiler.next(); + if (compiler.tokenIs(`than`)) { + compiler.next(); + const value2 = compiler.getValue(); + return { + domain: `core`, + type: `less`, + value1, + value2, + negate + }; + } + return null; + default: + const value2 = compiler.getValue(); + return { + domain: `core`, + type: `is`, + value1, + value2, + negate + }; + } + } else if (value1) { + // It's a boolean if + return { + domain: `core`, + type: `boolean`, + value: value1 + }; + } + } catch (err) { + compiler.warning(`Can't get a value`); + return 0; + } + return null; + }, + + test: (program, condition) => { + var comparison; + switch (condition.type) { + case `boolean`: + return program.getValue(condition.value); + case `numeric`: + let v = program.getValue(condition.value1); + let test = v === ` ` || isNaN(v); + return condition.negate ? test : !test; + case `even`: + return (program.getValue(condition.value1) % 2) === 0; + case `odd`: + return (program.getValue(condition.value1) % 2) === 1; + case `is`: + comparison = program.compare(program, condition.value1, condition.value2); + return condition.negate ? comparison !== 0 : comparison === 0; + case `greater`: + comparison = program.compare(program, condition.value1, condition.value2); + return condition.negate ? comparison <= 0 : comparison > 0; + case `less`: + comparison = program.compare(program, condition.value1, condition.value2); + return condition.negate ? comparison >= 0 : comparison < 0; + case `not`: + return !program.getValue(condition.value); + case `moduleRunning`: + let moduleRecord = program.getSymbolRecord(condition.name); + if (EasyCoder.scripts.hasOwnProperty(moduleRecord.program) ) { + let p = EasyCoder.scripts[moduleRecord.program]; + return condition.sense ? p.running : !p.running; + } + return !condition.sense; + case `includes`: + const value1 = JSON.parse(program.getValue(condition.value1)); + const value2 = program.getValue(condition.value2); + return value1.includes(value2); + } + return false; + } + } +}; +const EasyCoder_Browser = { + + name: `EasyCoder_Browser`, + + A: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `a`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + Alert: { + + compile: (compiler) => { + const lino = compiler.getLino(); + const value = compiler.getNextValue(); + compiler.addCommand({ + domain: `browser`, + keyword: `alert`, + lino, + value + }); + return true; + }, + + run: (program) => { + const command = program[program.pc]; + const value = program.getFormattedValue(command.value); + alert(value); + return command.pc + 1; + } + }, + + Attach: { + + compile: (compiler) => { + const lino = compiler.getLino(); + compiler.next(); + if (compiler.isSymbol()) { + // const symbol = compiler.getProgram()[compiler.getSymbol().pc]; + const symbol = compiler.getSymbolRecord(); + let type = symbol.keyword; + switch (type) { + case `a`: + case `blockquote`: + case `button`: + case `canvas`: + case `div`: + case `fieldset`: + case `file`: + case `form`: + case `h1`: + case `h2`: + case `h3`: + case `h4`: + case `h5`: + case `h6`: + case `image`: + case `img`: + case `input`: + case `label`: + case `legend`: + case `li`: + case `option`: + case `p`: + case `pre`: + case `select`: + case `span`: + case `table`: + case `td`: + case `text`: + case `textarea`: + case `tr`: + case `ul`: + compiler.next(); + if (compiler.tokenIs(`to`)) { + let cssID = null; + if (compiler.nextTokenIs(`body`)) { + if (type=== `div`) { + cssId = `body`; + compiler.next(); + } else { + throw Error(`Body variable must be a div`); + } + } + else cssId = compiler.getValue(); + let onError = 0; + if (compiler.tokenIs(`or`)) { + compiler.next(); + onError = compiler.getPc() + 1; + compiler.completeHandler(); + } + compiler.addCommand({ + domain: `browser`, + keyword: `attach`, + lino, + type, + symbol: symbol.name, + cssId, + onError + }); + return true; + } + break; + default: + compiler.addWarning(`type '${symbol.keyword}' not recognized in browser 'attach'`); + return false; + } + } + compiler.addWarning(`Unrecognised syntax in 'attach'`); + return false; + }, + + run: (program) => { + const command = program[program.pc]; + let content = null; + let element = null; + if (command.cssId === `body`) { + element = document.body; + } else { + content = program.value.evaluate(program, command.cssId).content; + element = document.getElementById(content); + } + if (!element) { + if (command.onError) { + program.run(command.onError); + } else { + program.runtimeError(command.lino, `No such element: '${content}'`); + } + return 0; + } + const target = program.getSymbolRecord(command.symbol); + target.element[target.index] = element; + target.value[target.index] = { + type: `constant`, + numeric: false, + content + }; + if (command.type === `popup`) { + // Register a popup + program.popups.push(element.id); + // Handle closing of the popup + window.onclick = function (event) { + if (program.popups.includes(event.target.id)) { + event.target.style.display = `none`; + } + }; + } + return command.pc + 1; + } + }, + + Audioclip: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `audioclip`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + BLOCKQUOTE: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `blockquote`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + BUTTON: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `button`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + CANVAS: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `canvas`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + Clear: { + + compile: (compiler) => { + const lino = compiler.getLino(); + if (compiler.nextTokenIs(`body`)) { + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `clear`, + lino, + name: null + }); + return true; + } + if (compiler.isSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + if (symbolRecord.extra === `dom`) { + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `clear`, + lino, + name: symbolRecord.name + }); + return true; + } + } + return false; + }, + + run: (program) => { + const command = program[program.pc]; + if (command.name) { + const targetRecord = program.getSymbolRecord(command.name); + const target = targetRecord.element[targetRecord.index]; + switch (targetRecord.keyword) { + case `input`: + case `textarea`: + target.value = ``; + break; + default: + target.innerHTML = ``; + break; + } + } else { + document.body.innerHTML = ``; + } + return command.pc + 1; + } + }, + + Convert: { + + compile: (compiler) => { + const lino = compiler.getLino(); + if (compiler.nextTokenIs(`whitespace`)) { + if (compiler.nextTokenIs(`in`)) { + if (compiler.nextIsSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + if (symbolRecord.isVHolder) { + if (compiler.nextTokenIs(`to`)) { + const mode = compiler.nextToken(); + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `convert`, + lino, + name: symbolRecord.name, + mode + }); + return true; + } + } + } + } + } + return false; + }, + + run: (program) => { + const command = program[program.pc]; + const targetRecord = program.getSymbolRecord(command.name); + const content = targetRecord.value[targetRecord.index].content; + let value = content; + switch (command.mode) { + case `print`: + value = value.split(`%0a`).join(`\n`).split(`%0A`).join(`\n`).split(`%0d`).join(``).split(`$0D`).join(``); + break; + case `html`: + value = value.split(`%0a`).join(`
`).split(`%0A`).join(`
`).split(`%0d`).join(``).split(`$0D`).join(``); + break; + } + targetRecord.value[targetRecord.index].content = value; + return command.pc + 1; + } + }, + + Create: { + + compile: (compiler) => { + const lino = compiler.getLino(); + if (compiler.nextIsSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + const keyword = symbolRecord.keyword; + if (keyword === `audioclip`) { + if (compiler.nextTokenIs(`from`)) { + const value = compiler.getNextValue(); + compiler.addCommand({ + domain: `browser`, + keyword: `create`, + type: `audioclip`, + name: symbolRecord.name, + lino, + value + }); + return true; + } + return false; + } + if ([`a`, + `blockquote`, + `button`, + `canvas`, + `div`, + `fieldset`, + `file`, + `form`, + `h1`, + `h2`, + `h3`, + `h4`, + `h5`, + `h6`, + `hr`, + `image`, + `img`, + `input`, + `label`, + `legend`, + `li`, + `option`, + `p`, + `pre`, + `progress`, + `select`, + `span`, + `table`, + `tr`, + `td`, + `text`, + `textarea`, + `ul` + ].includes(keyword)) { + if (compiler.nextTokenIs(`in`)) { + if (compiler.nextTokenIs(`body`)) { + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `create`, + lino, + name: symbolRecord.name, + parent: `body` + }); + return true; + } + if (compiler.isSymbol()) { + const parentRecord = compiler.getSymbolRecord(); + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `create`, + lino, + name: symbolRecord.name, + parent: parentRecord.name + }); + return true; + } + } else { + const imports = compiler.imports; + if (imports && imports.length > 0) { + // This section is used by Codex to force run in Run panel, which must be the first import + compiler.addCommand({ + domain: `browser`, + keyword: `create`, + lino, + name: symbolRecord.name, + parent: imports[0], + imported: true + }); + return true; + } else { + compiler.addCommand({ + domain: `browser`, + keyword: `create`, + lino, + name: symbolRecord.name, + parent: `body` + }); + return true; + } + } + } + } + return false; + }, + + run: (program) => { + const command = program[program.pc]; + const targetRecord = program.getSymbolRecord(command.name); + switch (command.type) { + case `audioclip`: + targetRecord.value[targetRecord.index] = command.value; + break; + default: + let parent; + if (command.parent === `body`) { + parent = document.body; + } else { + const p = command.imported ? EasyCoder.scripts[program.parent] : program; + const parentRecord = p.getSymbolRecord(command.parent); + if (!parentRecord.element[parentRecord.index]) { + program.runtimeError(command.pc, `Element ${parentRecord.name} does not exist.`); + } + parent = parentRecord.element[parentRecord.index]; + } + targetRecord.element[targetRecord.index] = document.createElement(targetRecord.keyword); + targetRecord.element[targetRecord.index].id = + `ec-${targetRecord.name}-${targetRecord.index}-${EasyCoder.elementId++}`; + if (targetRecord.keyword === `a`) { + targetRecord.element[targetRecord.index].setAttribute(`href`, `#`); + } + parent.appendChild(targetRecord.element[targetRecord.index]); + break; + } + return command.pc + 1; + } + }, + + Disable: { + + compile: (compiler) => { + const lino = compiler.getLino(); + if (compiler.nextIsSymbol()) { + const symbol = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `disable`, + lino, + symbol + }); + return true; + } + compiler.addWarning(`Unrecognised syntax in 'disable'`); + return false; + }, + + run: (program) => { + const command = program[program.pc]; + const symbol = program.getSymbolRecord(command.symbol); + const target = document.getElementById(symbol.value[symbol.index].content); + target.disabled = `true`; + return command.pc + 1; + } + }, + + DIV: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `div`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + Enable: { + + compile: (compiler) => { + const lino = compiler.getLino(); + if (compiler.nextIsSymbol()) { + const symbol = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `enable`, + lino, + symbol + }); + return true; + } + compiler.addWarning(`Unrecognised syntax in 'enable'`); + return false; + }, + + run: (program) => { + const command = program[program.pc]; + const symbol = program.getSymbolRecord(command.symbol); + const target = document.getElementById(symbol.value[symbol.index].content); + target.disabled = false; + return command.pc + 1; + } + }, + + FIELDSET: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `fieldset`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + FILE: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `file`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + Focus: { + + compile: (compiler) => { + const lino = compiler.getLino(); + if (compiler.nextIsSymbol()) { + const symbol = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `focus`, + lino, + symbol + }); + return true; + } + compiler.addWarning(`Unrecognised syntax in 'focus'`); + return false; + }, + + run: (program) => { + const command = program[program.pc]; + const symbol = program.getSymbolRecord(command.symbol); + const element = symbol.element[symbol.index]; + element.focus(); + return command.pc + 1; + } + }, + + FORM: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `form`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + Get: { + + compile: (compiler) => { + const lino = compiler.getLino(); + if (compiler.nextIsSymbol()) { + const target = compiler.getToken(); + let targetRecord = compiler.getSymbolRecord(); + if (compiler.nextTokenIs(`from`)) { + if (compiler.nextTokenIs(`storage`)) { + if (compiler.nextTokenIs(`as`)) { + const key = compiler.getNextValue(); + compiler.addCommand({ + domain: `browser`, + keyword: `get`, + action: `getStorage`, + lino, + target, + key + }); + return true; + } else { + compiler.addCommand({ + domain: `browser`, + keyword: `get`, + action: `listStorage`, + lino, + target + }); + return true; + } + } + if (compiler.isSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + if (symbolRecord.keyword === `select`) { + if (targetRecord.keyword === `option`) { + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `get`, + action: `getOption`, + lino, + target, + select: symbolRecord.name + }); + return true; + } + throw Error(`Invalid variable type`); + } + if (symbolRecord.keyword !== `form`) { + throw Error(`Invalid variable type`); + } + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `get`, + action: `getForm`, + lino, + target, + form: symbolRecord.name + }); + return true; + } + else { + let targetRecord = compiler.getSymbolRecord(target); + } + } + } + compiler.addWarning(`Unrecognised syntax in 'get'`); + return false; + }, + + run: (program) => { + const command = program[program.pc]; + const targetRecord = program.getSymbolRecord(command.target); + switch (command.action) { + case `getForm`: + const formRecord = program.getSymbolRecord(command.form); + const form = document.getElementById(formRecord.value[formRecord.index].content); + const data = new FormData(form); + const content = {}; + for (const entry of data) { + content[entry[0]] = entry[1].replace(/\r/g, ``).replace(/\n/g, `%0a`); + } + targetRecord.value[targetRecord.index] = { + type: `constant`, + numeric: false, + content: JSON.stringify(content) + }; + break; + case `listStorage`: + const items = []; + for (let i = 0, len = window.localStorage.length; i < len; i++) { + items.push(localStorage.key(i)); + } + targetRecord.value[targetRecord.index] = { + type: `constant`, + numeric: false, + content: JSON.stringify(items) + }; + break; + case `getStorage`: + let value = window.localStorage.getItem(program.getValue(command.key)); + if (typeof value === `undefined`) { + value = null; + } + targetRecord.value[targetRecord.index] = { + type: `constant`, + numeric: false, + content: value + }; + break; + case `getOption`: + let selectRecord = program.getSymbolRecord(command.select); + let select = selectRecord.element[selectRecord.index]; + let option = select.options[select.selectedIndex]; + targetRecord.element[targetRecord.index] = option; + break; + } + return command.pc + 1; + } + }, + + H1: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `h1`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + H2: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `h2`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + H3: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `h3`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + H4: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `h4`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + H5: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `h5`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + H6: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `h6`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + Highlight: { + + compile: (compiler) => { + const lino = compiler.getLino(); + if (compiler.nextIsSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + if (symbolRecord.extra === `dom`) { + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `highlight`, + lino, + name: symbolRecord.name + }); + return true; + } + } + return false; + }, + + run: (program) => { + const command = program[program.pc]; + const targetRecord = program.getSymbolRecord(command.name); + const element = targetRecord.element[targetRecord.index]; + element.select(); + return command.pc + 1; + } + }, + + History: { + + compile: (compiler) => { + const lino = compiler.getLino(); + const type = compiler.nextToken(); + switch (type) { + case `push`: + case `set`: + case `replace`: + compiler.next(); + let url = ``; + let state = ``; + let title = ``; + while (true) { + const token = compiler.getToken(); + if (token === `url`) { + url = compiler.getNextValue(); + } else if (token === `state`) { + state = compiler.getNextValue(); + } else if (token === `title`) { + title = compiler.getNextValue(); + } else { + break; + } + } + compiler.addCommand({ + domain: `browser`, + keyword: `history`, + lino, + type, + url, + state, + title + }); + return true; + case `pop`: + case `back`: + case `forward`: + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `history`, + lino, + type + }); + return true; + } + return false; + }, + + run: (program) => { + if (!program.script) { + program.script = `script${Date.now()/1000}`; + } + const command = program[program.pc]; + let state = program.getValue(command.state); + if (!state) { + state = `{"script":"${program.script}"}`; + } + let title = program.getValue(command.title); + const url = program.getValue(command.url); + switch (command.type) { + case `push`: + if (!window.history.state) { + program.runtimeError(command.lino, `No state history; you need to call 'history set' on the parent`); + return 0; + } + window.history.pushState(state, ``, url); + break; + case `set`: + case `replace`: + window.history.replaceState(state, title, url); + break; + case `pop`: + case `back`: + window.history.back(); + break; + case `forward`: + window.history.forward(); + break; + } + return command.pc + 1; + } + }, + + HR: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `hr`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + IMAGE: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `image`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + IMG: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `img`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + INPUT: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `input`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + LABEL: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `label`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + LEGEND: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `legend`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + LI: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `li`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + Location: { + + compile: (compiler) => { + const lino = compiler.getLino(); + let newWindow = false; + if (compiler.nextTokenIs(`new`)) { + newWindow = true; + compiler.next(); + } + const location = compiler.getValue(); + compiler.addCommand({ + domain: `browser`, + keyword: `location`, + lino, + location, + newWindow + }); + return true; + }, + + run: (program) => { + const command = program[program.pc]; + const location = program.getValue(command.location); + if (command.newWindow) { + window.open(location, `_blank`); + } else { + window.location = location; + } + return command.pc + 1; + } + }, + + Mail: { + + compile: (compiler) => { + const lino = compiler.getLino(); + if (compiler.nextTokenIs(`to`)) { + const to = compiler.getNextValue(); + let subject = ``; + let body = ``; + if (compiler.tokenIs(`subject`)) { + subject = compiler.getNextValue(); + if (compiler.tokenIs(`body`) || compiler.tokenIs(`message`)) { + compiler.next(); + body = compiler.getValue(); + } + } + compiler.addCommand({ + domain: `browser`, + keyword: `mail`, + lino, + to, + subject, + body + }); + return true; + } + return false; + }, + + run: (program) => { + const command = program[program.pc]; + if (command.subject) { + window.location.href = `mailto:${program.getValue(command.to)}` + + `?subject=${program.getValue(command.subject)}&body=${encodeURIComponent(program.getValue(command.body))}`; + } else { + window.location.href = `mailto:${program.getValue(command.to)}`; + } + return command.pc + 1; + } + }, + + On: { + + compile: (compiler) => { + const lino = compiler.getLino(); + const action = compiler.nextToken(); + switch (action) { + case `change`: + compiler.next(); + if (compiler.isSymbol()) { + const symbol = compiler.getSymbolRecord(); + compiler.next(); + if (symbol.extra !== `dom`) { + return false; + } + compiler.addCommand({ + domain: `browser`, + keyword: `on`, + lino, + action, + symbol: symbol.name + }); + return compiler.completeHandler(); + } + break; + case `click`: + if (compiler.nextTokenIs(`document`)) { + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `on`, + lino, + action: `clickDocument` + }); + return compiler.completeHandler(); + } + if (compiler.isSymbol()) { + const symbol = compiler.getSymbolRecord(); + compiler.next(); + if (symbol.extra !== `dom`) { + return false; + } + compiler.addCommand({ + domain: `browser`, + keyword: `on`, + lino, + action, + symbol: symbol.name + }); + return compiler.completeHandler(); + } + break; + case `key`: + case `leave`: + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `on`, + lino, + action + }); + return compiler.completeHandler(); + case `window`: + if (compiler.nextTokenIs(`resize`)) { + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `on`, + lino, + action: `windowResize` + }); + return compiler.completeHandler(); + } + return false; + case `browser`: + case `restore`: + if (action === `browser` && !compiler.nextTokenIs(`back`)) { + return false; + } + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `on`, + lino, + action: `browserBack` + }); + return compiler.completeHandler(); + case `swipe`: + if ([`left`, `right`].includes(compiler.nextToken())) { + const direction = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `on`, + lino, + action: `swipe`, + direction + }); + return compiler.completeHandler(); + } + return false; + case `pick`: + if (compiler.nextIsSymbol()) { + const symbol = compiler.getSymbolRecord(); + compiler.next(); + if (symbol.extra !== `dom`) { + return false; + } + compiler.addCommand({ + domain: `browser`, + keyword: `on`, + lino, + action, + symbol: symbol.name + }); + return compiler.completeHandler(); + } + return false; + case `drag`: + case `drop`: + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `on`, + lino, + action + }); + return compiler.completeHandler(); + } + compiler.addWarning(`Unrecognised syntax in 'on'`); + return false; + }, + + run: (program) => { + let targetRecord; + const command = program[program.pc]; + switch (command.action) { + case `change`: + targetRecord = program.getSymbolRecord(command.symbol); + targetRecord.program = program.script; + targetRecord.element.forEach(function (target, index) { + if (target) { + target.targetRecord = targetRecord; + target.targetIndex = index; + target.targetPc = command.pc + 2; + target.addEventListener(`change`, (event) => { + event.stopPropagation(); + if (program.length > 0) { + const eventTarget = event.target; + if (typeof eventTarget.targetRecord !== `undefined`) { + eventTarget.targetRecord.index = eventTarget.targetIndex; + setTimeout(function () { + EasyCoder.timestamp = Date.now(); + let p = EasyCoder.scripts[eventTarget.targetRecord.program]; + p.run(eventTarget.targetPc); + }, 1); + } + } + }); + } + }); + break; + case `click`: + targetRecord = program.getSymbolRecord(command.symbol); + targetRecord.program = program.script; + targetRecord.element.forEach(function (target, index) { + if (target) { + target.targetRecord = targetRecord; + target.targetIndex = index; + target.targetPc = command.pc + 2; + target.onclick = function (event) { + event.stopPropagation(); + if (program.length > 0) { + const eventTarget = event.target; + if (eventTarget.type != `radio`) { + eventTarget.blur(); + } + if (typeof eventTarget.targetRecord !== `undefined`) { + eventTarget.targetRecord.index = eventTarget.targetIndex; + setTimeout(function () { + EasyCoder.timestamp = Date.now(); + let p = EasyCoder.scripts[eventTarget.targetRecord.program]; + p.run(eventTarget.targetPc); + }, 1); + } + } + return false; + }; + } + }); + break; + case `clickDocument`: + program.targetPc = command.pc + 2; + const interceptClickEvent = (e) => { + EasyCoder.timestamp = Date.now(); + let target = e.target || e.srcElement; + let href = ``; + while (target.parentNode) { + if (target.tagName === `A`) { + href = target.href; + program.docPath = href.slice(-(href.length - window.location.href.length)); + break; + } + target = target.parentNode; + } + while (target.parentNode) { + if (target.id.indexOf(`ec-`) === 0) { + let id = target.id.slice(3); + let pos = id.indexOf(`-`); + program.varName = id.slice(0, pos); + id = id.slice(pos + 1); + pos = id.indexOf(`-`); + program.varIndex = parseInt(id.slice(0, pos)); + break; + } + target = target.parentNode; + } + if (href.indexOf(window.location.href) === 0) { + program.run(program.targetPc); + e.preventDefault(); + } + }; + if (document.addEventListener) { + document.addEventListener(`click`, interceptClickEvent); + } else if (document.attachEvent) { + document.attachEvent(`onclick`, interceptClickEvent); + } + break; + case `swipe`: + let xDown; + const getTouches = (evt) => { + return evt.touches || // browser API + evt.originalEvent.touches; // jQuery + }; + const handleTouchStart = (evt) => { + const firstTouch = getTouches(evt)[0]; + xDown = firstTouch.clientX; + }; + const handleTouchMove = (evt) => { + evt.stopImmediatePropagation(); + if (!xDown) { + return; + } + const xUp = evt.touches[0].clientX; + const xDiff = xDown - xUp; + if (Math.abs(xDiff) > 150) { + xDown = null; + if (xDiff > 0 && program.onSwipeLeft) { + program.run(program.onSwipeLeft); + } else if (xDiff < 0 && program.onSwipeRight) { + program.run(program.onSwipeRight); + } + } + }; + switch (command.direction) { + case `left`: + program.onSwipeLeft = command.pc + 2; + break; + case `right`: + program.onSwipeRight = command.pc + 2; + break; + } + document.addEventListener(`touchstart`, handleTouchStart, false); + document.addEventListener(`touchmove`, handleTouchMove, false); + break; + case `pick`: + const pickRecord = program.getSymbolRecord(command.symbol); + document.pickRecord = pickRecord; + pickRecord.element.forEach(function (element, index) { + document.pickIndex = index; + element.pickIndex = index; + // Set up the mouse down and up listeners + element.mouseDownPc = command.pc + 2; + // Check if touch device + let isTouchDevice = `ontouchstart` in element; + if (isTouchDevice) { + element.addEventListener(`touchstart`, function (e) { + const element = e.targetTouches[0].target; + document.pickX = e.touches[0].clientX; + document.pickY = e.touches[0].clientY; + element.blur(); + setTimeout(function () { + document.pickRecord.index = element.pickIndex; + program.run(element.mouseDownPc); + }, 1); + }, false); + element.addEventListener(`touchmove`, function (e) { + document.dragX = e.touches[0].clientX; + document.dragY = e.touches[0].clientY; + setTimeout(function () { + program.run(document.mouseMovePc); + }, 1); + return false; + }, false); + element.addEventListener(`touchend`, function () { + setTimeout(function () { + program.run(document.mouseUpPc); + }, 1); + return false; + }); + } else { + element.onmousedown = function (event) { + let e = event ? event : window.event; + e.stopPropagation(); + // IE uses srcElement, others use target + if (program.length > 0) { + const element = e.target ? e.target : e.srcElement; + element.offsetX = e.offsetX; + element.offsetY = e.offsetY; + document.pickX = e.clientX; + document.pickY = e.clientY; + element.blur(); + setTimeout(function () { + document.pickRecord.index = element.pickIndex; + program.run(element.mouseDownPc); + }, 1); + } + document.onmousemove = function (event) { + let e = event ? event : window.event; + e.stopPropagation(); + document.dragX = e.clientX; + document.dragY = e.clientY; + if (document.onmousemove) { + setTimeout(function () { + program.run(document.mouseMovePc); + }, 1); + } + return false; + }; + window.onmouseup = function () { + document.onmousemove = null; + document.onmouseup = null; + setTimeout(function () { + if (program && program.run) { + program.run(document.mouseUpPc); + } + }, 1); + return false; + }; + return false; + }; + } + }); + break; + case `drag`: + // Set up the move listener + document.mouseMovePc = command.pc + 2; + break; + case `drop`: + // Set up the move listener + document.mouseUpPc = command.pc + 2; + break; + case `key`: + if (typeof document.onKeyListeners === `undefined`) { + document.onKeyListeners = []; + } + if (!document.onKeyListeners.includes(program)) { + document.onKeyListeners.push(program); + } + program.onKeyPc = command.pc + 2; + document.onkeypress = function (event) { + for (const program of document.onKeyListeners) { + program.key = event.key; + try { + setTimeout(function () { + program.run(program.onKeyPc); + }, 1); + } catch (err) { + console.log(`Error: ${err.message}`); + } } + return true; + }; + break; + case `windowResize`: + program.onWindowResize = command.pc + 2; + window.addEventListener('resize', function() { + program.run(program.onWindowResize); + }); + break; + case `browserBack`: + program.onBrowserBack = command.pc + 2; + break; + case `leave`: + window.addEventListener(`beforeunload`, function () { + program.run(command.pc + 2); + }); + break; + default: + break; + } + return command.pc + 1; + } + }, + + OPTION: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `option`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + P: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `p`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + Play: { + + compile: (compiler) => { + const lino = compiler.getLino(); + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.keyword === `audioclip`) { + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `play`, + lino, + target: targetRecord.name + }); + return true; } } return false; }, - run: program => { - let command = program[program.pc]; - let content = program.getValue(command.item); - let on = program.getValue(command.on); - content = content.split(on); - let elements = content.length; - targetRecord = program.getSymbolRecord(command.target); - targetRecord.elements = elements; - for (let n = 0; n < elements; n++) { - targetRecord.value[n] = { - type: `constant`, - numeric: false, - content: content[n] - }; + run: (program) => { + const command = program[program.pc]; + const targetRecord = program.getSymbolRecord(command.target); + const url = program.value.evaluate(program, targetRecord.value[targetRecord.index]).content; + new Audio(url).play(); + return command.pc + 1; + } + }, + + PRE: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `pre`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + PROGRESS: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `progress`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + Put: { + + compile: (compiler) => { + const lino = compiler.getLino(); + // Get the value + const value = compiler.getNextValue(); + if (compiler.tokenIs(`into`)) { + if (compiler.nextTokenIs(`storage`)) { + if (compiler.nextTokenIs(`as`)) { + const key = compiler.getNextValue(); + compiler.addCommand({ + domain: `browser`, + keyword: `put`, + lino, + value, + key + }); + return true; + } + } } - targetRecord.index = 0; + return false; + }, + + // runtime + + run: (program) => { + const command = program[program.pc]; + window.localStorage.setItem(program.getValue(command.key), program.getValue(command.value)); return command.pc + 1; } }, - Stop: { + Remove: { - compile: compiler => { + compile: (compiler) => { const lino = compiler.getLino(); - compiler.next(); - if (compiler.more() && compiler.isSymbol() && !compiler.getToken().endsWith(`:`)) { - const symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.keyword === `module`) { + if (compiler.nextTokenIs(`element`)) { + if (compiler.nextIsSymbol()) { + const element = compiler.getSymbolRecord(); + if (element.extra != `dom`) { + compiler.warning(`'${element.name}' is not a DOM element`); + return false; + } compiler.next(); compiler.addCommand({ - domain: `core`, - keyword: `stop`, + domain: `browser`, + keyword: `remove`, + type: `removeElement`, lino, - name: symbolRecord.name + element: element.name }); return true; - } else { - return false; } } - compiler.addCommand({ - domain: `core`, - keyword: `stop`, - lino, - next: 0 - }); + if (compiler.tokenIs(`attribute`)) { + const attribute = compiler.getNextValue(); + if (compiler.tokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.extra !== `dom`) { + throw new Error(`Inappropriate type '${targetRecord.keyword}'`); + } + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `remove`, + type: `removeAttribute`, + lino, + attribute, + target: targetRecord.name + }); + return true; + } + } + } + try { + const key = compiler.getValue(); + if (compiler.tokenIs(`from`)) { + if (compiler.nextTokenIs(`storage`)) { + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `remove`, + type: `removeStorage`, + key + }); + return true; + } + } + } catch (err) { + return false; + } + return false; + }, + + // runtime + + run: (program) => { + const command = program[program.pc]; + switch (command.type) { + case `removeAttribute`: + const attribute = program.getValue(command.attribute); + const targetRecord = program.getSymbolRecord(command.target); + target = targetRecord.element[targetRecord.index]; + target.removeAttribute(attribute); + break; + case `removeElement`: + const elementRecord = program.getSymbolRecord(command.element); + const element = elementRecord.element[elementRecord.index]; + if (element) { + element.parentElement.removeChild(element); + } + break; + case `removeStorage`: + const key = program.getValue(command.key); + window.localStorage.removeItem(key); + break; + } + return command.pc + 1; + } + }, + + Request: { + + compile: (compiler) => { + const lino = compiler.getLino(); + if (compiler.nextToken() === `fullscreen`) { + let option = ``; + if (compiler.nextToken() === `exit`) { + option = `exit`; + compiler.next(); + } + compiler.addCommand({ + domain: `browser`, + keyword: `request`, + lino, + option + }); + return true; + } + return false; + }, + + run: (program) => { + const command = program[program.pc]; + if (command.option === `exit`) { + document.exitFullscreen(); + } else { + document.documentElement.requestFullscreen(); + } + return command.pc + 1; + } + }, + + SELECT: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `select`, false, `dom`); return true; }, - run: program => { + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + Scroll: { + + compile: (compiler) => { + const lino = compiler.getLino(); + let name = null; + if (compiler.nextIsSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + name = symbolRecord.name; + compiler.next(); + } + if (compiler.tokenIs(`to`)) { + const to = compiler.getNextValue(); + compiler.addCommand({ + domain: `browser`, + keyword: `scroll`, + lino, + name, + to + }); + return true; + } + return false; + }, + + run: (program) => { const command = program[program.pc]; + const to = program.getValue(command.to); if (command.name) { const symbolRecord = program.getSymbolRecord(command.name); - EasyCoder.scripts[symbolRecord.program].exit(); - symbolRecord.program = null; + symbolRecord.element[symbolRecord.index].scrollTo(0, to); } else { - return 0; + window.scrollTo(0, to); } return command.pc + 1; } }, - Take: { + SECTION: { - compile: compiler => { + compile: (compiler) => { + compiler.compileVariable(`browser`, `section`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + Set: { + + compile: (compiler) => { const lino = compiler.getLino(); - compiler.next(); - // Get the (first) value - const value1 = compiler.getValue(); - if (compiler.tokenIs(`from`)) { - compiler.next(); - if (compiler.isSymbol()) { - const symbol = compiler.getSymbol(); - const variable = compiler.getCommandAt(symbol.pc); - if (variable.isVHolder) { - if (compiler.peek() === `giving`) { - // This variable must be treated as a second value - const value2 = compiler.getValue(); - compiler.next(); - const target = compiler.getToken(); + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + const target = targetRecord.name; + if (targetRecord.extra === `dom`) { + const token = compiler.nextToken(); + if (token === `from`) { + if (compiler.nextIsSymbol()) { + if (targetRecord.keyword === `select`) { + const sourceRecord = compiler.getSymbolRecord(); + if (sourceRecord.keyword === `variable`) { + var display = null; + if (compiler.nextTokenIs(`as`)) { + display = compiler.getNextValue(); + } + compiler.addCommand({ + domain: `browser`, + keyword: `set`, + lino, + type: `setSelect`, + select: target, + source: sourceRecord.name, + display + }); + return true; + } + return false; + } + const source = compiler.getToken(); compiler.next(); compiler.addCommand({ - domain: `core`, - keyword: `take`, + domain: `browser`, + keyword: `set`, lino, - value1, - value2, + type: `setContentVar`, + source, target }); - } else { - // Here the variable is the target. + return true; + } + } + } + } else { + let token = compiler.getToken(); + if (token === `the`) { + token = compiler.nextToken(); + } + if (token === `title`) { + if (compiler.nextTokenIs(`to`)) { + const value = compiler.getNextValue(); + compiler.addCommand({ + domain: `browser`, + keyword: `set`, + lino, + type: `setTitle`, + value + }); + return true; + } + } else if (token === `content`) { + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextIsSymbol()) { const target = compiler.getToken(); + if (compiler.nextTokenIs(`from`)) { + if (compiler.nextIsSymbol()) { + const source = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `set`, + lino, + type: `setContentVar`, + source, + target + }); + return true; + } + } + if (compiler.tokenIs(`to`)) { + const value = compiler.getNextValue(); + compiler.addCommand({ + domain: `browser`, + keyword: `set`, + lino, + type: `setContent`, + value, + target + }); + return true; + } + } + throw new Error(`'${compiler.getToken()}' is not a symbol`); + } + } else if (token === `class`) { + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const symbol = compiler.getSymbolRecord(); + if (symbol.extra === `dom`) { + if (compiler.nextTokenIs(`to`)) { + const value = compiler.getNextValue(); + compiler.addCommand({ + domain: `browser`, + keyword: `set`, + lino, + type: `setClass`, + symbolName: symbol.name, + value + }); + return true; + } + } + } + } + } else if (token === `id`) { + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const symbol = compiler.getSymbolRecord(); + if (symbol.extra === `dom`) { + if (compiler.nextTokenIs(`to`)) { + const value = compiler.getNextValue(); + compiler.addCommand({ + domain: `browser`, + keyword: `set`, + lino, + type: `setId`, + symbolName: symbol.name, + value + }); + return true; + } + } + } + } + } else if (token === `text`) { + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const symbol = compiler.getSymbolRecord(); + switch (symbol.keyword) { + case `button`: + case `input`: + case `span`: + case `label`: + case `legend`: + if (compiler.nextTokenIs(`to`)) { + const value = compiler.getNextValue(); + compiler.addCommand({ + domain: `browser`, + keyword: `set`, + lino, + type: `setText`, + symbolName: symbol.name, + value + }); + return true; + } + break; + default: + break; + } + } + } + } else if (token === `size`) { + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const symbol = compiler.getSymbolRecord(); + switch (symbol.keyword) { + case `input`: + if (compiler.nextTokenIs(`to`)) { + const value = compiler.getNextValue(); + compiler.addCommand({ + domain: `browser`, + keyword: `set`, + lino, + type: `setSize`, + symbolName: symbol.name, + value + }); + return true; + } + } + } + } + } else if (token === `attribute`) { + compiler.next(); + const attributeName = compiler.getValue(); + if (compiler.tokenIs(`of`)) { + if (compiler.nextIsSymbol(true)) { + const symbolRecord = compiler.getSymbolRecord(); + const symbolName = symbolRecord.name; compiler.next(); + let attributeValue = { + type: `boolean`, + content: true + }; + if (compiler.tokenIs(`to`)) { + attributeValue = compiler.getNextValue(); + } compiler.addCommand({ - domain: `core`, - keyword: `take`, + domain: `browser`, + keyword: `set`, lino, - value1, - target + type: `setAttribute`, + symbolName, + attributeName, + attributeValue }); + return true; } - return true; - } else { - compiler.warning(`core 'take'': Expected value holder`); } - } else { - // Here we have 2 values so 'giving' must come next - const value2 = compiler.getValue(); - if (compiler.tokenIs(`giving`)) { - compiler.next(); - const target = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `take`, - lino, - value1, - value2, - target - }); - return true; - } else { - compiler.warning(`core 'take'': Expected "giving"`); + } else if (token === `attributes`) { + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + const symbolName = symbolRecord.name; + if (symbolRecord.extra !== `dom`) { + compiler.warning(`'${symbolName}' is not a DOM type`); + return false; + } + if (compiler.nextTokenIs(`to`)) { + const attributes = compiler.getNextValue(); + if (attributes) { + compiler.addCommand({ + domain: `browser`, + keyword: `set`, + lino, + type: `setAttributes`, + symbolName, + attributes + }); + return true; + } + } + } + } + compiler.warning(`'${compiler.getToken()}' is not a symbol`); + return false; + } else if (token === `style`) { + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + const symbolName = symbolRecord.name; + if (symbolRecord.extra !== `dom`) { + compiler.warning(`'${symbolName}' is not a DOM type`); + return false; + } + if (compiler.nextTokenIs(`to`)) { + const styleValue = compiler.getNextValue(); + if (styleValue) { + compiler.addCommand({ + domain: `browser`, + keyword: `set`, + lino, + type: `setStyles`, + symbolName, + styleValue + }); + return true; + } + } + } + compiler.warning(`'${compiler.getToken()}' is not a symbol`); + return false; + } + const styleName = compiler.getValue(); + let type = `setStyle`; + let symbolName = ``; + token = compiler.getToken(); + if (token === `of`) { + if (compiler.nextToken() === `body`) { + type = `setBodyStyle`; + } else if (compiler.isSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + symbolName = symbolRecord.name; + if (symbolRecord.extra !== `dom`) { + throw Error(`'${symbolName}' is not a DOM type`); + } + } else { + throw Error(`'${compiler.getToken()}' is not a known symbol`); + } + if (compiler.nextTokenIs(`to`)) { + const styleValue = compiler.getNextValue(); + if (styleValue) { + compiler.addCommand({ + domain: `browser`, + keyword: `set`, + lino, + type, + symbolName, + styleName, + styleValue + }); + return true; + } + } + } + else if (token === `to`) { + const styleValue = compiler.getNextValue(); + if (styleValue) { + compiler.addCommand({ + domain: `browser`, + keyword: `set`, + lino, + type: `setHeadStyle`, + styleName, + styleValue + }); + return true; + } + } + } else if (token === `default`) { + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + if (symbolRecord.keyword === `select`) { + if (compiler.nextTokenIs(`to`)) { + const value = compiler.getNextValue(); + compiler.addCommand({ + domain: `browser`, + keyword: `set`, + lino, + type: `setDefault`, + name: symbolRecord.name, + value + }); + return true; + } + } + } } } } + compiler.addWarning(`Unrecognised syntax in 'set'`); return false; }, - run: program => { + run: (program) => { const command = program[program.pc]; - const value1 = command.value1; - const value2 = command.value2; - const target = program.getSymbolRecord(command.target); - if (target.isVHolder) { - const value = target.value[target.index]; - if (value2) { - const result = program.getValue(value2) - - program.getValue(value1); - target.value[target.index] = { - type: `constant`, - numeric: true, - content: result - }; + let symbol; + let value; + let target; + let targetId; + let targetRecord; + let cssId; + let selectRecord; + switch (command.type) { + case `setContentVar`: + const sourceVar = program.getSymbolRecord(command.source); + targetRecord = program.getSymbolRecord(command.target); + const source = document.getElementById(sourceVar.value[sourceVar.index].content); + target = targetRecord.element[targetRecord.index]; + if (!target) { + targetId = program.getValue(targetRecord.value[targetRecord.index]); + target = document.getElementById(targetId); + } + target.innerHTML = source.innerHTML; + break; + case `setContent`: + value = program.getValue(command.value); + targetRecord = program.getSymbolRecord(command.target); + target = targetRecord.element[targetRecord.index]; + if (!target) { + cssId = targetRecord.value[targetRecord.index].content; + if (!cssId) { + program.runtimeError(command.lino, + `Variable '${targetRecord.name}' has not been attached to a DOM element.`); + return 0; + } + target = document.getElementById(cssId); + } + targetRecord.element[targetRecord.index] = target; + switch (targetRecord.keyword) { + case `text`: + case `textarea`: + target.value = value; + break; + case `input`: + target.value = value; + break; + default: + target.innerHTML = value; + break; + } + break; + case `setSelect`: + // The source is assumed to be an array + sourceRecord = program.getSymbolRecord(command.source); + const sourceData = program.getValue(sourceRecord.value[sourceRecord.index]); + var itemArray = ``; + try { + itemArray = JSON.parse(sourceData); + } catch (err) { + program.runtimeError(command.lino, `Can't parse JSON`); + return 0; + } + // The target is assumed to be a SELECT + selectRecord = program.getSymbolRecord(command.select); + const select = selectRecord.element[selectRecord.index]; + select.options.length = 0; + // Get the name of the display field + const display = program.getValue(command.display); + // For each item, set the title and inner HTML + itemArray.forEach(function (item) { + const title = display ? program.decode(item[display]) : null; + const opt = document.createElement(`option`); + const innerHTML = title ? title : item; + opt.innerHTML = innerHTML; + const value = title ? JSON.stringify(item) : item; + opt.value = value; + select.appendChild(opt); + }); + if (display) { + select.selectedIndex = itemArray.indexOf(display); } else { - if (!value.numeric && isNaN(value.content)) { - program.nonNumericValueError(command.lino); + select.selectedIndex = -1; + } + break; + case `setClass`: + symbol = program.getSymbolRecord(command.symbolName); + target = symbol.element[symbol.index]; + if (!target) { + targetId = program.getValue(symbol.value[symbol.index]); + target = document.getElementById(targetId); + } + program.getValue(command.value).split(` `).forEach(function(item) { + target.classList.remove(item); + target.classList.add(item); + }); + break; + case `setId`: + symbol = program.getSymbolRecord(command.symbolName); + target = symbol.element[symbol.index]; + if (!target) { + targetId = program.getValue(symbol.value[symbol.index]); + target = document.getElementById(targetId); + } + target.id = program.getValue(command.value); + break; + case `setText`: + symbol = program.getSymbolRecord(command.symbolName); + target = symbol.element[symbol.index]; + if (!target) { + targetId = program.getValue(symbol.value[symbol.index]); + target = document.getElementById(targetId); + } + value = program.getValue(command.value); + switch (symbol.keyword) { + case `button`: + case `span`: + case `label`: + case `legend`: + target.innerHTML = value; + break; + case `input`: + target.value = value; + break; + default: + break; + } + break; + case `setSize`: + symbol = program.getSymbolRecord(command.symbolName); + if (symbol.keyword === `input`) { + target = symbol.element[symbol.index]; + if (!target) { + targetId = program.getValue(symbol.value[symbol.index]); + target = document.getElementById(targetId); } - const result = parseInt(program.getValue(value)) - parseInt(program.getValue(value1)); - target.value[target.index] = { - type: `constant`, - numeric: true, - content: result - }; + target.size = program.getValue(command.value); + } else { + program.runtimeError(command.lino, `Inappropriate variable type '${symbol.name}'`); } - } else { - program.variableDoesNotHoldAValueError(command.lino, target.name); + break; + case `setAttribute`: + symbol = program.getSymbolRecord(command.symbolName); + target = symbol.element[symbol.index]; + if (!target) { + targetId = program.getValue(symbol.value[symbol.index]); + target = document.getElementById(targetId); + } + const attributeName = program.getValue(command.attributeName); + if (command.attributeValue.type === `boolean`) { + target.setAttribute(attributeName, command.attributeValue.content); + } else { + target.setAttribute(attributeName, program.getValue(command.attributeValue)); + } + break; + case `setAttributes`: + symbol = program.getSymbolRecord(command.symbolName); + target = symbol.element[symbol.index]; + if (!target) { + targetId = program.getValue(symbol.value[symbol.index]); + target = document.getElementById(targetId); + } + for (let n = target.attributes.length - 1; n >= 0; n--) { + target.removeAttribute(target.attributes[n].name); + } + let attributes = program.getValue(command.attributes); + let list = attributes.split(" "); + for (let n = 0; n < list.length; n++) { + let attribute = list[n]; + let p = attribute.indexOf(`=`); + if (p > 0) { + target.setAttribute(attribute.substr(0, p), attribute.substr(p + 1)); + } + else { + target.setAttribute(attribute, attribute); + } + } + break; + case `setStyle`: + case `setStyles`: + symbol = program.getSymbolRecord(command.symbolName); + target = symbol.element[symbol.index]; + if (!target) { + const symbolElement = symbol.value[symbol.index]; + if (!symbolElement.type) { + program.runtimeError(command.lino, `Variable '${symbol.name}' is not attached to a DOM element.`); + return 0; + } + targetId = program.getValue(symbolElement); + target = document.getElementById(targetId); + } + const styleValue = program.getValue(command.styleValue); + if (!symbol.value[symbol.index]) { + program.runtimeError(command.lino, `Variable '${symbol.name}' has not been assigned.`); + return 0; + } + switch (command.type) { + case `setStyle`: + target.style[command.styleName.content] = styleValue; + break; + case `setStyles`: + target.style.cssText = styleValue; + break; + } + break; + case `setHeadStyle`: + const headStyleName = program.getValue(command.styleName); + const headStyleValue = program.getValue(command.styleValue); + var style = document.createElement('style'); + style.innerHTML = `${headStyleName} ${headStyleValue}`; + for (let i = 0; i < document.head.childNodes.length; i++) { + let node = document.head.childNodes[i]; + if (node.tagName === `STYLE`) { + let data = node.innerHTML; + if (data.indexOf(`${headStyleName} `) === 0) { + document.head.removeChild(node); + break; + } + } + } + document.head.appendChild(style); + break; + case `setBodyStyle`: + const bodyStyleValue = program.getValue(command.styleValue); + switch (command.styleName.content) { + case `background`: + document.body.style.background = bodyStyleValue; + break; + default: + program.runtimeError(command.lino, + `Unsupported body attribute '${command.styleName.content}'`); + return 0; + } + break; + case `setTitle`: + document.title = program.getValue(command.value); + break; + case `setDefault`: + selectRecord = program.getSymbolRecord(command.name); + value = program.getValue(command.value); + const element = selectRecord.element[selectRecord.index]; + for (let n = 0; n < element.options.length; n++) { + if (element.options[n].value === value) { + element.selectedIndex = n; + break; + } + } + break; + default: + break; } return command.pc + 1; } }, - Toggle: { + SPAN: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `span`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + TABLE: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `table`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + TR: { + + compile: (compiler) => { + compiler.compileVariable(`browser`, `tr`, false, `dom`); + return true; + }, + + run: (program) => { + return program[program.pc].pc + 1; + } + }, + + TD: { - compile: compiler => { - const lino = compiler.getLino(); - compiler.next(); - if (compiler.isSymbol()) { - const symbolPc = compiler.getSymbolPc(); - compiler.next(); - compiler.addCommand({ - domain: `core`, - keyword: `toggle`, - lino, - symbol: symbolPc - }); - return true; - } - return false; + compile: (compiler) => { + compiler.compileVariable(`browser`, `td`, false, `dom`); + return true; }, - run: program => { - const command = program[program.pc]; - const symbol = program[command.symbol]; - if (symbol.isVHolder) { - const handler = program.domain[symbol.domain]; - const content = handler.value.get(program, symbol.value[symbol.index]).content; - handler.value.put(symbol, { - type: `boolean`, - content: !content - }); - } else { - program.variableDoesNotHoldAValueError(command.lino, symbol.name); - } - return command.pc + 1; + run: (program) => { + return program[program.pc].pc + 1; } }, - Variable: { + TEXTAREA: { - compile: compiler => { - compiler.compileVariable(`core`, `variable`, true); + compile: (compiler) => { + compiler.compileVariable(`browser`, `textarea`, false, `dom`); return true; }, - run: program => { + run: (program) => { return program[program.pc].pc + 1; } }, - Wait: { + Trace: { - compile: compiler => { + compile: (compiler) => { const lino = compiler.getLino(); - compiler.next(); - const value = compiler.getValue(compiler); - const scale = compiler.getToken(); - let multiplier = 1000; - switch (scale) { - case `milli`: - case `millis`: - compiler.next(); - multiplier = 1; - break; - case `tick`: - case `ticks`: - compiler.next(); - multiplier = 10; - break; - case `second`: - case `seconds`: - compiler.next(); - multiplier = 1000; - break; - case `minute`: - case `minutes`: - compiler.next(); - multiplier = 60000; - break; + const variables = []; + if (compiler.nextIsSymbol()) { + while (compiler.isSymbol()) { + variables.push(compiler.getToken()); + compiler.next(); + } + let alignment = `horizontal`; + if (compiler.tokenIs(`horizontal`) || compiler.tokenIs(`vertical`)) { + alignment = compiler.getToken(); + compiler.next(); + } + compiler.addCommand({ + domain: `browser`, + keyword: `trace`, + variant: `setup`, + lino, + variables, + alignment + }); + return true; } compiler.addCommand({ - domain: `core`, - keyword: `wait`, - lino, - value, - multiplier + domain: `browser`, + keyword: `trace`, + variant: `run`, + lino }); return true; }, - run: program => { + run: (program) => { const command = program[program.pc]; - const value = program.getValue(command.value); - setTimeout(function () { - if (program.run) { - program.run(command.pc + 1); + switch (command.variant) { + case `setup`: + console.log(`Set up tracer`); + program.tracer = { + variables: command.variables, + alignment: command.alignment + }; + break; + case `run`: + console.log(`Run tracer`); + if (!program.tracer) { + program.tracer = { + variables: [], + alignment: `horizontal` + }; } - }, value * command.multiplier); - return 0; + if (!program.tracing) { + const tracer = document.getElementById(`easycoder-tracer`); + if (tracer) { + tracer.innerHTML = + `
` + + `` + + `
` + + `
`; + tracer.style.display = `none`; + } + program.tracing = true; + } + program.stop = false; + break; + } + return program.pc + 1; } }, - While: { + UL: { - compile: compiler => { - const lino = compiler.getLino(); - compiler.next(); - const condition = compiler.getCondition(); - const pc = compiler.getPc(); - compiler.addCommand({ - domain: `core`, - keyword: `while`, - lino, - condition - }); - // Skip when test fails - const skip = compiler.getPc(); - compiler.addCommand({ - domain: `core`, - keyword: `goto`, - goto: 0 - }); - // Do the body - compiler.compileOne(); - // Repeat the test - compiler.addCommand({ - domain: `core`, - keyword: `goto`, - goto: pc - }); - // Fixup the 'goto' on completion - compiler.getCommandAt(skip).goto = compiler.getPc(); + compile: (compiler) => { + compiler.compileVariable(`browser`, `ul`, false, `dom`); return true; }, - run: program => { - const command = program[program.pc]; - const condition = command.condition; - const test = program.condition.test(program, condition); - if (test) { - return program.pc + 2; - } - return program.pc + 1; + run: (program) => { + return program[program.pc].pc + 1; } }, - getHandler: (name) => { - switch (name) { - case `add`: - return EasyCoder_Core.Add; - case `alias`: - return EasyCoder_Core.Alias; - case `append`: - return EasyCoder_Core.Append; - case `begin`: - return EasyCoder_Core.Begin; - case `callback`: - return EasyCoder_Core.Callback; - case `clear`: - return EasyCoder_Core.Clear; - case `close`: - return EasyCoder_Core.Close; - case `continue`: - return EasyCoder_Core.Continue; - case `debug`: - return EasyCoder_Core.Debug; - case `decode`: - return EasyCoder_Core.Decode; - case `divide`: - return EasyCoder_Core.Divide; - case `dummy`: - return EasyCoder_Core.Dummy; - case `encode`: - return EasyCoder_Core.Encode; - case `end`: - return EasyCoder_Core.End; - case `exit`: - return EasyCoder_Core.Exit; - case `filter`: - return EasyCoder_Core.Filter; - case `fork`: - return EasyCoder_Core.Fork; - case `go`: - case `goto`: - return EasyCoder_Core.Go; - case `gosub`: - return EasyCoder_Core.Gosub; - case `if`: - return EasyCoder_Core.If; - case `import`: - return EasyCoder_Core.Import; - case `index`: - return EasyCoder_Core.Index; - case `load`: - return EasyCoder_Core.Load; - case `module`: - return EasyCoder_Core.Module; - case `multiply`: - return EasyCoder_Core.Multiply; - case `negate`: - return EasyCoder_Core.Negate; - case `on`: - return EasyCoder_Core.On; - case `print`: - return EasyCoder_Core.Print; - case `put`: - return EasyCoder_Core.Put; - case `replace`: - return EasyCoder_Core.Replace; - case `require`: - return EasyCoder_Core.Require; - case `return`: - return EasyCoder_Core.Return; - case `run`: - return EasyCoder_Core.Run; - case `sanitize`: - return EasyCoder_Core.Sanitize; - case `script`: - return EasyCoder_Core.Script; - case `send`: - return EasyCoder_Core.Send; - case `set`: - return EasyCoder_Core.Set; - case `sort`: - return EasyCoder_Core.Sort; - case `split`: - return EasyCoder_Core.Split; - case `stop`: - return EasyCoder_Core.Stop; - case `take`: - return EasyCoder_Core.Take; - case `toggle`: - return EasyCoder_Core.Toggle; - case `variable`: - return EasyCoder_Core.Variable; - case `wait`: - return EasyCoder_Core.Wait; - case `while`: - return EasyCoder_Core.While; + Upload: { + + compile: (compiler) => { + const lino = compiler.getLino(); + if (compiler.nextIsSymbol()) { + const file = compiler.getToken(); + if (compiler.nextTokenIs(`to`)) { + const path = compiler.getNextValue(); + if (compiler.tokenIs(`with`)) { + if (compiler.nextIsSymbol()) { + const progress = compiler.getToken(); + if (compiler.nextTokenIs(`and`)) { + if (compiler.nextIsSymbol()) { + const status = compiler.getToken(); + compiler.next(); + compiler.addCommand({ + domain: `browser`, + keyword: `upload`, + lino, + file, + path, + progress, + status + }); + return true; + } + } + } + } + } + } + return false; + }, + + run: (program) => { + const command = program[program.pc]; + const fileSpec = program.getSymbolRecord(command.file); + const path = program.getValue(command.path); + const progressSpec = program.getSymbolRecord(command.progress); + const statusSpec = program.getSymbolRecord(command.status); + + const file = fileSpec.element[fileSpec.index]; + const progress = progressSpec.element[progressSpec.index]; + const status = statusSpec.element[statusSpec.index]; + + const setProgress = (value) => { + if (progress) { + progress.value = value; + } + }; + const setStatus = (value) => { + if (status) { + status.innerHTML = value; + } + }; + + const source = file.files[0]; + if (source) { + const formData = new FormData(); + formData.append(`source`, source); + formData.append(`path`, path); + const ajax = new XMLHttpRequest(); + ajax.upload.addEventListener(`progress`, function (event) { + const percent = Math.round((event.loaded / event.total) * 100); + setProgress(percent); + setStatus(`${Math.round(percent)}%...`); + }, false); + ajax.addEventListener(`load`, function (event) { + const response = event.target.responseText; + setProgress(0); + setStatus(``); + console.log(response); + }, false); + ajax.addEventListener(`error`, function () { + setStatus(`Upload failed`); + console.log(`Upload failed`); + }, false); + ajax.addEventListener(`abort`, function () { + setStatus(`Upload aborted`); + console.log(`Upload aborted`); + }, false); + ajax.onreadystatechange = function () { + if (this.readyState === 4) { + const command = program.ajaxCommand; + const status = this.status; + switch (status) { + case 200: + program.run(command.pc + 1); + break; + case 0: + break; + default: + try { + program.runtimeError(command.lino, `Error ${status}`); + } catch (err) { + program.reportError(err, program); + } + break; + } + } + }; + program.ajaxCommand = command; + const postpath = path.startsWith(`http`) ? path : `${window.location.origin}//${path}`; + ajax.open(`POST`, postpath); + ajax.send(formData); + } + return 0; + } + }, + + getHandler: (name) => { + switch (name) { + case `a`: + return EasyCoder_Browser.A; + case `alert`: + return EasyCoder_Browser.Alert; + case `attach`: + return EasyCoder_Browser.Attach; + case `audioclip`: + return EasyCoder_Browser.Audioclip; + case `blockquote`: + return EasyCoder_Browser.BLOCKQUOTE; + case `button`: + return EasyCoder_Browser.BUTTON; + case `canvas`: + return EasyCoder_Browser.CANVAS; + case `clear`: + return EasyCoder_Browser.Clear; + case `convert`: + return EasyCoder_Browser.Convert; + case `create`: + return EasyCoder_Browser.Create; + case `disable`: + return EasyCoder_Browser.Disable; + case `div`: + return EasyCoder_Browser.DIV; + case `enable`: + return EasyCoder_Browser.Enable; + case `fieldset`: + return EasyCoder_Browser.FIELDSET; + case `file`: + return EasyCoder_Browser.FILE; + case `focus`: + return EasyCoder_Browser.Focus; + case `form`: + return EasyCoder_Browser.FORM; + case `fullscreen`: + return EasyCoder_Browser.FullScreen; + case `get`: + return EasyCoder_Browser.Get; + case `h1`: + return EasyCoder_Browser.H1; + case `h2`: + return EasyCoder_Browser.H2; + case `h3`: + return EasyCoder_Browser.H3; + case `h4`: + return EasyCoder_Browser.H4; + case `h5`: + return EasyCoder_Browser.H5; + case `h6`: + return EasyCoder_Browser.H6; + case `highlight`: + return EasyCoder_Browser.Highlight; + case `history`: + return EasyCoder_Browser.History; + case `hr`: + return EasyCoder_Browser.HR; + case `image`: + return EasyCoder_Browser.IMAGE; + case `img`: + return EasyCoder_Browser.IMG; + case `input`: + return EasyCoder_Browser.INPUT; + case `label`: + return EasyCoder_Browser.LABEL; + case `legend`: + return EasyCoder_Browser.LEGEND; + case `li`: + return EasyCoder_Browser.LI; + case `location`: + return EasyCoder_Browser.Location; + case `mail`: + return EasyCoder_Browser.Mail; + case `on`: + return EasyCoder_Browser.On; + case `option`: + return EasyCoder_Browser.OPTION; + case `p`: + return EasyCoder_Browser.P; + case `play`: + return EasyCoder_Browser.Play; + case `pre`: + return EasyCoder_Browser.PRE; + case `progress`: + return EasyCoder_Browser.PROGRESS; + case `put`: + return EasyCoder_Browser.Put; + case `remove`: + return EasyCoder_Browser.Remove; + case `request`: + return EasyCoder_Browser.Request; + case `select`: + return EasyCoder_Browser.SELECT; + case `scroll`: + return EasyCoder_Browser.Scroll; + case `section`: + return EasyCoder_Browser.SECTION; + case `set`: + return EasyCoder_Browser.Set; + case `span`: + return EasyCoder_Browser.SPAN; + case `table`: + return EasyCoder_Browser.TABLE; + case `tr`: + return EasyCoder_Browser.TR; + case `td`: + return EasyCoder_Browser.TD; + case `textarea`: + return EasyCoder_Browser.TEXTAREA; + case `trace`: + return EasyCoder_Browser.Trace; + case `ul`: + return EasyCoder_Browser.UL; + case `upload`: + return EasyCoder_Browser.Upload; default: - return false; + return null; } }, - run: program => { - // Look up the appropriate handler and call it - // If it's not there throw an error + run: (program) => { const command = program[program.pc]; - const handler = EasyCoder_Core.getHandler(command.keyword); + const handler = EasyCoder_Browser.getHandler(command.keyword); if (!handler) { - program.runtimeError(command.lino, - `Unknown keyword '${command.keyword}' in 'core' package`); + program.runtimeError(command.lino, `Unknown keyword '${command.keyword}' in 'browser' package`); } return handler.run(program); }, - isNegate: (compiler) => { - const token = compiler.getToken(); - if (token === `not`) { - compiler.next(); - return true; - } - return false; - }, - value: { - compile: compiler => { + compile: (compiler) => { if (compiler.isSymbol()) { - const name = compiler.getToken(); const symbolRecord = compiler.getSymbolRecord(); - switch (symbolRecord.keyword) { - case `module`: - compiler.next(); - return { - domain: `core`, - type: `module`, - name - }; - case `variable`: - const type = compiler.nextToken(); - if ([`format`, `modulo`].includes(type)) { - const value = compiler.getNextValue(); + if (compiler.nextTokenIs(`exists`)) { + if (symbolRecord.extra === `dom`) { + compiler.next(); return { - domain: `core`, - type, - name, - value + domain: `browser`, + type: `exists`, + value: symbolRecord.name }; } + return null; + } + switch (symbolRecord.keyword) { + case `file`: + case `input`: + case `select`: + case `textarea`: return { - domain: `core`, - type: `symbol`, - name + domain: `browser`, + type: symbolRecord.keyword, + value: symbolRecord.name }; } return null; } - var token = compiler.getToken(); - if (token === `true`) { - compiler.next(); - return { - domain: `core`, - type: `boolean`, - content: true - }; - } - if (token === `false`) { - compiler.next(); - return { - domain: `core`, - type: `boolean`, - content: false - }; - } - if (token === `random`) { - compiler.next(); - const range = compiler.getValue(); - return { - domain: `core`, - type: `random`, - range - }; - } - if (token === `cos`) { - compiler.next(); - const angle_c = compiler.getValue(); - compiler.skip(`radius`); - const radius_c = compiler.getValue(); - return { - domain: `core`, - type: `cos`, - angle_c, - radius_c - }; - } - if (token === `sin`) { + if (compiler.tokenIs(`the`)) { compiler.next(); - const angle_s = compiler.getValue(); - compiler.skip(`radius`); - const radius_s = compiler.getValue(); - return { - domain: `core`, - type: `sin`, - angle_s, - radius_s - }; } - if (token === `tan`) { + let offset = false; + if (compiler.tokenIs(`offset`)) { + offset = true; compiler.next(); - const angle_t = compiler.getValue(); - compiler.skip(`radius`); - const radius_t = compiler.getValue(); - return { - domain: `core`, - type: `tan`, - angle_t, - radius_t - }; } - if ([`now`, `today`, `newline`, `break`, `empty`, `uuid`].includes(token)) { + + let type = compiler.getToken(); + let text; + let attribute; + switch (type) { + case `mobile`: + case `portrait`: + case `landscape`: + case `br`: + case `location`: + case `key`: + case `hostname`: compiler.next(); return { - domain: `core`, - type: token - }; - } - if (token === `date`) { - const value = compiler.getNextValue(); - return { - domain: `core`, - type: `date`, - value + domain: `browser`, + type }; - } - if ([`encode`, `decode`, `lowercase`, `hash`, `reverse`].includes(token)) { + case `content`: + case `text`: + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const symbol = compiler.getSymbolRecord(); + compiler.next(); + return { + domain: `browser`, + type: `contentOf`, + symbol: symbol.name + }; + } + throw new Error(`'${compiler.getToken()}' is not a symbol`); + } + return null; + case `selected`: + let arg = compiler.nextToken(); + if ([`index`, `item`].includes(arg)) { + if ([`in`, `of`].includes(compiler.nextToken())) { + if (compiler.nextIsSymbol()) { + const symbol = compiler.getSymbolRecord(); + if ([`ul`, `ol`, `select`].includes(symbol.keyword)) { + compiler.next(); + return { + domain: `browser`, + type: `selected`, + symbol: symbol.name, + arg + }; + } + } + } + } + return null; + case `color`: compiler.next(); const value = compiler.getValue(); return { - domain: `core`, - type: token, + domain: `browser`, + type, value }; - } - if (token === `element`) { - const element = compiler.getNextValue(); + case `attribute`: + attribute = compiler.getNextValue(); if (compiler.tokenIs(`of`)) { - if (compiler.nextIsSymbol()) { + compiler.next(); + if (compiler.isSymbol()) { const symbolRecord = compiler.getSymbolRecord(); - compiler.next(); - if (symbolRecord.keyword === `variable`) { + if (symbolRecord.extra === `dom`) { + compiler.next(); return { - domain: `core`, - type: `element`, - element, + domain: `browser`, + type: `attributeOf`, + attribute, symbol: symbolRecord.name }; } } } return null; - } - if (token === `property`) { - const property = compiler.getNextValue(); + case `style`: + const style = compiler.getNextValue(); if (compiler.tokenIs(`of`)) { if (compiler.nextIsSymbol()) { const symbolRecord = compiler.getSymbolRecord(); - compiler.next(); - if (symbolRecord.keyword === `variable`) { + if (symbolRecord.extra === `dom`) { + compiler.next(); return { - domain: `core`, - type: `property`, - property, - symbol: symbolRecord.name + domain: `browser`, + type, + style, + target: symbolRecord.name }; } } } return null; - } - if (token === `arg`) { - const value = compiler.getNextValue(); - if (compiler.tokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const target = compiler.getSymbolRecord(); - compiler.next(); - return { - domain: `core`, - type: `arg`, - value, - target: target.name - }; - } + case `confirm`: + text = compiler.getNextValue(); + return { + domain: `browser`, + type: `confirm`, + text + }; + case `prompt`: + text = compiler.getNextValue(); + let pre = null; + if (compiler.tokenIs(`with`)) { + pre = compiler.getNextValue(); } - } - if ([`character`, `char`].includes(token)) { - let index = compiler.getNextValue(); - if (compiler.tokenIs(`of`)) { - let value = compiler.getNextValue(); + return { + domain: `browser`, + type: `prompt`, + text, + pre + }; + case `screen`: + attribute = compiler.nextToken(); + if ([`width`, `height`].includes(attribute)) { + compiler.next(); return { - domain: `core`, - type: `char`, - index, - value + domain: `browser`, + type, + attribute }; } - } - if (compiler.tokenIs(`the`)) { - compiler.next(); - } - const type = compiler.getToken(); - switch (type) { - case `elements`: - if ([`of`, `in`].includes(compiler.nextToken())) { - if (compiler.nextIsSymbol()) { - const name = compiler.getToken(); - compiler.next(); - return { - domain: `core`, - type, - name - }; - } - } break; - case `index`: - if (compiler.nextTokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - if (compiler.peek() === `in`) { - const value1 = compiler.getValue(); - const value2 = compiler.getNextValue(); - return { - domain: `core`, - type: `indexOf`, - value1, - value2 - }; - } else { - const name = compiler.getToken(); - compiler.next(); - return { - domain: `core`, - type, - name - }; - } - } else { - const value1 = compiler.getValue(); - if (compiler.tokenIs(`in`)) { - const value2 = compiler.getNextValue(); - return { - domain: `core`, - type: `indexOf`, - value1, - value2 - }; - } - } + case `top`: + case `bottom`: + case `left`: + case `right`: + case `width`: + case `height`: + return EasyCoder_Browser.value.getCoord(compiler, type, offset); + case `scroll`: + if (compiler.nextTokenIs(`position`)) { + compiler.next(); + return { + domain: `browser`, + type: `scrollPosition` + }; + } + return null; + case `document`: + if (compiler.nextTokenIs(`path`)) { + compiler.next(); + return { + domain: `browser`, + type: `docPath` + }; } - break; - case `value`: - if (compiler.nextTokenIs(`of`)) { + return null; + case `storage`: + if (compiler.nextTokenIs(`keys`)) { compiler.next(); - const value = compiler.getValue(); return { - domain: `core`, - type: `valueOf`, - value + domain: `browser`, + type: `storageKeys` }; } - break; - case `length`: - if (compiler.nextTokenIs(`of`)) { + return null; + case `parent`: + switch (compiler.nextToken()) { + case `name`: compiler.next(); - const value = compiler.getValue(); return { - domain: `core`, - type: `lengthOf`, - value + domain: `browser`, + type: `varName` + }; + case `index`: + compiler.next(); + return { + domain: `browser`, + type: `varIndex` }; } - break; - case `left`: - case `right`: - try { - const count = compiler.getNextValue(); - if (compiler.tokenIs(`of`)) { - const value = compiler.getNextValue(); - return { - domain: `core`, - type, - count, - value - }; - } - } catch (err) { - return null; + return null; + case `history`: + if (compiler.nextTokenIs(`state`)) { + compiler.next(); + return { + domain: `browser`, + type: `historyState` + }; } - break; - case `from`: - const from = compiler.getNextValue(); - const to = compiler.tokenIs(`to`) ? compiler.getNextValue() : null; - if (compiler.tokenIs(`of`)) { - const value = compiler.getNextValue(); + return null; + case `pick`: + case `drag`: + if (compiler.nextTokenIs(`position`)) { + compiler.next(); return { - domain: `core`, - type, - from, - to, - value + domain: `browser`, + type: `${type}Position` }; } - break; - case `position`: - let nocase = false; - if (compiler.nextTokenIs(`nocase`)) { - nocase = true; + } + return null; + }, + + getCoord: (compiler, type, offset) => { + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextTokenIs(`window`)) { compiler.next(); + return { + domain: `browser`, + type, + symbol: `window`, + offset + }; } - if (compiler.tokenIs(`of`)) { - var last = false; - if (compiler.nextTokenIs(`the`)) { - if (compiler.nextTokenIs(`last`)) { - compiler.next(); - last = true; - } - } - const needle = compiler.getValue(); - if (compiler.tokenIs(`in`)) { - const haystack = compiler.getNextValue(); + let symbolRecord = null; + if (compiler.isSymbol()) { + symbolRecord = compiler.getSymbolRecord(); + if (symbolRecord.extra === `dom`) { + compiler.next(); return { - domain: `core`, - type: `position`, - needle, - haystack, - last, - nocase + domain: `browser`, + type, + symbol: symbolRecord.name, + offset }; } } - break; - case `payload`: - if (compiler.nextTokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const callbackRecord = compiler.getSymbolRecord(); - if (callbackRecord.keyword === `callback`) { - compiler.next(); - return { - domain: `core`, - type: `payload`, - callback: callbackRecord.name - }; - } - } - } - break; - case `message`: - case `error`: - case `millisecond`: - compiler.next(); - return { - domain: `core`, - type - }; - } return null; }, get: (program, value) => { + let symbolRecord; + let element; + let target; + let content; switch (value.type) { - case `boolean`: + case `file`: + case `input`: + case `select`: + case `textarea`: + symbolRecord = program.getSymbolRecord(value.value); + target = symbolRecord.element[symbolRecord.index]; return { - type: `boolean`, + type: `constant`, numeric: false, - content: value.content + content: target.value }; - case `elements`: + case `exists`: + symbolRecord = program.getSymbolRecord(value.value); return { - type: `constant`, - numeric: true, - content: program.getSymbolRecord(value.name).elements + domain: `browser`, + type: `boolean`, + content: typeof symbolRecord.element[symbolRecord.index] !== `undefined` }; - case `index`: + case `mobile`: return { - type: `constant`, - numeric: true, - content: program.getSymbolRecord(value.name).index + domain: `browser`, + type: `boolean`, + content: (typeof window.orientation !== `undefined`) || (navigator.userAgent.indexOf(`IEMobile`) !== -1) }; - case `random`: - const range = program.evaluate(value.range); + case `portrait`: return { - type: `constant`, - numeric: true, - content: Math.floor((Math.random() * range.content)) + domain: `browser`, + type: `boolean`, + content: document.documentElement.clientWidth < document.documentElement.clientHeight }; - case `cos`: - const angle_c = program.getValue(value.angle_c); - const radius_c = program.getValue(value.radius_c); + case `landscape`: return { - type: `constant`, - numeric: true, - content: parseInt(Math.cos(parseFloat(angle_c) * 0.01745329) * radius_c, 10) + domain: `browser`, + type: `boolean`, + content: document.documentElement.clientWidth >= document.documentElement.clientHeight }; - case `sin`: - const angle_s = program.getValue(value.angle_s); - const radius_s = program.getValue(value.radius_s); + case `br`: return { type: `constant`, - numeric: true, - content: parseInt(Math.sin(parseFloat(angle_s) * 0.01745329) * radius_s, 10) + numeric: false, + content: decodeURIComponent(`%3Cbr%20%2F%3E`) }; - case `tan`: - const angle_t = program.getValue(value.angle_t); - const radius_t = program.getValue(value.radius_t); + case `attributeOf`: + symbolRecord = program.getSymbolRecord(value.symbol); + const attribute = program.getValue(value.attribute); + target = symbolRecord.element[symbolRecord.index]; + if (attribute.indexOf(`data-`) === 0) { + return program.getSimpleValue(target.dataset[attribute.substr(5)]); + } + return program.getSimpleValue(target[attribute]); + case `style`: + symbolRecord = program.getSymbolRecord(value.target); + const style = program.getValue(value.style); + target = symbolRecord.element[symbolRecord.index]; + return program.getSimpleValue(target.style[style]); + case `confirm`: return { - type: `constant`, - numeric: true, - content: parseInt(Math.tan(parseFloat(angle_t) * 0.01745329) * radius_t, 10) + type: `boolean`, + content: window.confirm(program.getValue(value.text)) }; - case `valueOf`: - const v = parseInt(program.getValue(value.value)); + case `prompt`: + const text = program.getValue(value.text); + const pre = program.getValue(value.pre); return { type: `constant`, - numeric: true, - content: v ? v : 0 + numeric: false, + content: pre ? window.prompt(text, pre) : window.prompt(text) }; - case `lengthOf`: + case `contentOf`: + symbolRecord = program.getSymbolRecord(value.symbol); + target = symbolRecord.element[symbolRecord.index]; + switch (symbolRecord.keyword) { + case `input`: + case `textarea`: + content = target.value; + break; + case `pre`: + content = target.innerHTML; + break; + default: + content = target.innerHTML.split(`\n`).join(``); + break; + } return { type: `constant`, - numeric: true, - content: program.getValue(value.value).length + numeric: false, + content }; - case `left`: + case `selected`: + symbolRecord = program.getSymbolRecord(value.symbol); + target = symbolRecord.element[symbolRecord.index]; + let selectedIndex = target.selectedIndex; + let selectedText = selectedIndex >= 0 ? target.options[selectedIndex].text : ``; + content = (value.arg === `index`) ? selectedIndex : selectedText; return { type: `constant`, numeric: false, - content: program.getValue(value.value).substr(0, program.getValue(value.count)) + content }; - case `right`: - const str = program.getValue(value.value); + case `top`: + if (value.symbol == `window`) { + return { + type: `constant`, + numeric: true, + content: window.screenY + }; + } + symbolRecord = program.getSymbolRecord(value.symbol); + element = symbolRecord.element[symbolRecord.index]; + content = Math.round(value.offset ? element.offsetTop : element.getBoundingClientRect().top); return { type: `constant`, - numeric: false, - content: str.substr(str.length - program.getValue(value.count)) + numeric: true, + content }; - case `from`: - const from = program.getValue(value.from); - const to = value.to ? program.getValue(value.to) : null; - const fstr = program.getValue(value.value); + case `bottom`: + if (value.symbol == `window`) { + return { + type: `constant`, + numeric: true, + content: window.screenY + window.innerHeight + }; + } + symbolRecord = program.getSymbolRecord(value.symbol); + content = Math.round(symbolRecord.element[symbolRecord.index].getBoundingClientRect().bottom); return { type: `constant`, - numeric: false, - content: to ? fstr.substr(from, to) : fstr.substr(from) + numeric: true, + content }; - case `position`: - let needle = program.getValue(value.needle); - let haystack = program.getValue(value.haystack); - if (value.nocase) { - needle = needle.toLowerCase(); - haystack = haystack.toLowerCase(); + case `left`: + if (value.symbol == `window`) { + return { + type: `constant`, + numeric: true, + content: window.screenLeft + }; } + symbolRecord = program.getSymbolRecord(value.symbol); + element = symbolRecord.element[symbolRecord.index]; + content = Math.round(value.offset ? element.offsetLeft : element.getBoundingClientRect().left); return { type: `constant`, numeric: true, - content: value.last ? haystack.lastIndexOf(needle) : haystack.indexOf(needle) + content }; - case `payload`: + case `right`: + if (value.symbol == `window`) { + return { + type: `constant`, + numeric: true, + content: window.screenX + window.innerWidth + }; + } + symbolRecord = program.getSymbolRecord(value.symbol); + content = Math.round(symbolRecord.element[symbolRecord.index].getBoundingClientRect().right); return { type: `constant`, - numeric: false, - content: program.getSymbolRecord(value.callback).payload + numeric: true, + content }; - case `modulo`: - const symbolRecord = program.getSymbolRecord(value.name); - const modval = program.evaluate(value.value); + case `width`: + if (value.symbol == `window`) { + return { + type: `constant`, + numeric: true, + content: window.innerWidth + }; + } + symbolRecord = program.getSymbolRecord(value.symbol); + content = Math.round(symbolRecord.element[symbolRecord.index].getBoundingClientRect().width); return { type: `constant`, numeric: true, - content: symbolRecord.value[symbolRecord.index].content % modval.content - }; - case `format`: - const fmtRecord = program.getSymbolRecord(value.name); - const fmtValue = program.getValue(fmtRecord.value[fmtRecord.index]) * 1000; - try { - const spec = JSON.parse(program.getValue(value.value)); - switch (spec.mode) { - case `time`: - - return { - type: `constant`, - numeric: true, - content: new Date(fmtValue).toLocaleTimeString(spec.locale, spec.options) - }; - case `date`: - default: - const date = new Date(fmtValue); - const content = (spec.format === `iso`) - ? `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()}` - : date.toLocaleDateString(spec.locale, spec.options); - return { - type: `constant`, - numeric: true, - content - }; - } - } catch (err) { - program.runtimeError(program[program.pc].lino, `Can't parse ${value.value}`); - return null; + content + }; + case `height`: + if (value.symbol == `window`) { + return { + type: `constant`, + numeric: true, + content: window.innerHeight + }; } - case `empty`: + symbolRecord = program.getSymbolRecord(value.symbol); + content = Math.round(symbolRecord.element[symbolRecord.index].getBoundingClientRect().height); + return { + type: `constant`, + numeric: true, + content + }; + case `color`: + const styleValue = program.value.evaluate(program, value.value).content; + const hex = styleValue.toString(16).padStart(6, `0`); return { type: `constant`, numeric: false, - content: `` + content: `#${hex}` }; - case `now`: + case `docPath`: return { type: `constant`, - numeric: true, - content: Math.floor(Date.now() / 1000) + numeric: false, + content: program.docPath }; - case `millisecond`: + case `storageKeys`: return { type: `constant`, - numeric: true, - content: Math.floor(Date.now()) + numeric: false, + content: JSON.stringify(Object.keys(localStorage)) }; - case `today`: - const date = new Date(); - date.setHours(0, 0, 0, 0); + case `location`: + return { + type: `constant`, + numeric: false, + content: window.location.href + }; + case `historyState`: + return { + type: `constant`, + numeric: false, + content: window.history.state + }; + case `scrollPosition`: return { type: `constant`, numeric: true, - content: Math.floor(date.getTime() / 1000) + content: scrollPosition }; - case `date`: - content = Date.parse(program.getValue(value.value)) / 1000; - if (isNaN(content)) { - program.runtimeError(program[program.pc].lino, `Invalid date format; expecting 'yyyy-mm-dd'`); - return null; - } + case `varName`: + return { + type: `constant`, + numeric: false, + content: program.varName + }; + case `varIndex`: return { type: `constant`, numeric: true, - content + content: program.varIndex }; - case `newline`: + case `key`: return { type: `constant`, numeric: false, - content: `\n` + content: program.key }; - case `break`: + case `hostname`: return { type: `constant`, numeric: false, - content: `
` + content: location.hostname }; - case `uuid`: + case `screen`: + return { + type: `constant`, + numeric: true, + content: screen[value.attribute] + }; + case `pickPosition`: return { type: `constant`, numeric: false, - content: `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function(c) { - var r = Math.random() * 16 | 0, v = c == `x` ? r : (r & 0x3 | 0x8); - return v.toString(16); + content: JSON.stringify({ + "x": document.pickX, + "y": document.pickY }) }; - case `encode`: + case `dragPosition`: return { type: `constant`, numeric: false, - content: program.encode(program.getValue(value.value)) + content: JSON.stringify({ + "x": document.dragX, + "y": document.dragY + }) }; - case `decode`: + } + } + }, + + condition: { + + compile: (compiler) => { + if (compiler.tokenIs(`confirm`)) { + const value = compiler.getNextValue(); return { + domain: `browser`, + type: `confirm`, + value + }; + } else if (compiler.tokenIs(`element`)) { + if (compiler.nextIsSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + if (symbolRecord.extra === `dom`) { + const token = compiler.nextToken(); + if (token === `has`) { + if (compiler.nextTokenIs(`the`)) { + compiler.next(); + } + if (compiler.tokenIs(`focus`)) { + compiler.next(); + return { + domain: `browser`, + type: `focus`, + element: symbolRecord.name + }; + } + } else if (token === `contains`) { + const position = compiler.getNextValue(); + return { + domain: `browser`, + type: `contains`, + element: symbolRecord.name, + position + }; + } + } + } + } + return null; + }, + + test: (program, condition) => { + switch (condition.type) { + case `confirm`: + return confirm(program.getValue(condition.value)); + case `focus`: + const focusRecord = program.getSymbolRecord(condition.element); + return focusRecord.element[focusRecord.index] === document.activeElement; + case `contains`: + const containsRecord = program.getSymbolRecord(condition.element); + const element = containsRecord.element[containsRecord.index]; + const bounds = element.getBoundingClientRect(); + const left = Math.round(bounds.left); + const right = Math.round(bounds.right); + const top = Math.round(bounds.top); + const bottom = Math.round(bounds.bottom); + const position = JSON.parse(program.getValue(condition.position)); + const x = position.x; + const y = position.y; + if (x >= left && x <= right && y >= top && y <= bottom) { + return true; + } + return false; + } + } + }, + + setStyles: (id, styleString) => { + const element = document.getElementById(id); + const styles = styleString.split(`;`); + for (const item of styles) { + const style = item.split(`:`); + element.setAttribute(style[0], style[1]); + } + } +}; + +let scrollPosition = 0; + +window.addEventListener(`scroll`, function () { + scrollPosition = this.scrollY; +}); + +window.onpopstate = function (event) { + window.EasyCoder.timestamp = Date.now(); + const state = JSON.parse(event.state); + if (state && state.script) { + const program = window.EasyCoder.scripts[state.script]; + if (program) { + if (program.onBrowserBack) { + program.run(program.onBrowserBack); + } + } else { + console.log(`No script property in window state object`); + } + } +}; +const EasyCoder_Json = { + + name: `EasyCoder_JSON`, + + Json: { + + compile: (compiler) => { + const lino = compiler.getLino(); + const request = compiler.nextToken(); + let item; + switch (request) { + case `set`: + compiler.next(); + if (compiler.isSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.keyword === `variable`) { + if (compiler.nextTokenIs(`to`)) { + const type = compiler.nextToken(); + if (`["array","object"]`.includes(type)) { + compiler.next(); + compiler.addCommand({ + domain: `json`, + keyword: `json`, + lino, + request: `setVariable`, + target: targetRecord.name, + type + }); + return true; + } + } + } else if (targetRecord.keyword === `select`) { + if (compiler.nextTokenIs(`from`)) { + compiler.next(); + if (compiler.isSymbol()) { + const sourceRecord = compiler.getSymbolRecord(); + if (sourceRecord.keyword === `variable`) { + var display = null; + if (compiler.nextTokenIs(`as`)) { + display = compiler.getNextValue(); + } + compiler.addCommand({ + domain: `json`, + keyword: `json`, + lino, + request: `setList`, + target: targetRecord.name, + source: sourceRecord.name, + display + }); + return true; + } + } + } + } + break; + } + break; + case `sort`: + case `shuffle`: + case `format`: + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.keyword === `variable`) { + compiler.next(); + compiler.addCommand({ + domain: `json`, + keyword: `json`, + lino, + request, + target: targetRecord.name + }); + return true; + } + } + break; + case `parse`: + if (compiler.nextTokenIs(`url`)) { + const source = compiler.getNextValue(); + if (compiler.tokenIs(`as`)) { + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.keyword === `variable`) { + compiler.next(); + compiler.addCommand({ + domain: `json`, + keyword: `json`, + lino, + request, + source, + target: targetRecord.name + }); + return true; + } + } + } + } + break; + case `delete`: + const what = compiler.nextToken(); + if ([`property`, `element`].includes(what)) { + const value = compiler.getNextValue(); + if ([`from`, `of`].includes(compiler.getToken())) { + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.keyword === `variable`) { + compiler.next(); + compiler.addCommand({ + domain: `json`, + keyword: `json`, + lino, + request, + what, + value, + target: targetRecord.name + }); + return true; + } + } + } + } + break; + case `rename`: + const oldName = compiler.getNextValue(); + if (compiler.tokenIs(`to`)) { + const newName = compiler.getNextValue(); + if (compiler.tokenIs(`in`)) { + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.keyword === `variable`) { + compiler.next(); + compiler.addCommand({ + domain: `json`, + keyword: `json`, + lino, + request, + oldName, + newName, + target: targetRecord.name + }); + return true; + } + } + } + } + break; + case `add`: + item = compiler.getNextValue(); + if (compiler.tokenIs(`to`)) { + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.keyword === `variable`) { + compiler.next(); + compiler.addCommand({ + domain: `json`, + keyword: `json`, + lino, + request, + item, + target: targetRecord.name + }); + return true; + } + } + } + break; + case `split`: + item = compiler.getNextValue(); + let on = `\n`; + if (compiler.tokenIs(`on`)) { + on = compiler.getNextValue(); + } + if ([`giving`, `into`].includes(compiler.getToken())) { + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.keyword === `variable`) { + compiler.next(); + compiler.addCommand({ + domain: `json`, + keyword: `json`, + lino, + request, + item, + on, + target: targetRecord.name + }); + return true; + } + } + } + break; + case `replace`: + if (compiler.nextTokenIs(`element`)) { + const index = compiler.getNextValue(); + if (compiler.tokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.keyword === `variable`) { + if ([`by`, `with`].includes(compiler.nextToken())) { + const value = compiler.getNextValue(); + compiler.addCommand({ + domain: `json`, + keyword: `json`, + lino, + request, + target: targetRecord.name, + index, + value + }); + return true; + } + } + } + } + } + break; + } + compiler.addWarning(`Unrecognised json command syntax`); + return false; + }, + + run: (program) => { + const command = program[program.pc]; + let sourceRecord; + let targetRecord; + let record; + let content; + let array; + switch (command.request) { + case `setVariable`: + targetRecord = program.getSymbolRecord(command.target); + content = (command.type === `array`) ? `[]` : `{}`; + targetRecord.value[targetRecord.index] = { type: `constant`, numeric: false, - content: program.decode(program.getValue(value.value)) + content }; - case `reverse`: - return { + break; + case `setList`: + // The source is assumed to be a JSON array + sourceRecord = program.getSymbolRecord(command.source); + const sourceData = program.getValue(sourceRecord.value[sourceRecord.index]); + var itemArray = ``; + try { + itemArray = JSON.parse(sourceData); + } catch (err) { + program.runtimeError(command.lino, `Can't parse JSON`); + return 0; + } + // The target is assumed to be a SELECT + targetRecord = program.getSymbolRecord(command.target); + const target = targetRecord.element[targetRecord.index]; + target.options.length = 0; + // Get the name of the display field + const display = program.getValue(command.display); + // For each item, set the title and inner HTML + itemArray.forEach(function (item) { + const title = display ? program.decode(item[display]) : null; + const opt = document.createElement(`option`); + const innerHTML = title ? title : item; + opt.innerHTML = innerHTML; + const value = title ? JSON.stringify(item) : item; + opt.value = value; + target.appendChild(opt); + }); + target.selectedIndex = -1; + break; + case `sort`: + targetRecord = program.getSymbolRecord(command.target); + const list = program.getValue(targetRecord.value[targetRecord.index]); + content = list ? JSON.stringify(JSON.parse(list).sort()) : null; + targetRecord.value[targetRecord.index] = { type: `constant`, numeric: false, - content: program.getValue(value.value).split(``).reverse().join(``) + content }; - case `lowercase`: - return { + break; + case `shuffle`: + targetRecord = program.getSymbolRecord(command.target); + array = JSON.parse(program.getValue(targetRecord.value[targetRecord.index])); + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + targetRecord.value[targetRecord.index] = { type: `constant`, numeric: false, - content: program.getValue(value.value).toLowerCase() + content: JSON.stringify(array) }; - case `hash`: - const hashval = program.getValue(value.value); - let hash = 0; - if (hashval.length === 0) return hash; - for (let i = 0; i < hashval.length; i++) { - const chr = hashval.charCodeAt(i); - hash = ((hash << 5) - hash) + chr; - // hash |= 0; // Convert to 32bit integer + break; + case `format`: + targetRecord = program.getSymbolRecord(command.target); + const val = JSON.parse(program.getValue(targetRecord.value[targetRecord.index])); + targetRecord.value[targetRecord.index] = { + type: `constant`, + numeric: false, + content: JSON.stringify(val, null, 2) + }; + break; + case `parse`: + var source = program.getValue(command.source); + targetRecord = program.getSymbolRecord(command.target); + content = { + url: source + }; + var n = source.indexOf(`://`); + if (n >= 0) { + n += 3; + content.protocol = source.substr(0, n); + source = source.substr(n); + } + n = source.indexOf(`?`); + if (n > 0) { + content.domain = source.substr(0, n); + content.arg = source.substr(n + 1); + } else { + content.domain = source; } - return { + if (content.domain.endsWith(`/`)) { + content.domain = content.domain.slice(0, -1); + } + n = content.domain.indexOf(`/`); + if (n > 0) { + content.path = content.domain.substr(n + 1); + content.domain = content.domain.substr(0, n); + } + else { + content.path = ``; + } + targetRecord.value[targetRecord.index] = { type: `constant`, - numeric: true, - content: hash + numeric: false, + content: JSON.stringify(content, null, 2) }; - case `element`: - const element = program.getValue(value.element); - const elementRecord = program.getSymbolRecord(value.symbol); - var elementContent = ``; - try { - elementContent = JSON.parse(program.getValue(elementRecord.value[elementRecord.index]))[element]; - } catch (err) { - program.runtimeError(program[program.pc].lino, `Can't parse JSON`); - return null; + break; + case `delete`: + switch (command.what) { + case `property`: + const name = program.getValue(command.value); + targetRecord = program.getSymbolRecord(command.target); + record = JSON.parse(targetRecord.value[targetRecord.index].content); + delete record[name]; + targetRecord.value[targetRecord.index].content = JSON.stringify(record); + break; + case `element`: + const element = program.getValue(command.value); + targetRecord = program.getSymbolRecord(command.target); + record = JSON.parse(targetRecord.value[targetRecord.index].content); + record.splice(element, 1); + targetRecord.value[targetRecord.index].content = JSON.stringify(record); + break; } - return { + break; + case `rename`: + const oldName = program.getValue(command.oldName); + const newName = program.getValue(command.newName); + targetRecord = program.getSymbolRecord(command.target); + record = JSON.parse(targetRecord.value[targetRecord.index].content); + content = record[oldName]; + delete record[oldName]; + record[newName] = content; + targetRecord.value[targetRecord.index].content = JSON.stringify(record); + break; + case `add`: + content = program.getValue(command.item); + targetRecord = program.getSymbolRecord(command.target); + const existing = targetRecord.value[targetRecord.index].content; + record = existing ? JSON.parse(existing) : []; + record.push([`[`, `{`].includes(content[0]) ? JSON.parse(content) :content); + targetRecord.value[targetRecord.index] = { type: `constant`, numeric: false, - content: typeof elementContent === `object` ? - JSON.stringify(elementContent) : elementContent + content: JSON.stringify(record) }; - case `property`: - const property = program.getValue(value.property); - const propertyRecord = program.getSymbolRecord(value.symbol); - let propertyContent = program.getValue(propertyRecord.value[propertyRecord.index]); - var content = ``; - if (property && propertyContent) { - if (typeof propertyContent === `object`) { - content = propertyContent[property]; - } else if ([`{`, `]`].includes(propertyContent.charAt(0))) { - try { - content = JSON.parse(propertyContent)[property]; - } catch (err) { - console.log(`Can't parse '${propertyContent}': ${err.message}`); + break; + case `split`: + content = program.getValue(command.item); + const on = program.getValue(command.on); + targetRecord = program.getSymbolRecord(command.target); + targetRecord.value[targetRecord.index] = { + type: `constant`, + numeric: false, + content: JSON.stringify(content.split(on)) + }; + break; + case `replace`: + targetRecord = program.getSymbolRecord(command.target); + const index = program.getValue(command.index); + const value = program.getValue(command.value); + const current = targetRecord.value[targetRecord.index].content; + record = current ? JSON.parse(current) : []; + if (index > record.length - 1) { + program.runtimeError(command.lino, `Index out of range`); + } + record[index] = value; + targetRecord.value[targetRecord.index].content = JSON.stringify(record); + break; + } + return command.pc + 1; + } + }, + + getHandler: (name) => { + switch (name) { + case `json`: + return EasyCoder_Json.Json; + default: + return null; + } + }, + + run: (program) => { + const command = program[program.pc]; + const handler = EasyCoder_Json.getHandler(command.keyword); + if (!handler) { + program.runtimeError(command.lino, `Unknown keyword '${command.keyword}' in 'json' package`); + } + return handler.run(program); + }, + + value: { + + compile: (compiler) => { + if (compiler.tokenIs(`the`)) { + compiler.next(); + } + if (compiler.tokenIs(`json`)) { + const type = compiler.nextToken(); + if ([`size`, `count`, `keys`].includes(type)) { + compiler.skip(`of`); + if (compiler.isSymbol()) { + const target = compiler.getSymbolRecord(); + compiler.next(); + if (target.isVHolder) { + return { + domain: `json`, + type, + name: target.name + }; + } + } + } else if (type === `index`) { + if (compiler.nextTokenIs(`of`)) { + const item = compiler.getNextValue(); + if (compiler.tokenIs(`in`)) { + const list = compiler.getNextValue(); + return { + domain: `json`, + type, + item, + list + }; } } } + } + return null; + }, + + get: (program, value) => { + let symbolRecord; + let data; + let content; + switch (value.type) { + case `size`: + case `count`: + symbolRecord = program.getSymbolRecord(value.name); + data = program.getValue(symbolRecord.value[symbolRecord.index]); + let array; + try { + array = JSON.parse(data); + } catch (err) { + array = []; + } return { type: `constant`, - numeric: !Array.isArray(content) && !isNaN(content), - content: typeof content === `object` ? JSON.stringify(content) : content - }; - case `module`: - const module = program.getSymbolRecord(value.name); - return { - type: `boolean`, - numeric: false, - content: module.program + numeric: true, + content: array ? array.length : 0 }; - case `message`: - content = program.message; + case `keys`: + symbolRecord = program.getSymbolRecord(value.name); + data = program.getValue(symbolRecord.value[symbolRecord.index]); + content = data ? JSON.stringify(Object.keys(JSON.parse(data)).sort()) : `[]`; return { type: `constant`, numeric: false, content }; - case `error`: - content = program.errorMessage; + case `index`: + const item = program.getValue(value.item); + const list = JSON.parse(program.getValue(value.list)); + content = list.findIndex(function (value) { + return value === item; + }); return { type: `constant`, - numeric: false, + numeric: true, content }; - case `indexOf`: - const value1 = program.getValue(value.value1); - const value2 = program.getValue(value.value2); - try { - content = JSON.parse(value2).indexOf(value1); - return { - type: `constant`, - numeric: true, - content - }; - } catch (err) { - program.runtimeError(program[program.pc].lino, `Can't parse ${value2}`); + } + } + }, + + condition: { + + compile: () => {}, + + test: () => {} + } +}; +const EasyCoder_Rest = { + + name: `EasyCoder_Rest`, + + Rest: { + + compile: (compiler) => { + const lino = compiler.getLino(); + const request = compiler.nextToken(); + switch (request) { + case `get`: + if (compiler.nextIsSymbol(true)) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.keyword === `variable`) { + if (compiler.nextTokenIs(`from`)) { + const url = compiler.getNextValue(); + let fixup = compiler.getPc(); + compiler.addCommand({ + domain: `rest`, + keyword: `rest`, + lino, + request: `get`, + target: targetRecord.name, + url, + onError: null + }); + if (compiler.tokenIs(`or`)) { + compiler.next(); + compiler.getCommandAt(fixup).onError = compiler.getPc() + 1; + compiler.completeHandler(); + } + return true; + } + } } break; - case `arg`: - const name = program.getValue(value.value); - const target = program.getSymbolRecord(value.target); - content = target[name]; + case `post`: + let value = null; + if (compiler.nextTokenIs(`to`)) { + compiler.next(); + } else { + value = compiler.getValue(); + if (compiler.tokenIs(`to`)) { + compiler.next(); + } else { + break; + } + } + const url = compiler.getValue(); + if (!url) { + throw new Error(command.lino, `No URL present`); + } + let target = null; + if (compiler.tokenIs(`giving`)) { + if (compiler.nextIsSymbol()) { + const targetRecord = compiler.getSymbolRecord(); + if (targetRecord.isVHolder) { + target = targetRecord.name; + compiler.next(); + } else { + throw new Error(`'${targetRecord.name}' cannot hold a value`); + } + } + } + compiler.addCommand({ + domain: `rest`, + keyword: `rest`, + lino, + request: `post`, + value, + url, + target, + onError: compiler.getPc() + 2 + }); + onError = null; + if (compiler.tokenIs(`or`)) { + compiler.next(); + // onError = compiler.getPc() + 1; + compiler.completeHandler(); + } + return true; + } + return false; + }, + + createCORSRequest: (method, url) => { + var xhr = new XMLHttpRequest(); + if (`withCredentials` in xhr) { + + // Check if the XMLHttpRequest object has a "withCredentials" property. + // "withCredentials" only exists on XMLHTTPRequest2 objects. + xhr.open(method, url, true); + + } else if (typeof XDomainRequest != `undefined`) { + + // Otherwise, check if XDomainRequest. + // XDomainRequest only exists in IE, and is IE's way of making CORS requests. + xhr = new XDomainRequest(); + xhr.open(method, url); + + } else { + + // Otherwise, CORS is not supported by the browser. + xhr = null; + + } + return xhr; + }, + + run: (program) => { + const command = program[program.pc]; + const url = program.getValue(command.url); + const path = url.startsWith(`http`) ? url + : url[0] === `/` ? url.substr(1) : `${window.location.origin}/${url}`; + + const request = EasyCoder_Rest.Rest.createCORSRequest(command.request, path); + if (!request) { + program.runtimeError(command.lino, `CORS not supported`); + return; + } + request.script = program.script; + request.pc = program.pc; + + request.onload = function () { + let s = request.script; + let p = EasyCoder.scripts[s]; + let pc = request.pc; + let c = p[pc]; + if (200 <= request.status && request.status < 400) { + var content = request.responseText.trim(); + if (c.target) { + const targetRecord = program.getSymbolRecord(command.target); + targetRecord.value[targetRecord.index] = { + type: `constant`, + numeric: false, + content + }; + targetRecord.used = true; + } + p.run(c.pc + 1); + } else { + const error = `${request.status} ${request.statusText}`; + if (c.onError) { + p.errorMessage = `Exception trapped: ${error}`; + p.run(c.onError); + } else { + p.runtimeError(c.lino, `Error: ${error}`); + } + } + }; + + request.onerror = function () { + if (command.onError) { + program.errorMessage = this.responseText; + program.run(command.onError); + } else { + const error = this.responseText; + program.runtimeError(command.lino, error); + } + }; + + switch (command.request) { + case `get`: + // console.log(`GET from ${path}`); + request.send(); + break; + case `post`: + const value = program.getValue(command.value); + console.log(`POST to ${path}`); + //console.log(`value=${value}`); + request.setRequestHeader(`Content-type`, `application/json; charset=UTF-8`); + request.send(value); + break; + } + return 0; + } + }, + + getHandler: (name) => { + switch (name) { + case `rest`: + return EasyCoder_Rest.Rest; + default: + return null; + } + }, + + run: (program) => { + const command = program[program.pc]; + const handler = EasyCoder_Rest.getHandler(command.keyword); + if (!handler) { + program.runtimeError(command.lino, `Unknown keyword '${command.keyword}' in 'rest' package`); + } + return handler.run(program); + }, + + value: { + + compile: () => { + return null; + }, + + get: () => { + return null; + } + }, + + condition: { + + compile: () => {}, + + test: () => {} + } +}; +// eslint-disable-next-line no-unused-vars +const EasyCoder_Compare = (program, value1, value2) => { + + const val1 = program.value.evaluate(program, value1); + const val2 = program.value.evaluate(program, value2); + var v1 = val1.content; + var v2 = val2.content; + if (v1 && val1.numeric) { + if (!val2.numeric) { + v2 = (v2 === `` || v2 === `-` || typeof v2 === `undefined`) ? 0 : parseInt(v2); + } + } else { + if (v2 && val2.numeric) { + v2 = v2.toString(); + } + if (typeof v1 === `undefined`) { + v1 = ``; + } + if (typeof v2 === `undefined`) { + v2 = ``; + } + } + if (v1 > v2) { + return 1; + } + if (v1 < v2) { + return -1; + } + return 0; +}; +// eslint-disable-next-line no-unused-vars +const EasyCoder_Condition = { + + name: `EasyCoder_Condition`, + + compile: (compiler) => { + // See if any of the domains can handle it + compiler.mark(); + for (const domainName of Object.keys(compiler.domain)) { + // console.log(`Try domain '${domainName}' for condition`); + const domain = compiler.domain[domainName]; + const code = domain.condition.compile(compiler); + if (code) { return { - type: `constant`, - numeric: !isNaN(content), - content + domain: name, + ...code }; - case `char`: - let index = program.getValue(value.index); - let string = program.getValue(value.value); - return { + } + compiler.rewind(); + } + }, + + // runtime + + test: (program, condition) => { + const handler = program.domain[condition.domain]; + return handler.condition.test(program, condition); + } +}; +const EasyCoder_Value = { + + name: `EasyCoder_Value`, + + getItem: (compiler) => { + const token = compiler.getToken(); + if (!token) { + return null; + } + + // Check for a boolean + if (token === `true`) { + compiler.next(); + return { + type: `boolean`, + content: true + }; + } + + if (token === `false`) { + compiler.next(); + return { + type: `boolean`, + content: false + }; + } + + // Check for a string constant + if (token.charAt(0) === `\``) { + compiler.next(); + const value = { + type: `constant`, + numeric: false, + content: token.substring(1, token.length - 1) + }; + return value; + } + + // Check for a numeric constant + if (token.charAt(0).match(/[0-9-]/)) { + const val = eval(token); + if (Number.isInteger(val)) { + compiler.next(); + const value = { type: `constant`, - numeric: false, - content: string[index] + numeric: true, + content: val }; + return value; + } else { + throw new Error(`'${token}' is not an integer`); + } + } + + // See if any of the domains can handle it + const index = compiler.getIndex(); + for (const name of Object.keys(compiler.domain)) { + compiler.rewindTo(index); + const handler = compiler.domain[name]; + const code = handler.value.compile(compiler); + if (code) { + return code; + } + } + return null; + }, + + compile: compiler => { + const token = compiler.getToken(); + const item = EasyCoder_Value.getItem(compiler); + if (!item) { + throw new Error(`Undefined value: '${token}'`); + } + + if (compiler.getToken() === `cat`) { + const value = { + type: `cat`, + numeric: false, + parts: [item] + }; + while (compiler.tokenIs(`cat`)) { + compiler.next(); + value.parts.push(compiler.value.getItem(compiler)); } + return value; + } + + return item; + }, + + // runtime + + doValue: (program, value) => { + // console.log('Value:doValue:value: '+JSON.stringify(value,null,2)); + // See if it's a constant string, a variable or something else + if (typeof value.type === `undefined`) { + program.runtimeError(program[program.pc].lino, `Undefined value (variable not initialized?)`); return null; - }, + } + const type = value.type; + switch (type) { + case `cat`: + return { + type: `constant`, + numeric: false, + content: value.parts.reduce(function (acc, part) { + let value = EasyCoder_Value.doValue(program, part); + return acc + (value ? value.content : ``); + }, ``) + }; + case `boolean`: + case `constant`: + return value; + case `symbol`: + const symbol = program.getSymbolRecord(value.name); + if (symbol.isVHolder) { + const symbolValue = symbol.value[symbol.index]; + if (symbolValue) { + const v = symbolValue.content; + if (v === null || typeof v === `undefined`) { + symbolValue.content = symbolValue.numeric ? 0 : ``; + } + return symbolValue; + } else { + return null; + } + } else { + const handler = program.domain[symbol.domain].value; + return handler.get(program, value); + } + default: + break; + } + // Call the given domain to handle a value + const handler = program.domain[value.domain].value; + return handler.get(program, value); + }, - put: (symbol, value) => { - symbol.value[symbol.index] = value; + constant: (content, numeric) => { + return { + type: `constant`, + numeric, + content + }; + }, + + evaluate: (program, value) => { + if (!value) { + return { + type: `constant`, + numeric: false, + content: `` + }; + } + const result = EasyCoder_Value.doValue(program, value); + if (result) { + return result; } + program.runtimeError(program[program.pc].lino, `Can't decode value: ` + value); }, - condition: { + getValue: (program, value) => { + return EasyCoder_Value.evaluate(program, value).content; + }, + + // tools + + encode: (value, encoding) => { + if (value) { + switch (encoding) { + case `ec`: + return value.replace(/\n/g, `%0a`) + .replace(/\r/g, `%0d`) + .replace(/"/g, `~dq~`) + .replace(/'/g, `~sq~`) + .replace(/\\/g, `~bs~`); + case `url`: + return encodeURIComponent(value.replace(/\s/g, `+`)); + case `sanitize`: + return value.normalize(`NFD`).replace(/[\u0300-\u036f]/g, ``); + default: + return value; + } + } + return value; + }, + + decode: (value, encoding) => { + if (value) { + switch (encoding) { + case `ec`: + return value.replace(/%0a/g, `\n`) + .replace(/%0d/g, `\r`) + .replace(/~dq~/g, `"`) + .replace(/~sq~/g, `'`) + .replace(/~bs~/g, `\\`); + case `url`: + const decoded = decodeURIComponent(value); + return decoded.replace(/\+/g, ` `); + default: + return value; + } + } + return value; + } +}; +const EasyCoder_Run = { + + name: `EasyCoder_Run`, + + run: (program, pc) =>{ + + const queue = []; - compile: compiler => { - if (compiler.isSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.keyword === `module`) { - if (compiler.nextTokenIs(`is`)) { - let sense = true; - if (compiler.nextTokenIs(`not`)) { - compiler.next(); - sense = false; - } - if (compiler.tokenIs(`running`)) { - compiler.next(); - return { - domain: `core`, - type: `moduleRunning`, - name: symbolRecord.name, - sense - }; - } + const minIndent = (scriptLines) => { + let count = 9999; + scriptLines.forEach(function (element) { + const item = element.line; + let n = 0; + while (n < item.length) { + if (item[n] !== ` `) { + break; } - return null; + n++; } - } - if (compiler.tokenIs(`not`)) { - const value = compiler.getNextValue(); - return { - domain: `core`, - type: `not`, - value - }; - } - try { - const value1 = compiler.getValue(); - const token = compiler.getToken(); - if (token === `includes`) { - const value2 = compiler.getNextValue(); - return { - domain: `core`, - type: `includes`, - value1, - value2 - }; + if (n > 0 && n < count) { + count = n; } - if (token === `is`) { - compiler.next(); - const negate = EasyCoder_Core.isNegate(compiler); - const test = compiler.getToken(); - switch (test) { - case `numeric`: - compiler.next(); - return { - domain: `core`, - type: `numeric`, - value1, - negate - }; - case `even`: - compiler.next(); - return { - domain: `core`, - type: `even`, - value1 - }; - case `odd`: - compiler.next(); - return { - domain: `core`, - type: `odd`, - value1 - }; - case `greater`: - compiler.next(); - if (compiler.tokenIs(`than`)) { - compiler.next(); - const value2 = compiler.getValue(); - return { - domain: `core`, - type: `greater`, - value1, - value2, - negate + }); + return 0; + }; + + if (queue.length) { + queue.push(pc); + return; + } + program.register(program); + queue.push(pc); + while (queue.length > 0) { + program.pc = queue.shift(); + program.watchdog = 0; + while (program.running) { + if (program.watchdog > 1000000) { + program.lino = program[program.pc].lino; + program.reportError( + new Error(`Program runaway intercepted.\nHave you forgotten to increment a loop counter?`, program), + program); + break; + } + program.watchdog++; + const domain = program[program.pc].domain; + if (program.debugStep) { + console.log(`${program.script}: Line ${program[program.pc].lino}: PC: ${program.pc} ${domain}:${program[program.pc].keyword}`); + } + const handler = program.domain[domain]; + if (!handler) { + program.runtimeError(program[program.pc].lino, `Unknown domain '${domain}'`); + break; + } + program.pc = handler.run(program); + if (!program.pc) { + break; + } + if (program.stop) { + program.tracing = false; + break; + } + if (program.tracing) { + const command = program[program.pc]; + const scriptLines = program.source.scriptLines; + const minSpace = minIndent(scriptLines); + const tracer = document.getElementById(`easycoder-tracer`); + if (!tracer) { + program.runtimeError(command.lino, `Element 'easycoder-tracer' was not found`); + return; + } + tracer.style.display = `block`; + tracer.style.visibility = `visible`; + var variables = ``; + if (program.tracer) { + const content = document.getElementById(`easycoder-tracer-content`); + if (content) { + program.tracer.variables.forEach(function (name, index, array) { + const symbol = program.getSymbolRecord(name); + if (symbol.elements > 1) { + variables += `${name}: ${symbol.index}/${symbol.elements}: `; + for (var n = 0; n < symbol.elements; n++) { + const value = symbol.value[n]; + if (value) { + variables += `${value.content} `; + } else { + variables += `undefined `; + } + } + } else { + const value = symbol.value[symbol.index]; + if (value) { + variables += `${name}: ${value.content}`; + } else { + variables += `${name}: undefined`; + } + } + switch (program.tracer.alignment) { + case `horizontal`: + if (index < array.length - 1) { + variables += `, `; + } + break; + case `vertical`: + variables += `
`; + break; + } + }); + variables += `
`; + var trace = ``; + for (var n = 5; n > 0; n--) { + if (command.lino) { + const text = scriptLines[command.lino - n].line.substr(minSpace); + trace += ``; + } + trace += `
`; + } + content.innerHTML = `${variables} ${trace}`; + content.style.display = `block`; + const run = document.getElementById(`easycoder-run-button`); + const step = document.getElementById(`easycoder-step-button`); + + run.onclick = function () { + run.blur(); + program.tracing = false; + const content = document.getElementById(`easycoder-tracer-content`); + content.style.display = `none`; + try { + EasyCoder_Run.run(program, program.resume); + } catch (err) { + const message = `Error in run handler: ` + err.message; + console.log(message); + alert(message); + } }; - } - return null; - case `less`: - compiler.next(); - if (compiler.tokenIs(`than`)) { - compiler.next(); - const value2 = compiler.getValue(); - return { - domain: `core`, - type: `less`, - value1, - value2, - negate + + step.onclick = function () { + console.log(`step`); + step.blur(); + program.tracing = true; + const content = document.getElementById(`easycoder-tracer-content`); + content.style.display = `block`; + try { + program.run(program.resume); + } catch (err) { + const message = `Error in step handler: ` + err.message; + console.log(message); + alert(message); + } }; - } - return null; - default: - const value2 = compiler.getValue(); - return { - domain: `core`, - type: `is`, - value1, - value2, - negate - }; - } - } else if (value1) { - // It's a boolean if - return { - domain: `core`, - type: `boolean`, - value: value1 - }; - } - } catch (err) { - compiler.warning(`Can't get a value`); - return 0; - } - return null; - }, + } - test: (program, condition) => { - var comparison; - switch (condition.type) { - case `boolean`: - return program.getValue(condition.value); - case `numeric`: - let v = program.getValue(condition.value1); - let test = v === ` ` || isNaN(v); - return condition.negate ? test : !test; - case `even`: - return (program.getValue(condition.value1) % 2) === 0; - case `odd`: - return (program.getValue(condition.value1) % 2) === 1; - case `is`: - comparison = program.compare(program, condition.value1, condition.value2); - return condition.negate ? comparison !== 0 : comparison === 0; - case `greater`: - comparison = program.compare(program, condition.value1, condition.value2); - return condition.negate ? comparison <= 0 : comparison > 0; - case `less`: - comparison = program.compare(program, condition.value1, condition.value2); - return condition.negate ? comparison >= 0 : comparison < 0; - case `not`: - return !program.getValue(condition.value); - case `moduleRunning`: - let moduleRecord = program.getSymbolRecord(condition.name); - if (EasyCoder.scripts.hasOwnProperty(moduleRecord.program) ) { - let p = EasyCoder.scripts[moduleRecord.program]; - return condition.sense ? p.running : !p.running; + program.resume = program.pc; + program.pc = 0; + } + break; } - return !condition.sense; - case `includes`: - const value1 = JSON.parse(program.getValue(condition.value1)); - const value2 = program.getValue(condition.value2); - return value1.includes(value2); } - return false; + } + }, + + exit: (program) => { + if (program.onExit) { + program.run(program.onExit); + } + let parent = program.parent; + let afterExit = program.afterExit; + delete EasyCoder.scripts[program.script]; + if (program.module) { + delete program.module.program; + } + Object.keys(program).forEach(function(key) { + delete program[key]; + }); + if (parent && afterExit) { + EasyCoder.scripts[parent].run(afterExit); } } }; -const EasyCoder = { +// eslint-disable-next-line no-unused-vars +const EasyCoder_Compiler = { - name: `EasyCoder_Main`, + name: `EasyCoder_Compiler`, - domain: { - core: EasyCoder_Core + getTokens: function() { + return this.tokens; }, - elementId: 0, - - runtimeError: function (lino, message) { - this.lino = lino; - this.reportError({ - message: `Line ${(lino >= 0) ? lino : ``}: ${message}` - }, this.program); - if (this.program) { - this.program.aborted = true; - } + addWarning: function(message) { + this.warnings.push(message); }, - nonNumericValueError: function (lino) { - this.runtimeError(lino, `Non-numeric value`); + + warning: function(message) { + this.addWarning(message); }, - variableDoesNotHoldAValueError: function (lino, name) { - this.runtimeError(lino, `Variable '${name}' does not hold a value`); + + unrecognisedSymbol: function(item) { + this.addWarning(`Unrecognised symbol '${item}'`); }, - reportError: function (err, program, source) { - if (!err.message) { - console.log(`An error occurred - origin was ${err.path[0]}`); - return; - } - if (!this.compiling && !program) { - const errString = `Error: ${err.message}`; - alert(errString); - console.log(errString); - return; - } - // const compiler = EasyCoder_Compiler; - const { - tokens, - scriptLines - } = source ? source : program.source; - const lino = this.compiling ? tokens[EasyCoder_Compiler.getIndex()].lino : program[program.pc].lino; - var errString = this.compiling ? `Compile error` : `Runtime error in '${program.script}'`; - errString += `:\n`; - var start = lino - 5; - start = start < 0 ? 0 : start; - for (var n = start; n < lino; n++) { - const nn = (`` + (n + 1)).padStart(4, ` `); - errString += nn + ` ` + scriptLines[n].line.split(`\\s`).join(` `) + `\n`; - } - errString += `${err.message}\n`; - const warnings = EasyCoder_Compiler.getWarnings(); - if (warnings.length) { - errString += `Warnings:\n`; - for (const warning of warnings) { - errString += `${warning}\n`; - } - } - console.log(errString); - alert(errString); + getWarnings: function() { + return this.warnings; }, - getSymbolRecord: function (name) { - const target = this[this.symbols[name].pc]; - if (target.alias) { - return this.getSymbolRecord(target.alias); - } - if (target.exporter) { - // if (target.exporter != this.script) { - return EasyCoder.scripts[target.exporter].getSymbolRecord(target.exportedName); - // } - } - return target; + getIndex: function() { + return this.index; }, - verifySymbol: function (name) { - return this.symbols.hasOwnProperty(name); + next: function(step = 1) { + this.index = this.index + step; }, - encode: function (value) { - return EasyCoder_Value.encode(value, this.encoding); + peek: function() { + return this.tokens[this.index + 1].token; }, - decode: function (value) { - return EasyCoder_Value.decode(value, this.encoding); + more: function() { + return this.index < this.tokens.length; }, - evaluate: function (value) { - return EasyCoder_Value.evaluate(this, value); + getToken: function() { + if (this.index >= this.tokens.length) { + return null; + } + const item = this.tokens[this.index]; + return item ? this.tokens[this.index].token : null; }, - getValue: function (value) { - return EasyCoder_Value.getValue(this, value); + nextToken: function() { + this.next(); + return this.getToken(); }, - getFormattedValue: function (value) { - const v = EasyCoder_Value.evaluate(this, value); - if (v.numeric) { - return v.content; + tokenIs: function(token) { + if (this.index >= this.tokens.length) { + return false; } - if (v.type === `boolean`) { - return v.content ? `true` : `false`; + return token === this.tokens[this.index].token; + }, + + nextTokenIs: function(token) { + this.next(); + return this.tokenIs(token); + }, + + skip: function(token) { + if (this.index >= this.tokens.length) { + return null; } - if (this.isJsonString(v.content)) { - try { - const parsed = JSON.parse(v.content); - return JSON.stringify(parsed, null, 2); - } catch (err) { - this.reportError(err); - return `{}`; - } + this.next(); + if (this.tokenIs(token)) { + this.next(); } - return v.content; }, - getSimpleValue: function (content) { - if (content === true || content === false) { - return { - type: `boolean`, - content - }; - } - return { - type: `constant`, - numeric: Number.isInteger(content), - content - }; + prev: function() { + this.index--; }, - run: function (pc) { - if (pc) { - this.program = this; - EasyCoder_Run.run(this, pc); + getLino: function() { + if (this.index >= this.tokens.length) { + return 0; } + return this.tokens[this.index].lino; }, - exit: function () { - EasyCoder_Run.exit(this); + getTarget: function(index = this.index) { + return this.tokens[index].token; }, - register: (program) => { - this.program = program; + getTargetPc: function(index = this.index) { + return this.symbols[this.getTarget(index)].pc; }, - require: function(type, src, cb) { - const element = document.createElement(type === `css` ? `link` : `script`); - switch (type) { - case `css`: - element.type = `text/css`; - element.href = src; - element.rel = `stylesheet`; - break; - case `js`: - element.type = `text/javascript`; - element.src = src; - break; - default: - return; + getCommandAt: function(pc) { + return this.program[pc]; + }, + + isSymbol: function(required = false) { + const isSymbol = this.getTarget() in this.symbols; + if (isSymbol) return true; + if (required) { + throw new Error(`Unknown symbol: '${this.getTarget()}'`); } - element.onload = function () { - console.log(`${Date.now() - EasyCoder.timestamp} ms: Library ${src} loaded`); - cb(); - }; - document.head.appendChild(element); + return false; + }, + + nextIsSymbol: function(required = false) { + this.next(); + return this.isSymbol(required); }, - isUndefined: item => { - return typeof item === `undefined`; + getSymbol: function(required = false) { + if (this.isSymbol(required)) { + return this.symbols[this.getToken()]; + } }, - isJsonString: function (str) { - try { - JSON.parse(str); - } catch (e) { - return false; - } - return true; + getSymbolPc: function(required = false) { + return this.getSymbol(required).pc; }, - runScript: function (program) { - const command = program[program.pc]; - const script = program.getValue(command.script); - const imports = command.imports; - imports.caller = program.script; - const moduleRecord = command.module ? program.getSymbolRecord(command.module) : null; - try { - EasyCoder.tokeniseAndCompile(script.split(`\n`), imports, moduleRecord, this.script, command.then); - } catch (err) { - EasyCoder.reportError(err, program, program.source); - if (program.onError) { - program.run(program.onError); - } else { - let parent = EasyCoder.scripts[program.parent]; - if (parent && parent.onError) { - parent.run(parent.onError); - } - } - return; - } - if (command.nowait) { - EasyCoder.run(program.nextPc); - } + getSymbolRecord: function() { + const record = this.program[this.getSymbolPc(true)]; + record.used = true; + return record; }, - close: function () {}, + getSymbols: function() { + return this.symbols; + }, - compileScript: function (source, imports, module, parent) { - const { - tokens - } = source; - this.compiling = true; - const compiler = EasyCoder_Compiler; - this.compiler = compiler; - compiler.value = EasyCoder_Value; - compiler.condition = EasyCoder_Condition; - compiler.domain = this.domain; - compiler.imports = imports; - compiler.continue = false; - const program = EasyCoder_Compiler.compile(tokens); - // console.log('Program: ' + JSON.stringify(program, null, 2)); - this.compiling = false; + getProgram: function() { + return this.program; + }, - program.EasyCoder = this; - program.value = EasyCoder_Value; - program.condition = EasyCoder_Condition; - program.compare = EasyCoder_Compare; - program.source = source; - program.run = this.run; - program.exit = this.exit; - program.runScript = this.runScript; - program.evaluate = this.evaluate; - program.getValue = this.getValue; - program.getFormattedValue = this.getFormattedValue; - program.getSimpleValue = this.getSimpleValue; - program.encode = this.encode; - program.decode = this.decode; - program.domain = this.domain; - program.require = this.require; - program.isUndefined = this.isUndefined; - program.isJsonString = this.isJsonString; - program.checkPlugin = this.checkPlugin; - program.getPlugin = this.getPlugin; - program.addLocalPlugin = this.addLocalPlugin; - program.getPluginsPath = this.getPluginsPath; - program.getSymbolRecord = this.getSymbolRecord; - program.verifySymbol = this.verifySymbol; - program.runtimeError = this.runtimeError; - program.nonNumericValueError = this.nonNumericValueError; - program.variableDoesNotHoldAValueError = this.variableDoesNotHoldAValueError; - program.reportError = this.reportError; - program.register = this.register; - program.symbols = compiler.getSymbols(); - program.unblocked = false; - program.encoding = `ec`; - program.popups = []; - program.stack = []; - program.queue = [0]; - program.module = module; - program.parent = parent; - if (module) { - module.program = program.script; - } - return program; + getPc: function() { + return this.program.length; }, - tokeniseFile: function(file) { - const scriptLines = []; - const tokens = []; - let index = 0; - file.forEach(function (line, lino) { - scriptLines.push({ - lino: lino + 1, - line - }); - const len = line.length; - let token = ``; - let inSpace = true; - for (let n = 0; n < len; n++) { - const c = line[n]; - if (c.trim().length == 0) { - if (inSpace) { - continue; - } - tokens.push({ - index, - lino: lino + 1, - token - }); - index++; - token = ``; - inSpace = true; - continue; - } - inSpace = false; - if (c === `\``) { - m = n; - while (++n < line.length) { - if (line[n] === `\``) { - break; - } - } - token = line.substr(m, n - m + 1); - } else if (c == `!`) { - break; - } else { - token += c; - } - } - if (token.length > 0) { - tokens.push({ - index, - lino: lino + 1, - token - }); - } - }); - return {scriptLines, tokens}; + getValue: function() { + return this.value.compile(this); }, - tokeniseAndCompile: function (file, imports, module, parent, then) { - // console.log('Tokenise script: '); - let program = null; - const startCompile = Date.now(); - const source = this.tokeniseFile(file); - try { - program = this.compileScript(source, imports, module, parent); - this.scriptIndex++; - if (!program.script) { - program.script = this.scriptIndex; - } - const finishCompile = Date.now(); - console.log(`${finishCompile - this.timestamp} ms: ` + - `Compiled ${program.script}: ${source.scriptLines.length} lines (${source.tokens.length} tokens) in ` + - `${finishCompile - startCompile} ms`); - } catch (err) { - if (err.message !== `stop`) { - let parentRecord = EasyCoder.scripts[parent]; - this.reportError(err, parentRecord, source); - if (parentRecord && parentRecord.onError) { - parentRecord.run(parentRecord.onError); - } - // Remove this script - if (EasyCoder_Compiler.script) { - delete EasyCoder.scripts[EasyCoder_Compiler.script]; - delete EasyCoder_Compiler.script; - } - } - return; - } - if (program) { - EasyCoder.scripts[program.script] = program; - if (module) { - module.program = program.script; - } - program.afterExit = then; - program.running = true; - EasyCoder_Run.run(program, 0); - } + getNextValue: function() { + this.next(); + return this.getValue(); }, - tokenise: function(source) { - const script = source.split(`\n`); - if (!this.tokenising) { - try { - this.tokeniseAndCompile(script); - } catch (err) { - this.reportError(err, null, source); - } - this.tokenising = true; - } + getCondition: function() { + return this.condition.compile(this); }, - setPluginCount: function(count) { - EasyCoder.plugins = []; - EasyCoder.pluginCount = count; + constant: function(content, numeric = false) { + return this.value.constant(content, numeric); }, - checkPlugin: function(name) { - return EasyCoder.domain[name]; + addCommand: function(item) { + const pc = this.program.length; + this.program.push({ + pc, + ...item + }); }, - getPlugin: function(name, src, onload) { - if (EasyCoder.domain[name]) { - onload(); - return; - } - const script = document.createElement(`script`); - script.type = `text/javascript`; - let location = document.scripts[0].src; - location = location.substring(0, location.indexOf(`/easycoder.js`)); - // script.src = `${location}/${src}?ver=${EasyCoder.version}`; - script.src = `${src}?ver=${EasyCoder.version}`; - script.onload = function () { - console.log(`${Date.now() - EasyCoder.timestamp} ms: Plugin ${src} loaded`); - onload(); + addSymbol: function(name, pc) { + this.symbols[name] = { + pc }; - document.head.appendChild(script); }, - addGlobalPlugin: function(name, handler) { - // alert(`Add plugin ${name}`); - EasyCoder.plugins.push({ - name, - handler - }); - if (EasyCoder.plugins.length === EasyCoder.pluginCount) { - EasyCoder.plugins.forEach(function (plugin) { - EasyCoder.domain[plugin.name] = plugin.handler; - }); - EasyCoder.tokenise(EasyCoder.source); - } + mark: function() { + this.savedMark = this.index; }, - addLocalPlugin: function(name, handler, callback) { - EasyCoder.domain[name] = handler; - callback(); + rewind: function() { + this.index = this.savedMark; }, - getPluginsPath: function() { - return EasyCoder.pluginsPath; + rewindTo: function(index) { + this.index = index; }, - loadPluginJs: function(path) { - console.log(`${Date.now() - this.timestamp} ms: Load ${path}/easycoder/plugins.js`); - const script = document.createElement(`script`); - script.src = `${window.location.origin}${path}/easycoder/plugins.js?ver=${this.version}`; - script.type = `text/javascript`; - script.onload = () => { - EasyCoder_Plugins.getGlobalPlugins( - this.timestamp, - path, - this.setPluginCount, - this.getPlugin, - this.addGlobalPlugin - ); - }; - script.onerror = () => { - if (path) { - this.loadPluginJs(path.slice(0, path.lastIndexOf(`/`))); - } else { - this.reportError({ - message: `Can't load plugins.js` - }, this.program, this.source); - } - }; - document.head.appendChild(script); - this.pluginsPath = path; + completeHandler: function() { + const lino = this.getLino(); + // Add a 'goto' to skip the action + const goto = this.getPc(); + this.addCommand({ + domain: `core`, + keyword: `goto`, + lino, + goto: 0 + }); + // Add the action + this.compileOne(); + // If `continue` is set + if (this.continue) { + this.addCommand({ + domain: `core`, + keyword: `goto`, + lino, + goto: this.getPc() + 1 + }); + this.continue = false; + } + // else add a 'stop' + else { + this.addCommand({ + domain: `core`, + keyword: `stop`, + lino, + next: 0 + }); + } + // Fixup the 'goto' + this.getCommandAt(goto).goto = this.getPc(); + return true; }, - start: function(source) { - this.source = source; - this.scriptIndex = 0; - let pathname = window.location.pathname; - if (pathname.endsWith(`/`)) { - pathname = pathname.slice(0, -1); - } else { - pathname = ``; - } - if (typeof EasyCoder_Plugins === `undefined`) { - this.loadPluginJs(pathname); - } else { - this.pluginsPath = pathname; - EasyCoder_Plugins.getGlobalPlugins( - this.timestamp, - pathname, - this.setPluginCount, - this.getPlugin, - this.addGlobalPlugin - ); + compileVariable: function(domain, keyword, isVHolder = false, extra = null) { + this.next(); + const lino = this.getLino(); + const item = this.getTokens()[this.getIndex()]; + if (this.symbols[item.token]) { + throw new Error(`Duplicate variable name '${item.token}'`); } - } -}; -const EasyCoder_Run = { - - name: `EasyCoder_Run`, - - run: (program, pc) =>{ - - const queue = []; - - const minIndent = (scriptLines) => { - let count = 9999; - scriptLines.forEach(function (element) { - const item = element.line; - let n = 0; - while (n < item.length) { - if (item[n] !== ` `) { - break; - } - n++; - } - if (n > 0 && n < count) { - count = n; - } - }); - return 0; + const pc = this.getPc(); + this.next(); + this.addSymbol(item.token, pc); + const command = { + domain, + keyword, + lino, + isSymbol: true, + used: false, + isVHolder, + name: item.token, + elements: 1, + index: 0, + value: [{}], + element: [], + extra }; + if (extra === `dom`) { + command.element = []; + } + this.addCommand(command); + return command; + }, - if (queue.length) { - queue.push(pc); + compileToken: function() { + // Try each domain in turn until one can handle the command + const token = this.getToken(); + if (!token) { return; } - program.register(program); - queue.push(pc); - while (queue.length > 0) { - program.pc = queue.shift(); - program.watchdog = 0; - while (program.running) { - if (program.watchdog > 1000000) { - program.lino = program[program.pc].lino; - program.reportError( - new Error(`Program runaway intercepted.\nHave you forgotten to increment a loop counter?`, program), - program); - break; - } - program.watchdog++; - const domain = program[program.pc].domain; - if (program.debugStep) { - console.log(`${program.script}: Line ${program[program.pc].lino}: PC: ${program.pc} ${domain}:${program[program.pc].keyword}`); - } - const handler = program.domain[domain]; - if (!handler) { - program.runtimeError(program[program.pc].lino, `Unknown domain '${domain}'`); - break; - } - program.pc = handler.run(program); - if (!program.pc) { - break; - } - if (program.stop) { - program.tracing = false; - break; - } - if (program.tracing) { - const command = program[program.pc]; - const scriptLines = program.source.scriptLines; - const minSpace = minIndent(scriptLines); - const tracer = document.getElementById(`easycoder-tracer`); - if (!tracer) { - program.runtimeError(command.lino, `Element 'easycoder-tracer' was not found`); + // console.log(`Compile ${token}`); + this.mark(); + for (const domainName of Object.keys(this.domain)) { + // console.log(`Try domain ${domainName} for token ${token}`); + const domain = this.domain[domainName]; + if (domain) { + const handler = domain.getHandler(token); + if (handler) { + if (handler.compile(this)) { return; } - tracer.style.display = `block`; - tracer.style.visibility = `visible`; - var variables = ``; - if (program.tracer) { - const content = document.getElementById(`easycoder-tracer-content`); - if (content) { - program.tracer.variables.forEach(function (name, index, array) { - const symbol = program.getSymbolRecord(name); - if (symbol.elements > 1) { - variables += `${name}: ${symbol.index}/${symbol.elements}: `; - for (var n = 0; n < symbol.elements; n++) { - const value = symbol.value[n]; - if (value) { - variables += `${value.content} `; - } else { - variables += `undefined `; - } - } - } else { - const value = symbol.value[symbol.index]; - if (value) { - variables += `${name}: ${value.content}`; - } else { - variables += `${name}: undefined`; - } - } - switch (program.tracer.alignment) { - case `horizontal`: - if (index < array.length - 1) { - variables += `, `; - } - break; - case `vertical`: - variables += `
`; - break; - } - }); - variables += `
`; - var trace = ``; - for (var n = 5; n > 0; n--) { - if (command.lino) { - const text = scriptLines[command.lino - n].line.substr(minSpace); - trace += ``; - } - trace += `
`; - } - content.innerHTML = `${variables} ${trace}`; - content.style.display = `block`; - const run = document.getElementById(`easycoder-run-button`); - const step = document.getElementById(`easycoder-step-button`); - - run.onclick = function () { - run.blur(); - program.tracing = false; - const content = document.getElementById(`easycoder-tracer-content`); - content.style.display = `none`; - try { - EasyCoder_Run.run(program, program.resume); - } catch (err) { - const message = `Error in run handler: ` + err.message; - console.log(message); - alert(message); - } - }; - - step.onclick = function () { - console.log(`step`); - step.blur(); - program.tracing = true; - const content = document.getElementById(`easycoder-tracer-content`); - content.style.display = `block`; - try { - program.run(program.resume); - } catch (err) { - const message = `Error in step handler: ` + err.message; - console.log(message); - alert(message); - } - }; - } - - program.resume = program.pc; - program.pc = 0; - } - break; } } + this.rewind(); } + console.log(`No handler found`); + throw new Error(`I don't understand '${token}...'`); }, - exit: (program) => { - if (program.onExit) { - program.run(program.onExit); + compileOne: function() { + const keyword = this.getToken(); + if (!keyword) { + return; } - let parent = program.parent; - let afterExit = program.afterExit; - delete EasyCoder.scripts[program.script]; - if (program.module) { - delete program.module.program; + // console.log(`Compile keyword '${keyword}'`); + this.warnings = []; + const pc = this.program.length; + // First check for a label + if (keyword.endsWith(`:`)) { + const name = keyword.substring(0, keyword.length - 1); + if (this.symbols[name]) { + throw new Error(`Duplicate symbol: '${name}'`); + } + this.symbols[name] = { + pc + }; + this.index++; + } else { + this.compileToken(); } - Object.keys(program).forEach(function(key) { - delete program[key]; + }, + + compileFromHere: function(stopOn) { + while (this.index < this.tokens.length) { + const token = this.tokens[this.index]; + const keyword = token.token; + if (keyword === `else`) { + return this.program; + } + this.compileOne(); + if (stopOn.indexOf(keyword) > -1) { + break; + } + } + }, + + compile: function(tokens) { + this.tokens = tokens; + this.index = 0; + this.program = []; + this.program.script = 0; + this.program.symbols = {}; + this.symbols = this.program.symbols; + this.warnings = []; + this.compileFromHere([]); + this.addCommand({ + domain: `core`, + keyword: `exit`, + lino: this.getLino(), + next: 0 }); - if (parent && afterExit) { - EasyCoder.scripts[parent].run(afterExit); + // console.log('Symbols: ' + JSON.stringify(this.symbols, null, 2)); + for (const symbol in this.symbols) { + const record = this.program[this.symbols[symbol].pc]; + if (record.isSymbol && !record.used && !record.exporter) { + console.log(`Symbol '${record.name}' has not been used.`); + } } + return this.program; } }; -const EasyCoder_Value = { +const EasyCoder = { - name: `EasyCoder_Value`, + name: `EasyCoder_Main`, - getItem: (compiler) => { - const token = compiler.getToken(); - if (!token) { - return null; - } + domain: { + core: EasyCoder_Core, + browser: EasyCoder_Browser, + json: EasyCoder_Json, + rest: EasyCoder_Rest + }, - // Check for a boolean - if (token === `true`) { - compiler.next(); - return { - type: `boolean`, - content: true - }; - } + elementId: 0, - if (token === `false`) { - compiler.next(); - return { - type: `boolean`, - content: false - }; + runtimeError: function (lino, message) { + this.lino = lino; + this.reportError({ + message: `Line ${(lino >= 0) ? lino : ``}: ${message}` + }, this.program); + if (this.program) { + this.program.aborted = true; } + }, + nonNumericValueError: function (lino) { + this.runtimeError(lino, `Non-numeric value`); + }, + variableDoesNotHoldAValueError: function (lino, name) { + this.runtimeError(lino, `Variable '${name}' does not hold a value`); + }, - // Check for a string constant - if (token.charAt(0) === `\``) { - compiler.next(); - const value = { - type: `constant`, - numeric: false, - content: token.substring(1, token.length - 1) - }; - return value; + reportError: function (err, program, source) { + if (!err.message) { + console.log(`An error occurred - origin was ${err.path[0]}`); + return; } - - // Check for a numeric constant - if (token.charAt(0).match(/[0-9-]/)) { - const val = eval(token); - if (Number.isInteger(val)) { - compiler.next(); - const value = { - type: `constant`, - numeric: true, - content: val - }; - return value; - } else { - throw new Error(`'${token}' is not an integer`); - } + if (!this.compiling && !program) { + const errString = `Error: ${err.message}`; + alert(errString); + console.log(errString); + return; } - - // See if any of the domains can handle it - const index = compiler.getIndex(); - for (const name of Object.keys(compiler.domain)) { - compiler.rewindTo(index); - const handler = compiler.domain[name]; - const code = handler.value.compile(compiler); - if (code) { - return code; + // const compiler = EasyCoder_Compiler; + const { + tokens, + scriptLines + } = source ? source : program.source; + const lino = this.compiling ? tokens[EasyCoder_Compiler.getIndex()].lino : program[program.pc].lino; + var errString = this.compiling ? `Compile error` : `Runtime error in '${program.script}'`; + errString += `:\n`; + var start = lino - 5; + start = start < 0 ? 0 : start; + for (var n = start; n < lino; n++) { + const nn = (`` + (n + 1)).padStart(4, ` `); + errString += nn + ` ` + scriptLines[n].line.split(`\\s`).join(` `) + `\n`; + } + errString += `${err.message}\n`; + const warnings = EasyCoder_Compiler.getWarnings(); + if (warnings.length) { + errString += `Warnings:\n`; + for (const warning of warnings) { + errString += `${warning}\n`; } } - return null; + console.log(errString); + alert(errString); }, - compile: compiler => { - const token = compiler.getToken(); - const item = EasyCoder_Value.getItem(compiler); - if (!item) { - throw new Error(`Undefined value: '${token}'`); + getSymbolRecord: function (name) { + const target = this[this.symbols[name].pc]; + if (target.alias) { + return this.getSymbolRecord(target.alias); } - - if (compiler.getToken() === `cat`) { - const value = { - type: `cat`, - numeric: false, - parts: [item] - }; - while (compiler.tokenIs(`cat`)) { - compiler.next(); - value.parts.push(compiler.value.getItem(compiler)); - } - return value; + if (target.exporter) { + // if (target.exporter != this.script) { + return EasyCoder.scripts[target.exporter].getSymbolRecord(target.exportedName); + // } } + return target; + }, - return item; + verifySymbol: function (name) { + return this.symbols.hasOwnProperty(name); }, - // runtime + encode: function (value) { + return EasyCoder_Value.encode(value, this.encoding); + }, - doValue: (program, value) => { - // console.log('Value:doValue:value: '+JSON.stringify(value,null,2)); - // See if it's a constant string, a variable or something else - if (typeof value.type === `undefined`) { - program.runtimeError(program[program.pc].lino, `Undefined value (variable not initialized?)`); - return null; + decode: function (value) { + return EasyCoder_Value.decode(value, this.encoding); + }, + + evaluate: function (value) { + return EasyCoder_Value.evaluate(this, value); + }, + + getValue: function (value) { + return EasyCoder_Value.getValue(this, value); + }, + + getFormattedValue: function (value) { + const v = EasyCoder_Value.evaluate(this, value); + if (v.numeric) { + return v.content; } - const type = value.type; - switch (type) { - case `cat`: + if (v.type === `boolean`) { + return v.content ? `true` : `false`; + } + if (this.isJsonString(v.content)) { + try { + const parsed = JSON.parse(v.content); + return JSON.stringify(parsed, null, 2); + } catch (err) { + this.reportError(err); + return `{}`; + } + } + return v.content; + }, + + getSimpleValue: function (content) { + if (content === true || content === false) { return { - type: `constant`, - numeric: false, - content: value.parts.reduce(function (acc, part) { - let value = EasyCoder_Value.doValue(program, part); - return acc + (value ? value.content : ``); - }, ``) + type: `boolean`, + content }; - case `boolean`: - case `constant`: - return value; - case `symbol`: - const symbol = program.getSymbolRecord(value.name); - if (symbol.isVHolder) { - const symbolValue = symbol.value[symbol.index]; - if (symbolValue) { - const v = symbolValue.content; - if (v === null || typeof v === `undefined`) { - symbolValue.content = symbolValue.numeric ? 0 : ``; - } - return symbolValue; - } else { - return null; - } - } else { - const handler = program.domain[symbol.domain].value; - return handler.get(program, value); - } - default: + } + return { + type: `constant`, + numeric: Number.isInteger(content), + content + }; + }, + + run: function (pc) { + if (pc) { + this.program = this; + EasyCoder_Run.run(this, pc); + } + }, + + exit: function () { + EasyCoder_Run.exit(this); + }, + + register: (program) => { + this.program = program; + }, + + require: function(type, src, cb) { + let prefix = ``; + if (src[0] == `/`) { + prefix = window.location + `/`; + } + const element = document.createElement(type === `css` ? `link` : `script`); + switch (type) { + case `css`: + element.type = `text/css`; + element.href = `${prefix}${src}`; + element.rel = `stylesheet`; break; + case `js`: + element.type = `text/javascript`; + element.src = `${prefix}${src}`; + break; + default: + return; } - // Call the given domain to handle a value - const handler = program.domain[value.domain].value; - return handler.get(program, value); + element.onload = function () { + console.log(`${Date.now() - EasyCoder.timestamp} ms: Library ${prefix}${src} loaded`); + cb(); + }; + document.head.appendChild(element); }, - constant: (content, numeric) => { - return { - type: `constant`, - numeric, - content - }; + isUndefined: item => { + return typeof item === `undefined`; }, - evaluate: (program, value) => { - if (!value) { - return { - type: `constant`, - numeric: false, - content: `` - }; + isJsonString: function (str) { + try { + JSON.parse(str); + } catch (e) { + return false; } - const result = EasyCoder_Value.doValue(program, value); - if (result) { - return result; + return true; + }, + + runScript: function (program) { + const command = program[program.pc]; + const script = program.getValue(command.script); + const imports = command.imports; + imports.caller = program.script; + const moduleRecord = command.module ? program.getSymbolRecord(command.module) : null; + try { + EasyCoder.tokeniseAndCompile(script.split(`\n`), imports, moduleRecord, this.script, command.then); + } catch (err) { + EasyCoder.reportError(err, program, program.source); + if (program.onError) { + program.run(program.onError); + } else { + let parent = EasyCoder.scripts[program.parent]; + if (parent && parent.onError) { + parent.run(parent.onError); + } + } + return; + } + if (command.nowait) { + EasyCoder.run(program.nextPc); } - program.runtimeError(program[program.pc].lino, `Can't decode value: ` + value); }, - getValue: (program, value) => { - return EasyCoder_Value.evaluate(program, value).content; + close: function () {}, + + compileScript: function (source, imports, module, parent) { + const { + tokens + } = source; + this.compiling = true; + const compiler = EasyCoder_Compiler; + this.compiler = compiler; + compiler.value = EasyCoder_Value; + compiler.condition = EasyCoder_Condition; + compiler.domain = this.domain; + compiler.imports = imports; + compiler.continue = false; + const program = EasyCoder_Compiler.compile(tokens); + // console.log('Program: ' + JSON.stringify(program, null, 2)); + this.compiling = false; + + program.EasyCoder = this; + program.value = EasyCoder_Value; + program.condition = EasyCoder_Condition; + program.compare = EasyCoder_Compare; + program.source = source; + program.run = this.run; + program.exit = this.exit; + program.runScript = this.runScript; + program.evaluate = this.evaluate; + program.getValue = this.getValue; + program.getFormattedValue = this.getFormattedValue; + program.getSimpleValue = this.getSimpleValue; + program.encode = this.encode; + program.decode = this.decode; + program.domain = this.domain; + program.require = this.require; + program.isUndefined = this.isUndefined; + program.isJsonString = this.isJsonString; + program.getSymbolRecord = this.getSymbolRecord; + program.verifySymbol = this.verifySymbol; + program.runtimeError = this.runtimeError; + program.nonNumericValueError = this.nonNumericValueError; + program.variableDoesNotHoldAValueError = this.variableDoesNotHoldAValueError; + program.reportError = this.reportError; + program.register = this.register; + program.symbols = compiler.getSymbols(); + program.unblocked = false; + program.encoding = `ec`; + program.popups = []; + program.stack = []; + program.queue = [0]; + program.module = module; + program.parent = parent; + if (module) { + module.program = program.script; + } + return program; }, - // tools + tokeniseFile: function(file) { + const scriptLines = []; + const tokens = []; + let index = 0; + file.forEach(function (line, lino) { + scriptLines.push({ + lino: lino + 1, + line + }); + const len = line.length; + let token = ``; + let inSpace = true; + for (let n = 0; n < len; n++) { + const c = line[n]; + if (c.trim().length == 0) { + if (inSpace) { + continue; + } + tokens.push({ + index, + lino: lino + 1, + token + }); + index++; + token = ``; + inSpace = true; + continue; + } + inSpace = false; + if (c === `\``) { + m = n; + while (++n < line.length) { + if (line[n] === `\``) { + break; + } + } + token = line.substr(m, n - m + 1); + } else if (c == `!`) { + break; + } else { + token += c; + } + } + if (token.length > 0) { + tokens.push({ + index, + lino: lino + 1, + token + }); + } + }); + return {scriptLines, tokens}; + }, - encode: (value, encoding) => { - if (value) { - switch (encoding) { - case `ec`: - return value.replace(/\n/g, `%0a`) - .replace(/\r/g, `%0d`) - .replace(/"/g, `~dq~`) - .replace(/'/g, `~sq~`) - .replace(/\\/g, `~bs~`); - case `url`: - return encodeURIComponent(value.replace(/\s/g, `+`)); - case `sanitize`: - return value.normalize(`NFD`).replace(/[\u0300-\u036f]/g, ``); - default: - return value; + tokeniseAndCompile: function (file, imports, module, parent, then) { + // console.log('Tokenise script: '); + let program = null; + const startCompile = Date.now(); + const source = this.tokeniseFile(file); + try { + program = this.compileScript(source, imports, module, parent); + if (!program.script) { + program.script = EasyCoder.scriptIndex; + EasyCoder.scriptIndex++; + } + const finishCompile = Date.now(); + console.log(`${finishCompile - this.timestamp} ms: ` + + `Compiled ${program.script}: ${source.scriptLines.length} lines (${source.tokens.length} tokens) in ` + + `${finishCompile - startCompile} ms`); + } catch (err) { + if (err.message !== `stop`) { + let parentRecord = EasyCoder.scripts[parent]; + this.reportError(err, parentRecord, source); + if (parentRecord && parentRecord.onError) { + parentRecord.run(parentRecord.onError); + } + // Remove this script + if (EasyCoder_Compiler.script) { + delete EasyCoder.scripts[EasyCoder_Compiler.script]; + delete EasyCoder_Compiler.script; + } } + return; + } + if (program) { + EasyCoder.scripts[program.script] = program; + if (module) { + module.program = program.script; + } + program.afterExit = then; + program.running = true; + EasyCoder_Run.run(program, 0); } - return value; }, - decode: (value, encoding) => { - if (value) { - switch (encoding) { - case `ec`: - return value.replace(/%0a/g, `\n`) - .replace(/%0d/g, `\r`) - .replace(/~dq~/g, `"`) - .replace(/~sq~/g, `'`) - .replace(/~bs~/g, `\\`); - case `url`: - const decoded = decodeURIComponent(value); - return decoded.replace(/\+/g, ` `); - default: - return value; + start: function(source) { + EasyCoder.scriptIndex = 0; + const script = source.split(`\n`); + if (!this.tokenising) { + try { + this.tokeniseAndCompile(script); + } catch (err) { + this.reportError(err, null, source); } + this.tokenising = true; } - return value; - } + }, }; EasyCoder.version = `2.6.1`; EasyCoder.timestamp = Date.now(); diff --git a/easycoder/easycoder.zip b/easycoder/easycoder.zip deleted file mode 100644 index 205507f..0000000 Binary files a/easycoder/easycoder.zip and /dev/null differ diff --git a/easycoder/plugins.js b/easycoder/plugins.js deleted file mode 100644 index 908b4df..0000000 --- a/easycoder/plugins.js +++ /dev/null @@ -1,126 +0,0 @@ -// eslint-disable-next-line no-unused-vars -const EasyCoder_Plugins = { - - getGlobalPlugins: (timestamp, path, setPluginCount, getPlugin, addPlugin) => { - - console.log(`${Date.now() - timestamp} ms: Load plugins`); - - /* - * To include EasyCoder global plugins in your site, add them here. - * It adds the selected plugins to every page of your site that uses EasyCoder. - * You can also dynamically load plugins before launching a script; see getLocalPlugin() below. - * - * setPluginCount() sets the number of plugins to add. - * getPlugin() loads a plugin from any URL. - * addPlugin() adds it to the EasyCoder system. - * When all the plugins have been added, EasyCoder starts up. - */ - - setPluginCount(6); // *** IMPORTANT *** the number of plugins you will be adding - - getPlugin(`browser`, - `easycoder/plugins/browser.js`, - function () { - addPlugin(`browser`, EasyCoder_Browser); - }); - - getPlugin(`json`, - `easycoder/plugins/json.js`, - function () { - addPlugin(`json`, EasyCoder_Json); - }); - - getPlugin(`rest`, - `easycoder/plugins/rest.js`, - function () { - addPlugin(`rest`, EasyCoder_Rest); - }); - - getPlugin(`svg`, - `easycoder/plugins/svg.js`, - function () { - addPlugin(`svg`, EasyCoder_SVG); - }); - - getPlugin(`showdown`, - `easycoder/plugins/showdown.js`, - function () { - addPlugin(`showdown`, EasyCoder_Showdown); - }); - - getPlugin(`vfx`, - `easycoder/plugins/vfx.js`, - function () { - addPlugin(`vfx`, EasyCoder_VFX); - }); - - }, - - getLocalPlugin: (path, name, getPlugin, addPlugin, callback) => { - - /* - * This lets you add a plugin before launching a script, using the 'plugin' command. - * You must provide a case for every plugin you will be adding; - * use 'ckeditor' as the pattern to follow. - */ - - switch (name) { - case `codemirror`: - getPlugin(name, - `easycoder/plugins/codemirror.js`, - function () { - addPlugin(name, EasyCoder_CodeMirror, callback); - }); - break; - case `ckeditor`: - getPlugin(name, - `easycoder/plugins/ckeditor.js`, - function () { - addPlugin(name, EasyCoder_CKEditor, callback); - }); - break; - case `ui`: - getPlugin(name, - `easycoder/plugins/ui.js`, - function () { - addPlugin(name, EasyCoder_UI, callback); - }); - break; - case `anagrams`: - getPlugin(name, - `easycoder/plugins/anagrams.js`, - function () { - addPlugin(name, EasyCoder_Anagrams, callback); - }); - break; - case `gmap`: - getPlugin(name, - `easycoder/plugins/gmap.js`, - function () { - addPlugin(name, EasyCoder_GMap, callback); - }); - break; - case `life`: - getPlugin(name, - `easycoder/plugins/life.js`, - function () { - addPlugin(name, EasyCoder_Life, callback); - }); - break; - case `wof`: - getPlugin(name, - `easycoder/plugins/wof.js`, - function () { - addPlugin(name, EasyCoder_WOF, callback); - }); - break; - default: - console.log(`Plugin '${name}' not found.`); - break; - } - }, - - rest: () => { - return `rest.php`; - } -}; diff --git a/easycoder/plugins/anagrams.js b/easycoder/plugins/anagrams.js index 594a751..e1c98ab 100644 --- a/easycoder/plugins/anagrams.js +++ b/easycoder/plugins/anagrams.js @@ -40,4 +40,7 @@ const EasyCoder_Anagrams = { compile: () => {} } -}; \ No newline at end of file +}; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.anagrams = EasyCoder_Anagrams; diff --git a/easycoder/plugins/aws.js b/easycoder/plugins/aws.js index d86a1fd..79c5c09 100644 --- a/easycoder/plugins/aws.js +++ b/easycoder/plugins/aws.js @@ -228,4 +228,7 @@ const EasyCoder_AWS = { test: () => {} } -}; \ No newline at end of file +}; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.aws = EasyCoder_AWS; diff --git a/easycoder/plugins/browser.js b/easycoder/plugins/browser.js deleted file mode 100644 index ebce442..0000000 --- a/easycoder/plugins/browser.js +++ /dev/null @@ -1,3340 +0,0 @@ -const EasyCoder_Browser = { - - name: `EasyCoder_Browser`, - - A: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `a`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - Alert: { - - compile: (compiler) => { - const lino = compiler.getLino(); - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `browser`, - keyword: `alert`, - lino, - value - }); - return true; - }, - - run: (program) => { - const command = program[program.pc]; - const value = program.getFormattedValue(command.value); - alert(value); - return command.pc + 1; - } - }, - - Attach: { - - compile: (compiler) => { - const lino = compiler.getLino(); - compiler.next(); - if (compiler.isSymbol()) { - // const symbol = compiler.getProgram()[compiler.getSymbol().pc]; - const symbol = compiler.getSymbolRecord(); - let type = symbol.keyword; - switch (type) { - case `a`: - case `blockquote`: - case `button`: - case `canvas`: - case `div`: - case `fieldset`: - case `file`: - case `form`: - case `h1`: - case `h2`: - case `h3`: - case `h4`: - case `h5`: - case `h6`: - case `image`: - case `img`: - case `input`: - case `label`: - case `legend`: - case `li`: - case `option`: - case `p`: - case `pre`: - case `select`: - case `span`: - case `table`: - case `td`: - case `text`: - case `textarea`: - case `tr`: - case `ul`: - compiler.next(); - if (compiler.tokenIs(`to`)) { - let cssID = null; - if (compiler.nextTokenIs(`body`)) { - if (type=== `div`) { - cssId = `body`; - compiler.next(); - } else { - throw Error(`Body variable must be a div`); - } - } - else cssId = compiler.getValue(); - let onError = 0; - if (compiler.tokenIs(`or`)) { - compiler.next(); - onError = compiler.getPc() + 1; - compiler.completeHandler(); - } - compiler.addCommand({ - domain: `browser`, - keyword: `attach`, - lino, - type, - symbol: symbol.name, - cssId, - onError - }); - return true; - } - break; - default: - compiler.addWarning(`type '${symbol.keyword}' not recognized in browser 'attach'`); - return false; - } - } - compiler.addWarning(`Unrecognised syntax in 'attach'`); - return false; - }, - - run: (program) => { - const command = program[program.pc]; - let content = null; - let element = null; - if (command.cssId === `body`) { - element = document.body; - } else { - content = program.value.evaluate(program, command.cssId).content; - element = document.getElementById(content); - } - if (!element) { - if (command.onError) { - program.run(command.onError); - } else { - program.runtimeError(command.lino, `No such element: '${content}'`); - } - return 0; - } - const target = program.getSymbolRecord(command.symbol); - target.element[target.index] = element; - target.value[target.index] = { - type: `constant`, - numeric: false, - content - }; - if (command.type === `popup`) { - // Register a popup - program.popups.push(element.id); - // Handle closing of the popup - window.onclick = function (event) { - if (program.popups.includes(event.target.id)) { - event.target.style.display = `none`; - } - }; - } - return command.pc + 1; - } - }, - - Audioclip: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `audioclip`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - BLOCKQUOTE: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `blockquote`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - BUTTON: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `button`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - CANVAS: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `canvas`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - Clear: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextTokenIs(`body`)) { - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `clear`, - lino, - name: null - }); - return true; - } - if (compiler.isSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.extra === `dom`) { - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `clear`, - lino, - name: symbolRecord.name - }); - return true; - } - } - return false; - }, - - run: (program) => { - const command = program[program.pc]; - if (command.name) { - const targetRecord = program.getSymbolRecord(command.name); - const target = targetRecord.element[targetRecord.index]; - switch (targetRecord.keyword) { - case `input`: - case `textarea`: - target.value = ``; - break; - default: - target.innerHTML = ``; - break; - } - } else { - document.body.innerHTML = ``; - } - return command.pc + 1; - } - }, - - Convert: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextTokenIs(`whitespace`)) { - if (compiler.nextTokenIs(`in`)) { - if (compiler.nextIsSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.isVHolder) { - if (compiler.nextTokenIs(`to`)) { - const mode = compiler.nextToken(); - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `convert`, - lino, - name: symbolRecord.name, - mode - }); - return true; - } - } - } - } - } - return false; - }, - - run: (program) => { - const command = program[program.pc]; - const targetRecord = program.getSymbolRecord(command.name); - const content = targetRecord.value[targetRecord.index].content; - let value = content; - switch (command.mode) { - case `print`: - value = value.split(`%0a`).join(`\n`).split(`%0A`).join(`\n`).split(`%0d`).join(``).split(`$0D`).join(``); - break; - case `html`: - value = value.split(`%0a`).join(`
`).split(`%0A`).join(`
`).split(`%0d`).join(``).split(`$0D`).join(``); - break; - } - targetRecord.value[targetRecord.index].content = value; - return command.pc + 1; - } - }, - - Create: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - const keyword = symbolRecord.keyword; - if (keyword === `audioclip`) { - if (compiler.nextTokenIs(`from`)) { - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `browser`, - keyword: `create`, - type: `audioclip`, - name: symbolRecord.name, - lino, - value - }); - return true; - } - return false; - } - if ([`a`, - `blockquote`, - `button`, - `canvas`, - `div`, - `fieldset`, - `file`, - `form`, - `h1`, - `h2`, - `h3`, - `h4`, - `h5`, - `h6`, - `hr`, - `image`, - `img`, - `input`, - `label`, - `legend`, - `li`, - `option`, - `p`, - `pre`, - `progress`, - `select`, - `span`, - `table`, - `tr`, - `td`, - `text`, - `textarea`, - `ul` - ].includes(keyword)) { - if (compiler.nextTokenIs(`in`)) { - if (compiler.nextTokenIs(`body`)) { - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `create`, - lino, - name: symbolRecord.name, - parent: `body` - }); - return true; - } - if (compiler.isSymbol()) { - const parentRecord = compiler.getSymbolRecord(); - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `create`, - lino, - name: symbolRecord.name, - parent: parentRecord.name - }); - return true; - } - } else { - const imports = compiler.imports; - if (imports && imports.length > 0) { - // This section is used by Codex to force run in Run panel, which must be the first import - compiler.addCommand({ - domain: `browser`, - keyword: `create`, - lino, - name: symbolRecord.name, - parent: imports[0], - imported: true - }); - return true; - } else { - compiler.addCommand({ - domain: `browser`, - keyword: `create`, - lino, - name: symbolRecord.name, - parent: `body` - }); - return true; - } - } - } - } - return false; - }, - - run: (program) => { - const command = program[program.pc]; - const targetRecord = program.getSymbolRecord(command.name); - switch (command.type) { - case `audioclip`: - targetRecord.value[targetRecord.index] = command.value; - break; - default: - let parent; - if (command.parent === `body`) { - parent = document.body; - } else { - const p = command.imported ? EasyCoder.scripts[program.parent] : program; - const parentRecord = p.getSymbolRecord(command.parent); - if (!parentRecord.element[parentRecord.index]) { - program.runtimeError(command.pc, `Element ${parentRecord.name} does not exist.`); - } - parent = parentRecord.element[parentRecord.index]; - } - targetRecord.element[targetRecord.index] = document.createElement(targetRecord.keyword); - targetRecord.element[targetRecord.index].id = - `ec-${targetRecord.name}-${targetRecord.index}-${EasyCoder.elementId++}`; - if (targetRecord.keyword === `a`) { - targetRecord.element[targetRecord.index].setAttribute(`href`, `#`); - } - parent.appendChild(targetRecord.element[targetRecord.index]); - break; - } - return command.pc + 1; - } - }, - - Disable: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const symbol = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `disable`, - lino, - symbol - }); - return true; - } - compiler.addWarning(`Unrecognised syntax in 'disable'`); - return false; - }, - - run: (program) => { - const command = program[program.pc]; - const symbol = program.getSymbolRecord(command.symbol); - const target = document.getElementById(symbol.value[symbol.index].content); - target.disabled = `true`; - return command.pc + 1; - } - }, - - DIV: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `div`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - Enable: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const symbol = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `enable`, - lino, - symbol - }); - return true; - } - compiler.addWarning(`Unrecognised syntax in 'enable'`); - return false; - }, - - run: (program) => { - const command = program[program.pc]; - const symbol = program.getSymbolRecord(command.symbol); - const target = document.getElementById(symbol.value[symbol.index].content); - target.disabled = false; - return command.pc + 1; - } - }, - - FIELDSET: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `fieldset`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - FILE: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `file`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - Focus: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const symbol = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `focus`, - lino, - symbol - }); - return true; - } - compiler.addWarning(`Unrecognised syntax in 'focus'`); - return false; - }, - - run: (program) => { - const command = program[program.pc]; - const symbol = program.getSymbolRecord(command.symbol); - const element = symbol.element[symbol.index]; - element.focus(); - return command.pc + 1; - } - }, - - FORM: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `form`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - Get: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const target = compiler.getToken(); - let targetRecord = compiler.getSymbolRecord(); - if (compiler.nextTokenIs(`from`)) { - if (compiler.nextTokenIs(`storage`)) { - if (compiler.nextTokenIs(`as`)) { - const key = compiler.getNextValue(); - compiler.addCommand({ - domain: `browser`, - keyword: `get`, - action: `getStorage`, - lino, - target, - key - }); - return true; - } else { - compiler.addCommand({ - domain: `browser`, - keyword: `get`, - action: `listStorage`, - lino, - target - }); - return true; - } - } - if (compiler.isSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.keyword === `select`) { - if (targetRecord.keyword === `option`) { - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `get`, - action: `getOption`, - lino, - target, - select: symbolRecord.name - }); - return true; - } - throw Error(`Invalid variable type`); - } - if (symbolRecord.keyword !== `form`) { - throw Error(`Invalid variable type`); - } - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `get`, - action: `getForm`, - lino, - target, - form: symbolRecord.name - }); - return true; - } - else { - let targetRecord = compiler.getSymbolRecord(target); - } - } - } - compiler.addWarning(`Unrecognised syntax in 'get'`); - return false; - }, - - run: (program) => { - const command = program[program.pc]; - const targetRecord = program.getSymbolRecord(command.target); - switch (command.action) { - case `getForm`: - const formRecord = program.getSymbolRecord(command.form); - const form = document.getElementById(formRecord.value[formRecord.index].content); - const data = new FormData(form); - const content = {}; - for (const entry of data) { - content[entry[0]] = entry[1].replace(/\r/g, ``).replace(/\n/g, `%0a`); - } - targetRecord.value[targetRecord.index] = { - type: `constant`, - numeric: false, - content: JSON.stringify(content) - }; - break; - case `listStorage`: - const items = []; - for (let i = 0, len = window.localStorage.length; i < len; i++) { - items.push(localStorage.key(i)); - } - targetRecord.value[targetRecord.index] = { - type: `constant`, - numeric: false, - content: JSON.stringify(items) - }; - break; - case `getStorage`: - let value = window.localStorage.getItem(program.getValue(command.key)); - if (typeof value === `undefined`) { - value = null; - } - targetRecord.value[targetRecord.index] = { - type: `constant`, - numeric: false, - content: value - }; - break; - case `getOption`: - let selectRecord = program.getSymbolRecord(command.select); - let select = selectRecord.element[selectRecord.index]; - let option = select.options[select.selectedIndex]; - targetRecord.element[targetRecord.index] = option; - break; - } - return command.pc + 1; - } - }, - - H1: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `h1`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - H2: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `h2`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - H3: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `h3`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - H4: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `h4`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - H5: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `h5`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - H6: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `h6`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - Highlight: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.extra === `dom`) { - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `highlight`, - lino, - name: symbolRecord.name - }); - return true; - } - } - return false; - }, - - run: (program) => { - const command = program[program.pc]; - const targetRecord = program.getSymbolRecord(command.name); - const element = targetRecord.element[targetRecord.index]; - element.select(); - return command.pc + 1; - } - }, - - History: { - - compile: (compiler) => { - const lino = compiler.getLino(); - const type = compiler.nextToken(); - switch (type) { - case `push`: - case `set`: - case `replace`: - compiler.next(); - let url = ``; - let state = ``; - while (true) { - const token = compiler.getToken(); - if (token === `url`) { - url = compiler.getNextValue(); - } else if (token === `state`) { - state = compiler.getNextValue(); - } else { - break; - } - } - compiler.addCommand({ - domain: `browser`, - keyword: `history`, - lino, - type, - url, - state - }); - return true; - case `pop`: - case `back`: - case `forward`: - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `history`, - lino, - type - }); - return true; - } - return false; - }, - - run: (program) => { - if (!program.script) { - program.script = `script${Date.now()/1000}`; - } - const command = program[program.pc]; - let state = program.getValue(command.state); - if (!state) { - state = `{"script":"${program.script}"}`; - } - const url = program.getValue(command.url); - switch (command.type) { - case `push`: - if (!window.history.state) { - program.runtimeError(command.lino, `No state history; you need to call 'history set' on the parent`); - return 0; - } - window.history.pushState(state, ``, url); - break; - case `set`: - case `replace`: - window.history.replaceState(state, ``, url); - break; - case `pop`: - case `back`: - window.history.back(); - break; - case `forward`: - window.history.forward(); - break; - } - return command.pc + 1; - } - }, - - HR: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `hr`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - IMAGE: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `image`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - IMG: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `img`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - INPUT: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `input`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - LABEL: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `label`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - LEGEND: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `legend`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - LI: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `li`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - Location: { - - compile: (compiler) => { - const lino = compiler.getLino(); - let newWindow = false; - if (compiler.nextTokenIs(`new`)) { - newWindow = true; - compiler.next(); - } - const location = compiler.getValue(); - compiler.addCommand({ - domain: `browser`, - keyword: `location`, - lino, - location, - newWindow - }); - return true; - }, - - run: (program) => { - const command = program[program.pc]; - const location = program.getValue(command.location); - if (command.newWindow) { - window.open(location, `_blank`); - } else { - window.location = location; - } - return command.pc + 1; - } - }, - - Mail: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextTokenIs(`to`)) { - const to = compiler.getNextValue(); - let subject = ``; - let body = ``; - if (compiler.tokenIs(`subject`)) { - subject = compiler.getNextValue(); - if (compiler.tokenIs(`body`) || compiler.tokenIs(`message`)) { - compiler.next(); - body = compiler.getValue(); - } - } - compiler.addCommand({ - domain: `browser`, - keyword: `mail`, - lino, - to, - subject, - body - }); - return true; - } - return false; - }, - - run: (program) => { - const command = program[program.pc]; - if (command.subject) { - window.location.href = `mailto:${program.getValue(command.to)}` + - `?subject=${program.getValue(command.subject)}&body=${encodeURIComponent(program.getValue(command.body))}`; - } else { - window.location.href = `mailto:${program.getValue(command.to)}`; - } - return command.pc + 1; - } - }, - - On: { - - compile: (compiler) => { - const lino = compiler.getLino(); - const action = compiler.nextToken(); - switch (action) { - case `change`: - compiler.next(); - if (compiler.isSymbol()) { - const symbol = compiler.getSymbolRecord(); - compiler.next(); - if (symbol.extra !== `dom`) { - return false; - } - compiler.addCommand({ - domain: `browser`, - keyword: `on`, - lino, - action, - symbol: symbol.name - }); - return compiler.completeHandler(); - } - break; - case `click`: - if (compiler.nextTokenIs(`document`)) { - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `on`, - lino, - action: `clickDocument` - }); - return compiler.completeHandler(); - } - if (compiler.isSymbol()) { - const symbol = compiler.getSymbolRecord(); - compiler.next(); - if (symbol.extra !== `dom`) { - return false; - } - compiler.addCommand({ - domain: `browser`, - keyword: `on`, - lino, - action, - symbol: symbol.name - }); - return compiler.completeHandler(); - } - break; - case `key`: - case `leave`: - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `on`, - lino, - action - }); - return compiler.completeHandler(); - case `window`: - if (compiler.nextTokenIs(`resize`)) { - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `on`, - lino, - action: `windowResize` - }); - return compiler.completeHandler(); - } - return false; - case `browser`: - case `restore`: - if (action === `browser` && !compiler.nextTokenIs(`back`)) { - return false; - } - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `on`, - lino, - action: `browserBack` - }); - return compiler.completeHandler(); - case `swipe`: - if ([`left`, `right`].includes(compiler.nextToken())) { - const direction = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `on`, - lino, - action: `swipe`, - direction - }); - return compiler.completeHandler(); - } - return false; - case `pick`: - if (compiler.nextIsSymbol()) { - const symbol = compiler.getSymbolRecord(); - compiler.next(); - if (symbol.extra !== `dom`) { - return false; - } - compiler.addCommand({ - domain: `browser`, - keyword: `on`, - lino, - action, - symbol: symbol.name - }); - return compiler.completeHandler(); - } - return false; - case `drag`: - case `drop`: - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `on`, - lino, - action - }); - return compiler.completeHandler(); - } - compiler.addWarning(`Unrecognised syntax in 'on'`); - return false; - }, - - run: (program) => { - let targetRecord; - const command = program[program.pc]; - switch (command.action) { - case `change`: - targetRecord = program.getSymbolRecord(command.symbol); - targetRecord.program = program.script; - targetRecord.element.forEach(function (target, index) { - if (target) { - target.targetRecord = targetRecord; - target.targetIndex = index; - target.targetPc = command.pc + 2; - target.addEventListener(`change`, (event) => { - event.stopPropagation(); - if (program.length > 0) { - const eventTarget = event.target; - if (typeof eventTarget.targetRecord !== `undefined`) { - eventTarget.targetRecord.index = eventTarget.targetIndex; - setTimeout(function () { - EasyCoder.timestamp = Date.now(); - let p = EasyCoder.scripts[eventTarget.targetRecord.program]; - p.run(eventTarget.targetPc); - }, 1); - } - } - }); - } - }); - break; - case `click`: - targetRecord = program.getSymbolRecord(command.symbol); - targetRecord.program = program.script; - targetRecord.element.forEach(function (target, index) { - if (target) { - target.targetRecord = targetRecord; - target.targetIndex = index; - target.targetPc = command.pc + 2; - target.onclick = function (event) { - event.stopPropagation(); - if (program.length > 0) { - const eventTarget = event.target; - if (eventTarget.type != `radio`) { - eventTarget.blur(); - } - if (typeof eventTarget.targetRecord !== `undefined`) { - eventTarget.targetRecord.index = eventTarget.targetIndex; - setTimeout(function () { - EasyCoder.timestamp = Date.now(); - let p = EasyCoder.scripts[eventTarget.targetRecord.program]; - p.run(eventTarget.targetPc); - }, 1); - } - } - return false; - }; - } - }); - break; - case `clickDocument`: - program.targetPc = command.pc + 2; - const interceptClickEvent = (e) => { - EasyCoder.timestamp = Date.now(); - let target = e.target || e.srcElement; - let href = ``; - while (target.parentNode) { - if (target.tagName === `A`) { - href = target.href; - program.docPath = href.slice(-(href.length - window.location.href.length)); - break; - } - target = target.parentNode; - } - while (target.parentNode) { - if (target.id.indexOf(`ec-`) === 0) { - let id = target.id.slice(3); - let pos = id.indexOf(`-`); - program.varName = id.slice(0, pos); - id = id.slice(pos + 1); - pos = id.indexOf(`-`); - program.varIndex = parseInt(id.slice(0, pos)); - break; - } - target = target.parentNode; - } - if (href.indexOf(window.location.href) === 0) { - program.run(program.targetPc); - e.preventDefault(); - } - }; - if (document.addEventListener) { - document.addEventListener(`click`, interceptClickEvent); - } else if (document.attachEvent) { - document.attachEvent(`onclick`, interceptClickEvent); - } - break; - case `swipe`: - let xDown; - const getTouches = (evt) => { - return evt.touches || // browser API - evt.originalEvent.touches; // jQuery - }; - const handleTouchStart = (evt) => { - const firstTouch = getTouches(evt)[0]; - xDown = firstTouch.clientX; - }; - const handleTouchMove = (evt) => { - evt.stopImmediatePropagation(); - if (!xDown) { - return; - } - const xUp = evt.touches[0].clientX; - const xDiff = xDown - xUp; - if (Math.abs(xDiff) > 150) { - xDown = null; - if (xDiff > 0 && program.onSwipeLeft) { - program.run(program.onSwipeLeft); - } else if (xDiff < 0 && program.onSwipeRight) { - program.run(program.onSwipeRight); - } - } - }; - switch (command.direction) { - case `left`: - program.onSwipeLeft = command.pc + 2; - break; - case `right`: - program.onSwipeRight = command.pc + 2; - break; - } - document.addEventListener(`touchstart`, handleTouchStart, false); - document.addEventListener(`touchmove`, handleTouchMove, false); - break; - case `pick`: - const pickRecord = program.getSymbolRecord(command.symbol); - document.pickRecord = pickRecord; - pickRecord.element.forEach(function (element, index) { - document.pickIndex = index; - element.pickIndex = index; - // Set up the mouse down and up listeners - element.mouseDownPc = command.pc + 2; - // Check if touch device - let isTouchDevice = `ontouchstart` in element; - if (isTouchDevice) { - element.addEventListener(`touchstart`, function (e) { - const element = e.targetTouches[0].target; - document.pickX = e.touches[0].clientX; - document.pickY = e.touches[0].clientY; - element.blur(); - setTimeout(function () { - document.pickRecord.index = element.pickIndex; - program.run(element.mouseDownPc); - }, 1); - }, false); - element.addEventListener(`touchmove`, function (e) { - document.dragX = e.touches[0].clientX; - document.dragY = e.touches[0].clientY; - setTimeout(function () { - program.run(document.mouseMovePc); - }, 1); - return false; - }, false); - element.addEventListener(`touchend`, function () { - setTimeout(function () { - program.run(document.mouseUpPc); - }, 1); - return false; - }); - } else { - element.onmousedown = function (event) { - let e = event ? event : window.event; - e.stopPropagation(); - // IE uses srcElement, others use target - if (program.length > 0) { - const element = e.target ? e.target : e.srcElement; - element.offsetX = e.offsetX; - element.offsetY = e.offsetY; - document.pickX = e.clientX; - document.pickY = e.clientY; - element.blur(); - setTimeout(function () { - document.pickRecord.index = element.pickIndex; - program.run(element.mouseDownPc); - }, 1); - } - document.onmousemove = function (event) { - let e = event ? event : window.event; - e.stopPropagation(); - document.dragX = e.clientX; - document.dragY = e.clientY; - if (document.onmousemove) { - setTimeout(function () { - program.run(document.mouseMovePc); - }, 1); - } - return false; - }; - window.onmouseup = function () { - document.onmousemove = null; - document.onmouseup = null; - setTimeout(function () { - if (program && program.run) { - program.run(document.mouseUpPc); - } - }, 1); - return false; - }; - return false; - }; - } - }); - break; - case `drag`: - // Set up the move listener - document.mouseMovePc = command.pc + 2; - break; - case `drop`: - // Set up the move listener - document.mouseUpPc = command.pc + 2; - break; - case `key`: - if (typeof document.onKeyListeners === `undefined`) { - document.onKeyListeners = []; - } - if (!document.onKeyListeners.includes(program)) { - document.onKeyListeners.push(program); - } - program.onKeyPc = command.pc + 2; - document.onkeypress = function (event) { - for (const program of document.onKeyListeners) { - program.key = event.key; - try { - setTimeout(function () { - program.run(program.onKeyPc); - }, 1); - } catch (err) { - console.log(`Error: ${err.message}`); - } - } - return true; - }; - break; - case `windowResize`: - program.onWindowResize = command.pc + 2; - window.addEventListener('resize', function() { - program.run(program.onWindowResize); - }); - break; - case `browserBack`: - program.onBrowserBack = command.pc + 2; - break; - case `leave`: - window.addEventListener(`beforeunload`, function () { - program.run(command.pc + 2); - }); - break; - default: - break; - } - return command.pc + 1; - } - }, - - OPTION: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `option`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - P: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `p`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - Play: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.keyword === `audioclip`) { - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `play`, - lino, - target: targetRecord.name - }); - return true; - } - } - return false; - }, - - run: (program) => { - const command = program[program.pc]; - const targetRecord = program.getSymbolRecord(command.target); - const url = program.value.evaluate(program, targetRecord.value[targetRecord.index]).content; - new Audio(url).play(); - return command.pc + 1; - } - }, - - PRE: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `pre`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - PROGRESS: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `progress`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - Put: { - - compile: (compiler) => { - const lino = compiler.getLino(); - // Get the value - const value = compiler.getNextValue(); - if (compiler.tokenIs(`into`)) { - if (compiler.nextTokenIs(`storage`)) { - if (compiler.nextTokenIs(`as`)) { - const key = compiler.getNextValue(); - compiler.addCommand({ - domain: `browser`, - keyword: `put`, - lino, - value, - key - }); - return true; - } - } - } - return false; - }, - - // runtime - - run: (program) => { - const command = program[program.pc]; - window.localStorage.setItem(program.getValue(command.key), program.getValue(command.value)); - return command.pc + 1; - } - }, - - Remove: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextTokenIs(`element`)) { - if (compiler.nextIsSymbol()) { - const element = compiler.getSymbolRecord(); - if (element.extra != `dom`) { - compiler.warning(`'${element.name}' is not a DOM element`); - return false; - } - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `remove`, - type: `removeElement`, - lino, - element: element.name - }); - return true; - } - } - if (compiler.tokenIs(`attribute`)) { - const attribute = compiler.getNextValue(); - if (compiler.tokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.extra !== `dom`) { - throw new Error(`Inappropriate type '${targetRecord.keyword}'`); - } - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `remove`, - type: `removeAttribute`, - lino, - attribute, - target: targetRecord.name - }); - return true; - } - } - } - try { - const key = compiler.getValue(); - if (compiler.tokenIs(`from`)) { - if (compiler.nextTokenIs(`storage`)) { - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `remove`, - type: `removeStorage`, - key - }); - return true; - } - } - } catch (err) { - return false; - } - return false; - }, - - // runtime - - run: (program) => { - const command = program[program.pc]; - switch (command.type) { - case `removeAttribute`: - const attribute = program.getValue(command.attribute); - const targetRecord = program.getSymbolRecord(command.target); - target = targetRecord.element[targetRecord.index]; - target.removeAttribute(attribute); - break; - case `removeElement`: - const elementRecord = program.getSymbolRecord(command.element); - const element = elementRecord.element[elementRecord.index]; - if (element) { - element.parentElement.removeChild(element); - } - break; - case `removeStorage`: - const key = program.getValue(command.key); - window.localStorage.removeItem(key); - break; - } - return command.pc + 1; - } - }, - - Request: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextToken() === `fullscreen`) { - let option = ``; - if (compiler.nextToken() === `exit`) { - option = `exit`; - compiler.next(); - } - compiler.addCommand({ - domain: `browser`, - keyword: `request`, - lino, - option - }); - return true; - } - return false; - }, - - run: (program) => { - const command = program[program.pc]; - if (command.option === `exit`) { - document.exitFullscreen(); - } else { - document.documentElement.requestFullscreen(); - } - return command.pc + 1; - } - }, - - SELECT: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `select`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - Scroll: { - - compile: (compiler) => { - const lino = compiler.getLino(); - let name = null; - if (compiler.nextIsSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - name = symbolRecord.name; - compiler.next(); - } - if (compiler.tokenIs(`to`)) { - const to = compiler.getNextValue(); - compiler.addCommand({ - domain: `browser`, - keyword: `scroll`, - lino, - name, - to - }); - return true; - } - return false; - }, - - run: (program) => { - const command = program[program.pc]; - const to = program.getValue(command.to); - if (command.name) { - const symbolRecord = program.getSymbolRecord(command.name); - symbolRecord.element[symbolRecord.index].scrollTo(0, to); - } else { - window.scrollTo(0, to); - } - return command.pc + 1; - } - }, - - SECTION: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `section`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - Set: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - const target = targetRecord.name; - if (targetRecord.extra === `dom`) { - const token = compiler.nextToken(); - if (token === `from`) { - if (compiler.nextIsSymbol()) { - if (targetRecord.keyword === `select`) { - const sourceRecord = compiler.getSymbolRecord(); - if (sourceRecord.keyword === `variable`) { - var display = null; - if (compiler.nextTokenIs(`as`)) { - display = compiler.getNextValue(); - } - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setSelect`, - select: target, - source: sourceRecord.name, - display - }); - return true; - } - return false; - } - const source = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setContentVar`, - source, - target - }); - return true; - } - } - } - } else { - let token = compiler.getToken(); - if (token === `the`) { - token = compiler.nextToken(); - } - if (token === `title`) { - if (compiler.nextTokenIs(`to`)) { - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setTitle`, - value - }); - return true; - } - } else if (token === `content`) { - if (compiler.nextTokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const target = compiler.getToken(); - if (compiler.nextTokenIs(`from`)) { - if (compiler.nextIsSymbol()) { - const source = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setContentVar`, - source, - target - }); - return true; - } - } - if (compiler.tokenIs(`to`)) { - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setContent`, - value, - target - }); - return true; - } - } - throw new Error(`'${compiler.getToken()}' is not a symbol`); - } - } else if (token === `class`) { - if (compiler.nextTokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const symbol = compiler.getSymbolRecord(); - if (symbol.extra === `dom`) { - if (compiler.nextTokenIs(`to`)) { - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setClass`, - symbolName: symbol.name, - value - }); - return true; - } - } - } - } - } else if (token === `id`) { - if (compiler.nextTokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const symbol = compiler.getSymbolRecord(); - if (symbol.extra === `dom`) { - if (compiler.nextTokenIs(`to`)) { - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setId`, - symbolName: symbol.name, - value - }); - return true; - } - } - } - } - } else if (token === `text`) { - if (compiler.nextTokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const symbol = compiler.getSymbolRecord(); - switch (symbol.keyword) { - case `button`: - case `input`: - case `span`: - case `label`: - case `legend`: - if (compiler.nextTokenIs(`to`)) { - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setText`, - symbolName: symbol.name, - value - }); - return true; - } - break; - default: - break; - } - } - } - } else if (token === `size`) { - if (compiler.nextTokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const symbol = compiler.getSymbolRecord(); - switch (symbol.keyword) { - case `input`: - if (compiler.nextTokenIs(`to`)) { - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setSize`, - symbolName: symbol.name, - value - }); - return true; - } - } - } - } - } else if (token === `attribute`) { - compiler.next(); - const attributeName = compiler.getValue(); - if (compiler.tokenIs(`of`)) { - if (compiler.nextIsSymbol(true)) { - const symbolRecord = compiler.getSymbolRecord(); - const symbolName = symbolRecord.name; - compiler.next(); - let attributeValue = { - type: `boolean`, - content: true - }; - if (compiler.tokenIs(`to`)) { - attributeValue = compiler.getNextValue(); - } - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setAttribute`, - symbolName, - attributeName, - attributeValue - }); - return true; - } - } - } else if (token === `attributes`) { - if (compiler.nextTokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - const symbolName = symbolRecord.name; - if (symbolRecord.extra !== `dom`) { - compiler.warning(`'${symbolName}' is not a DOM type`); - return false; - } - if (compiler.nextTokenIs(`to`)) { - const attributes = compiler.getNextValue(); - if (attributes) { - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setAttributes`, - symbolName, - attributes - }); - return true; - } - } - } - } - compiler.warning(`'${compiler.getToken()}' is not a symbol`); - return false; - } else if (token === `style`) { - if (compiler.nextTokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - const symbolName = symbolRecord.name; - if (symbolRecord.extra !== `dom`) { - compiler.warning(`'${symbolName}' is not a DOM type`); - return false; - } - if (compiler.nextTokenIs(`to`)) { - const styleValue = compiler.getNextValue(); - if (styleValue) { - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setStyles`, - symbolName, - styleValue - }); - return true; - } - } - } - compiler.warning(`'${compiler.getToken()}' is not a symbol`); - return false; - } - const styleName = compiler.getValue(); - let type = `setStyle`; - let symbolName = ``; - token = compiler.getToken(); - if (token === `of`) { - if (compiler.nextToken() === `body`) { - type = `setBodyStyle`; - } else if (compiler.isSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - symbolName = symbolRecord.name; - if (symbolRecord.extra !== `dom`) { - throw Error(`'${symbolName}' is not a DOM type`); - } - } else { - throw Error(`'${compiler.getToken()}' is not a known symbol`); - } - if (compiler.nextTokenIs(`to`)) { - const styleValue = compiler.getNextValue(); - if (styleValue) { - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type, - symbolName, - styleName, - styleValue - }); - return true; - } - } - } - else if (token === `to`) { - const styleValue = compiler.getNextValue(); - if (styleValue) { - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setHeadStyle`, - styleName, - styleValue - }); - return true; - } - } - } else if (token === `default`) { - if (compiler.nextTokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.keyword === `select`) { - if (compiler.nextTokenIs(`to`)) { - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `browser`, - keyword: `set`, - lino, - type: `setDefault`, - name: symbolRecord.name, - value - }); - return true; - } - } - } - } - } - } - compiler.addWarning(`Unrecognised syntax in 'set'`); - return false; - }, - - run: (program) => { - const command = program[program.pc]; - let symbol; - let value; - let target; - let targetId; - let targetRecord; - let cssId; - let selectRecord; - switch (command.type) { - case `setContentVar`: - const sourceVar = program.getSymbolRecord(command.source); - targetRecord = program.getSymbolRecord(command.target); - const source = document.getElementById(sourceVar.value[sourceVar.index].content); - target = targetRecord.element[targetRecord.index]; - if (!target) { - targetId = program.getValue(targetRecord.value[targetRecord.index]); - target = document.getElementById(targetId); - } - target.innerHTML = source.innerHTML; - break; - case `setContent`: - value = program.getValue(command.value); - targetRecord = program.getSymbolRecord(command.target); - target = targetRecord.element[targetRecord.index]; - if (!target) { - cssId = targetRecord.value[targetRecord.index].content; - if (!cssId) { - program.runtimeError(command.lino, - `Variable '${targetRecord.name}' has not been attached to a DOM element.`); - return 0; - } - target = document.getElementById(cssId); - } - targetRecord.element[targetRecord.index] = target; - switch (targetRecord.keyword) { - case `text`: - case `textarea`: - target.value = value; - break; - case `input`: - target.value = value; - break; - default: - target.innerHTML = value; - break; - } - break; - case `setSelect`: - // The source is assumed to be an array - sourceRecord = program.getSymbolRecord(command.source); - const sourceData = program.getValue(sourceRecord.value[sourceRecord.index]); - var itemArray = ``; - try { - itemArray = JSON.parse(sourceData); - } catch (err) { - program.runtimeError(command.lino, `Can't parse JSON`); - return 0; - } - // The target is assumed to be a SELECT - selectRecord = program.getSymbolRecord(command.select); - const select = selectRecord.element[selectRecord.index]; - select.options.length = 0; - // Get the name of the display field - const display = program.getValue(command.display); - // For each item, set the title and inner HTML - itemArray.forEach(function (item) { - const title = display ? program.decode(item[display]) : null; - const opt = document.createElement(`option`); - const innerHTML = title ? title : item; - opt.innerHTML = innerHTML; - const value = title ? JSON.stringify(item) : item; - opt.value = value; - select.appendChild(opt); - }); - if (display) { - select.selectedIndex = itemArray.indexOf(display); - } else { - select.selectedIndex = -1; - } - break; - case `setClass`: - symbol = program.getSymbolRecord(command.symbolName); - target = symbol.element[symbol.index]; - if (!target) { - targetId = program.getValue(symbol.value[symbol.index]); - target = document.getElementById(targetId); - } - program.getValue(command.value).split(` `).forEach(function(item) { - target.classList.remove(item); - target.classList.add(item); - }); - break; - case `setId`: - symbol = program.getSymbolRecord(command.symbolName); - target = symbol.element[symbol.index]; - if (!target) { - targetId = program.getValue(symbol.value[symbol.index]); - target = document.getElementById(targetId); - } - target.id = program.getValue(command.value); - break; - case `setText`: - symbol = program.getSymbolRecord(command.symbolName); - target = symbol.element[symbol.index]; - if (!target) { - targetId = program.getValue(symbol.value[symbol.index]); - target = document.getElementById(targetId); - } - value = program.getValue(command.value); - switch (symbol.keyword) { - case `button`: - case `span`: - case `label`: - case `legend`: - target.innerHTML = value; - break; - case `input`: - target.value = value; - break; - default: - break; - } - break; - case `setSize`: - symbol = program.getSymbolRecord(command.symbolName); - if (symbol.keyword === `input`) { - target = symbol.element[symbol.index]; - if (!target) { - targetId = program.getValue(symbol.value[symbol.index]); - target = document.getElementById(targetId); - } - target.size = program.getValue(command.value); - } else { - program.runtimeError(command.lino, `Inappropriate variable type '${symbol.name}'`); - } - break; - case `setAttribute`: - symbol = program.getSymbolRecord(command.symbolName); - target = symbol.element[symbol.index]; - if (!target) { - targetId = program.getValue(symbol.value[symbol.index]); - target = document.getElementById(targetId); - } - const attributeName = program.getValue(command.attributeName); - if (command.attributeValue.type === `boolean`) { - target.setAttribute(attributeName, command.attributeValue.content); - } else { - target.setAttribute(attributeName, program.getValue(command.attributeValue)); - } - break; - case `setAttributes`: - symbol = program.getSymbolRecord(command.symbolName); - target = symbol.element[symbol.index]; - if (!target) { - targetId = program.getValue(symbol.value[symbol.index]); - target = document.getElementById(targetId); - } - for (let n = target.attributes.length - 1; n >= 0; n--) { - target.removeAttribute(target.attributes[n].name); - } - let attributes = program.getValue(command.attributes); - let list = attributes.split(" "); - for (let n = 0; n < list.length; n++) { - let attribute = list[n]; - let p = attribute.indexOf(`=`); - if (p > 0) { - target.setAttribute(attribute.substr(0, p), attribute.substr(p + 1)); - } - else { - target.setAttribute(attribute, attribute); - } - } - break; - case `setStyle`: - case `setStyles`: - symbol = program.getSymbolRecord(command.symbolName); - target = symbol.element[symbol.index]; - if (!target) { - const symbolElement = symbol.value[symbol.index]; - if (!symbolElement.type) { - program.runtimeError(command.lino, `Variable '${symbol.name}' is not attached to a DOM element.`); - return 0; - } - targetId = program.getValue(symbolElement); - target = document.getElementById(targetId); - } - const styleValue = program.getValue(command.styleValue); - if (!symbol.value[symbol.index]) { - program.runtimeError(command.lino, `Variable '${symbol.name}' has not been assigned.`); - return 0; - } - switch (command.type) { - case `setStyle`: - target.style[command.styleName.content] = styleValue; - break; - case `setStyles`: - target.style.cssText = styleValue; - break; - } - break; - case `setHeadStyle`: - const headStyleName = program.getValue(command.styleName); - const headStyleValue = program.getValue(command.styleValue); - var style = document.createElement('style'); - style.innerHTML = `${headStyleName} ${headStyleValue}`; - for (let i = 0; i < document.head.childNodes.length; i++) { - let node = document.head.childNodes[i]; - if (node.tagName === `STYLE`) { - let data = node.innerHTML; - if (data.indexOf(`${headStyleName} `) === 0) { - document.head.removeChild(node); - break; - } - } - } - document.head.appendChild(style); - break; - case `setBodyStyle`: - const bodyStyleValue = program.getValue(command.styleValue); - switch (command.styleName.content) { - case `background`: - document.body.style.background = bodyStyleValue; - break; - default: - program.runtimeError(command.lino, - `Unsupported body attribute '${command.styleName.content}'`); - return 0; - } - break; - case `setTitle`: - document.title = program.getValue(command.value); - break; - case `setDefault`: - selectRecord = program.getSymbolRecord(command.name); - value = program.getValue(command.value); - const element = selectRecord.element[selectRecord.index]; - for (let n = 0; n < element.options.length; n++) { - if (element.options[n].value === value) { - element.selectedIndex = n; - break; - } - } - break; - default: - break; - } - return command.pc + 1; - } - }, - - SPAN: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `span`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - TABLE: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `table`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - TR: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `tr`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - TD: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `td`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - TEXTAREA: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `textarea`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - Trace: { - - compile: (compiler) => { - const lino = compiler.getLino(); - const variables = []; - if (compiler.nextIsSymbol()) { - while (compiler.isSymbol()) { - variables.push(compiler.getToken()); - compiler.next(); - } - let alignment = `horizontal`; - if (compiler.tokenIs(`horizontal`) || compiler.tokenIs(`vertical`)) { - alignment = compiler.getToken(); - compiler.next(); - } - compiler.addCommand({ - domain: `browser`, - keyword: `trace`, - variant: `setup`, - lino, - variables, - alignment - }); - return true; - } - compiler.addCommand({ - domain: `browser`, - keyword: `trace`, - variant: `run`, - lino - }); - return true; - }, - - run: (program) => { - const command = program[program.pc]; - switch (command.variant) { - case `setup`: - console.log(`Set up tracer`); - program.tracer = { - variables: command.variables, - alignment: command.alignment - }; - break; - case `run`: - console.log(`Run tracer`); - if (!program.tracer) { - program.tracer = { - variables: [], - alignment: `horizontal` - }; - } - if (!program.tracing) { - const tracer = document.getElementById(`easycoder-tracer`); - if (tracer) { - tracer.innerHTML = - `
` + - `` + - `
` + - `
`; - tracer.style.display = `none`; - } - program.tracing = true; - } - program.stop = false; - break; - } - return program.pc + 1; - } - }, - - UL: { - - compile: (compiler) => { - compiler.compileVariable(`browser`, `ul`, false, `dom`); - return true; - }, - - run: (program) => { - return program[program.pc].pc + 1; - } - }, - - Upload: { - - compile: (compiler) => { - const lino = compiler.getLino(); - if (compiler.nextIsSymbol()) { - const file = compiler.getToken(); - if (compiler.nextTokenIs(`to`)) { - const path = compiler.getNextValue(); - if (compiler.tokenIs(`with`)) { - if (compiler.nextIsSymbol()) { - const progress = compiler.getToken(); - if (compiler.nextTokenIs(`and`)) { - if (compiler.nextIsSymbol()) { - const status = compiler.getToken(); - compiler.next(); - compiler.addCommand({ - domain: `browser`, - keyword: `upload`, - lino, - file, - path, - progress, - status - }); - return true; - } - } - } - } - } - } - return false; - }, - - run: (program) => { - const command = program[program.pc]; - const fileSpec = program.getSymbolRecord(command.file); - const path = program.getValue(command.path); - const progressSpec = program.getSymbolRecord(command.progress); - const statusSpec = program.getSymbolRecord(command.status); - - const file = fileSpec.element[fileSpec.index]; - const progress = progressSpec.element[progressSpec.index]; - const status = statusSpec.element[statusSpec.index]; - - const setProgress = (value) => { - if (progress) { - progress.value = value; - } - }; - const setStatus = (value) => { - if (status) { - status.innerHTML = value; - } - }; - - const source = file.files[0]; - if (source) { - const formData = new FormData(); - formData.append(`source`, source); - formData.append(`path`, path); - const ajax = new XMLHttpRequest(); - ajax.upload.addEventListener(`progress`, function (event) { - const percent = Math.round((event.loaded / event.total) * 100); - setProgress(percent); - setStatus(`${Math.round(percent)}%...`); - }, false); - ajax.addEventListener(`load`, function (event) { - const response = event.target.responseText; - setProgress(0); - setStatus(``); - console.log(response); - }, false); - ajax.addEventListener(`error`, function () { - setStatus(`Upload failed`); - console.log(`Upload failed`); - }, false); - ajax.addEventListener(`abort`, function () { - setStatus(`Upload aborted`); - console.log(`Upload aborted`); - }, false); - ajax.onreadystatechange = function () { - if (this.readyState === 4) { - const command = program.ajaxCommand; - const status = this.status; - switch (status) { - case 200: - program.run(command.pc + 1); - break; - case 0: - break; - default: - try { - program.runtimeError(command.lino, `Error ${status}`); - } catch (err) { - program.reportError(err, program); - } - break; - } - } - }; - program.ajaxCommand = command; - const postpath = path.startsWith(`http`) ? path : `${window.location.origin}/${EasyCoder_Plugins.rest()}/${path}`; - ajax.open(`POST`, postpath); - ajax.send(formData); - } - return 0; - } - }, - - getHandler: (name) => { - switch (name) { - case `a`: - return EasyCoder_Browser.A; - case `alert`: - return EasyCoder_Browser.Alert; - case `attach`: - return EasyCoder_Browser.Attach; - case `audioclip`: - return EasyCoder_Browser.Audioclip; - case `blockquote`: - return EasyCoder_Browser.BLOCKQUOTE; - case `button`: - return EasyCoder_Browser.BUTTON; - case `canvas`: - return EasyCoder_Browser.CANVAS; - case `clear`: - return EasyCoder_Browser.Clear; - case `convert`: - return EasyCoder_Browser.Convert; - case `create`: - return EasyCoder_Browser.Create; - case `disable`: - return EasyCoder_Browser.Disable; - case `div`: - return EasyCoder_Browser.DIV; - case `enable`: - return EasyCoder_Browser.Enable; - case `fieldset`: - return EasyCoder_Browser.FIELDSET; - case `file`: - return EasyCoder_Browser.FILE; - case `focus`: - return EasyCoder_Browser.Focus; - case `form`: - return EasyCoder_Browser.FORM; - case `fullscreen`: - return EasyCoder_Browser.FullScreen; - case `get`: - return EasyCoder_Browser.Get; - case `h1`: - return EasyCoder_Browser.H1; - case `h2`: - return EasyCoder_Browser.H2; - case `h3`: - return EasyCoder_Browser.H3; - case `h4`: - return EasyCoder_Browser.H4; - case `h5`: - return EasyCoder_Browser.H5; - case `h6`: - return EasyCoder_Browser.H6; - case `highlight`: - return EasyCoder_Browser.Highlight; - case `history`: - return EasyCoder_Browser.History; - case `hr`: - return EasyCoder_Browser.HR; - case `image`: - return EasyCoder_Browser.IMAGE; - case `img`: - return EasyCoder_Browser.IMG; - case `input`: - return EasyCoder_Browser.INPUT; - case `label`: - return EasyCoder_Browser.LABEL; - case `legend`: - return EasyCoder_Browser.LEGEND; - case `li`: - return EasyCoder_Browser.LI; - case `location`: - return EasyCoder_Browser.Location; - case `mail`: - return EasyCoder_Browser.Mail; - case `on`: - return EasyCoder_Browser.On; - case `option`: - return EasyCoder_Browser.OPTION; - case `p`: - return EasyCoder_Browser.P; - case `play`: - return EasyCoder_Browser.Play; - case `pre`: - return EasyCoder_Browser.PRE; - case `progress`: - return EasyCoder_Browser.PROGRESS; - case `put`: - return EasyCoder_Browser.Put; - case `remove`: - return EasyCoder_Browser.Remove; - case `request`: - return EasyCoder_Browser.Request; - case `select`: - return EasyCoder_Browser.SELECT; - case `scroll`: - return EasyCoder_Browser.Scroll; - case `section`: - return EasyCoder_Browser.SECTION; - case `set`: - return EasyCoder_Browser.Set; - case `span`: - return EasyCoder_Browser.SPAN; - case `table`: - return EasyCoder_Browser.TABLE; - case `tr`: - return EasyCoder_Browser.TR; - case `td`: - return EasyCoder_Browser.TD; - case `textarea`: - return EasyCoder_Browser.TEXTAREA; - case `trace`: - return EasyCoder_Browser.Trace; - case `ul`: - return EasyCoder_Browser.UL; - case `upload`: - return EasyCoder_Browser.Upload; - default: - return null; - } - }, - - run: (program) => { - const command = program[program.pc]; - const handler = EasyCoder_Browser.getHandler(command.keyword); - if (!handler) { - program.runtimeError(command.lino, `Unknown keyword '${command.keyword}' in 'browser' package`); - } - return handler.run(program); - }, - - value: { - - compile: (compiler) => { - if (compiler.isSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - if (compiler.nextTokenIs(`exists`)) { - if (symbolRecord.extra === `dom`) { - compiler.next(); - return { - domain: `browser`, - type: `exists`, - value: symbolRecord.name - }; - } - return null; - } - switch (symbolRecord.keyword) { - case `file`: - case `input`: - case `select`: - case `textarea`: - return { - domain: `browser`, - type: symbolRecord.keyword, - value: symbolRecord.name - }; - } - return null; - } - - if (compiler.tokenIs(`the`)) { - compiler.next(); - } - let offset = false; - if (compiler.tokenIs(`offset`)) { - offset = true; - compiler.next(); - } - - let type = compiler.getToken(); - let text; - let attribute; - switch (type) { - case `mobile`: - case `portrait`: - case `landscape`: - case `br`: - case `location`: - case `key`: - case `hostname`: - compiler.next(); - return { - domain: `browser`, - type - }; - case `content`: - case `text`: - if (compiler.nextTokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const symbol = compiler.getSymbolRecord(); - compiler.next(); - return { - domain: `browser`, - type: `contentOf`, - symbol: symbol.name - }; - } - throw new Error(`'${compiler.getToken()}' is not a symbol`); - } - return null; - case `selected`: - let arg = compiler.nextToken(); - if ([`index`, `item`].includes(arg)) { - if ([`in`, `of`].includes(compiler.nextToken())) { - if (compiler.nextIsSymbol()) { - const symbol = compiler.getSymbolRecord(); - if ([`ul`, `ol`, `select`].includes(symbol.keyword)) { - compiler.next(); - return { - domain: `browser`, - type: `selected`, - symbol: symbol.name, - arg - }; - } - } - } - } - return null; - case `color`: - compiler.next(); - const value = compiler.getValue(); - return { - domain: `browser`, - type, - value - }; - case `attribute`: - attribute = compiler.getNextValue(); - if (compiler.tokenIs(`of`)) { - compiler.next(); - if (compiler.isSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.extra === `dom`) { - compiler.next(); - return { - domain: `browser`, - type: `attributeOf`, - attribute, - symbol: symbolRecord.name - }; - } - } - } - return null; - case `style`: - const style = compiler.getNextValue(); - if (compiler.tokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.extra === `dom`) { - compiler.next(); - return { - domain: `browser`, - type, - style, - target: symbolRecord.name - }; - } - } - } - return null; - case `confirm`: - text = compiler.getNextValue(); - return { - domain: `browser`, - type: `confirm`, - text - }; - case `prompt`: - text = compiler.getNextValue(); - let pre = null; - if (compiler.tokenIs(`with`)) { - pre = compiler.getNextValue(); - } - return { - domain: `browser`, - type: `prompt`, - text, - pre - }; - case `screen`: - attribute = compiler.nextToken(); - if ([`width`, `height`].includes(attribute)) { - compiler.next(); - return { - domain: `browser`, - type, - attribute - }; - } - break; - case `top`: - case `bottom`: - case `left`: - case `right`: - case `width`: - case `height`: - return EasyCoder_Browser.value.getCoord(compiler, type, offset); - case `scroll`: - if (compiler.nextTokenIs(`position`)) { - compiler.next(); - return { - domain: `browser`, - type: `scrollPosition` - }; - } - return null; - case `document`: - if (compiler.nextTokenIs(`path`)) { - compiler.next(); - return { - domain: `browser`, - type: `docPath` - }; - } - return null; - case `storage`: - if (compiler.nextTokenIs(`keys`)) { - compiler.next(); - return { - domain: `browser`, - type: `storageKeys` - }; - } - return null; - case `parent`: - switch (compiler.nextToken()) { - case `name`: - compiler.next(); - return { - domain: `browser`, - type: `varName` - }; - case `index`: - compiler.next(); - return { - domain: `browser`, - type: `varIndex` - }; - } - return null; - case `history`: - if (compiler.nextTokenIs(`state`)) { - compiler.next(); - return { - domain: `browser`, - type: `historyState` - }; - } - return null; - case `pick`: - case `drag`: - if (compiler.nextTokenIs(`position`)) { - compiler.next(); - return { - domain: `browser`, - type: `${type}Position` - }; - } - } - return null; - }, - - getCoord: (compiler, type, offset) => { - if (compiler.nextTokenIs(`of`)) { - if (compiler.nextTokenIs(`window`)) { - compiler.next(); - return { - domain: `browser`, - type, - symbol: `window`, - offset - }; - } - let symbolRecord = null; - if (compiler.isSymbol()) { - symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.extra === `dom`) { - compiler.next(); - return { - domain: `browser`, - type, - symbol: symbolRecord.name, - offset - }; - } - } - } - return null; - }, - - get: (program, value) => { - let symbolRecord; - let element; - let target; - let content; - switch (value.type) { - case `file`: - case `input`: - case `select`: - case `textarea`: - symbolRecord = program.getSymbolRecord(value.value); - target = symbolRecord.element[symbolRecord.index]; - return { - type: `constant`, - numeric: false, - content: target.value - }; - case `exists`: - symbolRecord = program.getSymbolRecord(value.value); - return { - domain: `browser`, - type: `boolean`, - content: typeof symbolRecord.element[symbolRecord.index] !== `undefined` - }; - case `mobile`: - return { - domain: `browser`, - type: `boolean`, - content: (typeof window.orientation !== `undefined`) || (navigator.userAgent.indexOf(`IEMobile`) !== -1) - }; - case `portrait`: - return { - domain: `browser`, - type: `boolean`, - content: document.documentElement.clientWidth < document.documentElement.clientHeight - }; - case `landscape`: - return { - domain: `browser`, - type: `boolean`, - content: document.documentElement.clientWidth >= document.documentElement.clientHeight - }; - case `br`: - return { - type: `constant`, - numeric: false, - content: decodeURIComponent(`%3Cbr%20%2F%3E`) - }; - case `attributeOf`: - symbolRecord = program.getSymbolRecord(value.symbol); - const attribute = program.getValue(value.attribute); - target = symbolRecord.element[symbolRecord.index]; - if (attribute.indexOf(`data-`) === 0) { - return program.getSimpleValue(target.dataset[attribute.substr(5)]); - } - return program.getSimpleValue(target[attribute]); - case `style`: - symbolRecord = program.getSymbolRecord(value.target); - const style = program.getValue(value.style); - target = symbolRecord.element[symbolRecord.index]; - return program.getSimpleValue(target.style[style]); - case `confirm`: - return { - type: `boolean`, - content: window.confirm(program.getValue(value.text)) - }; - case `prompt`: - const text = program.getValue(value.text); - const pre = program.getValue(value.pre); - return { - type: `constant`, - numeric: false, - content: pre ? window.prompt(text, pre) : window.prompt(text) - }; - case `contentOf`: - symbolRecord = program.getSymbolRecord(value.symbol); - target = symbolRecord.element[symbolRecord.index]; - switch (symbolRecord.keyword) { - case `input`: - case `textarea`: - content = target.value; - break; - case `pre`: - content = target.innerHTML; - break; - default: - content = target.innerHTML.split(`\n`).join(``); - break; - } - return { - type: `constant`, - numeric: false, - content - }; - case `selected`: - symbolRecord = program.getSymbolRecord(value.symbol); - target = symbolRecord.element[symbolRecord.index]; - let selectedIndex = target.selectedIndex; - let selectedText = selectedIndex >= 0 ? target.options[selectedIndex].text : ``; - content = (value.arg === `index`) ? selectedIndex : selectedText; - return { - type: `constant`, - numeric: false, - content - }; - case `top`: - if (value.symbol == `window`) { - return { - type: `constant`, - numeric: true, - content: window.screenY - }; - } - symbolRecord = program.getSymbolRecord(value.symbol); - element = symbolRecord.element[symbolRecord.index]; - content = Math.round(value.offset ? element.offsetTop : element.getBoundingClientRect().top); - return { - type: `constant`, - numeric: true, - content - }; - case `bottom`: - if (value.symbol == `window`) { - return { - type: `constant`, - numeric: true, - content: window.screenY + window.innerHeight - }; - } - symbolRecord = program.getSymbolRecord(value.symbol); - content = Math.round(symbolRecord.element[symbolRecord.index].getBoundingClientRect().bottom); - return { - type: `constant`, - numeric: true, - content - }; - case `left`: - if (value.symbol == `window`) { - return { - type: `constant`, - numeric: true, - content: window.screenLeft - }; - } - symbolRecord = program.getSymbolRecord(value.symbol); - element = symbolRecord.element[symbolRecord.index]; - content = Math.round(value.offset ? element.offsetLeft : element.getBoundingClientRect().left); - return { - type: `constant`, - numeric: true, - content - }; - case `right`: - if (value.symbol == `window`) { - return { - type: `constant`, - numeric: true, - content: window.screenX + window.innerWidth - }; - } - symbolRecord = program.getSymbolRecord(value.symbol); - content = Math.round(symbolRecord.element[symbolRecord.index].getBoundingClientRect().right); - return { - type: `constant`, - numeric: true, - content - }; - case `width`: - if (value.symbol == `window`) { - return { - type: `constant`, - numeric: true, - content: window.innerWidth - }; - } - symbolRecord = program.getSymbolRecord(value.symbol); - content = Math.round(symbolRecord.element[symbolRecord.index].getBoundingClientRect().width); - return { - type: `constant`, - numeric: true, - content - }; - case `height`: - if (value.symbol == `window`) { - return { - type: `constant`, - numeric: true, - content: window.innerHeight - }; - } - symbolRecord = program.getSymbolRecord(value.symbol); - content = Math.round(symbolRecord.element[symbolRecord.index].getBoundingClientRect().height); - return { - type: `constant`, - numeric: true, - content - }; - case `color`: - const styleValue = program.value.evaluate(program, value.value).content; - const hex = styleValue.toString(16).padStart(6, `0`); - return { - type: `constant`, - numeric: false, - content: `#${hex}` - }; - case `docPath`: - return { - type: `constant`, - numeric: false, - content: program.docPath - }; - case `storageKeys`: - return { - type: `constant`, - numeric: false, - content: JSON.stringify(Object.keys(localStorage)) - }; - case `location`: - return { - type: `constant`, - numeric: false, - content: window.location.href - }; - case `historyState`: - return { - type: `constant`, - numeric: false, - content: window.history.state - }; - case `scrollPosition`: - return { - type: `constant`, - numeric: true, - content: scrollPosition - }; - case `varName`: - return { - type: `constant`, - numeric: false, - content: program.varName - }; - case `varIndex`: - return { - type: `constant`, - numeric: true, - content: program.varIndex - }; - case `key`: - return { - type: `constant`, - numeric: false, - content: program.key - }; - case `hostname`: - return { - type: `constant`, - numeric: false, - content: location.hostname - }; - case `screen`: - return { - type: `constant`, - numeric: true, - content: screen[value.attribute] - }; - case `pickPosition`: - return { - type: `constant`, - numeric: false, - content: JSON.stringify({ - "x": document.pickX, - "y": document.pickY - }) - }; - case `dragPosition`: - return { - type: `constant`, - numeric: false, - content: JSON.stringify({ - "x": document.dragX, - "y": document.dragY - }) - }; - } - } - }, - - condition: { - - compile: (compiler) => { - if (compiler.tokenIs(`confirm`)) { - const value = compiler.getNextValue(); - return { - domain: `browser`, - type: `confirm`, - value - }; - } else if (compiler.tokenIs(`element`)) { - if (compiler.nextIsSymbol()) { - const symbolRecord = compiler.getSymbolRecord(); - if (symbolRecord.extra === `dom`) { - const token = compiler.nextToken(); - if (token === `has`) { - if (compiler.nextTokenIs(`the`)) { - compiler.next(); - } - if (compiler.tokenIs(`focus`)) { - compiler.next(); - return { - domain: `browser`, - type: `focus`, - element: symbolRecord.name - }; - } - } else if (token === `contains`) { - const position = compiler.getNextValue(); - return { - domain: `browser`, - type: `contains`, - element: symbolRecord.name, - position - }; - } - } - } - } - return null; - }, - - test: (program, condition) => { - switch (condition.type) { - case `confirm`: - return confirm(program.getValue(condition.value)); - case `focus`: - const focusRecord = program.getSymbolRecord(condition.element); - return focusRecord.element[focusRecord.index] === document.activeElement; - case `contains`: - const containsRecord = program.getSymbolRecord(condition.element); - const element = containsRecord.element[containsRecord.index]; - const bounds = element.getBoundingClientRect(); - const left = Math.round(bounds.left); - const right = Math.round(bounds.right); - const top = Math.round(bounds.top); - const bottom = Math.round(bounds.bottom); - const position = JSON.parse(program.getValue(condition.position)); - const x = position.x; - const y = position.y; - if (x >= left && x <= right && y >= top && y <= bottom) { - return true; - } - return false; - } - } - }, - - setStyles: (id, styleString) => { - const element = document.getElementById(id); - const styles = styleString.split(`;`); - for (const item of styles) { - const style = item.split(`:`); - element.setAttribute(style[0], style[1]); - } - } -}; - -let scrollPosition = 0; - -window.addEventListener(`scroll`, function () { - scrollPosition = this.scrollY; -}); - -window.onpopstate = function (event) { - window.EasyCoder.timestamp = Date.now(); - const state = JSON.parse(event.state); - if (state && state.script) { - const program = window.EasyCoder.scripts[state.script]; - if (program) { - if (program.onBrowserBack) { - program.run(program.onBrowserBack); - } - } else { - console.log(`No script property in window state object`); - } - } -}; diff --git a/easycoder/plugins/ckeditor.js b/easycoder/plugins/ckeditor.js index 32d64dd..bbc8578 100644 --- a/easycoder/plugins/ckeditor.js +++ b/easycoder/plugins/ckeditor.js @@ -170,3 +170,6 @@ const EasyCoder_CKEditor = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.ckeditor = EasyCoder_CKEditor; diff --git a/easycoder/plugins/codemirror.js b/easycoder/plugins/codemirror.js index ccec906..266cb48 100644 --- a/easycoder/plugins/codemirror.js +++ b/easycoder/plugins/codemirror.js @@ -168,3 +168,6 @@ const EasyCoder_CodeMirror = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.codemirror = EasyCoder_CodeMirror; diff --git a/easycoder/plugins/dummy.js b/easycoder/plugins/dummy.js index fd416c4..36bbd02 100644 --- a/easycoder/plugins/dummy.js +++ b/easycoder/plugins/dummy.js @@ -63,4 +63,7 @@ const EasyCoder_Dummy = { } return handler.run(program); } -}; \ No newline at end of file +}; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.dummy = EasyCoder_Dummy; diff --git a/easycoder/plugins/gmap.js b/easycoder/plugins/gmap.js index 52ce2ea..404f88c 100644 --- a/easycoder/plugins/gmap.js +++ b/easycoder/plugins/gmap.js @@ -556,3 +556,6 @@ const EasyCoder_GMap = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.gmap = EasyCoder_GMap; diff --git a/easycoder/plugins/json.js b/easycoder/plugins/json.js deleted file mode 100644 index 80ade2a..0000000 --- a/easycoder/plugins/json.js +++ /dev/null @@ -1,510 +0,0 @@ -const EasyCoder_Json = { - - name: `EasyCoder_JSON`, - - Json: { - - compile: (compiler) => { - const lino = compiler.getLino(); - const request = compiler.nextToken(); - let item; - switch (request) { - case `set`: - compiler.next(); - if (compiler.isSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.keyword === `variable`) { - if (compiler.nextTokenIs(`to`)) { - const type = compiler.nextToken(); - if (`["array","object"]`.includes(type)) { - compiler.next(); - compiler.addCommand({ - domain: `json`, - keyword: `json`, - lino, - request: `setVariable`, - target: targetRecord.name, - type - }); - return true; - } - } - } else if (targetRecord.keyword === `select`) { - if (compiler.nextTokenIs(`from`)) { - compiler.next(); - if (compiler.isSymbol()) { - const sourceRecord = compiler.getSymbolRecord(); - if (sourceRecord.keyword === `variable`) { - var display = null; - if (compiler.nextTokenIs(`as`)) { - display = compiler.getNextValue(); - } - compiler.addCommand({ - domain: `json`, - keyword: `json`, - lino, - request: `setList`, - target: targetRecord.name, - source: sourceRecord.name, - display - }); - return true; - } - } - } - } - break; - } - break; - case `sort`: - case `shuffle`: - case `format`: - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.keyword === `variable`) { - compiler.next(); - compiler.addCommand({ - domain: `json`, - keyword: `json`, - lino, - request, - target: targetRecord.name - }); - return true; - } - } - break; - case `parse`: - if (compiler.nextTokenIs(`url`)) { - const source = compiler.getNextValue(); - if (compiler.tokenIs(`as`)) { - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.keyword === `variable`) { - compiler.next(); - compiler.addCommand({ - domain: `json`, - keyword: `json`, - lino, - request, - source, - target: targetRecord.name - }); - return true; - } - } - } - } - break; - case `delete`: - const what = compiler.nextToken(); - if ([`property`, `element`].includes(what)) { - const value = compiler.getNextValue(); - if ([`from`, `of`].includes(compiler.getToken())) { - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.keyword === `variable`) { - compiler.next(); - compiler.addCommand({ - domain: `json`, - keyword: `json`, - lino, - request, - what, - value, - target: targetRecord.name - }); - return true; - } - } - } - } - break; - case `rename`: - const oldName = compiler.getNextValue(); - if (compiler.tokenIs(`to`)) { - const newName = compiler.getNextValue(); - if (compiler.tokenIs(`in`)) { - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.keyword === `variable`) { - compiler.next(); - compiler.addCommand({ - domain: `json`, - keyword: `json`, - lino, - request, - oldName, - newName, - target: targetRecord.name - }); - return true; - } - } - } - } - break; - case `add`: - item = compiler.getNextValue(); - if (compiler.tokenIs(`to`)) { - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.keyword === `variable`) { - compiler.next(); - compiler.addCommand({ - domain: `json`, - keyword: `json`, - lino, - request, - item, - target: targetRecord.name - }); - return true; - } - } - } - break; - case `split`: - item = compiler.getNextValue(); - let on = `\n`; - if (compiler.tokenIs(`on`)) { - on = compiler.getNextValue(); - } - if ([`giving`, `into`].includes(compiler.getToken())) { - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.keyword === `variable`) { - compiler.next(); - compiler.addCommand({ - domain: `json`, - keyword: `json`, - lino, - request, - item, - on, - target: targetRecord.name - }); - return true; - } - } - } - break; - case `replace`: - if (compiler.nextTokenIs(`element`)) { - const index = compiler.getNextValue(); - if (compiler.tokenIs(`of`)) { - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.keyword === `variable`) { - if ([`by`, `with`].includes(compiler.nextToken())) { - const value = compiler.getNextValue(); - compiler.addCommand({ - domain: `json`, - keyword: `json`, - lino, - request, - target: targetRecord.name, - index, - value - }); - return true; - } - } - } - } - } - break; - } - compiler.addWarning(`Unrecognised json command syntax`); - return false; - }, - - run: (program) => { - const command = program[program.pc]; - let sourceRecord; - let targetRecord; - let record; - let content; - let array; - switch (command.request) { - case `setVariable`: - targetRecord = program.getSymbolRecord(command.target); - content = (command.type === `array`) ? `[]` : `{}`; - targetRecord.value[targetRecord.index] = { - type: `constant`, - numeric: false, - content - }; - break; - case `setList`: - // The source is assumed to be a JSON array - sourceRecord = program.getSymbolRecord(command.source); - const sourceData = program.getValue(sourceRecord.value[sourceRecord.index]); - var itemArray = ``; - try { - itemArray = JSON.parse(sourceData); - } catch (err) { - program.runtimeError(command.lino, `Can't parse JSON`); - return 0; - } - // The target is assumed to be a SELECT - targetRecord = program.getSymbolRecord(command.target); - const target = targetRecord.element[targetRecord.index]; - target.options.length = 0; - // Get the name of the display field - const display = program.getValue(command.display); - // For each item, set the title and inner HTML - itemArray.forEach(function (item) { - const title = display ? program.decode(item[display]) : null; - const opt = document.createElement(`option`); - const innerHTML = title ? title : item; - opt.innerHTML = innerHTML; - const value = title ? JSON.stringify(item) : item; - opt.value = value; - target.appendChild(opt); - }); - target.selectedIndex = -1; - break; - case `sort`: - targetRecord = program.getSymbolRecord(command.target); - const list = program.getValue(targetRecord.value[targetRecord.index]); - content = list ? JSON.stringify(JSON.parse(list).sort()) : null; - targetRecord.value[targetRecord.index] = { - type: `constant`, - numeric: false, - content - }; - break; - case `shuffle`: - targetRecord = program.getSymbolRecord(command.target); - array = JSON.parse(program.getValue(targetRecord.value[targetRecord.index])); - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } - targetRecord.value[targetRecord.index] = { - type: `constant`, - numeric: false, - content: JSON.stringify(array) - }; - break; - case `format`: - targetRecord = program.getSymbolRecord(command.target); - const val = JSON.parse(program.getValue(targetRecord.value[targetRecord.index])); - targetRecord.value[targetRecord.index] = { - type: `constant`, - numeric: false, - content: JSON.stringify(val, null, 2) - }; - break; - case `parse`: - var source = program.getValue(command.source); - targetRecord = program.getSymbolRecord(command.target); - content = { - url: source - }; - var n = source.indexOf(`://`); - if (n >= 0) { - n += 3; - content.protocol = source.substr(0, n); - source = source.substr(n); - } - n = source.indexOf(`?`); - if (n > 0) { - content.domain = source.substr(0, n); - content.arg = source.substr(n + 1); - } else { - content.domain = source; - } - if (content.domain.endsWith(`/`)) { - content.domain = content.domain.substr(0, content.domain.length - 1); - } - n = content.domain.indexOf(`/`); - if (n > 0) { - content.path = content.domain.substr(n + 1); - content.domain = content.domain.substr(0, n); - } - else { - content.path = ``; - } - targetRecord.value[targetRecord.index] = { - type: `constant`, - numeric: false, - content: JSON.stringify(content, null, 2) - }; - break; - case `delete`: - switch (command.what) { - case `property`: - const name = program.getValue(command.value); - targetRecord = program.getSymbolRecord(command.target); - record = JSON.parse(targetRecord.value[targetRecord.index].content); - delete record[name]; - targetRecord.value[targetRecord.index].content = JSON.stringify(record); - break; - case `element`: - const element = program.getValue(command.value); - targetRecord = program.getSymbolRecord(command.target); - record = JSON.parse(targetRecord.value[targetRecord.index].content); - record.splice(element, 1); - targetRecord.value[targetRecord.index].content = JSON.stringify(record); - break; - } - break; - case `rename`: - const oldName = program.getValue(command.oldName); - const newName = program.getValue(command.newName); - targetRecord = program.getSymbolRecord(command.target); - record = JSON.parse(targetRecord.value[targetRecord.index].content); - content = record[oldName]; - delete record[oldName]; - record[newName] = content; - targetRecord.value[targetRecord.index].content = JSON.stringify(record); - break; - case `add`: - content = program.getValue(command.item); - targetRecord = program.getSymbolRecord(command.target); - const existing = targetRecord.value[targetRecord.index].content; - record = existing ? JSON.parse(existing) : []; - record.push((`[`, `{`).includes(content[0]) ? JSON.parse(content) :content); - targetRecord.value[targetRecord.index] = { - type: `constant`, - numeric: false, - content: JSON.stringify(record) - }; - break; - case `split`: - content = program.getValue(command.item); - const on = program.getValue(command.on); - targetRecord = program.getSymbolRecord(command.target); - targetRecord.value[targetRecord.index] = { - type: `constant`, - numeric: false, - content: JSON.stringify(content.split(on)) - }; - break; - case `replace`: - targetRecord = program.getSymbolRecord(command.target); - const index = program.getValue(command.index); - const value = program.getValue(command.value); - const current = targetRecord.value[targetRecord.index].content; - record = current ? JSON.parse(current) : []; - if (index > record.length - 1) { - program.runtimeError(command.lino, `Index out of range`); - } - record[index] = value; - targetRecord.value[targetRecord.index].content = JSON.stringify(record); - break; - } - return command.pc + 1; - } - }, - - getHandler: (name) => { - switch (name) { - case `json`: - return EasyCoder_Json.Json; - default: - return null; - } - }, - - run: (program) => { - const command = program[program.pc]; - const handler = EasyCoder_Json.getHandler(command.keyword); - if (!handler) { - program.runtimeError(command.lino, `Unknown keyword '${command.keyword}' in 'json' package`); - } - return handler.run(program); - }, - - value: { - - compile: (compiler) => { - if (compiler.tokenIs(`the`)) { - compiler.next(); - } - if (compiler.tokenIs(`json`)) { - const type = compiler.nextToken(); - if ([`size`, `count`, `keys`].includes(type)) { - compiler.skip(`of`); - if (compiler.isSymbol()) { - const target = compiler.getSymbolRecord(); - compiler.next(); - if (target.isVHolder) { - return { - domain: `json`, - type, - name: target.name - }; - } - } - } else if (type === `index`) { - if (compiler.nextTokenIs(`of`)) { - const item = compiler.getNextValue(); - if (compiler.tokenIs(`in`)) { - const list = compiler.getNextValue(); - return { - domain: `json`, - type, - item, - list - }; - } - } - } - } - return null; - }, - - get: (program, value) => { - let symbolRecord; - let data; - let content; - switch (value.type) { - case `size`: - case `count`: - symbolRecord = program.getSymbolRecord(value.name); - data = program.getValue(symbolRecord.value[symbolRecord.index]); - let array; - try { - array = JSON.parse(data); - } catch (err) { - array = []; - } - return { - type: `constant`, - numeric: true, - content: array ? array.length : 0 - }; - case `keys`: - symbolRecord = program.getSymbolRecord(value.name); - data = program.getValue(symbolRecord.value[symbolRecord.index]); - content = data ? JSON.stringify(Object.keys(JSON.parse(data)).sort()) : `[]`; - return { - type: `constant`, - numeric: false, - content - }; - case `index`: - const item = program.getValue(value.item); - const list = JSON.parse(program.getValue(value.list)); - content = list.findIndex(function (value) { - return value === item; - }); - return { - type: `constant`, - numeric: true, - content - }; - } - } - }, - - condition: { - - compile: () => {}, - - test: () => {} - } -}; diff --git a/easycoder/plugins/life.js b/easycoder/plugins/life.js new file mode 100644 index 0000000..5d7d4f5 --- /dev/null +++ b/easycoder/plugins/life.js @@ -0,0 +1,94 @@ +// eslint-disable-next-line no-unused-vars +const EasyCoder_Life = { + + name: `EasyCoder_Life`, + + value: { + + compile: (compiler) => { + if (compiler.tokenIs(`the`)) { + compiler.nextToken(); + } + if (compiler.tokenIs(`neighbours`)) { + if (compiler.nextTokenIs(`of`)) { + if (compiler.nextIsSymbol()) { + const symbolRecord = compiler.getSymbolRecord(); + if (symbolRecord.keyword == `variable`) { + if (compiler.nextTokenIs(`cell`)) { + const cell = compiler.getNextValue(); + return { + domain: `life`, + type: `getNeighbours`, + name: symbolRecord.name, + cell + }; + } + } + } + } + } + return null; + }, + + get: (program, value) => { + switch (value.type) { + case `getNeighbours`: + const symbolRecord = program.getSymbolRecord(value.name); + const size = Math.sqrt(symbolRecord.elements); + const cell = program.getValue(value.cell); + const row = Math.trunc(cell / size); + const column = cell % size; + const map = symbolRecord.value; + let count = 0; + if (column > 0) { + if (map[cell - 1].content) { + count++; + } + } + if (column < size - 1) { + if (map[cell + 1].content) { + count++; + } + } + if (row > 0) { + for (let c = -1; c < 2; c++) { + let cc = column + c; + if (cc >= 0 && cc < size) { + if (map[cell - size + c].content) { + count++; + } + } + } + } + if (row < size - 1) { + for (let c = -1; c < 2; c++) { + let cc = column + c; + if (cc >= 0 && cc < size) { + if (map[cell + size + c].content) { + count++; + } + } + } + } + return { + type: `constant`, + numeric: true, + content: count + }; + } + return null; + } + }, + + getHandler: () => { + return null; + }, + + condition: { + + compile: () => {} + } +}; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.life = EasyCoder_Life; diff --git a/easycoder/plugins/showdown.js b/easycoder/plugins/showdown.js index 180e1b1..8573440 100644 --- a/easycoder/plugins/showdown.js +++ b/easycoder/plugins/showdown.js @@ -119,3 +119,6 @@ const EasyCoder_Showdown = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.showdown = EasyCoder_Showdown; diff --git a/easycoder/plugins/svg.js b/easycoder/plugins/svg.js index 09fbe57..8f15b92 100644 --- a/easycoder/plugins/svg.js +++ b/easycoder/plugins/svg.js @@ -616,3 +616,6 @@ const EasyCoder_SVG = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.svg = EasyCoder_SVG; diff --git a/easycoder/plugins/ui.js b/easycoder/plugins/ui.js index 17d8297..b0ac935 100644 --- a/easycoder/plugins/ui.js +++ b/easycoder/plugins/ui.js @@ -368,3 +368,6 @@ const EasyCoder_UI = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.ui = EasyCoder_UI; diff --git a/easycoder/plugins/vfx.js b/easycoder/plugins/vfx.js index f2ff5da..a358f76 100644 --- a/easycoder/plugins/vfx.js +++ b/easycoder/plugins/vfx.js @@ -341,3 +341,6 @@ const EasyCoder_VFX = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.vfx = EasyCoder_VFX; diff --git a/easycoder/plugins/wof.js b/easycoder/plugins/wof.js index 7904cb6..8c63fcd 100644 --- a/easycoder/plugins/wof.js +++ b/easycoder/plugins/wof.js @@ -280,3 +280,6 @@ const EasyCoder_roulette_wheel = { $.rotateWheel($); } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.wof = EasyCoder_WOF; diff --git a/js/plugins/browser.js b/js/easycoder/Browser.js similarity index 99% rename from js/plugins/browser.js rename to js/easycoder/Browser.js index 30e281f..474d9a8 100644 --- a/js/plugins/browser.js +++ b/js/easycoder/Browser.js @@ -2558,7 +2558,7 @@ const EasyCoder_Browser = { } }; program.ajaxCommand = command; - const postpath = path.startsWith(`http`) ? path : `${window.location.origin}/${EasyCoder_Plugins.rest()}/${path}`; + const postpath = path.startsWith(`http`) ? path : `${window.location.origin}//${path}`; ajax.open(`POST`, postpath); ajax.send(formData); } diff --git a/js/easycoder/Compile.js b/js/easycoder/Compile.js index 54e080f..16c2129 100644 --- a/js/easycoder/Compile.js +++ b/js/easycoder/Compile.js @@ -315,6 +315,7 @@ const EasyCoder_Compiler = { this.tokens = tokens; this.index = 0; this.program = []; + this.program.script = 0; this.program.symbols = {}; this.symbols = this.program.symbols; this.warnings = []; diff --git a/js/easycoder/Core.js b/js/easycoder/Core.js index 521344f..a576a55 100644 --- a/js/easycoder/Core.js +++ b/js/easycoder/Core.js @@ -870,46 +870,6 @@ const EasyCoder_Core = { } }, - Load: { - - compile: compiler => { - const lino = compiler.getLino(); - const type = compiler.nextToken(); - switch (type) { - case `plugin`: - const name = compiler.getNextValue(); - compiler.addCommand({ - domain: `core`, - keyword: `load`, - lino, - name - }); - return true; - } - return false; - }, - - run: program => { - const command = program[program.pc]; - const name = program.getValue(command.name); - switch (command.keyword) { - case `load`: - if (program.checkPlugin(name)) { - return command.pc + 1; - } - EasyCoder_Plugins.getLocalPlugin( - program.getPluginsPath, - name, - program.getPlugin, - program.addLocalPlugin, - function () { - program.run(command.pc + 1); - }); - return 0; - } - } - }, - Module: { compile: compiler => { @@ -2184,8 +2144,6 @@ const EasyCoder_Core = { return EasyCoder_Core.Import; case `index`: return EasyCoder_Core.Index; - case `load`: - return EasyCoder_Core.Load; case `module`: return EasyCoder_Core.Module; case `multiply`: diff --git a/js/plugins/json.js b/js/easycoder/Json.js similarity index 99% rename from js/plugins/json.js rename to js/easycoder/Json.js index fdf93a4..d727f8c 100644 --- a/js/plugins/json.js +++ b/js/easycoder/Json.js @@ -366,7 +366,7 @@ const EasyCoder_Json = { targetRecord = program.getSymbolRecord(command.target); const existing = targetRecord.value[targetRecord.index].content; record = existing ? JSON.parse(existing) : []; - record.push((`[`, `{`).includes(content[0]) ? JSON.parse(content) :content); + record.push([`[`, `{`].includes(content[0]) ? JSON.parse(content) :content); targetRecord.value[targetRecord.index] = { type: `constant`, numeric: false, diff --git a/js/easycoder/Main.js b/js/easycoder/Main.js index 8fb8404..669b878 100644 --- a/js/easycoder/Main.js +++ b/js/easycoder/Main.js @@ -3,7 +3,10 @@ const EasyCoder = { name: `EasyCoder_Main`, domain: { - core: EasyCoder_Core + core: EasyCoder_Core, + browser: EasyCoder_Browser, + json: EasyCoder_Json, + rest: EasyCoder_Rest }, elementId: 0, @@ -144,22 +147,26 @@ const EasyCoder = { }, require: function(type, src, cb) { + let prefix = ``; + if (src[0] == `/`) { + prefix = window.location + `/`; + } const element = document.createElement(type === `css` ? `link` : `script`); switch (type) { case `css`: element.type = `text/css`; - element.href = src; + element.href = `${prefix}${src}`; element.rel = `stylesheet`; break; case `js`: element.type = `text/javascript`; - element.src = src; + element.src = `${prefix}${src}`; break; default: return; } element.onload = function () { - console.log(`${Date.now() - EasyCoder.timestamp} ms: Library ${src} loaded`); + console.log(`${Date.now() - EasyCoder.timestamp} ms: Library ${prefix}${src} loaded`); cb(); }; document.head.appendChild(element); @@ -239,10 +246,6 @@ const EasyCoder = { program.require = this.require; program.isUndefined = this.isUndefined; program.isJsonString = this.isJsonString; - program.checkPlugin = this.checkPlugin; - program.getPlugin = this.getPlugin; - program.addLocalPlugin = this.addLocalPlugin; - program.getPluginsPath = this.getPluginsPath; program.getSymbolRecord = this.getSymbolRecord; program.verifySymbol = this.verifySymbol; program.runtimeError = this.runtimeError; @@ -325,9 +328,9 @@ const EasyCoder = { const source = this.tokeniseFile(file); try { program = this.compileScript(source, imports, module, parent); - this.scriptIndex++; if (!program.script) { - program.script = this.scriptIndex; + program.script = EasyCoder.scriptIndex; + EasyCoder.scriptIndex++; } const finishCompile = Date.now(); console.log(`${finishCompile - this.timestamp} ms: ` + @@ -359,7 +362,8 @@ const EasyCoder = { } }, - tokenise: function(source) { + start: function(source) { + EasyCoder.scriptIndex = 0; const script = source.split(`\n`); if (!this.tokenising) { try { @@ -370,104 +374,4 @@ const EasyCoder = { this.tokenising = true; } }, - - setPluginCount: function(count) { - EasyCoder.plugins = []; - EasyCoder.pluginCount = count; - }, - - checkPlugin: function(name) { - return EasyCoder.domain[name]; - }, - - getPlugin: function(name, src, onload) { - if (EasyCoder.domain[name]) { - onload(); - return; - } - const script = document.createElement(`script`); - script.type = `text/javascript`; - let location = document.scripts[0].src; - location = location.substring(0, location.indexOf(`/easycoder.js`)); - // script.src = `${location}/${src}?ver=${EasyCoder.version}`; - script.src = `${src}?ver=${EasyCoder.version}`; - script.onload = function () { - console.log(`${Date.now() - EasyCoder.timestamp} ms: Plugin ${src} loaded`); - onload(); - }; - document.head.appendChild(script); - }, - - addGlobalPlugin: function(name, handler) { - // alert(`Add plugin ${name}`); - EasyCoder.plugins.push({ - name, - handler - }); - if (EasyCoder.plugins.length === EasyCoder.pluginCount) { - EasyCoder.plugins.forEach(function (plugin) { - EasyCoder.domain[plugin.name] = plugin.handler; - }); - EasyCoder.tokenise(EasyCoder.source); - } - }, - - addLocalPlugin: function(name, handler, callback) { - EasyCoder.domain[name] = handler; - callback(); - }, - - getPluginsPath: function() { - return EasyCoder.pluginsPath; - }, - - loadPluginJs: function(path) { - console.log(`${Date.now() - this.timestamp} ms: Load ${path}/easycoder/plugins.js`); - const script = document.createElement(`script`); - script.src = `${window.location.origin}${path}/easycoder/plugins.js?ver=${this.version}`; - script.type = `text/javascript`; - script.onload = () => { - EasyCoder_Plugins.getGlobalPlugins( - this.timestamp, - path, - this.setPluginCount, - this.getPlugin, - this.addGlobalPlugin - ); - }; - script.onerror = () => { - if (path) { - this.loadPluginJs(path.slice(0, path.lastIndexOf(`/`))); - } else { - this.reportError({ - message: `Can't load plugins.js` - }, this.program, this.source); - } - }; - document.head.appendChild(script); - this.pluginsPath = path; - }, - - start: function(source) { - this.source = source; - this.scriptIndex = 0; - let pathname = window.location.pathname; - if (pathname.endsWith(`/`)) { - pathname = pathname.slice(0, -1); - } else { - pathname = ``; - } - if (typeof EasyCoder_Plugins === `undefined`) { - this.loadPluginJs(pathname); - } else { - this.pluginsPath = pathname; - EasyCoder_Plugins.getGlobalPlugins( - this.timestamp, - pathname, - this.setPluginCount, - this.getPlugin, - this.addGlobalPlugin - ); - } - } }; diff --git a/easycoder/plugins/rest.js b/js/easycoder/Rest.js similarity index 97% rename from easycoder/plugins/rest.js rename to js/easycoder/Rest.js index 3974890..581f3bf 100644 --- a/easycoder/plugins/rest.js +++ b/js/easycoder/Rest.js @@ -110,10 +110,8 @@ const EasyCoder_Rest = { run: (program) => { const command = program[program.pc]; const url = program.getValue(command.url); - const rest = EasyCoder_Plugins.rest(); const path = url.startsWith(`http`) ? url - : url[0] === `/` ? url.substr(1) - : `${window.location.origin}${rest ? `/${rest}` : ``}/${url}`; + : url[0] === `/` ? url.substr(1) : `${window.location.origin}/${url}`; const request = EasyCoder_Rest.Rest.createCORSRequest(command.request, path); if (!request) { diff --git a/js/plugins-all.js b/js/plugins-all.js deleted file mode 100644 index 378b8e0..0000000 --- a/js/plugins-all.js +++ /dev/null @@ -1,25 +0,0 @@ -// eslint-disable-next-line no-unused-vars -const EasyCoder_Plugins = { - - // eslint-disable-next-line no-unused-vars - getGlobalPlugins: (timestamp, path, setPluginCount, getPlugin, addPlugin) => { - setPluginCount(11); // *** IMPORTANT *** the number of plugins you will be adding - - addPlugin(`browser`, EasyCoder_Browser); - addPlugin(`json`, EasyCoder_Json); - addPlugin(`rest`, EasyCoder_Rest); - addPlugin(`ckeditor`, EasyCoder_CKEditor); - addPlugin(`codemirror`, EasyCoder_CodeMirror); - addPlugin(`gmap`, EasyCoder_GMap); - addPlugin(`showdown`, EasyCoder_Showdown); - addPlugin(`svg`, EasyCoder_SVG); - addPlugin(`ui`, EasyCoder_UI); - addPlugin(`vfx`, EasyCoder_WOF); - addPlugin(`wof`, EasyCoder_WOF); - addPlugin(`anagrams`, EasyCoder_Anagrams); - }, - - rest: () => { - return ``; - } -}; diff --git a/js/plugins-st.js b/js/plugins-st.js deleted file mode 100644 index 7d10406..0000000 --- a/js/plugins-st.js +++ /dev/null @@ -1,24 +0,0 @@ -// Plugins for StoryTeller - -// eslint-disable-next-line no-unused-vars -const EasyCoder_Plugins = { - - // eslint-disable-next-line no-unused-vars - getGlobalPlugins: (timestamp, path, setPluginCount, getPlugin, addPlugin) => { - setPluginCount(9); // *** IMPORTANT *** the number of plugins you will be adding - - addPlugin(`browser`, EasyCoder_Browser); - addPlugin(`json`, EasyCoder_Json); - addPlugin(`rest`, EasyCoder_Rest); - addPlugin(`gmap`, EasyCoder_GMap); - addPlugin(`showdown`, EasyCoder_Showdown); - addPlugin(`codemirror`, EasyCoder_CodeMirror); - addPlugin(`svg`, EasyCoder_SVG); - addPlugin(`ui`, EasyCoder_UI); - addPlugin(`vfx`, EasyCoder_VFX); - }, - - rest: () => { - return ``; - } -}; diff --git a/js/plugins/anagrams.js b/js/plugins/anagrams.js index 594a751..e1c98ab 100644 --- a/js/plugins/anagrams.js +++ b/js/plugins/anagrams.js @@ -40,4 +40,7 @@ const EasyCoder_Anagrams = { compile: () => {} } -}; \ No newline at end of file +}; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.anagrams = EasyCoder_Anagrams; diff --git a/js/plugins/aws.js b/js/plugins/aws.js index d86a1fd..79c5c09 100644 --- a/js/plugins/aws.js +++ b/js/plugins/aws.js @@ -228,4 +228,7 @@ const EasyCoder_AWS = { test: () => {} } -}; \ No newline at end of file +}; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.aws = EasyCoder_AWS; diff --git a/js/plugins/ckeditor.js b/js/plugins/ckeditor.js index 32d64dd..bbc8578 100644 --- a/js/plugins/ckeditor.js +++ b/js/plugins/ckeditor.js @@ -170,3 +170,6 @@ const EasyCoder_CKEditor = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.ckeditor = EasyCoder_CKEditor; diff --git a/js/plugins/codemirror.js b/js/plugins/codemirror.js index ccec906..266cb48 100644 --- a/js/plugins/codemirror.js +++ b/js/plugins/codemirror.js @@ -168,3 +168,6 @@ const EasyCoder_CodeMirror = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.codemirror = EasyCoder_CodeMirror; diff --git a/js/plugins/dummy.js b/js/plugins/dummy.js index fd416c4..36bbd02 100644 --- a/js/plugins/dummy.js +++ b/js/plugins/dummy.js @@ -63,4 +63,7 @@ const EasyCoder_Dummy = { } return handler.run(program); } -}; \ No newline at end of file +}; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.dummy = EasyCoder_Dummy; diff --git a/js/plugins/gmap.js b/js/plugins/gmap.js index 52ce2ea..404f88c 100644 --- a/js/plugins/gmap.js +++ b/js/plugins/gmap.js @@ -556,3 +556,6 @@ const EasyCoder_GMap = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.gmap = EasyCoder_GMap; diff --git a/js/plugins/life.js b/js/plugins/life.js index 37c6ecd..5d7d4f5 100644 --- a/js/plugins/life.js +++ b/js/plugins/life.js @@ -88,4 +88,7 @@ const EasyCoder_Life = { compile: () => {} } -}; \ No newline at end of file +}; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.life = EasyCoder_Life; diff --git a/js/plugins/rest.js b/js/plugins/rest.js deleted file mode 100644 index 3974890..0000000 --- a/js/plugins/rest.js +++ /dev/null @@ -1,216 +0,0 @@ -const EasyCoder_Rest = { - - name: `EasyCoder_Rest`, - - Rest: { - - compile: (compiler) => { - const lino = compiler.getLino(); - const request = compiler.nextToken(); - switch (request) { - case `get`: - if (compiler.nextIsSymbol(true)) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.keyword === `variable`) { - if (compiler.nextTokenIs(`from`)) { - const url = compiler.getNextValue(); - let fixup = compiler.getPc(); - compiler.addCommand({ - domain: `rest`, - keyword: `rest`, - lino, - request: `get`, - target: targetRecord.name, - url, - onError: null - }); - if (compiler.tokenIs(`or`)) { - compiler.next(); - compiler.getCommandAt(fixup).onError = compiler.getPc() + 1; - compiler.completeHandler(); - } - return true; - } - } - } - break; - case `post`: - let value = null; - if (compiler.nextTokenIs(`to`)) { - compiler.next(); - } else { - value = compiler.getValue(); - if (compiler.tokenIs(`to`)) { - compiler.next(); - } else { - break; - } - } - const url = compiler.getValue(); - if (!url) { - throw new Error(command.lino, `No URL present`); - } - let target = null; - if (compiler.tokenIs(`giving`)) { - if (compiler.nextIsSymbol()) { - const targetRecord = compiler.getSymbolRecord(); - if (targetRecord.isVHolder) { - target = targetRecord.name; - compiler.next(); - } else { - throw new Error(`'${targetRecord.name}' cannot hold a value`); - } - } - } - compiler.addCommand({ - domain: `rest`, - keyword: `rest`, - lino, - request: `post`, - value, - url, - target, - onError: compiler.getPc() + 2 - }); - onError = null; - if (compiler.tokenIs(`or`)) { - compiler.next(); - // onError = compiler.getPc() + 1; - compiler.completeHandler(); - } - return true; - } - return false; - }, - - createCORSRequest: (method, url) => { - var xhr = new XMLHttpRequest(); - if (`withCredentials` in xhr) { - - // Check if the XMLHttpRequest object has a "withCredentials" property. - // "withCredentials" only exists on XMLHTTPRequest2 objects. - xhr.open(method, url, true); - - } else if (typeof XDomainRequest != `undefined`) { - - // Otherwise, check if XDomainRequest. - // XDomainRequest only exists in IE, and is IE's way of making CORS requests. - xhr = new XDomainRequest(); - xhr.open(method, url); - - } else { - - // Otherwise, CORS is not supported by the browser. - xhr = null; - - } - return xhr; - }, - - run: (program) => { - const command = program[program.pc]; - const url = program.getValue(command.url); - const rest = EasyCoder_Plugins.rest(); - const path = url.startsWith(`http`) ? url - : url[0] === `/` ? url.substr(1) - : `${window.location.origin}${rest ? `/${rest}` : ``}/${url}`; - - const request = EasyCoder_Rest.Rest.createCORSRequest(command.request, path); - if (!request) { - program.runtimeError(command.lino, `CORS not supported`); - return; - } - request.script = program.script; - request.pc = program.pc; - - request.onload = function () { - let s = request.script; - let p = EasyCoder.scripts[s]; - let pc = request.pc; - let c = p[pc]; - if (200 <= request.status && request.status < 400) { - var content = request.responseText.trim(); - if (c.target) { - const targetRecord = program.getSymbolRecord(command.target); - targetRecord.value[targetRecord.index] = { - type: `constant`, - numeric: false, - content - }; - targetRecord.used = true; - } - p.run(c.pc + 1); - } else { - const error = `${request.status} ${request.statusText}`; - if (c.onError) { - p.errorMessage = `Exception trapped: ${error}`; - p.run(c.onError); - } else { - p.runtimeError(c.lino, `Error: ${error}`); - } - } - }; - - request.onerror = function () { - if (command.onError) { - program.errorMessage = this.responseText; - program.run(command.onError); - } else { - const error = this.responseText; - program.runtimeError(command.lino, error); - } - }; - - switch (command.request) { - case `get`: - // console.log(`GET from ${path}`); - request.send(); - break; - case `post`: - const value = program.getValue(command.value); - console.log(`POST to ${path}`); - //console.log(`value=${value}`); - request.setRequestHeader(`Content-type`, `application/json; charset=UTF-8`); - request.send(value); - break; - } - return 0; - } - }, - - getHandler: (name) => { - switch (name) { - case `rest`: - return EasyCoder_Rest.Rest; - default: - return null; - } - }, - - run: (program) => { - const command = program[program.pc]; - const handler = EasyCoder_Rest.getHandler(command.keyword); - if (!handler) { - program.runtimeError(command.lino, `Unknown keyword '${command.keyword}' in 'rest' package`); - } - return handler.run(program); - }, - - value: { - - compile: () => { - return null; - }, - - get: () => { - return null; - } - }, - - condition: { - - compile: () => {}, - - test: () => {} - } -}; diff --git a/js/plugins/showdown.js b/js/plugins/showdown.js index 180e1b1..8573440 100644 --- a/js/plugins/showdown.js +++ b/js/plugins/showdown.js @@ -119,3 +119,6 @@ const EasyCoder_Showdown = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.showdown = EasyCoder_Showdown; diff --git a/js/plugins/svg.js b/js/plugins/svg.js index 09fbe57..8f15b92 100644 --- a/js/plugins/svg.js +++ b/js/plugins/svg.js @@ -616,3 +616,6 @@ const EasyCoder_SVG = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.svg = EasyCoder_SVG; diff --git a/js/plugins/ui.js b/js/plugins/ui.js index 17d8297..b0ac935 100644 --- a/js/plugins/ui.js +++ b/js/plugins/ui.js @@ -368,3 +368,6 @@ const EasyCoder_UI = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.ui = EasyCoder_UI; diff --git a/js/plugins/vfx.js b/js/plugins/vfx.js index f2ff5da..a358f76 100644 --- a/js/plugins/vfx.js +++ b/js/plugins/vfx.js @@ -341,3 +341,6 @@ const EasyCoder_VFX = { test: () => {} } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.vfx = EasyCoder_VFX; diff --git a/js/plugins/wof.js b/js/plugins/wof.js index 7904cb6..8c63fcd 100644 --- a/js/plugins/wof.js +++ b/js/plugins/wof.js @@ -280,3 +280,6 @@ const EasyCoder_roulette_wheel = { $.rotateWheel($); } }; + +// eslint-disable-next-line no-unused-vars +EasyCoder.domain.wof = EasyCoder_WOF; diff --git a/server/plugins-sample.js b/server/plugins-sample.js deleted file mode 100644 index c1d0f1d..0000000 --- a/server/plugins-sample.js +++ /dev/null @@ -1,110 +0,0 @@ -const EasyCoder_Plugins = { - - getGlobalPlugins: (timestamp, path, setPluginCount, getPlugin, addPlugin) => { - - console.log(`${Date.now() - timestamp} ms: Load plugins`); - - /* - * To include EasyCoder global plugins in your site, add them here. - * It adds the selected plugins to every page of your site that uses EasyCoder. - * You can also dynamically load plugins before launching a script; see getLocalPlugin() below. - * - * setPluginCount() sets the number of plugins to add. - * getPlugin() loads a plugin from any URL. - * addPlugin() adds it to the EasyCoder system. - * When all the plugins have been added, EasyCoder starts up. - */ - - setPluginCount(3); // *** IMPORTANT *** the number of plugins you will be adding - - getPlugin(`browser`, - `${window.location.origin}${path}/wp-content/plugins/easycoder/plugins/browser.js`, - function () { - addPlugin(`browser`, EasyCoder_Browser); - }); - - getPlugin(`json`, - `${window.location.origin}${path}/wp-content/plugins/easycoder/plugins/json.js`, - function () { - addPlugin(`json`, EasyCoder_Json); - }); - - getPlugin(`rest`, - `${window.location.origin}${path}/wp-content/plugins/easycoder/plugins/rest.js`, - function () { - addPlugin(`rest`, EasyCoder_Rest); - }); - - }, - - getLocalPlugin: (path, name, getPlugin, addPlugin, callback) => { - - /* - * This lets you add a plugin before launching a script, using the 'plugin' command. - * You must provide a case for every plugin you will be adding; - * use any one of them as the pattern to follow. - */ - - switch (name) { - case `ckeditor`: - getPlugin(name, - `${window.location.origin}${path()}/wp-content/plugins/easycoder/plugins/ckeditor.js`, - function () { - addPlugin(name, EasyCoder_CKEditor, callback); - }); - break; - - case `codemirror`: - getPlugin(name, - `${window.location.origin}${path()}/wp-content/plugins/easycoder/plugins/codemirror.js`, - function () { - addPlugin(name, EasyCoder_CodeMirror, callback); - }); - break; - - case `ui`: - getPlugin(name, - `${window.location.origin}${path()}/wp-content/plugins/easycoder/plugins/ui.js`, - function () { - addPlugin(name, EasyCoder_UI, callback); - }); - break; - - case `gmap`: - getPlugin(name, - `${window.location.origin}${path()}/wp-content/plugins/easycoder/plugins/gmap.js`, - function () { - addPlugin(name, EasyCoder_GMap, callback); - }); - break; - - case `showdown`: - getPlugin(name, - `${window.location.origin}${path()}/wp-content/plugins/easycoder/plugins/showdown.js`, - function () { - addPlugin(name, EasyCoder_Showdown, callback); - }); - break; - - case `svg`: - getPlugin(name, - `${window.location.origin}${path()}/wp-content/plugins/easycoder/plugins/svg.js`, - function () { - addPlugin(name, EasyCoder_SVG, callback); - }); - break; - - default: - console.log(`Unknown plugin '${name}'`); - break; - } - }, - - rest: () => { - return `wp-content/plugins/easycoder/rest.php`; - } -}; - -exports = { - EasyCoder_Plugins -}; \ No newline at end of file diff --git a/server/scripted b/server/scripted deleted file mode 100644 index 431fb3b..0000000 --- a/server/scripted +++ /dev/null @@ -1,303 +0,0 @@ - script ScriptEditor - - div Body - div Container - div Buttons - div ScriptName - div ContentDiv - input NameEditor - textarea ContentEditor - span Status - span Span - button Open - button Save - button Delete - variable Hash - variable Name - variable CurrentName - variable Content - variable Current - variable Password - variable Password2 - variable Valid - -! The browser - div Overlay - div Scroller - div Media - div FileListing - div FileRow - button CloseButton - a FileName - variable Alpha - variable FileList - variable FileCount - variable File - variable Files - variable N - variable FileIsOpen - variable Item - - set the title to `Script Editor` - -GetPassword: - rest get Hash from `_load/hash` - if Hash is empty - begin - put prompt `Please type the admin password` with `` into Password - if Password is empty - begin - alert `You must set the admin password to continue.` - goto GetPassword - end - put prompt `Please confirm the admin password` with `` into Password2 - if Password2 is not Password - begin - alert `Passwords do not match. Please start again.` - goto GetPassword - end - rest get Hash from `_hash/` cat Password - rest post Hash to `_save/hash` - goto Start - end - -Login: - get Password from storage as `.ec-password` - if Password is empty - put prompt `Please type the admin password` with `` into Password - rest get Valid from `_verify/` cat Password or - begin - alert `Server error; please restart.` - exit - end - if Valid is `yes` put Password into storage as `.ec-password` - else - begin - alert `Incorrect password; please retry` - goto Login - end - -Start: - clear body - create Body in body - set the style of Body to `width:100vw;height:100vh` - create Container in Body - set the style of Container to - `width:70%;height:100%;margin:0 auto;background #ffe;display:flex;flex-direction:column` - - create Buttons in Container - set the style of Buttons to `height:3em;text-align:center` - - create Open in Buttons - set the style of Open to `margin-right:0.5em` - set the text of Open to `Open` - create Save in Buttons - set the text of Save to `Save` - set the style of Save to `margin-right:4em` - create Delete in Buttons - set the text of Delete to `Delete` - create Status in Buttons - set the style of Status to `float:right;padding:1em 2em 0 0;color:green` - - create ScriptName in Container - set the style of ScriptName to `height:2em;display:flex;margin:0.5em 0` - create Span in ScriptName - set the style of Span to `flex:15` - set the content of Span to `Script name: ` - create NameEditor in ScriptName - set the style of NameEditor to `flex:85;display:inline-block` - set the size of NameEditor to 40 - - create ContentDiv in Container - set the style of ContentDiv to `flex:1;width:100%;border:1px solid lightgray;overflow-y:scroll` - - create ContentEditor in ContentDiv - set the style of ContentEditor to `width:100%;height:100%` - - codemirror init basic profile `/wp-content/plugins/easycoder/plugins/codemirror-ecs.js` - require css `https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.46.0/addon/dialog/dialog.css` - require js `https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.46.0/addon/dialog/dialog.js` - require js `https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.46.0/addon/search/search.js` - require js `https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.46.0/addon/search/searchcursor.js` - require js `https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.46.0/addon/search/jump-to-line.js` - - create Overlay in body - set the style of Overlay to - `position:absolute;top:0;left:0;width:100vw;height:100vh;background:rgba(0,0,0,0.0);display:none` - - create Media in Overlay - set style of Media to `display:none;width:100%;height:100%;text-align:center` - - create FileListing in Media - set the style of FileListing to - `display:none;width:50%;height:75%;margin:auto;background-color:white;` - cat `padding:2em 2em 3em 2em;text-align:center;position: absolute;top: 50%;left: 50%;` - cat `transform: translateX(-50%) translateY(-50%)` - - create Scroller in FileListing - set the style of Scroller to `height:100%;overflow:scroll;text-align:left` - - create CloseButton in FileListing - set the text of CloseButton to `Close` - - codemirror attach to ContentEditor - codemirror set content of ContentEditor to Content - set FileIsOpen - - put empty into Current - - on click Save - begin - put the content of NameEditor into Name - if Name is empty - begin - set the content of Status to `No script name has been given` - go to ResetStatus - end - replace ` ` with `_` in Name - codemirror close ContentEditor - put the content of ContentEditor into Content - if Content is not Current - begin - rest post Content to `_save/scripts~` cat Name - put Content into Current - set the content of Status to `Script '` cat Name cat `' saved` - fork to ResetStatus - end - else - begin - set the content of Status to `Nothing has changed` - fork to ResetStatus - end - codemirror attach to ContentEditor - end - - on click Open go to DoOpen - - on click Delete - begin - put the content of NameEditor into Name - if Name is empty - begin - alert `Nothing to delete.` - stop - end - if confirm `Are you sure you want to delete '` cat Name cat `'?` - begin - codemirror close ContentEditor - rest post to `_delete/scripts~` cat Name - set the content of Status to `Script '` cat Name cat `' deleted` - set the content of NameEditor to empty - put empty into Content - set the content of ContentEditor to Content - put Content into Current - go to ResetStatus - end - end - stop - -DoOpen: - if FileIsOpen codemirror close ContentEditor - put the content of ContentEditor into Content - if Content is not Current - begin - if confirm `Content has changed. Do you want to save it?` - begin - rest post Content to `_save/scripts~` cat CurrentName -! rest post Content to `ec_scripts/set/name/` cat CurrentName - end - end - -! Animate the background - set style `display` of Overlay to `block` - put 0 into Alpha - while Alpha is less than 8 - begin - set style `background-color` of Overlay to `rgba(0,0,0,0.` cat Alpha cat `)` - wait 4 ticks - add 1 to Alpha - end - wait 10 ticks - -! Make the browser panel visible - set style `display` of Media to `block` - set style `display` of FileListing to `inline-block` - -! Fill the browser with content from the server - rest get Files from `_list/scripts` - put the json count of Files into FileCount - put empty into Content - put 0 into N - while N is less than FileCount - begin - put element N of Files into Item - if property `type` of Item is `file` json add Item to Content - add 1 to N - end - put empty into FileList - json sort Content - put empty into FileList - put the json count of Content into FileCount - set the elements of File to FileCount - set the elements of FileName to FileCount -! Add a row for each file - put empty into FileList - put 0 into N - while N is less than FileCount - begin - index File to N - index FileName to N - put `
` - cat `
` into File - replace `INDEX` with N in File - if N is even replace `ODDEVEN` with `ec-even` in File - else replace `ODDEVEN` with `ec-odd` in File - put FileList cat File into FileList - add 1 to N - end - - set the content of Scroller to FileList -! Add the document names - put 0 into N - while N is less than FileCount - begin - index File to N - index FileName to N - put element N of Content into File - attach FileRow to `ec-file-row-` cat N - attach FileName to `ec-file-name-` cat N - put property `name` of File into File - set the content of FileName to File - if N is even set style `background` of FileRow to `lightgray` - on click FileName go to SelectFile - add 1 to N - end - on click CloseButton - begin - put Current into Content - go to CloseBrowser - end - stop - -SelectFile: - index File to the index of FileName - set the content of NameEditor to File - rest get Content from `_load/scripts~` cat File - replace `\` with `\\` in Content - put Content into Current - set the content of Status to `Script '` cat File cat `' loaded` - fork to ResetStatus - set the title to `Script Editor - ` cat File - -CloseBrowser: - set style `background-color` of Overlay to `rgba(0,0,0,0.0)` - set style `display` of Overlay to `none` - set style `display` of Media to `none` - codemirror attach to ContentEditor - codemirror set content of ContentEditor to Content - stop - -ResetStatus: - wait 2 - set the content of Status to `` - stop