Plugin Directory

Changeset 2038748


Ignore:
Timestamp:
02/25/2019 11:02:46 AM (7 years ago)
Author:
gtanyware
Message:

Version 2.2, with gmap and showdown

Location:
easycoder/trunk
Files:
2 added
11 edited

Legend:

Unmodified
Added
Removed
  • easycoder/trunk/easycoder-min.js

    r2016526 r2038748  
    1 var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(d,h,g){d!=Array.prototype&&d!=Object.prototype&&(d[h]=g.value)};$jscomp.getGlobal=function(d){return"undefined"!=typeof window&&window===d?d:"undefined"!=typeof global&&null!=global?global:d};$jscomp.global=$jscomp.getGlobal(this);$jscomp.SYMBOL_PREFIX="jscomp_symbol_";
     1var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(d,h,f){d!=Array.prototype&&d!=Object.prototype&&(d[h]=f.value)};$jscomp.getGlobal=function(d){return"undefined"!=typeof window&&window===d?d:"undefined"!=typeof global&&null!=global?global:d};$jscomp.global=$jscomp.getGlobal(this);$jscomp.SYMBOL_PREFIX="jscomp_symbol_";
    22$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.Symbol=function(){var d=0;return function(h){return $jscomp.SYMBOL_PREFIX+(h||"")+d++}}();
    33$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var d=$jscomp.global.Symbol.iterator;d||(d=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[d]&&$jscomp.defineProperty(Array.prototype,d,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(d){var h=0;return $jscomp.iteratorPrototype(function(){return h<d.length?{done:!1,value:d[h++]}:{done:!0}})};
    44$jscomp.iteratorPrototype=function(d){$jscomp.initSymbolIterator();d={next:d};d[$jscomp.global.Symbol.iterator]=function(){return this};return d};$jscomp.makeIterator=function(d){$jscomp.initSymbolIterator();var h=d[Symbol.iterator];return h?h.call(d):$jscomp.arrayIterator(d)};$jscomp.owns=function(d,h){return Object.prototype.hasOwnProperty.call(d,h)};
    5 $jscomp.assign="function"==typeof Object.assign?Object.assign:function(d,h){for(var g=1;g<arguments.length;g++){var e=arguments[g];if(e)for(var b in e)$jscomp.owns(e,b)&&(d[b]=e[b])}return d};$jscomp.polyfill=function(d,h,g,e){if(h){g=$jscomp.global;d=d.split(".");for(e=0;e<d.length-1;e++){var b=d[e];b in g||(g[b]={});g=g[b]}d=d[d.length-1];e=g[d];h=h(e);h!=e&&null!=h&&$jscomp.defineProperty(g,d,{configurable:!0,writable:!0,value:h})}};
    6 $jscomp.polyfill("Object.assign",function(d){return d||$jscomp.assign},"es6","es3");$jscomp.iteratorFromArray=function(d,h){$jscomp.initSymbolIterator();d instanceof String&&(d+="");var g=0,e={next:function(){if(g<d.length){var b=g++;return{value:h(b,d[b]),done:!1}}e.next=function(){return{done:!0,value:void 0}};return e.next()}};e[Symbol.iterator]=function(){return e};return e};
    7 $jscomp.polyfill("Array.prototype.keys",function(d){return d?d:function(){return $jscomp.iteratorFromArray(this,function(d){return d})}},"es6","es3");$jscomp.checkStringArgs=function(d,h,g){if(null==d)throw new TypeError("The 'this' value for String.prototype."+g+" must not be null or undefined");if(h instanceof RegExp)throw new TypeError("First argument to String.prototype."+g+" must not be a regular expression");return d+""};
    8 $jscomp.polyfill("String.prototype.endsWith",function(d){return d?d:function(d,g){var e=$jscomp.checkStringArgs(this,d,"endsWith");d+="";void 0===g&&(g=e.length);g=Math.max(0,Math.min(g|0,e.length));for(var b=d.length;0<b&&0<g;)if(e[--g]!=d[--b])return!1;return 0>=b}},"es6","es3");$jscomp.polyfill("Object.is",function(d){return d?d:function(d,g){return d===g?0!==d||1/d===1/g:d!==d&&g!==g}},"es6","es3");
    9 $jscomp.polyfill("Array.prototype.includes",function(d){return d?d:function(d,g){var e=this;e instanceof String&&(e=String(e));var b=e.length;g=g||0;for(0>g&&(g=Math.max(g+b,0));g<b;g++){var a=e[g];if(a===d||Object.is(a,d))return!0}return!1}},"es7","es3");$jscomp.polyfill("String.prototype.includes",function(d){return d?d:function(d,g){return-1!==$jscomp.checkStringArgs(this,d,"includes").indexOf(d,g||0)}},"es6","es3");
     5$jscomp.assign="function"==typeof Object.assign?Object.assign:function(d,h){for(var f=1;f<arguments.length;f++){var e=arguments[f];if(e)for(var b in e)$jscomp.owns(e,b)&&(d[b]=e[b])}return d};$jscomp.polyfill=function(d,h,f,e){if(h){f=$jscomp.global;d=d.split(".");for(e=0;e<d.length-1;e++){var b=d[e];b in f||(f[b]={});f=f[b]}d=d[d.length-1];e=f[d];h=h(e);h!=e&&null!=h&&$jscomp.defineProperty(f,d,{configurable:!0,writable:!0,value:h})}};
     6$jscomp.polyfill("Object.assign",function(d){return d||$jscomp.assign},"es6","es3");$jscomp.iteratorFromArray=function(d,h){$jscomp.initSymbolIterator();d instanceof String&&(d+="");var f=0,e={next:function(){if(f<d.length){var b=f++;return{value:h(b,d[b]),done:!1}}e.next=function(){return{done:!0,value:void 0}};return e.next()}};e[Symbol.iterator]=function(){return e};return e};
     7$jscomp.polyfill("Array.prototype.keys",function(d){return d?d:function(){return $jscomp.iteratorFromArray(this,function(d){return d})}},"es6","es3");$jscomp.checkStringArgs=function(d,h,f){if(null==d)throw new TypeError("The 'this' value for String.prototype."+f+" must not be null or undefined");if(h instanceof RegExp)throw new TypeError("First argument to String.prototype."+f+" must not be a regular expression");return d+""};
     8$jscomp.polyfill("String.prototype.endsWith",function(d){return d?d:function(d,f){var e=$jscomp.checkStringArgs(this,d,"endsWith");d+="";void 0===f&&(f=e.length);f=Math.max(0,Math.min(f|0,e.length));for(var b=d.length;0<b&&0<f;)if(e[--f]!=d[--b])return!1;return 0>=b}},"es6","es3");$jscomp.polyfill("Object.is",function(d){return d?d:function(d,f){return d===f?0!==d||1/d===1/f:d!==d&&f!==f}},"es6","es3");
     9$jscomp.polyfill("Array.prototype.includes",function(d){return d?d:function(d,f){var e=this;e instanceof String&&(e=String(e));var b=e.length;f=f||0;for(0>f&&(f=Math.max(f+b,0));f<b;f++){var a=e[f];if(a===d||Object.is(a,d))return!0}return!1}},"es7","es3");$jscomp.polyfill("String.prototype.includes",function(d){return d?d:function(d,f){return-1!==$jscomp.checkStringArgs(this,d,"includes").indexOf(d,f||0)}},"es6","es3");
    1010$jscomp.polyfill("Math.trunc",function(d){return d?d:function(d){d=Number(d);if(isNaN(d)||Infinity===d||-Infinity===d||0===d)return d;var h=Math.floor(Math.abs(d));return 0>d?-h:h}},"es6","es3");$jscomp.polyfill("Number.isFinite",function(d){return d?d:function(d){return"number"!==typeof d?!1:!isNaN(d)&&Infinity!==d&&-Infinity!==d}},"es6","es3");$jscomp.polyfill("Number.isInteger",function(d){return d?d:function(d){return Number.isFinite(d)?d===Math.floor(d):!1}},"es6","es3");
    11 $jscomp.polyfill("String.prototype.repeat",function(d){return d?d:function(d){var g=$jscomp.checkStringArgs(this,null,"repeat");if(0>d||1342177279<d)throw new RangeError("Invalid count value");d|=0;for(var e="";d;)if(d&1&&(e+=g),d>>>=1)g+=g;return e}},"es6","es3");$jscomp.stringPadding=function(d,h){d=void 0!==d?String(d):" ";return 0<h&&d?d.repeat(Math.ceil(h/d.length)).substring(0,h):""};
    12 $jscomp.polyfill("String.prototype.padStart",function(d){return d?d:function(d,g){var e=$jscomp.checkStringArgs(this,null,"padStart");return $jscomp.stringPadding(g,d-e.length)+e}},"es8","es3");
    13 (function(){function d(h,g,e){function b(c,p){if(!g[c]){if(!h[c]){var l="function"==typeof require&&require;if(!p&&l)return l(c,!0);if(a)return a(c,!0);p=Error("Cannot find module '"+c+"'");throw p.code="MODULE_NOT_FOUND",p;}p=g[c]={exports:{}};h[c][0].call(p.exports,function(a){return b(h[c][1][a]||a)},p,p.exports,d,h,g,e)}return g[c].exports}for(var a="function"==typeof require&&require,c=0;c<e.length;c++)b(e[c]);return b}return d})()({1:[function(d,h,g){var e=d("./easycoder/Main");e.timestamp=
    14 Date.now();console.log("EasyCoder loaded; waiting for page");window.onload=function(){console.log(Date.now()-e.timestamp+" ms: Page loaded; reset timer & start EasyCoder");e.timestamp=Date.now();e.scripts={};window.EasyCoder=e;var b=document.getElementById("easycoder-script");if(b){b.style.display="none";try{e.start(b.innerText)}catch(a){e.reportError(a)}}}},{"./easycoder/Main":6}],2:[function(d,h,g){h.exports=function(d,b,a){b=d.value.evaluate(d,b);d=d.value.evaluate(d,a);a=b.content;var c=d.content;
    15 b.numeric&&!d.numeric&&(a=a.toString());!b.numeric&&d.numeric&&(c=c.toString());return a>c?1:a<c?-1:0}},{}],3:[function(d,h,g){var e=Object.assign||function(a){for(var c=1;c<arguments.length;c++){var b=arguments[c],d;for(d in b)Object.prototype.hasOwnProperty.call(b,d)&&(a[d]=b[d])}return a},b=this,a={getTokens:function(){return b.tokens},addWarning:function(a){b.warnings.push(a)},warning:function(c){a.addWarning(c)},unrecognisedSymbol:function(c){a.addWarning("Unrecognised symbol '"+c+"'")},getWarnings:function(){return b.warnings},
    16 getIndex:function(){return b.index},next:function(a){b.index+=void 0===a?1:a},peek:function(){return b.tokens[b.index+1].token},getToken:function(){return b.index>=b.tokens.length?null:b.tokens[b.index]?b.tokens[b.index].token:null},nextToken:function(){a.next();return a.getToken()},tokenIs:function(a){return b.index>=b.tokens.length?!1:a===b.tokens[b.index].token},nextTokenIs:function(c){a.next();return a.tokenIs(c)},skip:function(c){if(b.index>=b.tokens.length)return null;a.next();a.tokenIs(c)&&
    17 a.next()},prev:function(){b.index--},getLino:function(){return b.index>=b.tokens.length?0:b.tokens[b.index].lino},getTarget:function(a){a=void 0===a?b.index:a;return b.tokens[a].token},getTargetPc:function(c){c=void 0===c?b.index:c;return b.symbols[a.getTarget(c)].pc},getCommandAt:function(a){return b.program[a]},isSymbol:function(c){c=void 0===c?!1:c;if(a.getTarget()in b.symbols)return!0;if(c)throw Error("Unknown symbol: '"+a.getTarget()+"'");return!1},nextIsSymbol:function(c){c=void 0===c?!1:c;
    18 a.next();return a.isSymbol(c)},getSymbol:function(){return b.symbols[a.getToken()]},getSymbolPc:function(){return a.getSymbol().pc},getSymbolRecord:function(){return b.program[a.getSymbolPc()]},getSymbols:function(){return b.symbols},getProgram:function(){return b.program},getPc:function(){return b.program.length},getValue:function(){return a.value.compile(a)},getNextValue:function(){a.next();return a.getValue()},getCondition:function(){return a.condition.compile(a)},constant:function(c,b){return a.value.constant(c,
    19 void 0===b?!1:b)},addCommand:function(a){b.program.push(e({pc:b.program.length},a))},addSymbol:function(a,l){b.symbols[a]={pc:l}},mark:function(){b.savedMark=b.index},rewind:function(){b.index=b.savedMark},completeHandler:function(){var c=a.getLino(),b=a.getPc();a.addCommand({domain:"core",keyword:"goto",lino:c,goto:0});a.compileOne();a.addCommand({domain:"core",keyword:"stop",lino:c,next:0});a.getCommandAt(b).goto=a.getPc();return!0},compileVariable:function(c,l,d,f){d=void 0===d?!1:d;f=void 0===
    20 f?null:f;a.next();var p=a.getLino(),m=a.getTokens()[a.getIndex()];if(b.symbols[m.token])throw Error("Duplicate variable name '"+m.token+"'");var n=a.getPc();a.next();a.addSymbol(m.token,n);c={domain:c,keyword:l,lino:p,isSymbol:!0,isValueHolder:d,name:m.token,elements:1,index:0,value:[{}],element:[],extra:f};"dom"===f&&(c.element=[]);a.addCommand(c);return c},compileToken:function(){var c=a.getToken();if(c){a.mark();for(var b=$jscomp.makeIterator(Object.keys(a.domain)),d=b.next();!d.done;d=b.next()){if((d=
    21 a.domain[d.value])&&(d=d.getHandler(c))&&d.compile(a))return;a.rewind()}console.log("No handler found");throw Error("I don't understand '"+c+"...'");}},compileOne:function(){var c=a.getToken();if(c){b.warnings=[];var l=b.program.length;if(c.endsWith(":")){c=c.substring(0,c.length-1);if(b.symbols[c])throw Error("Duplicate symbol: '"+c+"'");b.symbols[c]={pc:l};b.index++}else a.compileToken()}},compileFromHere:function(c){for(;b.index<b.tokens.length;){var l=b.tokens[b.index].token;if("else"===l)return b.program;
    22 a.compileOne();if(-1<c.indexOf(l))break}},compile:function(c){b.tokens=c;b.index=0;b.program=[];b.symbols={};b.warnings=[];a.compileFromHere([]);a.addCommand({domain:"core",keyword:"stop",lino:a.getLino(),next:0});return b.program}};h.exports=a},{}],4:[function(d,h,g){var e=Object.assign||function(b){for(var a=1;a<arguments.length;a++){var c=arguments[a],l;for(l in c)Object.prototype.hasOwnProperty.call(c,l)&&(b[l]=c[l])}return b};h.exports={compile:function(b){b.mark();for(var a=$jscomp.makeIterator(Object.keys(b.domain)),
    23 c=a.next();!c.done;c=a.next()){if(c=b.domain[c.value].condition.compile(b))return e({domain:name},c);b.rewind()}},test:function(b,a){return b.domain[a.domain].condition.test(b,a)}}},{}],5:[function(d,h,g){var e=this,b={Add:{compile:function(a){var c=a.getLino();a.next();var b=a.getValue();if(a.tokenIs("to"))if(a.next(),a.isSymbol()){var d=a.getSymbol();if(a.getCommandAt(d.pc).isValueHolder){if("giving"===a.peek()){d=a.getValue();a.next();var f=a.getToken();a.next();a.addCommand({domain:"core",keyword:"add",
    24 lino:c,value1:b,value2:d,target:f})}else d=a.getToken(),a.next(),a.addCommand({domain:"core",keyword:"add",lino:c,value1:b,target:d});return!0}a.warning("core "+e.name+": Expected value holder")}else{d=a.getValue();if(a.tokenIs("giving"))return a.next(),f=a.getToken(),a.next(),a.addCommand({domain:"core",keyword:"add",lino:c,value1:b,value2:d,target:f}),!0;a.warning("core "+e.name+': Expected "giving"')}return!1},run:function(a){var c=a[a.pc],b=c.value1,d=c.value2,f=a.getSymbolRecord(c.target);if(f.isValueHolder){var e=
    25 f.value[f.index];d?(a=a.getValue(d)+a.getValue(b),f.value[f.index]={type:"constant",numeric:!0,content:a}):(e.numeric||a.nonNumericValueError(c.lino),a=e.content+a.getValue(b),f.value[f.index]={type:"constant",numeric:!0,content:a})}else a.VariableDoesNotHoldAValueError(c.lino,f.name);return c.pc+1}},Alias:{compile:function(a){var c=a.getLino();a.next();if(a.isSymbol()){var b=a.getToken();a.next();if(a.tokenIs("to")&&(a.next(),a.isSymbol())){var d=a.getToken();a.next();a.addCommand({domain:"core",
    26 keyword:"alias",lino:c,alias:b,symbol:d});return!0}}return!1},run:function(a){var c=a[a.pc],b=a.symbols[c.alias].pc,d=a[b],f=a.getSymbolRecord(c.symbol);a[b]={pc:d.pc,domain:f.domain,keyword:f.keyword,lino:d.lino,name:d.name,alias:c.symbol};return c.pc+1}},Begin:{compile:function(a){a.next();a.compileFromHere(["end"]);return!0},run:function(a){return a[a.pc].pc+1}},Clear:{compile:function(a){var c=a.getLino();a.next();if(a.isSymbol()){var b=a.getToken();a.next();a.addCommand({domain:"core",keyword:"clear",
    27 lino:c,symbol:b});return!0}return!1},run:function(a){var c=a[a.pc],b=a.getSymbolRecord(c.symbol);b.isValueHolder?(a.domain[b.domain].value.put(b,{type:"boolean",content:!1}),c.numeric=!1):a.VariableDoesNotHoldAValueError(c.lino,b.name);return c.pc+1}},Close:{compile:function(a){var c=a.getLino();if(a.nextIsSymbol()){var b=a.getSymbolRecord();if("module"===b.keyword)return a.next(),a.addCommand({domain:"core",keyword:"close",lino:c,module:b.name}),!0}return!1},run:function(a){var c=a[a.pc];a=a.getSymbolRecord(c.module).program;
    28 a.run(a.onClose);return c.pc+1}},Debug:{compile:function(a){var c=a.getLino();if(a.nextTokenIs("program")){a.next();if(["item","pc"].includes(a.getToken())){var b=a.getNextValue();a.addCommand({domain:"core",keyword:"debug",lino:c,item:b});return!0}a.addCommand({domain:"core",keyword:"debug",lino:c,item:-1});return!0}return a.tokenIs("symbols")?(a.next(),a.addCommand({domain:"core",keyword:"debug",lino:c,item:"symbols"}),!0):a.tokenIs("symbol")?(b=a.nextToken(),a.next(),a.addCommand({domain:"core",
    29 keyword:"debug",lino:c,item:"symbol",name:b}),!0):a.tokenIs("step")?(a.next(),a.addCommand({domain:"core",keyword:"debug",lino:c,item:"step"}),!0):!1},run:function(a){var c=a[a.pc],b=c.item;switch(b){case "symbols":console.log("Symbols: "+JSON.stringify(a.symbols,null,2));break;case "symbol":a=a.getSymbolRecord(c.name);b=a.exporter;delete a.exporter;console.log("Symbol: "+JSON.stringify(a,null,2));a.exporter=b;break;case "step":a.debugStep=!0;break;default:0<=b.content?console.log("Debug item "+b.content+
    30 ": "+JSON.stringify(a[b.content],null,2)):console.log("Debug program: "+JSON.stringify(a,null,2))}return c.pc+1}},Decode:{compile:function(a){var c=a.getLino();if(a.nextIsSymbol()){var b=a.getToken();a.next();a.addCommand({domain:"core",keyword:"decode",lino:c,symbol:b});return!0}return!1},run:function(a){var c=a[a.pc],b=a.getSymbolRecord(c.symbol);if(b.isValueHolder){var d=a.getValue(b.value[b.index]);b.value[b.index]={type:"constant",numeric:!1,content:a.decode(d)};c.numeric=!1}else a.VariableDoesNotHoldAValueError(c.lino,
    31 b.name);return c.pc+1}},Divide:{compile:function(a){var c=a.getLino();if(a.nextIsSymbol()){var b=a.getSymbol();var d=a.getCommandAt(b.pc).name}b=a.getValue();a.tokenIs("by")&&a.next();var f=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:c,value1:b,value2:f,target:d}),!0;a.warning("core "+e.name+": Expected value holder")}else return void 0===d&&a.warning("core "+e.name+": No target variable given"),
    32 a.addCommand({domain:"core",keyword:"divide",lino:c,value2:f,target:d}),!0;return!1},run:function(a){var c=a[a.pc],b=c.value1,d=c.value2,f=a.getSymbolRecord(c.target);if(f.isValueHolder){var e=f.value[f.index];b?(a=a.getValue(b)/a.getValue(d),f.value[f.index]={type:"constant",numeric:!0,content:Math.trunc(a)}):(e.numeric||a.nonNumericValueError(c,lino),a=e.content/a.getValue(d),f.value[f.index]={type:"constant",numeric:!0,content:Math.trunc(a)})}else a.VariableDoesNotHoldAValueError(c.lino,f.name);
    33 return c.pc+1}},Encode:{compile:function(a){var c=a.getLino();a.next();if(a.isSymbol()){var b=a.getToken();a.next();a.addCommand({domain:"core",keyword:"encode",lino:c,symbol:b});return!0}return!1},run:function(a){var c=a[a.pc],b=a.getSymbolRecord(c.symbol);if(b.isValueHolder){var d=a.getValue(b.value[b.index]);b.value[b.index]={type:"constant",numeric:!1,content:a.encode(d)};c.numeric=!1}else a.VariableDoesNotHoldAValueError(c.lino,b.name);return c.pc+1}},End:{compile:function(a){a.next();return!0},
    34 run:function(){return 0}},Fork:{compile:function(a){var c=a.getLino();a.next();a.tokenIs("to")&&a.next();var b=a.getToken();a.next();a.addCommand({domain:"core",keyword:"fork",lino:c,label:b});return!0},run:function(a){var c=a[a.pc];try{a.run(a.symbols[c.label].pc)}catch(l){console.log(l.message),alert(l.message)}return c.pc+1}},Go:{compile:function(a){var c=a.getLino();a.next();a.tokenIs("to")&&a.next();var b=a.getToken();a.next();a.addCommand({domain:"core",keyword:"go",lino:c,label:b});return!0},
    35 run:function(a){var c=a[a.pc];if(c.label&&a.verifySymbol(c.label)){var b=a.symbols[c.label];if(b)return b.pc;a.runtimeError(c.lino,"Unknown symbol '"+c.label+"'");return 0}return c.goto}},Gosub:{compile:function(a){var c=a.getLino();a.next();a.tokenIs("to")&&a.next();var b=a.getToken();a.next();a.addCommand({domain:"core",keyword:"gosub",lino:c,label:b});return!0},run:function(a){var c=a[a.pc];if(a.verifySymbol(c.label))return a.stack.push(a.pc+1),a.symbols[c.label].pc;a.runtimeError(c.lino,"Unknown symbol '"+
    36 c.label+"'");return 0}},If:{compile:function(a){var c=a.getLino();a.next();var b=a.condition.compile(a),d=a.getPc();a.addCommand({domain:"core",keyword:"if",lino:c,condition:b});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",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 c=a[a.pc];
    37 return a.condition.test(a,c.condition)?c.pc+1:c.else}},Import:{compile:function(a){var c=a.imports,b=a.getProgram();c=$jscomp.makeIterator(c);for(var d=c.next();!d.done;d=c.next()){d=d.value;var f=a.nextToken(),e=d.keyword;if(f===e){if(f=a.compileVariable(d.domain,e,!0),f=b[a.getSymbols()[f.name].pc],f.element=d.element,f.exporter=d.exporter,f.exportedName=d.name,f.extra=d.extra,f.imported=!0,!a.tokenIs("and"))break}else throw Error("Mismatched import variable type for '"+d.name+"'");}return!0},run:function(a){return a[a.pc].pc+
    38 1}},Index:{compile:function(a){var c=a.getLino();if(a.nextIsSymbol(!0)){var b=a.getToken();if(a.nextTokenIs("to")){var d=a.getNextValue();a.addCommand({domain:"core",keyword:"index",lino:c,symbol:b,value:d});return!0}}return!1},run:function(a){var c=a[a.pc],b=a.getSymbolRecord(c.symbol),d=a.getValue(c.value);d>=b.elements&&a.runtimeError(c.lino,"Array index "+d+" is out of range for '"+b.name+"'");b.index=d;b.imported&&(b.exporter.getSymbolRecord(b.exportedName).index=d);return c.pc+1}},Load:{compile:function(a){var c=
    39 a.getLino();a.next();if(a.tokenIs("plugin")){a.next();var b=a.getValue();a.addCommand({domain:"core",keyword:"load",lino:c,name:b});return!0}},run:function(a){var c=a[a.pc],b=a.getValue(c.name);if(a.checkPlugin(b))return c.pc+1;EasyCoder_Plugins.getLocalPlugin(a.getPluginsPath,b,a.getPlugin,a.addLocalPlugin,function(){a.run(c.pc+1)});return 0}},Module:{compile:function(a){a.compileVariable("core","module");return!0},run:function(a){a=a[a.pc];a.program=null;return a.pc+1}},Multiply:{compile:function(a){var c=
    40 a.getLino();a.next();if(a.isSymbol()){var b=a.getSymbol();var d=a.getCommandAt(b.pc).name}b=a.getValue();a.tokenIs("by")&&a.next();var f=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:c,value1:b,value2:f,target:d}),!0;a.warning("core multiply: Expected value holder")}else return void 0===d&&a.warning("core multiply: No target variable given"),a.addCommand({domain:"core",keyword:"multiply",
    41 lino:c,value2:f,target:d}),!0;return!1},run:function(a){var c=a[a.pc],b=c.value1,d=c.value2,f=a.getSymbolRecord(c.target);if(f.isValueHolder){var e=f.value[f.index];b?(a=a.getValue(b)*a.getValue(d),f.value[f.index]={type:"constant",numeric:!0,content:a}):(e.numeric||a.nonNumericValueError(c,lino),a=e.content*a.getValue(d),f.value[f.index]={type:"constant",numeric:!0,content:a})}else a.VariableDoesNotHoldAValueError(c.lino,f.name);return c.pc+1}},Negate:{compile:function(a){var c=a.getLino();a.next();
    42 if(a.isSymbol()){var b=a.getToken();a.next();a.addCommand({domain:"core",keyword:"negate",lino:c,symbol:b});return!0}return!1},run:function(a){var c=a[a.pc],b=a.getSymbolRecord(c.symbol);b.isValueHolder?b.value[b.index]={type:"constant",numeric:!0,content:-b.value[b.index].content}:a.VariableDoesNotHoldAValueError(c.lino,b.name);return c.pc+1}},On:{compile:function(a){var c=a.getLino(),b=a.nextToken();a.next();switch(b){case "trigger":case "close":case "restore":return a.addCommand({domain:"core",
    43 keyword:"on",lino:c,action:b}),a.completeHandler()}return!1},run:function(a){var c=a[a.pc];switch(c.action){case "trigger":a.onTrigger=c.pc+2;break;case "close":a.onClose=c.pc+2;break;case "restore":a.onRestore=c.pc+2;break;default:return a.runtimeError(c.lino,"Unknown action '"+c.action+"'"),0}return c.pc+1}},Print:{compile:function(a){var c=a.getLino();a.next();var b=a.getValue();a.addCommand({domain:"core",keyword:"print",lino:c,value:b});return!0},run:function(a){var c=a[a.pc];a=a.getFormattedValue(c.value);
    44 console.log("-> "+a);return c.pc+1}},Put:{compile:function(a){var c=a.getLino();a.next();var b=a.getValue();if(a.tokenIs("into")){a.next();if(a.isSymbol()){var d=a.getToken();a.next();a.addCommand({domain:"core",keyword:"put",lino:c,value:b,target:d});return!0}a.warning("core:put: No such variable: '"+a.getToken()+"'")}return!1},run:function(a){var c=a[a.pc],b=a.getSymbolRecord(c.target);b.isValueHolder||a.variableDoesNotHoldAValueError(c.lino,b.name);a=a.evaluate(c.value);b.value[b.index]=a;b.imported&&
    45 (b=b.exporter.getSymbolRecord(b.exportedName),b.value[b.index]=a);return c.pc+1}},Replace:{compile:function(a){var c=a.getLino();a.next();var b=a.getValue();if(a.tokenIs("with")){a.next();var d=a.getValue();if(a.tokenIs("in")&&(a.next(),a.isSymbol())){var f=a.getSymbolRecord();a.next();if(f.isValueHolder)return a.addCommand({domain:"core",keyword:"replace",lino:c,original:b,replacement:d,target:f.name}),!0}}return!1},run:function(a){var c=a[a.pc],b=a.getValue(c.original).split("\\r").join("\r").split("\\n").join("\n"),
    46 d=a.getValue(c.replacement),f=a.getSymbolRecord(c.target);a=a.getValue(f.value[f.index]).split(b).join(d);f.value[f.index]={type:"constant",numeric:!1,content:a};return c.pc+1}},Return:{compile:function(a){var c=a.getLino();a.next();a.addCommand({domain:"core",keyword:"return",lino:c});return!0},run:function(a){return a.stack.pop()}},Run:{compile:function(a){var c=a.getLino(),b=a.getNextValue(),d=[];if(a.tokenIs("with"))for(;;)if(a.nextIsSymbol(!0)){var f=a.getSymbolRecord();f.exporter=a.getProgram();
    47 d.push(f);a.next();if(!a.tokenIs("and"))break}if(a.tokenIs("as")&&a.nextIsSymbol(!0)){f=a.getSymbolRecord();a.next();if("module"!==f.keyword)throw Error("'"+f.name+"' is not a module");var e=f.name}a.addCommand({domain:"core",keyword:"run",lino:c,script:b,imports:d,module:e});return!0},run:function(a){a.nextPc=a.pc+1;a.runScript(a);return 0}},Script:{compile:function(a){var c=a.getLino(),b=a.nextToken();a.next();a.addCommand({domain:"core",keyword:"script",lino:c,name:b});return!0},run:function(a){var c=
    48 a[a.pc];a.script=c.name;EasyCoder.scripts[c.name]=a;console.log(Date.now()-EasyCoder.timestamp+" ms: Script: "+c.name);return c.pc+1}},Set:{compile:function(a){var c=a.getLino();a.next();if(a.isSymbol()){var b=a.getSymbolRecord();if(!b.isValueHolder)return!1;a.next();if(a.tokenIs("to")){a.next();for(var d=[];;){a.mark();try{d.push(a.getValue())}catch(f){a.rewind();break}}a.addCommand({domain:"core",keyword:"set",lino:c,type:"setArray",target:b.name,value:d});return!0}a.addCommand({domain:"core",keyword:"set",
    49 lino:c,type:"setBoolean",target:b.name});return!0}if(a.tokenIs("ready"))return a.next(),a.addCommand({domain:"core",keyword:"set",lino:c,type:"setReady"}),!0;a.tokenIs("the")&&a.next();if(a.tokenIs("elements")&&(a.next(),a.tokenIs("of"))){a.next();if(!a.isSymbol())throw Error("Unknown variable '"+a.getToken()+"'");b=a.getToken();a.next();if(a.tokenIs("to"))return a.next(),d=a.getValue(),a.addCommand({domain:"core",keyword:"set",lino:c,type:"setElements",symbol:b,value:d}),!0}if(a.tokenIs("encoding")){a.next();
    50 if(a.tokenIs("to"))return a.next(),b=a.getValue(),a.addCommand({domain:"core",keyword:"set",type:"encoding",lino:c,encoding:b}),!0;a.addWarning("Unknown encoding option")}return!1},run:function(a){var c=a[a.pc];switch(c.type){case "setBoolean":var b=a.getSymbolRecord(c.target);b.isValueHolder?(b.value[b.index]={type:"boolean",content:!0},c.numeric=!1):a.VariableDoesNotHoldAValueError(c.lino,b.name);break;case "setReady":a.parent.run(a.parent.nextPc);break;case "setElements":b=a.getSymbolRecord(c.symbol);
    51 b.elements=a.getValue(c.value);b.index=0;b.value=[];for(a=0;a<b.elements;a++)b.value.push({}),b.element.push("");break;case "setArray":a=a.getSymbolRecord(c.target);a.elements=c.value.length;a.value=c.value;break;case "encoding":a.encoding=a.getValue(c.encoding)}return c.pc+1}},Stop:{compile:function(a){var c=a.getLino();a.next();a.addCommand({domain:"core",keyword:"stop",lino:c,next:0});return!0},run:function(){return 0}},Take:{compile:function(a){var c=a.getLino();a.next();var b=a.getValue();if(a.tokenIs("from"))if(a.next(),
    52 a.isSymbol()){var d=a.getSymbol();if(a.getCommandAt(d.pc).isValueHolder){if("giving"===a.peek()){d=a.getValue();a.next();var f=a.getToken();a.next();a.addCommand({domain:"core",keyword:"take",lino:c,value1:b,value2:d,target:f})}else d=a.getToken(),a.next(),a.addCommand({domain:"core",keyword:"take",lino:c,value1:b,target:d});return!0}a.warning("core "+e.name+": Expected value holder")}else{d=a.getValue();if(a.tokenIs("giving"))return a.next(),f=a.getToken(),a.next(),a.addCommand({domain:"core",keyword:"take",
    53 lino:c,value1:b,value2:d,target:f}),!0;a.warning("core "+e.name+': Expected "giving"')}return!1},run:function(a){var c=a[a.pc],b=c.value1,d=c.value2,f=a.getSymbolRecord(c.target);if(f.isValueHolder){var e=f.value[f.index];d?(a=a.getValue(d)-a.getValue(b),f.value[f.index]={type:"constant",numeric:!0,content:a}):(e.numeric||a.nonNumericValueError(c,lino),a=a.getValue(e)-a.getValue(b),f.value[f.index]={type:"constant",numeric:!0,content:a})}else a.VariableDoesNotHoldAValueError(c.lino,f.name);return c.pc+
    54 1}},Toggle:{compile:function(a){var c=a.getLino();a.next();if(a.isSymbol()){var b=a.getSymbolPc();a.next();a.addCommand({domain:"core",keyword:"toggle",lino:c,symbol:b});return!0}return!1},run:function(a){var c=a[a.pc],b=a[c.symbol];if(b.isValueHolder){var d=a.domain[b.domain];a=d.value.get(a,b.value[b.index]).content;d.value.put(b,{type:"boolean",content:!a})}else a.VariableDoesNotHoldAValueError(c.lino,b.name);return c.pc+1}},Trigger:{compile:function(a){var c=a.getLino();a.next();return a.tokenIs("parent")?
    55 (a.next(),a.addCommand({domain:"core",keyword:"trigger",lino:c,module:"parent"}),!0):a.isSymbol()&&(moduleRecord=a.getSymbolRecord(),a.next(),"module"===moduleRecord.keyword)?(h=moduleRecord.name,a.addCommand({domain:"core",keyword:"trigger",lino:c,module:h}),!0):!1},run:function(a){var c=a[a.pc];if("parent"===c.module)a.parent&&a.parent.trigger(!0);else{var b=a.getSymbolRecord(c.module);if(b.exporter){exporterRecord=b.exporter.getSymbolRecord(c.module);var d=exporterRecord.program}else d=b.program;
    56 d?d.trigger():a.runtimeError(c.lino,b.name+" has not been initialized")}return c.pc+1}},Variable:{compile:function(a){a.compileVariable("core","variable",!0);return!0},run:function(a){return a[a.pc].pc+1}},Wait:{compile:function(a){var c=a.getLino();a.next();var b=a.getValue(a),d=1E3;switch(a.getToken()){case "milli":case "millis":a.next();d=1;break;case "tick":case "ticks":a.next();d=10;break;case "second":case "seconds":a.next();d=1E3;break;case "minute":case "minutes":a.next(),d=6E4}a.addCommand({domain:"core",
    57 keyword:"wait",lino:c,value:b,multiplier:d});return!0},run:function(a){var c=a[a.pc],b=a.getValue(c.value);setTimeout(function(){a.run(c.pc+1)},b*c.multiplier);return 0}},While:{compile:function(a){var c=a.getLino();a.next();var b=a.getCondition(),d=a.getPc();a.addCommand({domain:"core",keyword:"while",lino:c,condition:b});c=a.getPc();a.addCommand({domain:"core",keyword:"goto",goto:0});a.compileOne();a.addCommand({domain:"core",keyword:"goto",goto:d});a.getCommandAt(c).goto=a.getPc();return!0},run:function(a){return a.condition.test(a,
    58 a[a.pc].condition)?a.pc+2:a.pc+1}},getHandler:function(a){switch(a){case "add":return b.Add;case "alias":return b.Alias;case "begin":return b.Begin;case "clear":return b.Clear;case "close":return b.Close;case "debug":return b.Debug;case "decode":return b.Decode;case "divide":return b.Divide;case "encode":return b.Encode;case "end":return b.End;case "fork":return b.Fork;case "go":case "goto":return b.Go;case "gosub":return b.Gosub;case "if":return b.If;case "import":return b.Import;case "index":return b.Index;
    59 case "load":return b.Load;case "module":return b.Module;case "multiply":return b.Multiply;case "negate":return b.Negate;case "on":return b.On;case "print":return b.Print;case "put":return b.Put;case "replace":return b.Replace;case "return":return b.Return;case "run":return b.Run;case "script":return b.Script;case "set":return b.Set;case "stop":return b.Stop;case "take":return b.Take;case "toggle":return b.Toggle;case "trigger":return b.Trigger;case "variable":return b.Variable;case "wait":return b.Wait;
    60 case "while":return b.While;default:return!1}},run:function(a){var c=a[a.pc],d=b.getHandler(c.keyword);d||a.runtimeError(c.lino,"Unknown keyword '"+c.keyword+"' in 'core' package");return d.run(a)},isNegate:function(a){return"not"===a.getToken()?(a.next(),!0):!1},value:{compile:function(a){if(a.isSymbol()){var c=a.getToken();switch(a.getSymbolRecord().keyword){case "module":return a.next(),{domain:"core",type:"module",name:c};case "variable":return a.nextTokenIs("modulo")?(a=a.getNextValue(),{domain:"core",
    61 type:"modulo",name:c,value:a}):{domain:"core",type:"symbol",name:c}}return null}c=a.getToken();if("true"===c)return a.next(),{domain:"core",type:"boolean",content:!0};if("false"===c)return a.next(),{domain:"core",type:"boolean",content:!1};if("random"===c)return a.next(),{domain:"core",type:"random",range:a.getValue()};if("cos"===c)return a.next(),c=a.getValue(),a.skip("radius"),a=a.getValue(),{domain:"core",type:"cos",angle_c:c,radius_c:a};if("sin"===c)return a.next(),c=a.getValue(),a.skip("radius"),
    62 a=a.getValue(),{domain:"core",type:"sin",angle_s:c,radius_s:a};if("tan"===c)return a.next(),c=a.getValue(),a.skip("radius"),a=a.getValue(),{domain:"core",type:"tan",angle_t:c,radius_t:a};if("empty"===c)return a.next(),{domain:"core",type:"empty"};if("now"===c)return a.next(),{domain:"core",type:"now"};if("newline"===c)return a.next(),{domain:"core",type:"newline"};if("encode"===c)return a.next(),{domain:"core",type:"encode",value:a.getValue()};if("decode"===c)return a.next(),{domain:"core",type:"decode",
    63 value:a.getValue()};if("lowercase"===c)return a.next(),{domain:"core",type:"lowercase",value:a.getValue()};if("element"===c){c=a.getNextValue();if(a.tokenIs("of")&&a.nextIsSymbol()){var b=a.getSymbolRecord();a.next();if("variable"===b.keyword)return{domain:"core",type:"element",element:c,symbol:b.name}}return null}if("property"===c)return c=a.getNextValue(),a.tokenIs("of")&&a.nextIsSymbol()&&(b=a.getSymbolRecord(),a.next(),"variable"===b.keyword)?{domain:"core",type:"property",property:c,symbol:b.name}:
    64 null;a.tokenIs("the")&&a.next();switch(a.getToken()){case "index":a.next();if(a.tokenIs("of")&&(a.next(),a.isSymbol()))return c=a.getToken(),a.next(),{domain:"core",type:"index",name:c};break;case "value":a.next();if(a.tokenIs("of"))return a.next(),{domain:"core",type:"valueOf",value:a.getValue()};break;case "length":a.next();if(a.tokenIs("of"))return a.next(),{domain:"core",type:"lengthOf",value:a.getValue()};break;case "left":a.next();c=a.getValue();if(a.tokenIs("of"))return a.next(),a=a.getValue(),
    65 {domain:"core",type:"left",count:c,value:a};break;case "right":a.next();c=a.getValue();if(a.tokenIs("of"))return a.next(),a=a.getValue(),{domain:"core",type:"right",count:c,value:a};break;case "position":if(a.next(),a.tokenIs("of")&&(a.next(),c=!1,a.tokenIs("the")&&(a.next(),a.tokenIs("last")&&(a.next(),c=!0)),b=a.getValue(),a.tokenIs("in")))return a.next(),a=a.getValue(),{domain:"core",type:"position",needle:b,haystack:a,last:c}}return null},get:function(a,c){switch(c.type){case "boolean":return{type:"boolean",
    66 numeric:!1,content:c.content};case "index":return{type:"constant",numeric:!0,content:a.getSymbolRecord(c.name).index};case "random":return a=a.evaluate(c.range),{type:"constant",numeric:!0,content:Math.floor(Math.random()*a.content)};case "cos":var b=a.getValue(c.angle_c);a=a.getValue(c.radius_c);return{type:"constant",numeric:!0,content:parseInt(Math.cos(.01745329*parseFloat(b))*a,10)};case "sin":return b=a.getValue(c.angle_s),a=a.getValue(c.radius_s),{type:"constant",numeric:!0,content:parseInt(Math.sin(.01745329*
    67 parseFloat(b))*a,10)};case "tan":return b=a.getValue(c.angle_t),a=a.getValue(c.radius_t),{type:"constant",numeric:!0,content:parseInt(Math.tan(.01745329*parseFloat(b))*a,10)};case "valueOf":return a=parseInt(a.getValue(c.value)),{type:"constant",numeric:!0,content:a?a:0};case "lengthOf":return{type:"constant",numeric:!0,content:a.getValue(c.value).length};case "left":return{type:"constant",numeric:!1,content:a.getValue(c.value).substr(0,a.getValue(c.count))};case "right":return b=a.getValue(c.value),
    68 {type:"constant",numeric:!1,content:b.substr(b.length-a.getValue(c.count))};case "position":return b=a.getValue(c.needle),a=a.getValue(c.haystack),{type:"constant",numeric:!0,content:c.last?a.lastIndexOf(b):a.indexOf(b)};case "modulo":return b=a.getSymbolRecord(c.name),a=a.evaluate(c.value),{type:"constant",numeric:!0,content:b.value[b.index].content%a.content};case "empty":return{type:"constant",numeric:!1,content:""};case "now":return{type:"constant",numeric:!0,content:Date.now()/1E3};case "newline":return{type:"constant",
    69 numeric:!1,content:"\n"};case "encode":return{type:"constant",numeric:!1,content:a.encode(a.getValue(c.value))};case "decode":return{type:"constant",numeric:!1,content:a.decode(a.getValue(c.value))};case "lowercase":return{type:"constant",numeric:!1,content:a.getValue(c.value).toLowerCase()};case "element":b=a.getValue(c.element);c=a.getSymbolRecord(c.symbol);var d="";try{d=JSON.parse(a.getValue(c.value[c.index]))[b]}catch(f){a.runtimeError(command.lino,"Can't parse JSON");break}return{type:"constant",
    70 numeric:!1,content:"object"===typeof d?JSON.stringify(d):d};case "property":b=a.getValue(c.property);c=a.getSymbolRecord(c.symbol);a=a.getValue(c.value[c.index]);c="";if("object"===typeof a)c=a[b];else try{c=JSON.parse(a)[b]}catch(f){console.log("Can't parse '"+a+"': "+f.message),c=""}return{type:"constant",numeric:Number.isInteger(c),content:c};case "module":return{type:"boolean",numeric:!1,content:a.getSymbolRecord(c.name).program}}return null},put:function(a,c){a.value[a.index]=c}},condition:{compile:function(a){if(a.tokenIs("not"))return a.next(),
    71 {domain:"core",type:"not",value:a.getValue()};try{var c=a.getValue();if("is"===a.getToken()){a.next();var d=b.isNegate(a);switch(a.getToken()){case "numeric":return a.next(),{domain:"core",type:"numeric",value1:c};case "even":return a.next(),{domain:"core",type:"even",value1:c};case "odd":return a.next(),{domain:"core",type:"odd",value1:c};case "greater":a.next();if(a.tokenIs("than")){a.next();var e=a.getValue();return{domain:"core",type:"greater",value1:c,value2:e,negate:d}}break;case "less":a.next();
    72 if(a.tokenIs("than")){a.next();var f=a.getValue();return{domain:"core",type:"less",value1:c,value2:f,negate:d}}break;default:var g=a.getValue();return{domain:"core",type:"is",value1:c,value2:g,negate:d}}}else if(c)return{domain:"core",type:"boolean",value:c}}catch(m){return program.runtimeError(command.lino,m.message),0}return null},test:function(a,c){switch(c.type){case "boolean":return a.getValue(c.value);case "numeric":return Number.isInteger(a.getValue(c.value1));case "even":return 0===a.getValue(c.value1)%
    73 2;case "odd":return 1===a.getValue(c.value1)%2;case "is":return a=a.compare(a,c.value1,c.value2),c.negate?0!==a:0===a;case "greater":return a=a.compare(a,c.value1,c.value2),c.negate?0>=a:0<a;case "less":return a=a.compare(a,c.value1,c.value2),c.negate?0<=a:0>a;case "not":return!a.getValue(c.value)}return!1}}};h.exports=b},{}],6:[function(d,h,g){var e=this,b=d("./Tokenise"),a=d("./Compile"),c=d("./Run"),l=d("./Value"),p=d("./Condition"),f=d("./Compare"),k={domain:{core:d("./Core")},setupTracer:function(){var a=
    74 document.getElementById("easycoder-tracer");a&&(a.innerHTML='<div><input id="easycoder-run-button" type="button" value="Run" /><input id="easycoder-step-button" type="button" value="Step" /><div id="easycoder-tracer-content" style="border:1px solid black;padding:4px";width:100%></div>',a.style.display="none")},runtimeError:function(a,c){this.lino=a;this.reportError({message:"Line "+(0<=a?a:"")+": "+c},k.program)},nonNumericValueError:function(a){this.runtimeError(a,"Non-numeric value")},variableDoesNotHoldAValueError:function(a,
    75 c){this.runtimeError(a,"Variable '"+c+"' does not hold a value")},reportError:function(c,b,d){if(c.message)if(this.compiling||b){d=d?d:b.source;var e=d.tokens;d=d.scriptLines;e=this.compiling?e[a.getIndex()].lino:b[b.pc].lino;b=this.compiling?"Compile error":"Runtime error in '"+b.script+"'";b+=":\n";var f=e-5;for(f=0>f?0:f;f<e;f++){var m=(""+(f+1)).padStart(4," ");b+=m+" "+d[f].line.split("\\s").join(" ")+"\n"}b+=c.message+"\n";c=a.getWarnings();if(c.length)for(b+="Warnings:\n",c=$jscomp.makeIterator(c),
    76 d=c.next();!d.done;d=c.next())b+=d.value+"\n";console.log(b);alert(b);k.aborted=!0}else c="Error: "+c.message,alert(c),console.log(c);else console.log("An error occurred - origin was "+c.path[0])},getSymbolRecord:function(a){a=this[this.symbols[a].pc];return a.alias?this.getSymbolRecord(a.alias):a.exporter&&a.exporter!=this?a.exporter.getSymbolRecord(a.exportedName):a},verifySymbol:function(a){return this.symbols.hasOwnProperty(a)},encode:function(a){return l.encode(a,this.encoding)},decode:function(a){return l.decode(a,
    77 this.encoding)},evaluate:function(a){return l.evaluate(this,a)},getValue:function(a){return l.getValue(this,a)},getFormattedValue:function(a){a=l.getValue(this,a);return["[","{"].includes(a[0])?JSON.stringify(JSON.parse(a),null,2):a},getSimpleValue:function(a){return!0===a||!1===a?{type:"boolean",content:a}:{type:"constant",numeric:Number.isInteger(a),content:a}},run:function(a){k.program=this;c(this,a)},trigger:function(){this.onTrigger&&c(this,this.onTrigger)},addJS:function(a,c,b){var d=document.createElement("script");
    78 d.type="text/javascript";d.src=c+"/"+a+".js";d.onload=function(){b(a)};document.head.appendChild(d)},register:function(a){k.program=a},runScript:function(a){var c=a[a.pc],b=a.getValue(c.script),d=c.imports;a=c.module?a.getSymbolRecord(c.module):null;k.tokeniseScript(b.split("\n"),d,a,this)},close:function(){},compileScript:function(b,d,e,g){var h=b.tokens;this.compiling=!0;var m=Date.now();a.value=l;a.condition=p;a.domain=k.domain;a.imports=d;d=a.compile(h);var n=Date.now();console.log(n-k.timestamp+
    79 " ms: Compiled "+h.length+" tokens in "+(n-m+" ms"));this.compiling=!1;d.EasyCoder=k;d.value=l;d.condition=p;d.compare=f;d.source=b;d.run=this.run;d.runScript=this.runScript;d.evaluate=this.evaluate;d.getValue=this.getValue;d.getFormattedValue=this.getFormattedValue;d.getSimpleValue=this.getSimpleValue;d.encode=this.encode;d.decode=this.decode;d.domain=this.domain;d.trigger=this.trigger;d.addJS=this.addJS;d.checkPlugin=this.checkPlugin;d.getPlugin=this.getPlugin;d.addLocalPlugin=this.addLocalPlugin;
    80 d.getPluginsPath=this.getPluginsPath;d.getSymbolRecord=this.getSymbolRecord;d.verifySymbol=this.verifySymbol;d.runtimeError=this.runtimeError;d.nonNumericValueError=this.nonNumericValueError;d.variableDoesNotHoldAValueError=this.variableDoesNotHoldAValueError;d.reportError=this.reportError;d.register=this.register;d.symbols=a.getSymbols();d.encoding="ec";d.popups=[];d.stack=[];d.queue=[0];d.module=e;d.parent=g;e&&(e.program=d);this.setupTracer();c(d,0)},tokeniseScript:function(a,c,d,e){var f=Date.now();
    81 a=b.tokenise(a);var g=Date.now();console.log(g-k.timestamp+" ms: Tokenised "+a.scriptLines.length+" lines in "+(g-f+" ms"));try{k.compileScript(a,c,d,e)}catch(q){"stop"!==q.message&&this.reportError(q,k.program,a)}},tokenize:function(a){var c=a.split("\n");if(!e.tokenising){try{k.tokeniseScript(c)}catch(r){k.reportError(r,null,a)}e.tokenising=!0}},setPluginCount:function(a){e.plugins=[];e.pluginCount=a},checkPlugin:function(a){return k.domain[a]},getPlugin:function(a,c,b){k.domain[a]?b():(a=document.createElement("script"),
    82 a.type="text/javascript",a.src=c,a.onload=function(){console.log(Date.now()-k.timestamp+" ms: Plugin "+c+" loaded");b()},document.head.appendChild(a))},addGlobalPlugin:function(a,c){e.plugins.push({name:a,handler:c});e.plugins.length===e.pluginCount&&(e.plugins.forEach(function(a){k.domain[a.name]=a.handler}),k.tokenize(e.source))},addLocalPlugin:function(a,c,b){k.domain[a]=c;b()},getPluginsPath:function(){return k.pluginsPath},loadPluginJs:function(a){console.log(Date.now()-k.timestamp+" ms: Load plugins.js");
    83 var c=new XMLHttpRequest;c.open("GET",""+window.location.origin+a+"/easycoder/plugins.js",!0);c.onreadystatechange=function(){if(4===c.readyState)switch(c.status){case 200:var b=document.createElement("script");b.src=""+window.location.origin+a+"/easycoder/plugins.js";b.type="text/javascript";b.onload=function(){EasyCoder_Plugins.getGlobalPlugins(k.timestamp,a,k.setPluginCount,k.getPlugin,k.addGlobalPlugin)};document.head.appendChild(b);k.pluginsPath=a;break;case 404:a?k.loadPluginJs(a.slice(0,a.lastIndexOf("/"))):
    84 k.reportError({message:"Can't load plugins.js"},k.program,this.source)}};c.send()},start:function(a){e.source=a;a=window.location.pathname;a.endsWith("/")&&(a=a.slice(0,-1));k.loadPluginJs(a)}};h.exports=k},{"./Compare":2,"./Compile":3,"./Condition":4,"./Core":5,"./Run":7,"./Tokenise":8,"./Value":9}],7:[function(d,h,g){var e=function(b,a){var c=[],d=function(a){var c=9999;a.forEach(function(a){a=a.line;for(var b=0;b<a.length&&" "===a[b];)b++;0<b&&b<c&&(c=b)});return 0};if(c.length)c.push(a);else for(b.register(b),
    85 c.push(a);0<c.length&&!EasyCoder.aborted;)for(b.pc=c.shift(),b.watchdog=0,a={};;){if(1E6<b.watchdog){b.lino=b[b.pc].lino;b.reportError(Error("Program runaway intercepted.\nHave you forgotten to increment a loop counter?",b),b);break}b.watchdog++;var g=b[b.pc].domain;b.debugStep&&console.log(b.script+" "+b.pc+" "+g+":"+b[b.pc].keyword);var f=b.domain[g];if(!f){b.runtimeError(b[b.pc].lino,"Unknown domain '"+g+"'");break}b.pc=f.run(b);if(!b.pc)break;if(b.stop){b.tracing=!1;return}if(b.tracing){g=b[b.pc];
    86 f=b.source.scriptLines;var h=d(f),m=document.getElementById("easycoder-tracer");if(!m){b.runtimeError(g.lino,"Element 'easycoder-tracer' was not found");return}m.style.display="block";m.style.visibility="visible";var n="";if(b.tracer){if(m=document.getElementById("easycoder-tracer-content")){b.tracer.variables.forEach(function(a,c,d){var e=b.getSymbolRecord(a);if(1<e.elements)for(n+=a+": "+e.index+"/"+e.elements+": ",a=0;a<e.elements;a++){var f=e.value[a];n=f?n+(f.content+" "):n+"undefined "}else n=
    87 (e=e.value[e.index])?n+(a+": "+e.content):n+(a+": undefined");switch(b.tracer.alignment){case "horizontal":c<d.length-1&&(n+=", ");break;case "vertical":n+="<br>"}});n+="<hr>";for(var r="",t=5;0<t;t--)g.lino&&(r+='<input type="text" name="'+t+'"'+('value="'+f[g.lino-t].line.substr(h)+'"')+'style="width:100%;border:none;enabled:false">'),r+="<br>";m.innerHTML=n+" "+r;m.style.display="block";a.run=document.getElementById("easycoder-run-button");a.step=document.getElementById("easycoder-step-button");
    88 a.run.onclick=function(a){return function(){a.run.blur();b.tracing=!1;document.getElementById("easycoder-tracer-content").style.display="none";try{e(b,b.resume)}catch(q){var c="Error in run handler: "+q.message;console.log(c);alert(c)}}}(a);a.step.onclick=function(a){return function(){console.log("step");a.step.blur();b.tracing=!0;document.getElementById("easycoder-tracer-content").style.display="block";try{e(b,b.resume)}catch(q){var c="Error in step handler: "+q.message;console.log(c);alert(c)}}}(a)}b.resume=
    89 b.pc;b.pc=0}break}a={run:a.run,step:a.step}}};h.exports=e},{}],8:[function(d,h,g){var e={markComments:function(b){var a=b.list,c=void 0===b.index?0:b.index,d=void 0===b.inComment?!1:b.inComment,g=void 0===b.newList?[]:b.newList;if(c>=a.length)return g;var f=a[c];b=f.lino;f=f.token;var h={list:a,index:c+1,inComment:!1,newList:g.concat({lino:b,index:c,token:f})};g={list:a,index:c+1,inComment:!0,newList:g.concat({lino:b,index:c,comment:!0,token:f})};return d&&0<c&&b===a[c-1].lino?e.markComments(g):"!"===
    90 f.charAt(0)?e.markComments(g):e.markComments(h)},findStrings:function(b){var a=b.original,c=b.line,d=void 0===b.inComment?!1:b.inComment;b=void 0===b.inQuote?!1:b.inQuote;var g=c.charAt(0),f=b&&" "===g?"\\s":g;if(1===c.length)return f;c=c.substring(1);if("!"===g&&!b)return g+e.findStrings({original:a,line:c,inComment:!0,inQuote:!1});if("`"===g&&!d&&!b)return g+e.findStrings({original:a,line:c,inComment:d,inQuote:!0});if("`"===g&&b)return g+e.findStrings({original:a,line:c,inComment:d,inQuote:!1});
    91 if(!d&&!b&&!g.match(/[A-z0-9_\-+*/\- ]/)){if("'"==g||'"'==g)throw Error('Bad syntax in "'+a+'":\nStrings in EasyCoder must be enclosed in backticks.');throw Error('Unrecognised character "'+g+'" in "'+a+'".');}return f+e.findStrings({original:a,line:c,inComment:d,inQuote:b})},tokenise:function(b){b=b.map(function(a){return a.length?e.findStrings({original:a,line:a}):""}).map(function(a,b){return{lino:b+1,line:a}});var a=b.map(function(a){return a.line.trim().split(/\s+/).map(function(c,b){return{lino:a.lino,
    92 index:b,token:c}})});a=[].concat.apply([],a).filter(function(a){return a.token}).map(function(a){return{lino:a.lino,index:a.index,token:a.token.split("\\s").join(" ")}});a=e.markComments({list:a}).filter(function(a){return!a.comment});return{scriptLines:b,tokens:a}}};h.exports=e},{}],9:[function(d,h,g){var e={getItem:function(b){var a=b.getToken();if(!a)return null;if("true"===a)return b.next(),{type:"boolean",content:!0};if("false"===a)return b.next(),{type:"boolean",content:!1};if("`"===a.charAt(0))return b.next(),
    93 {type:"constant",numeric:!1,content:a.substring(1,a.length-1)};if(a[0].match(/[0-9\-]/)){var c=eval(a);if(Number.isInteger(c))return b.next(),{type:"constant",numeric:!0,content:c};throw Error("'"+a+"' is not an integer");}a=$jscomp.makeIterator(Object.keys(b.domain));for(c=a.next();!c.done;c=a.next())if(c=b.domain[c.value].value.compile(b))return c;return null},compile:function(b){var a=b.getToken(),c=b.value.getItem(b);if(!c)throw Error("Undefined value: '"+a+"'");if("cat"===b.getToken()){for(a=
    94 {type:"cat",numeric:!1,parts:[c]};b.tokenIs("cat");)b.next(),a.parts.push(b.value.getItem(b));return a}return c},doValue:function(b,a){switch(a.type){case "cat":return{type:"constant",numeric:!1,content:a.parts.reduce(function(a,c){return a+e.doValue(b,c).content},"")};case "boolean":case "constant":return a;case "symbol":var c=b.getSymbolRecord(a.name);return c.isValueHolder?(a=c.value[c.index])?(null===a.content&&(a.content=a.numeric?0:""),a):null:b.domain[c.domain].value.get(b,a)}return b.domain[a.domain].value.get(b,
    95 a)},constant:function(b,a){return{type:"constant",numeric:a,content:b}},evaluate:function(b,a){if(!a)return{type:"constant",numeric:!1,content:""};var c=e.doValue(b,a);if(c)return c;b.runtimeError(b[b.pc].lino,"Can't decode value: "+a)},getValue:function(b,a){return e.evaluate(b,a).content},encode:function(b,a){if(b)switch(a){case "ec":return b.replace(/'/g,"~sq~").replace(/"/g,"~dq~");case "url":return encodeURIComponent(b.replace(/\s/g,"+"))}return b},decode:function(b,a){if(b)switch(a){case "ec":return b.replace(/~dq~/g,
    96 '"').replace(/~sq~/g,"'");case "url":return decodeURIComponent(b).replace(/\+/g," ")}return b}};h.exports=e},{}]},{},[1]);
     11$jscomp.polyfill("String.prototype.repeat",function(d){return d?d:function(d){var f=$jscomp.checkStringArgs(this,null,"repeat");if(0>d||1342177279<d)throw new RangeError("Invalid count value");d|=0;for(var e="";d;)if(d&1&&(e+=f),d>>>=1)f+=f;return e}},"es6","es3");$jscomp.stringPadding=function(d,h){d=void 0!==d?String(d):" ";return 0<h&&d?d.repeat(Math.ceil(h/d.length)).substring(0,h):""};
     12$jscomp.polyfill("String.prototype.padStart",function(d){return d?d:function(d,f){var e=$jscomp.checkStringArgs(this,null,"padStart");return $jscomp.stringPadding(f,d-e.length)+e}},"es8","es3");
     13(function(){function d(h,f,e){function b(c,m){if(!f[c]){if(!h[c]){var g="function"==typeof require&&require;if(!m&&g)return g(c,!0);if(a)return a(c,!0);m=Error("Cannot find module '"+c+"'");throw m.code="MODULE_NOT_FOUND",m;}m=f[c]={exports:{}};h[c][0].call(m.exports,function(a){return b(h[c][1][a]||a)},m,m.exports,d,h,f,e)}return f[c].exports}for(var a="function"==typeof require&&require,c=0;c<e.length;c++)b(e[c]);return b}return d})()({1:[function(d,h,f){var e=d("./easycoder/Main");e.timestamp=
     14Date.now();console.log("EasyCoder loaded; waiting for page");window.onload=function(){console.log(Date.now()-e.timestamp+" ms: Page loaded; reset timer & start EasyCoder");e.timestamp=Date.now();e.scripts={};window.EasyCoder=e;var b=document.getElementById("easycoder-script");if(b){b.style.display="none";try{e.start(b.innerText)}catch(a){e.reportError(a)}}}},{"./easycoder/Main":6}],2:[function(d,h,f){h.exports=function(d,b,a){b=d.value.evaluate(d,b);d=d.value.evaluate(d,a);a=b.content;var c=d.content;
     15b.numeric&&!d.numeric&&(a=a.toString());!b.numeric&&d.numeric&&(c=c.toString());b.numeric||"undefined"!==typeof a||(a="");d.numeric||"undefined"!==typeof c||(c="");return a>c?1:a<c?-1:0}},{}],3:[function(d,h,f){var e=Object.assign||function(a){for(var c=1;c<arguments.length;c++){var b=arguments[c],d;for(d in b)Object.prototype.hasOwnProperty.call(b,d)&&(a[d]=b[d])}return a},b=this,a={getTokens:function(){return b.tokens},addWarning:function(a){b.warnings.push(a)},warning:function(c){a.addWarning(c)},
     16unrecognisedSymbol:function(c){a.addWarning("Unrecognised symbol '"+c+"'")},getWarnings:function(){return b.warnings},getIndex:function(){return b.index},next:function(a){b.index+=void 0===a?1:a},peek:function(){return b.tokens[b.index+1].token},getToken:function(){return b.index>=b.tokens.length?null:b.tokens[b.index]?b.tokens[b.index].token:null},nextToken:function(){a.next();return a.getToken()},tokenIs:function(a){return b.index>=b.tokens.length?!1:a===b.tokens[b.index].token},nextTokenIs:function(c){a.next();
     17return a.tokenIs(c)},skip:function(c){if(b.index>=b.tokens.length)return null;a.next();a.tokenIs(c)&&a.next()},prev:function(){b.index--},getLino:function(){return b.index>=b.tokens.length?0:b.tokens[b.index].lino},getTarget:function(a){a=void 0===a?b.index:a;return b.tokens[a].token},getTargetPc:function(c){c=void 0===c?b.index:c;return b.symbols[a.getTarget(c)].pc},getCommandAt:function(a){return b.program[a]},isSymbol:function(c){c=void 0===c?!1:c;if(a.getTarget()in b.symbols)return!0;if(c)throw Error("Unknown symbol: '"+
     18a.getTarget()+"'");return!1},nextIsSymbol:function(c){c=void 0===c?!1:c;a.next();return a.isSymbol(c)},getSymbol:function(){return b.symbols[a.getToken()]},getSymbolPc:function(){return a.getSymbol().pc},getSymbolRecord:function(){return b.program[a.getSymbolPc()]},getSymbols:function(){return b.symbols},getProgram:function(){return b.program},getPc:function(){return b.program.length},getValue:function(){return a.value.compile(a)},getNextValue:function(){a.next();return a.getValue()},getCondition:function(){return a.condition.compile(a)},
     19constant:function(c,g){return a.value.constant(c,void 0===g?!1:g)},addCommand:function(a){b.program.push(e({pc:b.program.length},a))},addSymbol:function(a,g){b.symbols[a]={pc:g}},mark:function(){b.savedMark=b.index},rewind:function(){b.index=b.savedMark},rewindTo:function(a){b.index=a},completeHandler:function(){var c=a.getLino(),g=a.getPc();a.addCommand({domain:"core",keyword:"goto",lino:c,goto:0});a.compileOne();a.addCommand({domain:"core",keyword:"stop",lino:c,next:0});a.getCommandAt(g).goto=a.getPc();
     20return!0},compileVariable:function(c,g,m,d){m=void 0===m?!1:m;d=void 0===d?null:d;a.next();var p=a.getLino(),l=a.getTokens()[a.getIndex()];if(b.symbols[l.token])throw Error("Duplicate variable name '"+l.token+"'");var n=a.getPc();a.next();a.addSymbol(l.token,n);c={domain:c,keyword:g,lino:p,isSymbol:!0,isValueHolder:m,name:l.token,elements:1,index:0,value:[{}],element:[],extra:d};"dom"===d&&(c.element=[]);a.addCommand(c);return c},compileToken:function(){var c=a.getToken();if(c){a.mark();for(var g=
     21$jscomp.makeIterator(Object.keys(a.domain)),b=g.next();!b.done;b=g.next()){if((b=a.domain[b.value])&&(b=b.getHandler(c))&&b.compile(a))return;a.rewind()}console.log("No handler found");throw Error("I don't understand '"+c+"...'");}},compileOne:function(){var c=a.getToken();if(c){b.warnings=[];var g=b.program.length;if(c.endsWith(":")){c=c.substring(0,c.length-1);if(b.symbols[c])throw Error("Duplicate symbol: '"+c+"'");b.symbols[c]={pc:g};b.index++}else a.compileToken()}},compileFromHere:function(c){for(;b.index<
     22b.tokens.length;){var g=b.tokens[b.index].token;if("else"===g)return b.program;a.compileOne();if(-1<c.indexOf(g))break}},compile:function(c){b.tokens=c;b.index=0;b.program=[];b.symbols={};b.warnings=[];a.compileFromHere([]);a.addCommand({domain:"core",keyword:"stop",lino:a.getLino(),next:0});return b.program}};h.exports=a},{}],4:[function(d,h,f){var e=Object.assign||function(b){for(var a=1;a<arguments.length;a++){var c=arguments[a],g;for(g in c)Object.prototype.hasOwnProperty.call(c,g)&&(b[g]=c[g])}return b};
     23h.exports={compile:function(b){b.mark();for(var a=$jscomp.makeIterator(Object.keys(b.domain)),c=a.next();!c.done;c=a.next()){if(c=b.domain[c.value].condition.compile(b))return e({domain:name},c);b.rewind()}},test:function(b,a){return b.domain[a.domain].condition.test(b,a)}}},{}],5:[function(d,h,f){var e=this,b={Add:{compile:function(a){var c=a.getLino();a.next();var g=a.getValue();if(a.tokenIs("to"))if(a.next(),a.isSymbol()){var b=a.getSymbol();if(a.getCommandAt(b.pc).isValueHolder){if("giving"===
     24a.peek()){b=a.getValue();a.next();var d=a.getToken();a.next();a.addCommand({domain:"core",keyword:"add",lino:c,value1:g,value2:b,target:d})}else b=a.getToken(),a.next(),a.addCommand({domain:"core",keyword:"add",lino:c,value1:g,target:b});return!0}a.warning("core "+e.name+": Expected value holder")}else{b=a.getValue();if(a.tokenIs("giving"))return a.next(),d=a.getToken(),a.next(),a.addCommand({domain:"core",keyword:"add",lino:c,value1:g,value2:b,target:d}),!0;a.warning("core "+e.name+': Expected "giving"')}return!1},
     25run:function(a){var c=a[a.pc],g=c.value1,b=c.value2,d=a.getSymbolRecord(c.target);if(d.isValueHolder){var e=d.value[d.index];b?(a=a.getValue(b)+a.getValue(g),d.value[d.index]={type:"constant",numeric:!0,content:a}):(e.numeric||a.nonNumericValueError(c.lino),a=e.content+a.getValue(g),d.value[d.index]={type:"constant",numeric:!0,content:a})}else a.VariableDoesNotHoldAValueError(c.lino,d.name);return c.pc+1}},Alias:{compile:function(a){var c=a.getLino();a.next();if(a.isSymbol()){var g=a.getToken();a.next();
     26if(a.tokenIs("to")&&(a.next(),a.isSymbol())){var b=a.getToken();a.next();a.addCommand({domain:"core",keyword:"alias",lino:c,alias:g,symbol:b});return!0}}return!1},run:function(a){var c=a[a.pc],g=a.symbols[c.alias].pc,b=a[g],d=a.getSymbolRecord(c.symbol);a[g]={pc:b.pc,domain:d.domain,keyword:d.keyword,lino:b.lino,name:b.name,alias:c.symbol};return c.pc+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",
     27"callback");return!0},run:function(a){return a[a.pc].pc+1}},Clear:{compile:function(a){var c=a.getLino();a.next();if(a.isSymbol()){var g=a.getSymbolRecord();if(g.isValueHolder)return g=a.getToken(),a.next(),a.addCommand({domain:"core",keyword:"clear",lino:c,symbol:g}),!0;a.warning("'Variable '"+g.name+"' does not hold a value")}return!1},run:function(a){var c=a[a.pc],g=a.getSymbolRecord(c.symbol);g.isValueHolder?(a.domain[g.domain].value.put(g,{type:"boolean",content:!1}),c.numeric=!1):a.VariableDoesNotHoldAValueError(c.lino,
     28g.name);return c.pc+1}},Close:{compile:function(a){var c=a.getLino();if(a.nextIsSymbol()){var g=a.getSymbolRecord();if("module"===g.keyword)return a.next(),a.addCommand({domain:"core",keyword:"close",lino:c,module:g.name}),!0}return!1},run:function(a){var c=a[a.pc];a=a.getSymbolRecord(c.module).program;a.run(a.onClose);return c.pc+1}},Debug:{compile:function(a){var c=a.getLino();if(a.nextTokenIs("program")){a.next();if(["item","pc"].includes(a.getToken())){var g=a.getNextValue();a.addCommand({domain:"core",
     29keyword:"debug",lino:c,item:g});return!0}a.addCommand({domain:"core",keyword:"debug",lino:c,item:-1});return!0}return a.tokenIs("symbols")?(a.next(),a.addCommand({domain:"core",keyword:"debug",lino:c,item:"symbols"}),!0):a.tokenIs("symbol")?(g=a.nextToken(),a.next(),a.addCommand({domain:"core",keyword:"debug",lino:c,item:"symbol",name:g}),!0):a.tokenIs("step")?(a.next(),a.addCommand({domain:"core",keyword:"debug",lino:c,item:"step"}),!0):!1},run:function(a){var c=a[a.pc],g=c.item;switch(g){case "symbols":console.log("Symbols: "+
     30JSON.stringify(a.symbols,null,2));break;case "symbol":a=a.getSymbolRecord(c.name);g=a.exporter;delete a.exporter;console.log("Symbol: "+JSON.stringify(a,null,2));a.exporter=g;break;case "step":a.debugStep=!0;break;default:0<=g.content?console.log("Debug item "+g.content+": "+JSON.stringify(a[g.content],null,2)):console.log("Debug program: "+JSON.stringify(a,null,2))}return c.pc+1}},Decode:{compile:function(a){var c=a.getLino();if(a.nextIsSymbol()){var g=a.getToken();a.next();a.addCommand({domain:"core",
     31keyword:"decode",lino:c,symbol:g});return!0}return!1},run:function(a){var c=a[a.pc],g=a.getSymbolRecord(c.symbol);if(g.isValueHolder){var b=a.getValue(g.value[g.index]);g.value[g.index]={type:"constant",numeric:!1,content:a.decode(b)};c.numeric=!1}else a.VariableDoesNotHoldAValueError(c.lino,g.name);return c.pc+1}},Divide:{compile:function(a){var c=a.getLino();if(a.nextIsSymbol()){var g=a.getSymbol();var b=a.getCommandAt(g.pc).name}g=a.getValue();a.tokenIs("by")&&a.next();var d=a.getValue();if(a.tokenIs("giving")){a.next();
     32if(a.isSymbol())return b=a.getSymbol(),b=a.getCommandAt(b.pc).name,a.next(),a.addCommand({domain:"core",keyword:"divide",lino:c,value1:g,value2:d,target:b}),!0;a.warning("core "+e.name+": Expected value holder")}else return"undefined"===typeof b&&a.warning("core "+e.name+": No target variable given"),a.addCommand({domain:"core",keyword:"divide",lino:c,value2:d,target:b}),!0;return!1},run:function(a){var c=a[a.pc],b=c.value1,d=c.value2,e=a.getSymbolRecord(c.target);if(e.isValueHolder){var f=e.value[e.index];
     33b?(a=a.getValue(b)/a.getValue(d),e.value[e.index]={type:"constant",numeric:!0,content:Math.trunc(a)}):(f.numeric||a.nonNumericValueError(c,lino),a=f.content/a.getValue(d),e.value[e.index]={type:"constant",numeric:!0,content:Math.trunc(a)})}else a.VariableDoesNotHoldAValueError(c.lino,e.name);return c.pc+1}},Encode:{compile:function(a){var c=a.getLino();a.next();if(a.isSymbol()){var b=a.getToken();a.next();a.addCommand({domain:"core",keyword:"encode",lino:c,symbol:b});return!0}return!1},run:function(a){var c=
     34a[a.pc],b=a.getSymbolRecord(c.symbol);if(b.isValueHolder){var d=a.getValue(b.value[b.index]);b.value[b.index]={type:"constant",numeric:!1,content:a.encode(d)};c.numeric=!1}else a.VariableDoesNotHoldAValueError(c.lino,b.name);return c.pc+1}},End:{compile:function(a){a.next();return!0},run:function(){return 0}},Fork:{compile:function(a){var c=a.getLino();a.next();a.nextTokenIs("to")&&a.next();var b=a.getToken();a.next();a.addCommand({domain:"core",keyword:"fork",lino:c,label:b});return!0},run:function(a){var c=
     35a[a.pc];try{a.run(a.symbols[c.label].pc)}catch(g){console.log(g.message),alert(g.message)}return c.pc+1}},Go:{compile:function(a){var c=a.getLino();a.nextTokenIs("to")&&a.next();var b=a.getToken();a.next();a.addCommand({domain:"core",keyword:"go",lino:c,label:b});return!0},run:function(a){var c=a[a.pc];if(c.label){if(a.verifySymbol(c.label)){var b=a.symbols[c.label];if(b)return b.pc}a.runtimeError(c.lino,"Unknown symbol '"+c.label+"'");return 0}return c.goto}},Gosub:{compile:function(a){var c=a.getLino();
     36a.nextTokenIs("to")&&a.next();var b=a.getToken();a.next();a.addCommand({domain:"core",keyword:"gosub",lino:c,label:b});return!0},run:function(a){var c=a[a.pc];if(a.verifySymbol(c.label))return a.stack.push(a.pc+1),a.symbols[c.label].pc;a.runtimeError(c.lino,"Unknown symbol '"+c.label+"'");return 0}},If:{compile:function(a){var c=a.getLino();a.next();var b=a.condition.compile(a),d=a.getPc();a.addCommand({domain:"core",keyword:"if",lino:c,condition:b});a.compileOne();if(!a.getToken())return a.getCommandAt(d).else=
     37a.getPc(),!0;a.tokenIs("else")?(c=a.getPc(),a.addCommand({domain:"core",keyword:"goto",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 c=a[a.pc];return a.condition.test(a,c.condition)?c.pc+1:c.else}},Import:{compile:function(a){var c=a.imports,b=a.getProgram();c=$jscomp.makeIterator(c);for(var d=c.next();!d.done;d=c.next()){d=d.value;var e=a.nextToken(),f=d.keyword;if(e===f){if(e=a.compileVariable(d.domain,
     38f,!0),e=b[a.getSymbols()[e.name].pc],e.element=d.element,e.exporter=d.exporter,e.exportedName=d.name,e.extra=d.extra,e.imported=!0,!a.tokenIs("and"))break}else throw Error("Mismatched import variable type for '"+d.name+"'");}if(a.tokenIs("and"))throw Error("Imports do not match exports");return!0},run:function(a){return a[a.pc].pc+1}},Index:{compile:function(a){var c=a.getLino();if(a.nextIsSymbol(!0)){var b=a.getToken();if(a.nextTokenIs("to")){var d=a.getNextValue();a.addCommand({domain:"core",keyword:"index",
     39lino:c,symbol:b,value:d});return!0}}return!1},run:function(a){var c=a[a.pc],b=a.getSymbolRecord(c.symbol),d=a.getValue(c.value);d>=b.elements&&a.runtimeError(c.lino,"Array index "+d+" is out of range for '"+b.name+"'");b.index=d;b.imported&&(b.exporter.getSymbolRecord(b.exportedName).index=d);return c.pc+1}},Load:{compile:function(a){var c=a.getLino();switch(a.nextToken()){case "plugin":var b=a.getNextValue();a.addCommand({domain:"core",keyword:"load",lino:c,name:b});return!0}return!1},run:function(a){var c=
     40a[a.pc],b=a.getValue(c.name);switch(c.keyword){case "load":if(a.checkPlugin(b))return c.pc+1;EasyCoder_Plugins.getLocalPlugin(a.getPluginsPath,b,a.getPlugin,a.addLocalPlugin,function(){a.run(c.pc+1)});return 0}}},Module:{compile:function(a){a.compileVariable("core","module");return!0},run:function(a){a=a[a.pc];a.program=null;return a.pc+1}},Multiply:{compile:function(a){var c=a.getLino();a.next();if(a.isSymbol()){var b=a.getSymbol();var d=a.getCommandAt(b.pc).name}b=a.getValue();a.tokenIs("by")&&
     41a.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:c,value1:b,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:c,value2:e,target:d}),!0;return!1},run:function(a){var c=a[a.pc],b=c.value1,d=c.value2,e=
     42a.getSymbolRecord(c.target);if(e.isValueHolder){var f=e.value[e.index];b?(a=a.getValue(b)*a.getValue(d),e.value[e.index]={type:"constant",numeric:!0,content:a}):(f.numeric||a.nonNumericValueError(c,lino),a=f.content*a.getValue(d),e.value[e.index]={type:"constant",numeric:!0,content:a})}else a.VariableDoesNotHoldAValueError(c.lino,e.name);return c.pc+1}},Negate:{compile:function(a){var c=a.getLino();a.next();if(a.isSymbol()){var b=a.getToken();a.next();a.addCommand({domain:"core",keyword:"negate",
     43lino:c,symbol:b});return!0}return!1},run:function(a){var c=a[a.pc],b=a.getSymbolRecord(c.symbol);b.isValueHolder?b.value[b.index]={type:"constant",numeric:!0,content:-b.value[b.index].content}:a.VariableDoesNotHoldAValueError(c.lino,b.name);return c.pc+1}},On:{compile:function(a){var c=a.getLino(),b=a.nextToken();switch(b){case "trigger":case "close":case "restore":case "message":return a.next(),a.addCommand({domain:"core",keyword:"on",lino:c,action:b}),a.completeHandler()}return a.isSymbol()&&(b=
     44a.getSymbolRecord(),"callback"===b.keyword)?(a.next(),a.addCommand({domain:"core",keyword:"on",lino:c,action:b.name}),a.completeHandler()):!1},run:function(a){var c=a[a.pc],b=c.pc+2;switch(c.action){case "trigger":a.onTrigger=b;break;case "close":a.onClose=b;break;case "restore":a.onRestore=b;break;case "message":a.onMessage=b;break;default:var d=a.getSymbolRecord(c.action);if(d)d.cb=b;else return a.runtimeError(c.lino,"Unknown action '"+c.action+"'"),0}return c.pc+1}},Print:{compile:function(a){var c=
     45a.getLino();a.next();var b=a.getValue();a.addCommand({domain:"core",keyword:"print",lino:c,value:b});return!0},run:function(a){var c=a[a.pc];a=a.getFormattedValue(c.value);console.log("-> "+a);return c.pc+1}},Put:{compile:function(a){var c=a.getLino();a.next();var b=a.getValue();if(a.tokenIs("into")){a.next();if(a.isSymbol()){var d=a.getToken();a.next();a.addCommand({domain:"core",keyword:"put",lino:c,value:b,target:d});return!0}a.warning("core:put: No such variable: '"+a.getToken()+"'")}return!1},
     46run:function(a){var c=a[a.pc],b=a.getSymbolRecord(c.target);b.isValueHolder||a.variableDoesNotHoldAValueError(c.lino,b.name);a=a.evaluate(c.value);b.value[b.index]=a;b.imported&&(b=b.exporter.getSymbolRecord(b.exportedName),b.value[b.index]=a);return c.pc+1}},Replace:{compile:function(a){var c=a.getLino();a.next();var b=a.getValue();if(a.tokenIs("with")){a.next();var d=a.getValue();if(a.tokenIs("in")&&(a.next(),a.isSymbol())){var e=a.getSymbolRecord();a.next();if(e.isValueHolder)return a.addCommand({domain:"core",
     47keyword:"replace",lino:c,original:b,replacement:d,target:e.name}),!0}}return!1},run:function(a){var c=a[a.pc],b=a.getValue(c.original),d=a.getValue(c.replacement),e=a.getSymbolRecord(c.target);a=a.getValue(e.value[e.index]).split(b).join(d);e.value[e.index]={type:"constant",numeric:!1,content:a};return c.pc+1}},Return:{compile:function(a){var c=a.getLino();a.next();a.addCommand({domain:"core",keyword:"return",lino:c});return!0},run:function(a){return a.stack.pop()}},Run:{compile:function(a){var c=
     48a.getLino(),b=a.getNextValue(),d=[];if(a.tokenIs("with"))for(;;)if(a.nextIsSymbol(!0)){var e=a.getSymbolRecord();e.exporter=a.getProgram();d.push(e);a.next();if(!a.tokenIs("and"))break}if(a.tokenIs("as")&&a.nextIsSymbol(!0)){e=a.getSymbolRecord();a.next();if("module"!==e.keyword)throw Error("'"+e.name+"' is not a module");var f=e.name}a.addCommand({domain:"core",keyword:"run",lino:c,script:b,imports:d,module:f});return!0},run:function(a){a.nextPc=a.pc+1;a.runScript(a);return 0}},Script:{compile:function(a){var c=
     49a.getLino(),b=a.nextToken();a.next();a.addCommand({domain:"core",keyword:"script",lino:c,name:b});return!0},run:function(a){var c=a[a.pc];a.script=c.name;EasyCoder.scripts[c.name]=a;console.log(Date.now()-EasyCoder.timestamp+" ms: Script: "+c.name);return c.pc+1}},Send:{compile:function(a){var c=a.getLino(),b=a.getNextValue();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=
     50d.name}a.next();a.addCommand({domain:"core",keyword:"send",lino:c,message:b,recipient:d})}return!0},run:function(a){var c=a[a.pc],b=a.getValue(c.message);if("parent"===c.recipient){var d=a.parent;d&&a.parent.onMessage&&(d.message=b,d.run(d.onMessage))}else a=a.getSymbolRecord(c.recipient),a.program.message=b,a.program.run(a.program.onMessage);return c.pc+1}},Set:{compile:function(a){var c=a.getLino();if(a.nextIsSymbol()){var b=a.getSymbolRecord();if(!b.isValueHolder)return!1;if(a.nextTokenIs("to")){a.next();
     51for(var d=[];;){a.mark();try{d.push(a.getValue())}catch(k){a.rewind();break}}a.addCommand({domain:"core",keyword:"set",lino:c,request:"setArray",target:b.name,value:d});return!0}a.addCommand({domain:"core",keyword:"set",lino:c,request:"setBoolean",target:b.name});return!0}if(a.tokenIs("ready"))return a.next(),a.addCommand({domain:"core",keyword:"set",lino:c,request:"setReady"}),!0;if(a.tokenIs("property")&&(b=a.getNextValue(),a.tokenIs("of")&&a.nextIsSymbol()&&(d=a.getSymbolRecord(),"variable"===
     52d.keyword&&a.nextTokenIs("to")))){var e=a.getNextValue();a.addCommand({domain:"core",keyword:"set",lino:c,request:"setProperty",target:d.name,name:b,value:e});return!0}a.tokenIs("the")&&a.next();if(a.tokenIs("elements")&&(a.next(),a.tokenIs("of"))){a.next();if(!a.isSymbol())throw Error("Unknown variable '"+a.getToken()+"'");b=a.getToken();a.next();if(a.tokenIs("to"))return a.next(),d=a.getValue(),a.addCommand({domain:"core",keyword:"set",lino:c,request:"setElements",symbol:b,value:d}),!0}if(a.tokenIs("encoding")){if(a.nextTokenIs("to"))return b=
     53a.getNextValue(),a.addCommand({domain:"core",keyword:"set",request:"encoding",lino:c,encoding:b}),!0;a.addWarning("Unknown encoding option");return!1}return a.tokenIs("payload")&&a.nextTokenIs("of")&&a.nextIsSymbol()&&(b=a.getSymbolRecord(),"callback"===b.keyword&&a.nextTokenIs("to"))?(d=a.getNextValue(),a.addCommand({domain:"core",keyword:"set",request:"setPayload",lino:c,callback:b.name,payload:d}),!0):!1},run:function(a){var c=a[a.pc];switch(c.request){case "setBoolean":var b=a.getSymbolRecord(c.target);
     54b.isValueHolder?(b.value[b.index]={type:"boolean",content:!0},c.numeric=!1):a.VariableDoesNotHoldAValueError(c.lino,b.name);break;case "setReady":a.parent.run(a.parent.nextPc);break;case "setElements":b=a.getSymbolRecord(c.symbol);b.elements=a.getValue(c.value);b.index=0;b.value=[];b.element=[];for(a=0;a<b.elements;a++)b.value.push({}),b.element.push({});break;case "setArray":b=a.getSymbolRecord(c.target);b.elements=c.value.length;b.value=c.value;break;case "encoding":a.encoding=a.getValue(c.encoding);
     55break;case "setProperty":b=a.getSymbolRecord(c.target);var d=a.getValue(b.value[b.index]);d||(d="{}");var e="";try{e=JSON.parse(d)}catch(k){return a.runtimeError(c.lino,"Can't parse "+b.name),0}d=a.getValue(c.name);if(a=a.evaluate(c.value))e[d]="boolean"===a.type?a.content:a.numeric?a.content:'{"'===a.content.substr(0,2)?JSON.parse(a.content):a.content.split('"').join('\\"'),b.value[b.index]={type:"constant",numeric:!1,content:JSON.stringify(e)};break;case "setPayload":a.getSymbolRecord(c.callback).payload=
     56a.getValue(c.payload)}return c.pc+1}},Stop:{compile:function(a){var c=a.getLino();a.next();a.addCommand({domain:"core",keyword:"stop",lino:c,next:0});return!0},run:function(){return 0}},Take:{compile:function(a){var c=a.getLino();a.next();var b=a.getValue();if(a.tokenIs("from"))if(a.next(),a.isSymbol()){var d=a.getSymbol();if(a.getCommandAt(d.pc).isValueHolder){if("giving"===a.peek()){d=a.getValue();a.next();var f=a.getToken();a.next();a.addCommand({domain:"core",keyword:"take",lino:c,value1:b,value2:d,
     57target:f})}else d=a.getToken(),a.next(),a.addCommand({domain:"core",keyword:"take",lino:c,value1:b,target:d});return!0}a.warning("core "+e.name+": Expected value holder")}else{d=a.getValue();if(a.tokenIs("giving"))return a.next(),f=a.getToken(),a.next(),a.addCommand({domain:"core",keyword:"take",lino:c,value1:b,value2:d,target:f}),!0;a.warning("core "+e.name+': Expected "giving"')}return!1},run:function(a){var c=a[a.pc],b=c.value1,d=c.value2,e=a.getSymbolRecord(c.target);if(e.isValueHolder){var f=
     58e.value[e.index];d?(a=a.getValue(d)-a.getValue(b),e.value[e.index]={type:"constant",numeric:!0,content:a}):(f.numeric||a.nonNumericValueError(c,lino),a=a.getValue(f)-a.getValue(b),e.value[e.index]={type:"constant",numeric:!0,content:a})}else a.VariableDoesNotHoldAValueError(c.lino,e.name);return c.pc+1}},Toggle:{compile:function(a){var c=a.getLino();a.next();if(a.isSymbol()){var b=a.getSymbolPc();a.next();a.addCommand({domain:"core",keyword:"toggle",lino:c,symbol:b});return!0}return!1},run:function(a){var c=
     59a[a.pc],b=a[c.symbol];if(b.isValueHolder){var d=a.domain[b.domain];a=d.value.get(a,b.value[b.index]).content;d.value.put(b,{type:"boolean",content:!a})}else a.VariableDoesNotHoldAValueError(c.lino,b.name);return c.pc+1}},Trigger:{compile:function(a){var c=a.getLino();a.next();return a.tokenIs("parent")?(a.next(),a.addCommand({domain:"core",keyword:"trigger",lino:c,module:"parent"}),!0):a.isSymbol()&&(moduleRecord=a.getSymbolRecord(),a.next(),"module"===moduleRecord.keyword)?(h=moduleRecord.name,a.addCommand({domain:"core",
     60keyword:"trigger",lino:c,module:h}),!0):!1},run:function(a){var c=a[a.pc];if("parent"===c.module)a.parent&&a.parent.trigger(!0);else{var b=a.getSymbolRecord(c.module);if(b.exporter){exporterRecord=b.exporter.getSymbolRecord(c.module);var d=exporterRecord.program}else d=b.program;d?d.trigger():a.runtimeError(c.lino,b.name+" has not been initialized")}return c.pc+1}},Variable:{compile:function(a){a.compileVariable("core","variable",!0);return!0},run:function(a){return a[a.pc].pc+1}},Wait:{compile:function(a){var c=
     61a.getLino();a.next();var b=a.getValue(a),d=1E3;switch(a.getToken()){case "milli":case "millis":a.next();d=1;break;case "tick":case "ticks":a.next();d=10;break;case "second":case "seconds":a.next();d=1E3;break;case "minute":case "minutes":a.next(),d=6E4}a.addCommand({domain:"core",keyword:"wait",lino:c,value:b,multiplier:d});return!0},run:function(a){var c=a[a.pc],b=a.getValue(c.value);setTimeout(function(){a.run(c.pc+1)},b*c.multiplier);return 0}},While:{compile:function(a){var c=a.getLino();a.next();
     62var b=a.getCondition(),d=a.getPc();a.addCommand({domain:"core",keyword:"while",lino:c,condition:b});c=a.getPc();a.addCommand({domain:"core",keyword:"goto",goto:0});a.compileOne();a.addCommand({domain:"core",keyword:"goto",goto:d});a.getCommandAt(c).goto=a.getPc();return!0},run:function(a){return a.condition.test(a,a[a.pc].condition)?a.pc+2:a.pc+1}},getHandler:function(a){switch(a){case "add":return b.Add;case "alias":return b.Alias;case "begin":return b.Begin;case "callback":return b.Callback;case "clear":return b.Clear;
     63case "close":return b.Close;case "debug":return b.Debug;case "decode":return b.Decode;case "divide":return b.Divide;case "encode":return b.Encode;case "end":return b.End;case "fork":return b.Fork;case "go":case "goto":return b.Go;case "gosub":return b.Gosub;case "if":return b.If;case "import":return b.Import;case "index":return b.Index;case "load":return b.Load;case "module":return b.Module;case "multiply":return b.Multiply;case "negate":return b.Negate;case "on":return b.On;case "print":return b.Print;
     64case "put":return b.Put;case "replace":return b.Replace;case "return":return b.Return;case "run":return b.Run;case "script":return b.Script;case "send":return b.Send;case "set":return b.Set;case "stop":return b.Stop;case "take":return b.Take;case "toggle":return b.Toggle;case "trigger":return b.Trigger;case "variable":return b.Variable;case "wait":return b.Wait;case "while":return b.While;default:return!1}},run:function(a){var c=a[a.pc],d=b.getHandler(c.keyword);d||a.runtimeError(c.lino,"Unknown keyword '"+
     65c.keyword+"' in 'core' package");return d.run(a)},isNegate:function(a){return"not"===a.getToken()?(a.next(),!0):!1},value:{compile:function(a){if(a.isSymbol()){var c=a.getToken();switch(a.getSymbolRecord().keyword){case "module":return a.next(),{domain:"core",type:"module",name:c};case "variable":return a.nextTokenIs("modulo")?(a=a.getNextValue(),{domain:"core",type:"modulo",name:c,value:a}):{domain:"core",type:"symbol",name:c}}return null}c=a.getToken();if("true"===c)return a.next(),{domain:"core",
     66type:"boolean",content:!0};if("false"===c)return a.next(),{domain:"core",type:"boolean",content:!1};if("random"===c)return a.next(),{domain:"core",type:"random",range:a.getValue()};if("cos"===c)return a.next(),c=a.getValue(),a.skip("radius"),a=a.getValue(),{domain:"core",type:"cos",angle_c:c,radius_c:a};if("sin"===c)return a.next(),c=a.getValue(),a.skip("radius"),a=a.getValue(),{domain:"core",type:"sin",angle_s:c,radius_s:a};if("tan"===c)return a.next(),c=a.getValue(),a.skip("radius"),a=a.getValue(),
     67{domain:"core",type:"tan",angle_t:c,radius_t:a};if("empty"===c)return a.next(),{domain:"core",type:"empty"};if("now"===c)return a.next(),{domain:"core",type:"now"};if("newline"===c)return a.next(),{domain:"core",type:"newline"};if("break"===c)return a.next(),{domain:"core",type:"break"};if("encode"===c)return a.next(),{domain:"core",type:"encode",value:a.getValue()};if("decode"===c)return a.next(),{domain:"core",type:"decode",value:a.getValue()};if("lowercase"===c)return a.next(),{domain:"core",type:"lowercase",
     68value:a.getValue()};if("element"===c){c=a.getNextValue();if(a.tokenIs("of")&&a.nextIsSymbol()){var b=a.getSymbolRecord();a.next();if("variable"===b.keyword)return{domain:"core",type:"element",element:c,symbol:b.name}}return null}if("property"===c)return c=a.getNextValue(),a.tokenIs("of")&&a.nextIsSymbol()&&(b=a.getSymbolRecord(),a.next(),"variable"===b.keyword)?{domain:"core",type:"property",property:c,symbol:b.name}:null;a.tokenIs("the")&&a.next();c=a.getToken();switch(c){case "elements":case "index":if(a.nextTokenIs("of")&&
     69a.nextIsSymbol())return b=a.getToken(),a.next(),{domain:"core",type:c,name:b};break;case "value":if(a.nextTokenIs("of"))return a.next(),{domain:"core",type:"valueOf",value:a.getValue()};break;case "length":if(a.nextTokenIs("of"))return a.next(),{domain:"core",type:"lengthOf",value:a.getValue()};break;case "left":case "right":b=a.getNextValue();if(a.tokenIs("of"))return a=a.getNextValue(),{domain:"core",type:c,count:b,value:a};break;case "from":b=a.getNextValue();var d=a.tokenIs("to")?a.getNextValue():
     70null;if(a.tokenIs("of"))return a=a.getNextValue(),{domain:"core",type:c,from:b,to:d,value:a};break;case "position":if(a.nextTokenIs("of")&&(c=!1,a.nextTokenIs("the")&&a.nextTokenIs("last")&&(a.next(),c=!0),b=a.getValue(),a.tokenIs("in")))return a=a.getNextValue(),{domain:"core",type:"position",needle:b,haystack:a,last:c};break;case "payload":if(a.nextTokenIs("of")&&a.nextIsSymbol()&&(c=a.getSymbolRecord(),"callback"===c.keyword))return a.next(),{domain:"core",type:"payload",callback:c.name};break;
     71case "message":case "error":return a.next(),{domain:"core",type:c}}return null},get:function(a,c){switch(c.type){case "boolean":return{type:"boolean",numeric:!1,content:c.content};case "elements":return{type:"constant",numeric:!0,content:a.getSymbolRecord(c.name).elements};case "index":return{type:"constant",numeric:!0,content:a.getSymbolRecord(c.name).index};case "random":return a=a.evaluate(c.range),{type:"constant",numeric:!0,content:Math.floor(Math.random()*a.content)};case "cos":var b=a.getValue(c.angle_c);
     72a=a.getValue(c.radius_c);return{type:"constant",numeric:!0,content:parseInt(Math.cos(.01745329*parseFloat(b))*a,10)};case "sin":return b=a.getValue(c.angle_s),a=a.getValue(c.radius_s),{type:"constant",numeric:!0,content:parseInt(Math.sin(.01745329*parseFloat(b))*a,10)};case "tan":return b=a.getValue(c.angle_t),a=a.getValue(c.radius_t),{type:"constant",numeric:!0,content:parseInt(Math.tan(.01745329*parseFloat(b))*a,10)};case "valueOf":return a=parseInt(a.getValue(c.value)),{type:"constant",numeric:!0,
     73content:a?a:0};case "lengthOf":return{type:"constant",numeric:!0,content:a.getValue(c.value).length};case "left":return{type:"constant",numeric:!1,content:a.getValue(c.value).substr(0,a.getValue(c.count))};case "right":return{type:"constant",numeric:!1,content:a.getValue(c.value).substr(rstr.length-a.getValue(c.count))};case "from":b=a.getValue(c.from);var d=c.to?a.getVaue(c.to):null;a=a.getValue(c.value);return{type:"constant",numeric:!1,content:d?a.substr(b,d):a.substr(b)};case "position":return b=
     74a.getValue(c.needle),a=a.getValue(c.haystack),{type:"constant",numeric:!0,content:c.last?a.lastIndexOf(b):a.indexOf(b)};case "payload":return{type:"constant",numeric:!1,content:a.getSymbolRecord(c.callback).payload};case "modulo":return b=a.getSymbolRecord(c.name),a=a.evaluate(c.value),{type:"constant",numeric:!0,content:b.value[b.index].content%a.content};case "empty":return{type:"constant",numeric:!1,content:""};case "now":return{type:"constant",numeric:!0,content:Date.now()/1E3};case "newline":return{type:"constant",
     75numeric:!1,content:"\n"};case "break":return{type:"constant",numeric:!1,content:"<br />"};case "encode":return{type:"constant",numeric:!1,content:a.encode(a.getValue(c.value))};case "decode":return{type:"constant",numeric:!1,content:a.decode(a.getValue(c.value))};case "lowercase":return{type:"constant",numeric:!1,content:a.getValue(c.value).toLowerCase()};case "element":b=a.getValue(c.element);c=a.getSymbolRecord(c.symbol);d="";try{d=JSON.parse(a.getValue(c.value[c.index]))[b]}catch(p){a.runtimeError(command.lino,
     76"Can't parse JSON");break}return{type:"constant",numeric:!1,content:"object"===typeof d?JSON.stringify(d):d};case "property":b=a.getValue(c.property);c=a.getSymbolRecord(c.symbol);a=a.getValue(c.value[c.index]);c="";if(b&&a)if("object"===typeof a)c=a[b];else if("{"===a.charAt(0))try{c=JSON.parse(a)[b]}catch(p){console.log("Can't parse '"+a+"': "+p.message)}return{type:"constant",numeric:Number.isInteger(c),content:"object"===typeof c?JSON.stringify(c):c};case "module":return{type:"boolean",numeric:!1,
     77content:a.getSymbolRecord(c.name).program};case "message":return c=a.message,{type:"constant",numeric:!1,content:c};case "error":return c=a.errorMessage,{type:"constant",numeric:!1,content:c}}return null},put:function(a,c){a.value[a.index]=c}},condition:{compile:function(a){if(a.tokenIs("not"))return a.next(),{domain:"core",type:"not",value:a.getValue()};try{var c=a.getValue();if("is"===a.getToken()){a.next();var d=b.isNegate(a);switch(a.getToken()){case "numeric":return a.next(),{domain:"core",type:"numeric",
     78value1:c};case "even":return a.next(),{domain:"core",type:"even",value1:c};case "odd":return a.next(),{domain:"core",type:"odd",value1:c};case "greater":a.next();if(a.tokenIs("than")){a.next();var e=a.getValue();return{domain:"core",type:"greater",value1:c,value2:e,negate:d}}break;case "less":a.next();if(a.tokenIs("than")){a.next();var f=a.getValue();return{domain:"core",type:"less",value1:c,value2:f,negate:d}}break;default:var h=a.getValue();return{domain:"core",type:"is",value1:c,value2:h,negate:d}}}else if(c)return{domain:"core",
     79type:"boolean",value:c}}catch(l){return a.warning("Can't get a value"),0}return null},test:function(a,c){switch(c.type){case "boolean":return a.getValue(c.value);case "numeric":return Number.isInteger(a.getValue(c.value1));case "even":return 0===a.getValue(c.value1)%2;case "odd":return 1===a.getValue(c.value1)%2;case "is":return a=a.compare(a,c.value1,c.value2),c.negate?0!==a:0===a;case "greater":return a=a.compare(a,c.value1,c.value2),c.negate?0>=a:0<a;case "less":return a=a.compare(a,c.value1,c.value2),
     80c.negate?0<=a:0>a;case "not":return!a.getValue(c.value)}return!1}}};h.exports=b},{}],6:[function(d,h,f){var e=this,b=d("./Tokenise"),a=d("./Compile"),c=d("./Run"),g=d("./Value"),m=d("./Condition"),p=d("./Compare"),k={domain:{core:d("./Core")},setupTracer:function(){var a=document.getElementById("easycoder-tracer");a&&(a.innerHTML='<div><input id="easycoder-run-button" type="button" value="Run" /><input id="easycoder-step-button" type="button" value="Step" /><div id="easycoder-tracer-content" style="border:1px solid black;padding:4px";width:100%></div>',
     81a.style.display="none")},runtimeError:function(a,c){this.lino=a;this.reportError({message:"Line "+(0<=a?a:"")+": "+c},k.program)},nonNumericValueError:function(a){this.runtimeError(a,"Non-numeric value")},variableDoesNotHoldAValueError:function(a,c){this.runtimeError(a,"Variable '"+c+"' does not hold a value")},reportError:function(c,b,d){if(c.message)if(this.compiling||b){d=d?d:b.source;var e=d.tokens;d=d.scriptLines;e=this.compiling?e[a.getIndex()].lino:b[b.pc].lino;b=this.compiling?"Compile error":
     82"Runtime error in '"+b.script+"'";b+=":\n";var g=e-5;for(g=0>g?0:g;g<e;g++){var l=(""+(g+1)).padStart(4," ");b+=l+" "+d[g].line.split("\\s").join(" ")+"\n"}b+=c.message+"\n";c=a.getWarnings();if(c.length)for(b+="Warnings:\n",c=$jscomp.makeIterator(c),d=c.next();!d.done;d=c.next())b+=d.value+"\n";console.log(b);alert(b);k.aborted=!0}else c="Error: "+c.message,alert(c),console.log(c);else console.log("An error occurred - origin was "+c.path[0])},getSymbolRecord:function(a){a=this[this.symbols[a].pc];
     83return a.alias?this.getSymbolRecord(a.alias):a.exporter&&a.exporter!=this?a.exporter.getSymbolRecord(a.exportedName):a},verifySymbol:function(a){return this.symbols.hasOwnProperty(a)},encode:function(a){return g.encode(a,this.encoding)},decode:function(a){return g.decode(a,this.encoding)},evaluate:function(a){return g.evaluate(this,a)},getValue:function(a){return g.getValue(this,a)},getFormattedValue:function(a){a=g.evaluate(this,a);return a.numeric?a.content:'{"'===a.content.substr(0,2)||"["===a.content.charAt(0)?
     84JSON.stringify(JSON.parse(a.content),null,2):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&&(k.program=this,c(this,a))},trigger:function(){this.onTrigger&&c(this,this.onTrigger)},register:function(a){k.program=a},require:function(a,c,b){a=document.createElement("script");a.type="text/javascript";a.src=c;a.onload=function(){console.log(Date.now()-k.timestamp+" ms: Library "+c+" loaded");
     85b()};document.head.appendChild(a)},isUndefined:function(a){return"undefined"===typeof a},runScript:function(a){var c=a[a.pc],b=a.getValue(c.script),d=c.imports;a=c.module?a.getSymbolRecord(c.module):null;k.tokeniseScript(b.split("\n"),d,a,this)},close:function(){},compileScript:function(c,b,d,e){var f=c.tokens;this.compiling=!0;var h=Date.now();a.value=g;a.condition=m;a.domain=k.domain;a.imports=b;b=a.compile(f);var l=Date.now();console.log(l-k.timestamp+" ms: Compiled "+f.length+" tokens in "+(l-
     86h+" ms"));this.compiling=!1;b.EasyCoder=k;b.value=g;b.condition=m;b.compare=p;b.source=c;b.run=this.run;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.trigger=this.trigger;b.require=this.require;b.isUndefined=this.isUndefined;b.checkPlugin=this.checkPlugin;b.getPlugin=this.getPlugin;b.addLocalPlugin=this.addLocalPlugin;b.getPluginsPath=
     87this.getPluginsPath;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=a.getSymbols();b.encoding="ec";b.popups=[];b.stack=[];b.queue=[0];b.module=d;b.parent=e;d&&(d.program=b);return b},tokeniseScript:function(a,d,e,g){var f=null,h=Date.now();a=b.tokenise(a);var l=
     88Date.now();console.log(l-k.timestamp+" ms: Tokenised "+a.scriptLines.length+" lines in "+(l-h+" ms"));try{f=k.compileScript(a,d,e,g)}catch(u){"stop"!==u.message&&this.reportError(u,k.program,a)}this.setupTracer();f&&c(f,0)},tokenize:function(a){var b=a.split("\n");if(!e.tokenising){try{k.tokeniseScript(b)}catch(q){k.reportError(q,null,a)}e.tokenising=!0}},setPluginCount:function(a){e.plugins=[];e.pluginCount=a},checkPlugin:function(a){return k.domain[a]},getPlugin:function(a,b,c){k.domain[a]?c():
     89(a=document.createElement("script"),a.type="text/javascript",a.src=b,a.onload=function(){console.log(Date.now()-k.timestamp+" ms: Plugin "+b+" loaded");c()},document.head.appendChild(a))},addGlobalPlugin:function(a,b){e.plugins.push({name:a,handler:b});e.plugins.length===e.pluginCount&&(e.plugins.forEach(function(a){k.domain[a.name]=a.handler}),k.tokenize(e.source))},addLocalPlugin:function(a,b,c){k.domain[a]=b;c()},getPluginsPath:function(){return k.pluginsPath},loadPluginJs:function(a){console.log(Date.now()-
     90k.timestamp+" ms: Load "+a+"/easycoder/plugins.js");var b=document.createElement("script");b.src=""+window.location.origin+a+"/easycoder/plugins.js";b.type="text/javascript";b.onload=function(){EasyCoder_Plugins.getGlobalPlugins(k.timestamp,a,k.setPluginCount,k.getPlugin,k.addGlobalPlugin)};b.onerror=function(){a?k.loadPluginJs(a.slice(0,a.lastIndexOf("/"))):k.reportError({message:"Can't load plugins.js"},k.program,e.source)};document.head.appendChild(b);k.pluginsPath=a},start:function(a){e.source=
     91a;a=window.location.pathname;a.endsWith("/")&&(a=a.slice(0,-1));k.loadPluginJs(a)}};h.exports=k},{"./Compare":2,"./Compile":3,"./Condition":4,"./Core":5,"./Run":7,"./Tokenise":8,"./Value":9}],7:[function(d,h,f){var e=function(b,a){var c=[],d=function(a){var b=9999;a.forEach(function(a){a=a.line;for(var c=0;c<a.length&&" "===a[c];)c++;0<c&&c<b&&(b=c)});return 0};if(c.length)c.push(a);else for(b.register(b),c.push(a);0<c.length&&!EasyCoder.aborted;)for(b.pc=c.shift(),b.watchdog=0,a={};;){if(1E6<b.watchdog){b.lino=
     92b[b.pc].lino;b.reportError(Error("Program runaway intercepted.\nHave you forgotten to increment a loop counter?",b),b);break}b.watchdog++;var f=b[b.pc].domain;b.debugStep&&console.log(b.script+" "+b.pc+" "+f+":"+b[b.pc].keyword);var h=b.domain[f];if(!h){b.runtimeError(b[b.pc].lino,"Unknown domain '"+f+"'");break}b.pc=h.run(b);if(!b.pc)break;if(b.stop){b.tracing=!1;return}if(b.tracing){f=b[b.pc];h=b.source.scriptLines;var k=d(h),l=document.getElementById("easycoder-tracer");if(!l){b.runtimeError(f.lino,
     93"Element 'easycoder-tracer' was not found");return}l.style.display="block";l.style.visibility="visible";var n="";if(b.tracer){if(l=document.getElementById("easycoder-tracer-content")){b.tracer.variables.forEach(function(a,c,d){var e=b.getSymbolRecord(a);if(1<e.elements)for(n+=a+": "+e.index+"/"+e.elements+": ",a=0;a<e.elements;a++){var g=e.value[a];n=g?n+(g.content+" "):n+"undefined "}else n=(e=e.value[e.index])?n+(a+": "+e.content):n+(a+": undefined");switch(b.tracer.alignment){case "horizontal":c<
     94d.length-1&&(n+=", ");break;case "vertical":n+="<br>"}});n+="<hr>";for(var q="",r=5;0<r;r--)f.lino&&(q+='<input type="text" name="'+r+'"'+('value="'+h[f.lino-r].line.substr(k)+'"')+'style="width:100%;border:none;enabled:false">'),q+="<br>";l.innerHTML=n+" "+q;l.style.display="block";a.run=document.getElementById("easycoder-run-button");a.step=document.getElementById("easycoder-step-button");a.run.onclick=function(a){return function(){a.run.blur();b.tracing=!1;document.getElementById("easycoder-tracer-content").style.display=
     95"none";try{e(b,b.resume)}catch(t){var c="Error in run handler: "+t.message;console.log(c);alert(c)}}}(a);a.step.onclick=function(a){return function(){console.log("step");a.step.blur();b.tracing=!0;document.getElementById("easycoder-tracer-content").style.display="block";try{e(b,b.resume)}catch(t){var c="Error in step handler: "+t.message;console.log(c);alert(c)}}}(a)}b.resume=b.pc;b.pc=0}break}a={run:a.run,step:a.step}}};h.exports=e},{}],8:[function(d,h,f){var e={markComments:function(b){var a=b.list,
     96c=void 0===b.index?0:b.index,d=void 0===b.inComment?!1:b.inComment,f=void 0===b.newList?[]:b.newList;if(c>=a.length)return f;var h=a[c];b=h.lino;h=h.token;var k={list:a,index:c+1,inComment:!1,newList:f.concat({lino:b,index:c,token:h})};f={list:a,index:c+1,inComment:!0,newList:f.concat({lino:b,index:c,comment:!0,token:h})};return d&&0<c&&b===a[c-1].lino?e.markComments(f):"!"===h.charAt(0)?e.markComments(f):e.markComments(k)},findStrings:function(b){var a=b.original,c=b.line,d=void 0===b.inComment?
     97!1:b.inComment;b=void 0===b.inQuote?!1:b.inQuote;var f=c.charAt(0),h=b&&[" ","\\t"].includes(f)?"\\s":f;if(1===c.length)return h;c=c.substring(1);if("!"===f&&!b)return f+e.findStrings({original:a,line:c,inComment:!0,inQuote:!1});if("`"===f&&!d&&!b)return f+e.findStrings({original:a,line:c,inComment:d,inQuote:!0});if("`"===f&&b)return f+e.findStrings({original:a,line:c,inComment:d,inQuote:!1});if(!d&&!b&&!f.match(/[A-z0-9_\-+*/\- \t]/)){if(["'",'"'].includes(f))throw Error('Bad syntax in "'+a+'":\nStrings in EasyCoder must be enclosed in backticks.');
     98throw Error('Unrecognised character "'+f+'" in "'+a+'".');}return h+e.findStrings({original:a,line:c,inComment:d,inQuote:b})},tokenise:function(b){b=b.map(function(a){var b=a.trim();return b.length?e.findStrings({original:a,line:b}):""}).map(function(a,b){return{lino:b+1,line:a}});var a=b.map(function(a){return a.line.trim().split(/\s+/).map(function(b,c){return{lino:a.lino,index:c,token:b}})});a=[].concat.apply([],a).filter(function(a){return a.token}).map(function(a){return{lino:a.lino,index:a.index,
     99token:a.token.split("\\s").join(" ")}});a=e.markComments({list:a}).filter(function(a){return!a.comment});return{scriptLines:b,tokens:a}}};h.exports=e},{}],9:[function(d,h,f){var e={getItem:function(b){var a=b.getToken();if(!a)return null;if("true"===a)return b.next(),{type:"boolean",content:!0};if("false"===a)return b.next(),{type:"boolean",content:!1};if("`"===a.charAt(0))return b.next(),{type:"constant",numeric:!1,content:a.substring(1,a.length-1)};if(a.charAt(0).match(/[0-9-]/)){var c=eval(a);
     100if(Number.isInteger(c))return b.next(),{type:"constant",numeric:!0,content:c};throw Error("'"+a+"' is not an integer");}a=b.getIndex();c=$jscomp.makeIterator(Object.keys(b.domain));for(var d=c.next();!d.done;d=c.next())if(d=d.value,b.rewindTo(a),d=b.domain[d].value.compile(b))return d;return null},compile:function(b){var a=b.getToken(),c=e.getItem(b);if(!c)throw Error("Undefined value: '"+a+"'");if("cat"===b.getToken()){for(a={type:"cat",numeric:!1,parts:[c]};b.tokenIs("cat");)b.next(),a.parts.push(b.value.getItem(b));
     101return a}return c},doValue:function(b,a){switch(a.type){case "cat":return{type:"constant",numeric:!1,content:a.parts.reduce(function(a,c){return a+e.doValue(b,c).content},"")};case "boolean":case "constant":return a;case "symbol":var c=b.getSymbolRecord(a.name);if(c.isValueHolder){if(a=c.value[c.index]){c=a.content;if(null===c||"undefined"===typeof c)a.content=a.numeric?0:"";return a}return null}return b.domain[c.domain].value.get(b,a)}return b.domain[a.domain].value.get(b,a)},constant:function(b,
     102a){return{type:"constant",numeric:a,content:b}},evaluate:function(b,a){if(!a)return{type:"constant",numeric:!1,content:""};var c=e.doValue(b,a);if(c)return c;b.runtimeError(b[b.pc].lino,"Can't decode value: "+a)},getValue:function(b,a){return e.evaluate(b,a).content},encode:function(b,a){if(b)switch(a){case "ec":return b.replace(/'/g,"~sq~").replace(/"/g,"~dq~").replace(/\n/g,"%0a").replace(/\r/g,"%0d");case "url":return encodeURIComponent(b.replace(/\s/g,"+"));case "sanitize":return b.normalize("NFD").replace(/[\u0300-\u036f]/g,
     103"")}return b},decode:function(b,a){if(b)switch(a){case "ec":return b.replace(/~dq~/g,'"').replace(/~sq~/g,"'").replace(/%0a/g,"\n").replace(/%0d/g,"\r");case "url":return decodeURIComponent(b).replace(/\+/g," ")}return b}};h.exports=e},{}]},{},[1]);
  • easycoder/trunk/easycoder.js

    r2016526 r2038748  
    22// EasyCoder
    33
    4 const EasyCoder = require('./easycoder/Main');
     4const EasyCoder = require(`./easycoder/Main`);
    55
    66EasyCoder.timestamp = Date.now();
     
    88
    99window.onload = function () {
    10   console.log(`${Date.now() - EasyCoder.timestamp} ms: Page loaded; reset timer & start EasyCoder`);
    11   EasyCoder.timestamp = Date.now();
    12   EasyCoder.scripts = {};
    13   window.EasyCoder = EasyCoder;
    14   const script = document.getElementById('easycoder-script');
    15   if (script) {
    16     script.style.display = 'none';
    17     try {
    18       EasyCoder.start(script.innerText);
    19     } catch (err) {
    20       EasyCoder.reportError(err);
    21     }
    22   }
     10    console.log(`${Date.now() - EasyCoder.timestamp} ms: Page loaded; reset timer & start EasyCoder`);
     11    EasyCoder.timestamp = Date.now();
     12    EasyCoder.scripts = {};
     13    window.EasyCoder = EasyCoder;
     14    const script = document.getElementById(`easycoder-script`);
     15    if (script) {
     16        script.style.display = `none`;
     17        try {
     18            EasyCoder.start(script.innerText);
     19        } catch (err) {
     20            EasyCoder.reportError(err);
     21        }
     22    }
    2323};
    2424},{"./easycoder/Main":6}],2:[function(require,module,exports){
     
    3535        v2 = v2.toString();
    3636    }
     37    if (!val1.numeric && typeof v1 === `undefined`) {
     38        v1 = ``;
     39    }
     40    if (!val2.numeric && typeof v2 === `undefined`) {
     41        v2 = ``;
     42    }
    3743    if (v1 > v2) {
    3844        return 1;
     
    216222    rewind: () => {
    217223        _this.index = _this.savedMark;
     224    },
     225
     226    rewindTo: index => {
     227        _this.index = index;
    218228    },
    219229
     
    301311            return;
    302312        }
    303         //      console.log(`Compile keyword '${keyword}'`);
     313        // console.log(`Compile keyword '${keyword}'`);
    304314        _this.warnings = [];
    305315        const pc = _this.program.length;
     
    544554    },
    545555
     556    Callback: {
     557
     558        compile: compiler => {
     559            compiler.compileVariable(`core`, `callback`);
     560            return true;
     561        },
     562
     563        run: program => {
     564            return program[program.pc].pc + 1;
     565        }
     566    },
    546567    Clear: {
    547568
     
    550571            compiler.next();
    551572            if (compiler.isSymbol()) {
    552                 const symbol = compiler.getToken();
    553                 compiler.next();
    554                 compiler.addCommand({
    555                     domain: `core`,
    556                     keyword: `clear`,
    557                     lino,
    558                     symbol
    559                 });
    560                 return true;
     573                const symbolRecord = compiler.getSymbolRecord();
     574                if (symbolRecord.isValueHolder) {
     575                    const symbol = compiler.getToken();
     576                    compiler.next();
     577                    compiler.addCommand({
     578                        domain: `core`,
     579                        keyword: `clear`,
     580                        lino,
     581                        symbol
     582                    });
     583                    return true;
     584                }
     585                compiler.warning(`'Variable '${symbolRecord.name}' does not hold a value`);
    561586            }
    562587            return false;
     
    768793            } else {
    769794                // Here we should already have the target.
    770                 if (target === undefined) {
     795                if (typeof target === `undefined`) {
    771796                    compiler.warning(`core ${_this.name}: No target variable given`);
    772797                }
     
    869894            const lino = compiler.getLino();
    870895            compiler.next();
    871             if (compiler.tokenIs(`to`)) {
     896            if (compiler.nextTokenIs(`to`)) {
    872897                compiler.next();
    873898            }
     
    899924        compile: compiler => {
    900925            const lino = compiler.getLino();
    901             compiler.next();
    902             if (compiler.tokenIs(`to`)) {
     926            if (compiler.nextTokenIs(`to`)) {
    903927                compiler.next();
    904928            }
     
    922946                        return pc.pc;
    923947                    }
    924                     program.runtimeError(command.lino, `Unknown symbol '${command.label}'`);
    925                     return 0;
    926                 }
     948                }
     949                program.runtimeError(command.lino, `Unknown symbol '${command.label}'`);
     950                return 0;
    927951            }
    928952            return command.goto;
     
    934958        compile: compiler => {
    935959            const lino = compiler.getLino();
    936             compiler.next();
    937             if (compiler.tokenIs(`to`)) {
     960            if (compiler.nextTokenIs(`to`)) {
    938961                compiler.next();
    939962            }
     
    10361059                }
    10371060            }
     1061            if (compiler.tokenIs(`and`)) {
     1062                throw new Error(`Imports do not match exports`);
     1063            }
    10381064            return true;
    10391065        },
     
    10881114        compile: compiler => {
    10891115            const lino = compiler.getLino();
    1090             compiler.next();
    1091             if (compiler.tokenIs(`plugin`)) {
    1092                 compiler.next();
    1093                 const name = compiler.getValue();
    1094                 compiler.addCommand({
    1095                     domain: `core`,
    1096                     keyword: `load`,
    1097                     lino,
    1098                     name
    1099                 });
    1100                 return true;
    1101             }
     1116            const type = compiler.nextToken();
     1117            switch (type) {
     1118                case `plugin`:
     1119                    const name = compiler.getNextValue();
     1120                    compiler.addCommand({
     1121                        domain: `core`,
     1122                        keyword: `load`,
     1123                        lino,
     1124                        name
     1125                    });
     1126                    return true;
     1127            }
     1128            return false;
    11021129        },
    11031130
     
    11051132            const command = program[program.pc];
    11061133            const name = program.getValue(command.name);
    1107             if (program.checkPlugin(name)) {
    1108                 return command.pc + 1;
    1109             }
    1110             EasyCoder_Plugins.getLocalPlugin(program.getPluginsPath, name, program.getPlugin, program.addLocalPlugin, function () {
    1111                 program.run(command.pc + 1);
    1112             });
    1113             return 0;
     1134            switch (command.keyword) {
     1135                case `load`:
     1136                    if (program.checkPlugin(name)) {
     1137                        return command.pc + 1;
     1138                    }
     1139                    EasyCoder_Plugins.getLocalPlugin(program.getPluginsPath, name, program.getPlugin, program.addLocalPlugin, function () {
     1140                        program.run(command.pc + 1);
     1141                    });
     1142                    return 0;
     1143            }
    11141144        }
    11151145    },
     
    11681198            } else {
    11691199                // Here we should already have the target.
    1170                 if (target === undefined) {
     1200                if (typeof target === `undefined`) {
    11711201                    compiler.warning(`core multiply: No target variable given`);
    11721202                }
     
    12551285            const lino = compiler.getLino();
    12561286            const action = compiler.nextToken();
    1257             compiler.next();
    12581287            switch (action) {
    12591288                case `trigger`:
    12601289                case `close`:
    12611290                case `restore`:
     1291                case `message`:
     1292                    compiler.next();
    12621293                    compiler.addCommand({
    12631294                        domain: `core`,
     
    12681299                    return compiler.completeHandler();
    12691300            }
     1301            if (compiler.isSymbol()) {
     1302                const symbolRecord = compiler.getSymbolRecord();
     1303                if (symbolRecord.keyword === `callback`) {
     1304                    compiler.next();
     1305                    compiler.addCommand({
     1306                        domain: `core`,
     1307                        keyword: `on`,
     1308                        lino,
     1309                        action: symbolRecord.name
     1310                    });
     1311                    return compiler.completeHandler();
     1312                }
     1313            }
    12701314            return false;
    12711315        },
     
    12731317        run: program => {
    12741318            const command = program[program.pc];
     1319            const cb = command.pc + 2;
    12751320            switch (command.action) {
    12761321                case `trigger`:
    1277                     program.onTrigger = command.pc + 2;
     1322                    program.onTrigger = cb;
    12781323                    break;
    12791324                case `close`:
    1280                     program.onClose = command.pc + 2;
     1325                    program.onClose = cb;
    12811326                    break;
    12821327                case `restore`:
    1283                     program.onRestore = command.pc + 2;
     1328                    program.onRestore = cb;
     1329                    break;
     1330                case `message`:
     1331                    program.onMessage = cb;
    12841332                    break;
    12851333                default:
    1286                     program.runtimeError(command.lino, `Unknown action '${command.action}'`);
    1287                     return 0;
     1334                    const callbacklRecord = program.getSymbolRecord(command.action);
     1335                    if (callbacklRecord) {
     1336                        callbacklRecord.cb = cb;
     1337                    } else {
     1338                        program.runtimeError(command.lino, `Unknown action '${command.action}'`);
     1339                        return 0;
     1340                    }
    12881341            }
    12891342            return command.pc + 1;
     
    13941447            const command = program[program.pc];
    13951448            const original = program.getValue(command.original);
    1396             const org = original.split(`\\r`).join(`\r`).split(`\\n`).join(`\n`);
     1449            // const org = original.split(`\\r`).join(`\r`).split(`\\n`).join(`\n`);
    13971450            const replacement = program.getValue(command.replacement);
    13981451            const target = program.getSymbolRecord(command.target);
    13991452            const value = program.getValue(target.value[target.index]);
    1400             const content = value.split(org).join(replacement);
     1453            const content = value.split(original).join(replacement);
    14011454            target.value[target.index] = {
    14021455                type: `constant`,
     
    15021555    },
    15031556
     1557    Send: {
     1558
     1559        compile: compiler => {
     1560            const lino = compiler.getLino();
     1561            const message = compiler.getNextValue();
     1562            var recipient;
     1563            if (compiler.tokenIs(`to`)) {
     1564                if (compiler.nextTokenIs(`parent`)) {
     1565                    recipient = `parent`;
     1566                } else if (compiler.isSymbol) {
     1567                    const moduleRecord = compiler.getSymbolRecord();
     1568                    if (moduleRecord.keyword !== `module`) {
     1569                        throw new Error(`'${moduleRecord.name}' is not a module`);
     1570                    }
     1571                    recipient = moduleRecord.name;
     1572                }
     1573                compiler.next();
     1574                compiler.addCommand({
     1575                    domain: `core`,
     1576                    keyword: `send`,
     1577                    lino,
     1578                    message,
     1579                    recipient
     1580                });
     1581            }
     1582            return true;
     1583        },
     1584
     1585        run: program => {
     1586            const command = program[program.pc];
     1587            const message = program.getValue(command.message);
     1588            if (command.recipient === `parent`) {
     1589                const parent = program.parent;
     1590                if (parent) {
     1591                    const onMessage = program.parent.onMessage;
     1592                    if (onMessage) {
     1593                        parent.message = message;
     1594                        parent.run(parent.onMessage);
     1595                    }
     1596                }
     1597            } else {
     1598                const recipient = program.getSymbolRecord(command.recipient);
     1599                recipient.program.message = message;
     1600                recipient.program.run(recipient.program.onMessage);
     1601            }
     1602            return command.pc + 1;
     1603        }
     1604    },
     1605
    15041606    Set: {
    15051607
    15061608        compile: compiler => {
    15071609            const lino = compiler.getLino();
    1508             compiler.next();
    1509             if (compiler.isSymbol()) {
     1610            if (compiler.nextIsSymbol()) {
    15101611                const targetRecord = compiler.getSymbolRecord();
    15111612                if (!targetRecord.isValueHolder) {
    15121613                    return false;
    15131614                }
    1514                 compiler.next();
    1515                 if (compiler.tokenIs(`to`)) {
     1615                if (compiler.nextTokenIs(`to`)) {
    15161616                    compiler.next();
    15171617                    const value = [];
     
    15291629                        keyword: `set`,
    15301630                        lino,
    1531                         type: `setArray`,
     1631                        request: `setArray`,
    15321632                        target: targetRecord.name,
    15331633                        value
     
    15391639                    keyword: `set`,
    15401640                    lino,
    1541                     type: `setBoolean`,
     1641                    request: `setBoolean`,
    15421642                    target: targetRecord.name
    15431643                });
     
    15501650                    keyword: `set`,
    15511651                    lino,
    1552                     type: `setReady`
     1652                    request: `setReady`
    15531653                });
    15541654                return true;
     1655            }
     1656            if (compiler.tokenIs(`property`)) {
     1657                const name = compiler.getNextValue();
     1658                if (compiler.tokenIs(`of`)) {
     1659                    if (compiler.nextIsSymbol()) {
     1660                        const targetRecord = compiler.getSymbolRecord();
     1661                        if (targetRecord.keyword === `variable`) {
     1662                            if (compiler.nextTokenIs(`to`)) {
     1663                                const value = compiler.getNextValue();
     1664                                compiler.addCommand({
     1665                                    domain: `core`,
     1666                                    keyword: `set`,
     1667                                    lino,
     1668                                    request: `setProperty`,
     1669                                    target: targetRecord.name,
     1670                                    name,
     1671                                    value
     1672                                });
     1673                                return true;
     1674                            }
     1675                        }
     1676                    }
     1677                }
    15551678            }
    15561679            if (compiler.tokenIs(`the`)) {
     
    15741697                            keyword: `set`,
    15751698                            lino,
    1576                             type: `setElements`,
     1699                            request: `setElements`,
    15771700                            symbol,
    15781701                            value
     
    15831706            }
    15841707            if (compiler.tokenIs(`encoding`)) {
    1585                 compiler.next();
    1586                 if (compiler.tokenIs(`to`)) {
    1587                     compiler.next();
    1588                     const encoding = compiler.getValue();
     1708                if (compiler.nextTokenIs(`to`)) {
     1709                    const encoding = compiler.getNextValue();
    15891710                    compiler.addCommand({
    15901711                        domain: `core`,
    15911712                        keyword: `set`,
    1592                         type: `encoding`,
     1713                        request: `encoding`,
    15931714                        lino,
    15941715                        encoding
     
    15991720                return false;
    16001721            }
     1722            if (compiler.tokenIs(`payload`)) {
     1723                if (compiler.nextTokenIs(`of`)) {
     1724                    if (compiler.nextIsSymbol()) {
     1725                        const callbackRecord = compiler.getSymbolRecord();
     1726                        if (callbackRecord.keyword === `callback`) {
     1727                            if (compiler.nextTokenIs(`to`)) {
     1728                                const payload = compiler.getNextValue();
     1729                                compiler.addCommand({
     1730                                    domain: `core`,
     1731                                    keyword: `set`,
     1732                                    request: `setPayload`,
     1733                                    lino,
     1734                                    callback: callbackRecord.name,
     1735                                    payload
     1736                                });
     1737                                return true;
     1738                            }
     1739                        }
     1740                    }
     1741                }
     1742            }
    16011743            return false;
    16021744        },
    16031745
    16041746        run: program => {
     1747            let targetRecord;
    16051748            const command = program[program.pc];
    1606             switch (command.type) {
     1749            switch (command.request) {
    16071750                case `setBoolean`:
    16081751                    const target = program.getSymbolRecord(command.target);
     
    16251768                    symbol.index = 0;
    16261769                    symbol.value = [];
     1770                    symbol.element = [];
    16271771                    for (var n = 0; n < symbol.elements; n++) {
    16281772                        symbol.value.push({});
    1629                         symbol.element.push(``);
     1773                        symbol.element.push({});
    16301774                    }
    16311775                    break;
    16321776                case `setArray`:
    1633                     const targetRecord = program.getSymbolRecord(command.target);
     1777                    targetRecord = program.getSymbolRecord(command.target);
    16341778                    targetRecord.elements = command.value.length;
    16351779                    targetRecord.value = command.value;
     
    16371781                case `encoding`:
    16381782                    program.encoding = program.getValue(command.encoding);
     1783                    break;
     1784                case `setProperty`:
     1785                    targetRecord = program.getSymbolRecord(command.target);
     1786                    let targetValue = program.getValue(targetRecord.value[targetRecord.index]);
     1787                    if (!targetValue) {
     1788                        targetValue = `{}`;
     1789                    }
     1790                    let targetJSON = ``;
     1791                    try {
     1792                        targetJSON = JSON.parse(targetValue);
     1793                    } catch (err) {
     1794                        program.runtimeError(command.lino, `Can't parse ${targetRecord.name}`);
     1795                        return 0;
     1796                    }
     1797                    const itemName = program.getValue(command.name);
     1798                    const itemValue = program.evaluate(command.value);
     1799                    if (itemValue) {
     1800                        if (itemValue.type === `boolean`) {
     1801                            targetJSON[itemName] = itemValue.content;
     1802                        } else {
     1803                            targetJSON[itemName] = itemValue.numeric ? itemValue.content : itemValue.content.substr(0, 2) === `{"` ? JSON.parse(itemValue.content) : itemValue.content.split(`"`).join(`\\"`);
     1804                        }
     1805                        targetRecord.value[targetRecord.index] = {
     1806                            type: `constant`,
     1807                            numeric: false,
     1808                            content: JSON.stringify(targetJSON)
     1809                        };
     1810                    }
     1811                    break;
     1812                case `setPayload`:
     1813                    program.getSymbolRecord(command.callback).payload = program.getValue(command.payload);
    16391814                    break;
    16401815                default:
     
    19722147            case `begin`:
    19732148                return EasyCoder_Core.Begin;
     2149            case `callback`:
     2150                return EasyCoder_Core.Callback;
    19742151            case `clear`:
    19752152                return EasyCoder_Core.Clear;
     
    20212198            case `script`:
    20222199                return EasyCoder_Core.Script;
     2200            case `send`:
     2201                return EasyCoder_Core.Send;
    20232202            case `set`:
    20242203                return EasyCoder_Core.Set;
     
    21782357                };
    21792358            }
     2359            if (token === `break`) {
     2360                compiler.next();
     2361                return {
     2362                    domain: `core`,
     2363                    type: `break`
     2364                };
     2365            }
    21802366            if (token === `encode`) {
    21812367                compiler.next();
     
    22442430                compiler.next();
    22452431            }
    2246             switch (compiler.getToken()) {
     2432            const type = compiler.getToken();
     2433            switch (type) {
     2434                case `elements`:
    22472435                case `index`:
    2248                     compiler.next();
    2249                     if (compiler.tokenIs(`of`)) {
    2250                         compiler.next();
    2251                         if (compiler.isSymbol()) {
     2436                    if (compiler.nextTokenIs(`of`)) {
     2437                        if (compiler.nextIsSymbol()) {
    22522438                            const name = compiler.getToken();
    22532439                            compiler.next();
    22542440                            return {
    22552441                                domain: `core`,
    2256                                 type: `index`,
     2442                                type,
    22572443                                name
    22582444                            };
     
    22612447                    break;
    22622448                case `value`:
    2263                     compiler.next();
    2264                     if (compiler.tokenIs(`of`)) {
     2449                    if (compiler.nextTokenIs(`of`)) {
    22652450                        compiler.next();
    22662451                        const value = compiler.getValue();
     
    22732458                    break;
    22742459                case `length`:
    2275                     compiler.next();
    2276                     if (compiler.tokenIs(`of`)) {
     2460                    if (compiler.nextTokenIs(`of`)) {
    22772461                        compiler.next();
    22782462                        const value = compiler.getValue();
     
    22852469                    break;
    22862470                case `left`:
    2287                     compiler.next();
    2288                     const leftCount = compiler.getValue();
     2471                case `right`:
     2472                    const count = compiler.getNextValue();
    22892473                    if (compiler.tokenIs(`of`)) {
    2290                         compiler.next();
    2291                         const leftValue = compiler.getValue();
     2474                        const value = compiler.getNextValue();
    22922475                        return {
    22932476                            domain: `core`,
    2294                             type: `left`,
    2295                             count: leftCount,
    2296                             value: leftValue
     2477                            type,
     2478                            count,
     2479                            value
    22972480                        };
    22982481                    }
    22992482                    break;
    2300                 case `right`:
    2301                     compiler.next();
    2302                     const rightCount = compiler.getValue();
     2483                case `from`:
     2484                    const from = compiler.getNextValue();
     2485                    const to = compiler.tokenIs(`to`) ? compiler.getNextValue() : null;
    23032486                    if (compiler.tokenIs(`of`)) {
    2304                         compiler.next();
    2305                         const rightValue = compiler.getValue();
     2487                        const value = compiler.getNextValue();
    23062488                        return {
    23072489                            domain: `core`,
    2308                             type: `right`,
    2309                             count: rightCount,
    2310                             value: rightValue
     2490                            type,
     2491                            from,
     2492                            to,
     2493                            value
    23112494                        };
    23122495                    }
    23132496                    break;
    23142497                case `position`:
    2315                     compiler.next();
    2316                     if (compiler.tokenIs(`of`)) {
    2317                         compiler.next();
     2498                    if (compiler.nextTokenIs(`of`)) {
    23182499                        var last = false;
    2319                         if (compiler.tokenIs(`the`)) {
    2320                             compiler.next();
    2321                             if (compiler.tokenIs(`last`)) {
     2500                        if (compiler.nextTokenIs(`the`)) {
     2501                            if (compiler.nextTokenIs(`last`)) {
    23222502                                compiler.next();
    23232503                                last = true;
     
    23262506                        const needle = compiler.getValue();
    23272507                        if (compiler.tokenIs(`in`)) {
    2328                             compiler.next();
    2329                             const haystack = compiler.getValue();
     2508                            const haystack = compiler.getNextValue();
    23302509                            return {
    23312510                                domain: `core`,
     
    23382517                    }
    23392518                    break;
     2519                case `payload`:
     2520                    if (compiler.nextTokenIs(`of`)) {
     2521                        if (compiler.nextIsSymbol()) {
     2522                            const callbackRecord = compiler.getSymbolRecord();
     2523                            if (callbackRecord.keyword === `callback`) {
     2524                                compiler.next();
     2525                                return {
     2526                                    domain: `core`,
     2527                                    type: `payload`,
     2528                                    callback: callbackRecord.name
     2529                                };
     2530                            }
     2531                        }
     2532                    }
     2533                    break;
     2534                case `message`:
     2535                case `error`:
     2536                    compiler.next();
     2537                    return {
     2538                        domain: `core`,
     2539                        type
     2540                    };
    23402541            }
    23412542            return null;
     
    23492550                        numeric: false,
    23502551                        content: value.content
     2552                    };
     2553                case `elements`:
     2554                    return {
     2555                        type: `constant`,
     2556                        numeric: true,
     2557                        content: program.getSymbolRecord(value.name).elements
    23512558                    };
    23522559                case `index`:
     
    24072614                    };
    24082615                case `right`:
    2409                     const rstr = program.getValue(value.value);
    24102616                    return {
    24112617                        type: `constant`,
    24122618                        numeric: false,
    2413                         content: rstr.substr(rstr.length - program.getValue(value.count))
     2619                        content: program.getValue(value.value).substr(rstr.length - program.getValue(value.count))
     2620                    };
     2621                case `from`:
     2622                    const from = program.getValue(value.from);
     2623                    const to = value.to ? program.getVaue(value.to) : null;
     2624                    const fstr = program.getValue(value.value);
     2625                    return {
     2626                        type: `constant`,
     2627                        numeric: false,
     2628                        content: to ? fstr.substr(from, to) : fstr.substr(from)
    24142629                    };
    24152630                case `position`:
     
    24202635                        numeric: true,
    24212636                        content: value.last ? haystack.lastIndexOf(needle) : haystack.indexOf(needle)
     2637                    };
     2638                case `payload`:
     2639                    return {
     2640                        type: `constant`,
     2641                        numeric: false,
     2642                        content: program.getSymbolRecord(value.callback).payload
    24222643                    };
    24232644                case `modulo`:
     
    24462667                        numeric: false,
    24472668                        content: `\n`
     2669                    };
     2670                case `break`:
     2671                    return {
     2672                        type: `constant`,
     2673                        numeric: false,
     2674                        content: `<br />`
    24482675                    };
    24492676                case `encode`:
     
    24852712                    const propertyContent = program.getValue(propertyRecord.value[propertyRecord.index]);
    24862713                    var content = ``;
    2487                     if (typeof propertyContent === `object`) {
    2488                         content = propertyContent[property];
    2489                     } else {
    2490                         try {
    2491                             content = JSON.parse(propertyContent)[property];
    2492                         } catch (err) {
    2493                             console.log(`Can't parse '${propertyContent}': ${err.message}`);
    2494                             content = ``;
     2714                    if (property && propertyContent) {
     2715                        if (typeof propertyContent === `object`) {
     2716                            content = propertyContent[property];
     2717                        } else if (propertyContent.charAt(0) === `{`) {
     2718                            try {
     2719                                content = JSON.parse(propertyContent)[property];
     2720                            } catch (err) {
     2721                                console.log(`Can't parse '${propertyContent}': ${err.message}`);
     2722                            }
    24952723                        }
    24962724                    }
     
    24982726                        type: `constant`,
    24992727                        numeric: Number.isInteger(content),
    2500                         content
     2728                        content: typeof content === `object` ? JSON.stringify(content) : content
    25012729                    };
    25022730                case `module`:
     
    25062734                        numeric: false,
    25072735                        content: module.program
     2736                    };
     2737                case `message`:
     2738                    content = program.message;
     2739                    return {
     2740                        type: `constant`,
     2741                        numeric: false,
     2742                        content
     2743                    };
     2744                case `error`:
     2745                    content = program.errorMessage;
     2746                    return {
     2747                        type: `constant`,
     2748                        numeric: false,
     2749                        content
    25082750                    };
    25092751            }
     
    26032845                }
    26042846            } catch (err) {
    2605                 program.runtimeError(command.lino, err.message);
     2847                compiler.warning(`Can't get a value`);
    26062848                return 0;
    26072849            }
     
    27482990
    27492991    getFormattedValue: function (value) {
    2750         const v = EasyCoder_Value.getValue(this, value);
    2751         if ([`[`, `{`].includes(v[0])) {
    2752             return JSON.stringify(JSON.parse(v), null, 2);
    2753         }
    2754         return v;
     2992        const v = EasyCoder_Value.evaluate(this, value);
     2993        if (v.numeric) {
     2994            return v.content;
     2995        }
     2996        if (v.content.substr(0, 2) === `{"` || v.content.charAt(0) === `[`) {
     2997            return JSON.stringify(JSON.parse(v.content), null, 2);
     2998        }
     2999        return v.content;
    27553000    },
    27563001
     
    27703015
    27713016    run: function (pc) {
    2772         EasyCoder.program = this;
    2773         EasyCoder_Run(this, pc);
     3017        if (pc) {
     3018            EasyCoder.program = this;
     3019            EasyCoder_Run(this, pc);
     3020        }
    27743021    },
    27753022
     
    27803027    },
    27813028
    2782     addJS: function (name, path, loaded) {
     3029    register: program => {
     3030        EasyCoder.program = program;
     3031    },
     3032
     3033    require: (program, src, cb) => {
    27833034        const script = document.createElement(`script`);
    27843035        script.type = `text/javascript`;
    2785         script.src = `${path}/${name}.js`;
     3036        script.src = src;
    27863037        script.onload = function () {
    2787             loaded(name);
     3038            console.log(`${Date.now() - EasyCoder.timestamp} ms: Library ${src} loaded`);
     3039            cb();
    27883040        };
    27893041        document.head.appendChild(script);
    27903042    },
    27913043
    2792     register: program => {
    2793         EasyCoder.program = program;
     3044    isUndefined: item => {
     3045        return typeof item === `undefined`;
    27943046    },
    27953047
     
    28363088        program.domain = this.domain;
    28373089        program.trigger = this.trigger;
    2838         program.addJS = this.addJS;
     3090        program.require = this.require;
     3091        program.isUndefined = this.isUndefined;
    28393092        program.checkPlugin = this.checkPlugin;
    28403093        program.getPlugin = this.getPlugin;
     
    28583111            module.program = program;
    28593112        }
    2860         this.setupTracer();
    2861         //    console.log('Run the script');
    2862         EasyCoder_Run(program, 0);
    2863         //  console.log('Program: '+ JSON.stringify(program, null, 2));
     3113        return program;
    28643114    },
    28653115
    28663116    tokeniseScript: function (file, imports, module, parent) {
    28673117        //  console.log('Tokenise script: ');
     3118        let program = null;
    28683119        const startTokenise = Date.now();
    28693120        const source = EasyCoder_Tokenise.tokenise(file);
     
    28723123        //  console.log('Source: ' + JSON.stringify(source, null, 2));
    28733124        try {
    2874             EasyCoder.compileScript(source, imports, module, parent);
     3125            program = EasyCoder.compileScript(source, imports, module, parent);
    28753126        } catch (err) {
    28763127            if (err.message !== `stop`) {
    28773128                this.reportError(err, EasyCoder.program, source);
    28783129            }
     3130        }
     3131        this.setupTracer();
     3132        //    console.log('Run the script');
     3133        if (program) {
     3134            //  console.log('Program: '+ JSON.stringify(program, null, 2));
     3135            EasyCoder_Run(program, 0);
    28793136        }
    28803137    },
     
    29383195    },
    29393196
    2940     loadPluginJs: pathname => {
    2941         console.log(`${Date.now() - EasyCoder.timestamp} ms: Load plugins.js`);
    2942         const fullPath = `${window.location.origin}${pathname}/easycoder/plugins.js`;
    2943         const ajax = new XMLHttpRequest();
    2944         ajax.open(`GET`, fullPath, true);
    2945         ajax.onreadystatechange = function () {
    2946             if (ajax.readyState === 4) {
    2947                 switch (ajax.status) {
    2948                     case 200:
    2949                         const script = document.createElement(`script`);
    2950                         script.src = `${window.location.origin}${pathname}/easycoder/plugins.js`;
    2951                         script.type = `text/javascript`;
    2952                         script.onload = function () {
    2953                             EasyCoder_Plugins.getGlobalPlugins(EasyCoder.timestamp, pathname, EasyCoder.setPluginCount, EasyCoder.getPlugin, EasyCoder.addGlobalPlugin);
    2954                         };
    2955                         document.head.appendChild(script);
    2956                         EasyCoder.pluginsPath = pathname;
    2957                         break;
    2958                     case 404:
    2959                         if (pathname) {
    2960                             EasyCoder.loadPluginJs(pathname.slice(0, pathname.lastIndexOf(`/`)));
    2961                         } else {
    2962                             EasyCoder.reportError({
    2963                                 message: `Can't load plugins.js`
    2964                             }, EasyCoder.program, this.source);
    2965                         }
    2966                         break;
    2967                 }
    2968             }
     3197    loadPluginJs: path => {
     3198        console.log(`${Date.now() - EasyCoder.timestamp} ms: Load ${path}/easycoder/plugins.js`);
     3199        const script = document.createElement(`script`);
     3200        script.src = `${window.location.origin}${path}/easycoder/plugins.js`;
     3201        script.type = `text/javascript`;
     3202        script.onload = function () {
     3203            EasyCoder_Plugins.getGlobalPlugins(EasyCoder.timestamp, path, EasyCoder.setPluginCount, EasyCoder.getPlugin, EasyCoder.addGlobalPlugin);
    29693204        };
    2970         ajax.send();
     3205        script.onerror = () => {
     3206            if (path) {
     3207                EasyCoder.loadPluginJs(path.slice(0, path.lastIndexOf(`/`)));
     3208            } else {
     3209                EasyCoder.reportError({
     3210                    message: `Can't load plugins.js`
     3211                }, EasyCoder.program, _this.source);
     3212            }
     3213        };
     3214        document.head.appendChild(script);
     3215        EasyCoder.pluginsPath = path;
    29713216    },
    29723217
     
    31893434    }) => {
    31903435        const c = line.charAt(0);
    3191         const ch = inQuote && c === ` ` ? `\\s` : c;
     3436        const ch = inQuote && [` `, `\\t`].includes(c) ? `\\s` : c;
    31923437        if (line.length === 1) {
    31933438            return ch;
     
    32033448                return c + EasyCoder_Tokenise.findStrings({ original, line: tail, inComment, inQuote: false });
    32043449            }
    3205             if (!inComment && !inQuote && !c.match(/[A-z0-9_\-+*/\- ]/)) {
    3206                 if (c == `'` || c == `"`) {
     3450            if (!inComment && !inQuote && !c.match(/[A-z0-9_\-+*/\- \t]/)) {
     3451                if ([`'`, `"`].includes(c)) {
    32073452                    throw new Error(`Bad syntax in "${original}":\nStrings in EasyCoder must be enclosed in backticks.`);
    32083453                } else {
     
    32203465
    32213466        // Convert quoted spaces to \\s
    3222         const markedSpaces = file.map(line => {
     3467        const markedSpaces = file.map(original => {
     3468            const line = original.trim();
    32233469            if (line.length) {
    3224                 return EasyCoder_Tokenise.findStrings({ original: line, line });
     3470                return EasyCoder_Tokenise.findStrings({ original, line });
    32253471            }
    32263472            return ``;
     
    32703516const EasyCoder_Value = {
    32713517
    3272   getItem: compiler => {
    3273     const token = compiler.getToken();
    3274     if (!token) {
    3275       return null;
    3276     }
    3277 
    3278     // Check for a boolean
    3279     if (token === 'true') {
    3280       compiler.next();
    3281       return {
    3282         type: 'boolean',
    3283         content: true
    3284       };
    3285     }
    3286 
    3287     if (token === 'false') {
    3288       compiler.next();
    3289       return {
    3290         type: 'boolean',
    3291         content: false
    3292       };
    3293     }
    3294 
    3295     // Check for a string constant
    3296     if (token.charAt(0) === '`') {
    3297       compiler.next();
    3298       const value = {
    3299         type: 'constant',
    3300         numeric: false,
    3301         content: token.substring(1, token.length - 1)
    3302       };
    3303       return value;
    3304     }
    3305 
    3306     // Check for a numeric constant
    3307     if (token[0].match(/[0-9\-]/)) {
    3308       const val = eval(token);
    3309       if (Number.isInteger(val)) {
    3310         compiler.next();
    3311         const value = {
    3312           type: 'constant',
    3313           numeric: true,
    3314           content: val
    3315         };
    3316         return value;
    3317       } else {
    3318         throw new Error(`'${token}' is not an integer`);
    3319       }
    3320     }
    3321 
    3322     // See if any of the domains can handle it
    3323     for (const name of Object.keys(compiler.domain)) {
    3324       const handler = compiler.domain[name];
    3325       const code = handler.value.compile(compiler);
    3326       if (code) {
    3327         return code;
    3328       }
    3329     }
    3330     return null;
    3331   },
    3332 
    3333   compile: compiler => {
    3334     const token = compiler.getToken();
    3335     const item = compiler.value.getItem(compiler);
    3336     if (!item) {
    3337       throw new Error(`Undefined value: '${token}'`);
    3338     }
    3339 
    3340     if (compiler.getToken() === 'cat') {
    3341       const value = {
    3342         type: 'cat',
    3343         numeric: false,
    3344         parts: [item]
    3345       };
    3346       while (compiler.tokenIs('cat')) {
    3347         compiler.next();
    3348         value.parts.push(compiler.value.getItem(compiler));
    3349       }
    3350       return value;
    3351     }
    3352 
    3353     return item;
    3354   },
    3355 
    3356   // runtime
    3357 
    3358   doValue: (program, value) => {
    3359     //  console.log('Value:doValue:value: '+JSON.stringify(value,null,2));
    3360     // See if it's a constant string, a variable or something else
    3361     const type = value.type;
    3362     switch (type) {
    3363       case 'cat':
    3364         return {
    3365           type: 'constant',
    3366           numeric: false,
    3367           content: value.parts.reduce(function (acc, part) {
    3368             return acc + EasyCoder_Value.doValue(program, part).content;
    3369           }, '')
    3370         };
    3371       case 'boolean':
    3372       case 'constant':
    3373         return value;
    3374       case 'symbol':
    3375         const symbol = program.getSymbolRecord(value.name);
    3376         if (symbol.isValueHolder) {
    3377           const symbolValue = symbol.value[symbol.index];
    3378           if (symbolValue) {
    3379             const v = symbolValue.content;
    3380             if (v === null) {
    3381               symbolValue.content = symbolValue.numeric ? 0 : '';
    3382             }
    3383             return symbolValue;
    3384           } else {
    3385             return null;
    3386           }
    3387         } else {
    3388           const handler = program.domain[symbol.domain].value;
    3389           return handler.get(program, value);
    3390         }
    3391         break;
    3392       default:
    3393         break;
    3394     }
    3395     // Call the given domain to handle a value
    3396     const handler = program.domain[value.domain].value;
    3397     return handler.get(program, value);
    3398   },
    3399 
    3400   constant: (content, numeric) => {
    3401     return {
    3402       type: 'constant',
    3403       numeric,
    3404       content
    3405     };
    3406   },
    3407 
    3408   // runtime
    3409 
    3410   evaluate: (program, value) => {
    3411     if (!value) {
    3412       return {
    3413         type: 'constant',
    3414         numeric: false,
    3415         content: ''
    3416       };
    3417     }
    3418     const result = EasyCoder_Value.doValue(program, value);
    3419     if (result) {
    3420       return result;
    3421     }
    3422     program.runtimeError(program[program.pc].lino, 'Can\'t decode value: ' + value);
    3423   },
    3424 
    3425   getValue: (program, value) => {
    3426     return EasyCoder_Value.evaluate(program, value).content;
    3427   },
    3428 
    3429   // tools
    3430 
    3431   encode: (value, encoding) => {
    3432     if (value) {
    3433       switch (encoding) {
    3434         case 'ec':
    3435           return value.replace(/\'/g, "~sq~").replace(/\"/g, '~dq~');
    3436         case 'url':
    3437           return encodeURIComponent(value.replace(/\s/g, '+'));
    3438       }
    3439     }
    3440     return value;
    3441   },
    3442 
    3443   decode: (value, encoding) => {
    3444     if (value) {
    3445       switch (encoding) {
    3446         case 'ec':
    3447           return value.replace(/~dq~/g, '"').replace(/~sq~/g, "'");
    3448         case 'url':
    3449           const decoded = decodeURIComponent(value);
    3450           return decoded.replace(/\+/g, ' ');
    3451       }
    3452     }
    3453     return value;
    3454   }
     3518    getItem: compiler => {
     3519        const token = compiler.getToken();
     3520        if (!token) {
     3521            return null;
     3522        }
     3523
     3524        // Check for a boolean
     3525        if (token === `true`) {
     3526            compiler.next();
     3527            return {
     3528                type: `boolean`,
     3529                content: true
     3530            };
     3531        }
     3532
     3533        if (token === `false`) {
     3534            compiler.next();
     3535            return {
     3536                type: `boolean`,
     3537                content: false
     3538            };
     3539        }
     3540
     3541        // Check for a string constant
     3542        if (token.charAt(0) === `\``) {
     3543            compiler.next();
     3544            const value = {
     3545                type: `constant`,
     3546                numeric: false,
     3547                content: token.substring(1, token.length - 1)
     3548            };
     3549            return value;
     3550        }
     3551
     3552        // Check for a numeric constant
     3553        if (token.charAt(0).match(/[0-9-]/)) {
     3554            const val = eval(token);
     3555            if (Number.isInteger(val)) {
     3556                compiler.next();
     3557                const value = {
     3558                    type: `constant`,
     3559                    numeric: true,
     3560                    content: val
     3561                };
     3562                return value;
     3563            } else {
     3564                throw new Error(`'${token}' is not an integer`);
     3565            }
     3566        }
     3567
     3568        // See if any of the domains can handle it
     3569        const index = compiler.getIndex();
     3570        for (const name of Object.keys(compiler.domain)) {
     3571            compiler.rewindTo(index);
     3572            const handler = compiler.domain[name];
     3573            const code = handler.value.compile(compiler);
     3574            if (code) {
     3575                return code;
     3576            }
     3577        }
     3578        return null;
     3579    },
     3580
     3581    compile: compiler => {
     3582        const token = compiler.getToken();
     3583        const item = EasyCoder_Value.getItem(compiler);
     3584        if (!item) {
     3585            throw new Error(`Undefined value: '${token}'`);
     3586        }
     3587
     3588        if (compiler.getToken() === `cat`) {
     3589            const value = {
     3590                type: `cat`,
     3591                numeric: false,
     3592                parts: [item]
     3593            };
     3594            while (compiler.tokenIs(`cat`)) {
     3595                compiler.next();
     3596                value.parts.push(compiler.value.getItem(compiler));
     3597            }
     3598            return value;
     3599        }
     3600
     3601        return item;
     3602    },
     3603
     3604    // runtime
     3605
     3606    doValue: (program, value) => {
     3607        //  console.log('Value:doValue:value: '+JSON.stringify(value,null,2));
     3608        // See if it's a constant string, a variable or something else
     3609        const type = value.type;
     3610        switch (type) {
     3611            case `cat`:
     3612                return {
     3613                    type: `constant`,
     3614                    numeric: false,
     3615                    content: value.parts.reduce(function (acc, part) {
     3616                        return acc + EasyCoder_Value.doValue(program, part).content;
     3617                    }, ``)
     3618                };
     3619            case `boolean`:
     3620            case `constant`:
     3621                return value;
     3622            case `symbol`:
     3623                const symbol = program.getSymbolRecord(value.name);
     3624                if (symbol.isValueHolder) {
     3625                    const symbolValue = symbol.value[symbol.index];
     3626                    if (symbolValue) {
     3627                        const v = symbolValue.content;
     3628                        if (v === null || typeof v === `undefined`) {
     3629                            symbolValue.content = symbolValue.numeric ? 0 : ``;
     3630                        }
     3631                        return symbolValue;
     3632                    } else {
     3633                        return null;
     3634                    }
     3635                } else {
     3636                    const handler = program.domain[symbol.domain].value;
     3637                    return handler.get(program, value);
     3638                }
     3639            default:
     3640                break;
     3641        }
     3642        // Call the given domain to handle a value
     3643        const handler = program.domain[value.domain].value;
     3644        return handler.get(program, value);
     3645    },
     3646
     3647    constant: (content, numeric) => {
     3648        return {
     3649            type: `constant`,
     3650            numeric,
     3651            content
     3652        };
     3653    },
     3654
     3655    // runtime
     3656
     3657    evaluate: (program, value) => {
     3658        if (!value) {
     3659            return {
     3660                type: `constant`,
     3661                numeric: false,
     3662                content: ``
     3663            };
     3664        }
     3665        const result = EasyCoder_Value.doValue(program, value);
     3666        if (result) {
     3667            return result;
     3668        }
     3669        program.runtimeError(program[program.pc].lino, `Can't decode value: ` + value);
     3670    },
     3671
     3672    getValue: (program, value) => {
     3673        return EasyCoder_Value.evaluate(program, value).content;
     3674    },
     3675
     3676    // tools
     3677
     3678    encode: (value, encoding) => {
     3679        if (value) {
     3680            switch (encoding) {
     3681                case `ec`:
     3682                    return value.replace(/'/g, `~sq~`).replace(/"/g, `~dq~`).replace(/\n/g, `%0a`).replace(/\r/g, `%0d`);
     3683                case `url`:
     3684                    return encodeURIComponent(value.replace(/\s/g, `+`));
     3685                case `sanitize`:
     3686                    return value.normalize(`NFD`).replace(/[\u0300-\u036f]/g, ``);
     3687            }
     3688        }
     3689        return value;
     3690    },
     3691
     3692    decode: (value, encoding) => {
     3693        if (value) {
     3694            switch (encoding) {
     3695                case `ec`:
     3696                    return value.replace(/~dq~/g, `"`).replace(/~sq~/g, `'`).replace(/%0a/g, `\n`).replace(/%0d/g, `\r`);
     3697                case `url`:
     3698                    const decoded = decodeURIComponent(value);
     3699                    return decoded.replace(/\+/g, ` `);
     3700            }
     3701        }
     3702        return value;
     3703    }
    34553704};
    34563705
  • easycoder/trunk/easycoder.php

    r2016526 r2038748  
    44  * Plugin URI: https://easycoder.software
    55  * Description: Control the appearance and behavior of your posts and pages by embedding simple English-like scripts, without the need to learn JavaScript.
    6   * Version: 2.1.9
     6  * Version: 2.2.0
    77  * Author: EasyCoder Software
    88  * Author URI: https://easycoder.software
     
    1717  function easycoder_enqueue_script() {   
    1818    wp_enqueue_script('easycoder_script', plugin_dir_url( __FILE__ )
    19             . 'easycoder-min.js', array(), '2.1.9');
     19            . 'easycoder-min.js', array(), '2.2.0');
    2020  }
    2121 
  • easycoder/trunk/ec-rest.txt

    r1990067 r2038748  
    2222# You can create a hash value using this URL:
    2323# {Your website}/wp-content/plugins/easycoder/rest.php/_hash/{password}
    24 password=$2y$10$VtzWvsaY4qRfZHKnbkvuyO9xmZ9SgK68FrTCWe9HUW6hrX3kXKpPO
     24password={encrypted password}
  • easycoder/trunk/plugins-sample.js

    r2016526 r2038748  
    4343     * This lets you add a plugin before launching a script, using the 'plugin' command.
    4444     * You must provide a case for every plugin you will be adding;
    45      * use 'ckeditor' as the pattern to follow.
     45     * use any one of them as the pattern to follow.
    4646     */
    4747   
     
    5454                });
    5555            break;
     56
     57        case `ui`:
     58            getPlugin(name,
     59                `${window.location.origin}${path()}/wp-content/plugins/easycoder/plugins/ui.js`,
     60                function() {
     61                    addPlugin(name, EasyCoder_UI, callback);
     62                });
     63            break;
     64
     65        case `gmap`:
     66            getPlugin(name,
     67                `${window.location.origin}${path()}/wp-content/plugins/easycoder/plugins/gmap.js`,
     68                function() {
     69                    addPlugin(name, EasyCoder_GMap, callback);
     70                });
     71            break;
     72
     73        case `showdown`:
     74            getPlugin(name,
     75                `${window.location.origin}${path()}/wp-content/plugins/easycoder/plugins/showdown.js`,
     76                function() {
     77                    addPlugin(name, EasyCoder_Showdown, callback);
     78                });
     79            break;
    5680        }
    5781    }
  • easycoder/trunk/plugins/browser.js

    r2016526 r2038748  
    142142    },
    143143
     144    Clear: {
     145
     146        compile: (compiler) => {
     147            const lino = compiler.getLino();
     148            if (compiler.nextIsSymbol()) {
     149                const symbolRecord = compiler.getSymbolRecord();
     150                if (symbolRecord.extra === `dom`) {
     151                    compiler.next();
     152                    compiler.addCommand({
     153                        domain: `browser`,
     154                        keyword: `clear`,
     155                        lino,
     156                        name: symbolRecord.name
     157                    });
     158                    return true;
     159                }
     160            }
     161            return false;
     162        },
     163
     164        run: (program) => {
     165            const command = program[program.pc];
     166            const targetRecord = program.getSymbolRecord(command.name);
     167            const target = targetRecord.element[targetRecord.index];
     168            target.innerHTML = ``;
     169            return command.pc + 1;
     170        }
     171    },
     172
    144173    Create: {
    145174
     
    227256                break;
    228257            default:
    229                 var parent;
     258                let parent;
    230259                if (command.parent === `body`) {
    231260                    parent = document.body;
     
    233262                    const parentRecord = program.getSymbolRecord(command.parent);
    234263                    if (!parentRecord.element[parentRecord.index]) {
    235                         parentRecord.element[parentRecord.index] =
    236                                 document.getElementById(parentRecord.value[parentRecord.index].content);
     264                        program.runtimeError(command.pc, `Element ${parentRecord.name} does not exist.`);
    237265                    }
    238266                    parent = parentRecord.element[parentRecord.index];
     
    407435                break;
    408436            case `getStorage`:
    409                 const value = window.localStorage.getItem(program.getValue(command.key));
     437                let value = window.localStorage.getItem(program.getValue(command.key));
     438                if (typeof value === `undefined`) {
     439                    value = null;
     440                }
    410441                targetRecord.value[targetRecord.index] = {
    411442                    type: `constant`,
     
    501532            case `replace`:
    502533                compiler.next();
    503                 var url = ``;
    504                 var state = ``;
     534                let url = ``;
     535                let state = ``;
    505536                while (true) {
    506537                    const token = compiler.getToken();
     
    542573            }
    543574            const command = program[program.pc];
    544             var state = program.getValue(command.state);
     575            let state = program.getValue(command.state);
    545576            if (!state) {
    546                 state = `{}`;
    547             }
    548             state = JSON.parse(state);
    549             state.script = program.script;
    550             state = JSON.stringify(state);
     577                state = `{"script":"${program.script}"}`;
     578            }
    551579            const url = program.getValue(command.url);
    552580            switch (command.type) {
     
    638666        compile: (compiler) => {
    639667            const lino = compiler.getLino();
    640             var newWindow = false;
     668            let newWindow = false;
    641669            if (compiler.nextTokenIs(`new`)) {
    642670                newWindow = true;
     
    671699            if (compiler.nextTokenIs(`to`)) {
    672700                const to = compiler.getNextValue();
    673                 var subject = ``;
    674                 var body = ``;
     701                let subject = ``;
     702                let body = ``;
    675703                if (compiler.tokenIs(`subject`)) {
    676704                    subject = compiler.getNextValue();
     
    697725            if (command.subject) {
    698726                window.location.href = `mailto:${program.getValue(command.to)}` +
    699                     `?subject=${program.getValue(command.subject)}&body=${program.getValue(command.body)}`;
     727                    `?subject=${program.getValue(command.subject)}&body=${encodeURIComponent(program.getValue(command.body))}`;
    700728            } else {
    701729                window.location.href = `mailto:${program.getValue(command.to)}`;
     
    801829                const changeItem = program.getSymbolRecord(command.symbol);
    802830                if (changeItem.keyword === `select`) {
    803                     const cssId = changeItem.value[changeItem.index].content;
    804                     if (cssId) {
    805                         const target = document.getElementById(cssId);
    806                         target.targetPc = command.pc + 2;
    807                         target.addEventListener(`change`, function () {
    808                             try {
    809                                 program.run(target.targetPc);
    810                             } catch (err) {
    811                                 console.log(err.message);
    812                                 alert(err.message);
    813                             }
    814                             return false;
    815                         });
    816                     }
     831                    const target = changeItem.element[changeItem.index];
     832                    target.targetPc = command.pc + 2;
     833                    target.addEventListener(`change`, function () {
     834                        try {
     835                            program.run(target.targetPc);
     836                        } catch (err) {
     837                            console.log(err.message);
     838                            alert(err.message);
     839                        }
     840                        return false;
     841                    });
    817842                }
    818843                break;
    819844            case `click`:
    820845                const targetRecord = program.getSymbolRecord(command.symbol);
    821                 targetRecord.element.forEach(function (element, index) {
    822                     var target = targetRecord.element[index];
     846                targetRecord.element.forEach(function (target, index) {
    823847                    target.targetRecord = targetRecord;
    824848                    target.targetIndex = index;
     
    842866                const interceptClickEvent = (e) => {
    843867                    EasyCoder.timestamp = Date.now();
    844                     var target = e.target || e.srcElement;
    845                     var href = ``;
     868                    let target = e.target || e.srcElement;
     869                    let href = ``;
    846870                    while (target.parentNode) {
    847871                        if (target.tagName === `A`) {
     
    854878                    while (target.parentNode) {
    855879                        if (target.id.indexOf(`ec-`) === 0) {
    856                             var id = target.id.slice(3);
    857                             var pos = id.indexOf(`-`);
     880                            let id = target.id.slice(3);
     881                            let pos = id.indexOf(`-`);
    858882                            program.varName = id.slice(0, pos);
    859883                            id = id.slice(pos + 1);
     
    876900                break;
    877901            case `swipe`:
    878                 var xDown;
     902                let xDown;
    879903                const getTouches = (evt) => {
    880904                    return evt.touches || // browser API
     
    913937                break;
    914938            case `key`:
     939                if (typeof document.onKeyListeners === `undefined`) {
     940                    document.onKeyListeners = [];
     941                }
     942                if (!document.onKeyListeners.includes(program)) {
     943                    document.onKeyListeners.push(program);
     944                }
    915945                program.onKeyPc = command.pc + 2;
    916946                document.onkeypress = function (event) {
    917                     program.key = event.key;
    918                     try {
    919                         setTimeout(function () {
    920                             program.run(program.onKeyPc);
    921                         }, 1);
    922                     } catch (err) {
    923                         console.log(`Error: ${err.message}`);
     947                    for (const program of document.onKeyListeners) {
     948                        program.key = event.key;
     949                        try {
     950                            setTimeout(function () {
     951                                program.run(program.onKeyPc);
     952                            }, 1);
     953                        } catch (err) {
     954                            console.log(`Error: ${err.message}`);
     955                        }
    924956                    }
    925957                    return true;
     
    12081240                }
    12091241            } else {
    1210                 var token = compiler.getToken();
     1242                let token = compiler.getToken();
    12111243                if (token === `the`) {
    12121244                    token = compiler.nextToken();
    12131245                }
    12141246                if (token === `title`) {
    1215                     compiler.skip(`to`);
    1216                     const value = compiler.getValue();
    1217                     compiler.addCommand({
    1218                         domain: `browser`,
    1219                         keyword: `set`,
    1220                         lino,
    1221                         type: `setTitle`,
    1222                         value
    1223                     });
    1224                     return true;
     1247                    if (compiler.nextTokenIs(`to`)) {
     1248                        const value = compiler.getNextValue();
     1249                        compiler.addCommand({
     1250                            domain: `browser`,
     1251                            keyword: `set`,
     1252                            lino,
     1253                            type: `setTitle`,
     1254                            value
     1255                        });
     1256                        return true;
     1257                    }
    12251258                } else if (token === `content`) {
    12261259                    if (compiler.nextTokenIs(`of`)) {
     
    13311364                        if (compiler.nextIsSymbol(true)) {
    13321365                            const symbolRecord = compiler.getSymbolRecord();
    1333                             // if (symbolRecord.domain !== 'browser') {
    1334                             //   return false;
    1335                             // }
    13361366                            const symbolName = symbolRecord.name;
    13371367                            compiler.next();
    1338                             var attributeValue = {
     1368                            let attributeValue = {
    13391369                                type: `boolean`,
    13401370                                content: true
     
    13591389                        if (compiler.nextIsSymbol()) {
    13601390                            const symbolRecord = compiler.getSymbolRecord();
    1361                             symbolName = symbolRecord.name;
     1391                            const symbolName = symbolRecord.name;
    13621392                            if (symbolRecord.extra !== `dom`) {
    13631393                                compiler.warning(`'${symbolName}' is not a DOM type`);
     
    13831413                    }
    13841414                    const styleName = compiler.getValue();
    1385                     var type = `setStyle`;
    1386                     var symbolName = ``;
     1415                    let type = `setStyle`;
     1416                    let symbolName = ``;
    13871417                    if (compiler.tokenIs(`of`)) {
    13881418                        if (compiler.nextToken() === `body`) {
     
    14131443                        }
    14141444                    }
     1445                } else if (token === `default`) {
     1446                    if (compiler.nextTokenIs(`of`)) {
     1447                        if (compiler.nextIsSymbol()) {
     1448                            const symbolRecord = compiler.getSymbolRecord();
     1449                            if (symbolRecord.keyword === `select`) {
     1450                                if (compiler.nextTokenIs(`to`)) {
     1451                                    const value = compiler.getNextValue();
     1452                                    compiler.addCommand({
     1453                                        domain: `browser`,
     1454                                        keyword: `set`,
     1455                                        lino,
     1456                                        type: `setDefault`,
     1457                                        name: symbolRecord.name,
     1458                                        value
     1459                                    });
     1460                                    return true;
     1461                                }
     1462                            }
     1463                        }
     1464                    }
    14151465                }
    14161466            }
     
    14211471        run: (program) => {
    14221472            const command = program[program.pc];
    1423             var symbol;
    1424             var value;
    1425             var targetVar;
    1426             var target;
    1427             var targetId;
    1428             var cssId;
     1473            let symbol;
     1474            let value;
     1475            let targetVar;
     1476            let target;
     1477            let targetId;
     1478            let cssId;
    14291479            switch (command.type) {
    14301480            case `setContentVar`:
     
    15571607                document.title = program.getValue(command.value);
    15581608                break;
     1609            case `setDefault`:
     1610                const selectRecord = program.getSymbolRecord(command.name);
     1611                value = program.getValue(command.value);
     1612                const element = selectRecord.element[selectRecord.index];
     1613                for (let n = 0; n < element.options.length; n++) {
     1614                    if (element.options[n].value === value) {
     1615                        element.selectedIndex = n;
     1616                        break;
     1617                    }
     1618                }
     1619                break;
    15591620            default:
    15601621                break;
     
    16101671                    compiler.next();
    16111672                }
    1612                 var alignment = `horizontal`;
     1673                let alignment = `horizontal`;
    16131674                if (compiler.tokenIs(`horizontal`) || compiler.tokenIs(`vertical`)) {
    16141675                    alignment = compiler.getToken();
     
    17371798                    const percent = Math.round((event.loaded / event.total) * 100);
    17381799                    setProgress(percent);
    1739                     setStatus(`${Math.round(percent)}% uploaded...please wait`);
     1800                    setStatus(`${Math.round(percent)}%...`);
    17401801                }, false);
    17411802                ajax.addEventListener(`load`, function (event) {
     
    17631824                            break;
    17641825                        default:
    1765                             var error = ``;
     1826                            let error = ``;
    17661827                            try {
    17671828                                error = JSON.parse(this.responseText ? this.responseText : `{}`);
     
    17991860        case `button`:
    18001861            return EasyCoder_Browser.BUTTON;
     1862        case `clear`:
     1863            return EasyCoder_Browser.Clear;
    18011864        case `create`:
    18021865            return EasyCoder_Browser.Create;
     
    18961959            if (compiler.isSymbol()) {
    18971960                const symbolRecord = compiler.getSymbolRecord();
     1961                if (compiler.nextTokenIs(`is`)) {
     1962                    if (compiler.nextTokenIs(`defined`)) {
     1963                        if (symbolRecord.extra === `dom`) {
     1964                            compiler.next();
     1965                            return {
     1966                                domain: `browser`,
     1967                                type: `defined`,
     1968                                value: symbolRecord.name
     1969                            };
     1970                        }
     1971                        return null;
     1972                    } else {
     1973                        compiler.prev();
     1974                    }
     1975                }
    18981976                switch (symbolRecord.keyword) {
    18991977                case `file`:
    19001978                case `input`:
    19011979                case `select`:
    1902                     compiler.next();
     1980                case `textarea`:
    19031981                    return {
    19041982                        domain: `browser`,
     
    19101988            }
    19111989
    1912             var token = compiler.getToken();
     1990            let token = compiler.getToken();
    19131991            if (token === `mobile`) {
    19141992                compiler.next();
     
    19352013            }
    19362014            if (token === `attribute`) {
    1937                 compiler.next();
    1938                 const attribute = compiler.getValue();
     2015                const attribute = compiler.getNextValue();
    19392016                if (compiler.tokenIs(`of`)) {
    19402017                    compiler.next();
     
    19552032            }
    19562033            if (token === `confirm`) {
    1957                 compiler.next();
    1958                 const text = compiler.getValue();
     2034                const text = compiler.getNextValue();
    19592035                return {
    19602036                    domain: `browser`,
    19612037                    type: `confirm`,
    1962                     text,
    1963                     pre
     2038                    text
    19642039                };
    19652040            }
    19662041            if (token === `prompt`) {
    1967                 compiler.next();
    1968                 const text = compiler.getValue();
     2042                const text = compiler.getNextValue();
    19692043                if (compiler.tokenIs(`with`)) {
    19702044                    const pre = compiler.getNextValue();
     
    20162090            }
    20172091            if ([`width`, `height`].includes(token)) {
    2018                 compiler.next();
    2019                 if (compiler.tokenIs(`of`)) {
    2020                     compiler.next();
    2021                     if (compiler.isSymbol()) {
     2092                if (compiler.nextTokenIs(`of`)) {
     2093                    if (compiler.nextIsSymbol()) {
    20222094                        const symbol = compiler.getSymbolRecord();
    20232095                        compiler.next();
     
    20892161
    20902162        get: (program, value) => {
    2091             var symbolRecord;
    2092             var element;
    2093             var target;
     2163            let symbolRecord;
     2164            let element;
     2165            let target;
     2166            let content;
    20942167            switch (value.type) {
    20952168            case `file`:
    20962169            case `input`:
    20972170            case `select`:
     2171            case `textarea`:
    20982172                symbolRecord = program.getSymbolRecord(value.value);
    20992173                target = symbolRecord.element[symbolRecord.index];
     
    21022176                    numeric: false,
    21032177                    content: target.value
     2178                };
     2179            case `defined`:
     2180                symbolRecord = program.getSymbolRecord(value.value);
     2181                return {
     2182                    domain: `browser`,
     2183                    type: `boolean`,
     2184                    content: typeof symbolRecord.element[symbolRecord.index] !== `undefined`
    21042185                };
    21052186            case `mobile`:
     
    21372218                symbolRecord = program.getSymbolRecord(value.symbol);
    21382219                target = symbolRecord.element[symbolRecord.index];
    2139                 var content;
    21402220                switch (symbolRecord.keyword) {
    21412221                case `input`:
    21422222                case `textarea`:
    21432223                    content = target.value;
     2224                    break;
     2225                case `pre`:
     2226                    content = target.innerHTML;
    21442227                    break;
    21452228                default:
     
    22342317
    22352318        compile: (compiler) => {
    2236             console.log(`Browser:compile`);
    22372319            if (compiler.tokenIs(`confirm`)) {
    22382320                compiler.next();
     
    22432325                    value
    22442326                };
     2327            } else if (compiler.tokenIs(`element`)) {
     2328                if (compiler.nextIsSymbol()) {
     2329                    const symbolRecord = compiler.getSymbolRecord();
     2330                    if (symbolRecord.extra === `dom`) {
     2331                        if (compiler.nextTokenIs(`has`)) {
     2332                            if (compiler.nextTokenIs(`the`)) {
     2333                                compiler.next();
     2334                            }
     2335                            if (compiler.tokenIs(`focus`)) {
     2336                                compiler.next();
     2337                                return {
     2338                                    domain: `browser`,
     2339                                    type: `focus`,
     2340                                    element: symbolRecord.name
     2341                                };
     2342                            }
     2343                        }
     2344                    }
     2345                }
    22452346            }
    22462347            return null;
     
    22512352            case `confirm`:
    22522353                return confirm(program.getValue(condition.value));
     2354            case `focus`:
     2355                const elementRecord = program.getSymbolRecord(condition.element);
     2356                return elementRecord.element[elementRecord.index] === document.activeElement;
    22532357            }
    22542358        }
     
    22652369};
    22662370
    2267 var scrollPosition = 0;
     2371let scrollPosition = 0;
    22682372
    22692373window.addEventListener(`scroll`, function () {
     
    22752379    const state = JSON.parse(event.state);
    22762380    const program = window.EasyCoder.scripts[state.script];
    2277     if (program.onRestore) {
    2278         program.run(program.onRestore);
     2381    if (program) {
     2382        if (program.onRestore) {
     2383            program.run(program.onRestore);
     2384        }
     2385    } else {
     2386        console.log(`No script property in window state object`);
    22792387    }
    22802388};
  • easycoder/trunk/plugins/ckeditor.js

    r2016526 r2038748  
    9898                editor = program.getSymbolRecord(command.editor);
    9999                editor.editor =
    100             CKEDITOR.appendTo(editor.element[editor.index].id, {
    101                 height: 400
    102             });
     100                CKEDITOR.appendTo(editor.element[editor.index].id, {
     101                    height: 400
     102                });
    103103                break;
    104104            case `close`:
     
    127127                return 0;
    128128            case `reset`:
    129                 for (name in CKEDITOR.instances) {
     129                for (const name in CKEDITOR.instances) {
    130130                    CKEDITOR.instances[name].destroy();
    131131                }
     
    157157    value: {
    158158
    159         compile: (compiler) => {
     159        compile: () => {
    160160            return null;
    161161        },
    162         get: (program, value) => {}
     162        get: () => {}
    163163    },
    164164
    165165    condition: {
    166166
    167         compile: (compiler) => {},
    168         test: (program, condition) => {}
     167        compile: () => {},
     168        test: () => {}
    169169    }
    170170};
  • easycoder/trunk/plugins/json.js

    r2016526 r2038748  
    6565            case `set`:
    6666                compiler.next();
    67                 if (compiler.tokenIs(`property`)) {
    68                     const name = compiler.getNextValue();
    69                     if (compiler.tokenIs(`of`)) {
    70                         if (compiler.nextIsSymbol()) {
    71                             const targetRecord = compiler.getSymbolRecord();
    72                             if (targetRecord.keyword === `variable`) {
    73                                 if (compiler.nextTokenIs(`to`)) {
    74                                     const value = compiler.getNextValue();
    75                                     compiler.addCommand({
    76                                         domain: `json`,
    77                                         keyword: `json`,
    78                                         lino,
    79                                         request: `setProperty`,
    80                                         target: targetRecord.name,
    81                                         name,
    82                                         value
    83                                     });
    84                                     return true;
    85                                 }
    86                             }
    87                         }
    88                     }
    89                     return null;
    90                 }
    9167                if (compiler.isSymbol()) {
    9268                    const targetRecord = compiler.getSymbolRecord();
     
    193169                }
    194170                const item = program.getValue(command.item);
    195                 if (itemData[item] === `undefined`) {
     171                if (typeof itemData[item] === `undefined`) {
    196172                    program.runtimeError(command.lino, `No such property '${item}'`);
    197173                }
     
    230206                    numeric: false,
    231207                    content
    232                 };
    233                 break;
    234             case `setProperty`:
    235                 targetRecord = program.getSymbolRecord(command.target);
    236                 const targetValue = targetRecord.value[targetRecord.index];
    237                 var targetItem = ``;
    238                 try {
    239                     targetItem = JSON.parse(program.getValue(targetValue));
    240                 } catch (err) {
    241                     program.runtimeError(command.lino, `Can't parse JSON`);
    242                     return 0;
    243                 }
    244                 const itemName = program.getValue(command.name);
    245                 const itemValue = program.evaluate(command.value);
    246                 //targetItem[itemName] = itemValue.content;
    247                 if (itemValue.type === `boolean`) {
    248                     targetItem[itemName] = itemValue.content;
    249                 } else {
    250                     targetItem[itemName] = itemValue.numeric ? itemValue.content :
    251                         itemValue.content.split(`"`).join(`\\"`);
    252                 }
    253                 targetRecord.value[targetRecord.index] = {
    254                     type: `constant`,
    255                     numeric: false,
    256                     content: JSON.stringify(targetItem)
    257208                };
    258209                break;
     
    270221                // The target is assumed to be a SELECT
    271222                targetRecord = program.getSymbolRecord(command.target);
    272                 const target = document.getElementById(targetRecord.value[targetRecord.index].content);
     223                const target = targetRecord.element[targetRecord.index];
    273224                target.options.length = 0;
    274225                // Get the name of the display field
     
    353304                        if (compiler.nextTokenIs(`from`)) {
    354305                            const url = compiler.getNextValue();
     306                            var onError = null;
     307                            if (compiler.tokenIs(`or`)) {
     308                                compiler.next();
     309                                onError = compiler.getPc() + 1;
     310                                compiler.completeHandler();
     311                            }
    355312                            compiler.addCommand({
    356313                                domain: `json`,
     
    359316                                request: `get`,
    360317                                target: targetRecord.name,
    361                                 url
     318                                url,
     319                                onError
    362320                            });
    363321                            return true;
     
    379337                }
    380338                const url = compiler.getValue();
     339                onError = null;
     340                if (compiler.tokenIs(`or`)) {
     341                    compiler.next();
     342                    onError = compiler.getPc() + 1;
     343                    compiler.completeHandler();
     344                }
    381345                compiler.addCommand({
    382346                    domain: `json`,
     
    385349                    request: `post`,
    386350                    value,
    387                     url
     351                    url,
     352                    onError
    388353                });
    389354                return true;
     
    408373                        if (command.request === `get`) {
    409374                            var content = this.responseText;
    410                             if (![`[`, `{`].includes(content[0])) {
     375                            if (![`[`, `{`].includes(content.charAt(0))) {
    411376                                content = program.decode(content);
    412377                            }
     
    423388                        break;
    424389                    default:
    425                         const error = this.responseText;
    426                         try {
     390                        if (command.onError) {
     391                            program.errorMessage = this.responseText;
     392                            program.run(command.onError);
     393                        } else {
     394                            const error = this.responseText;
    427395                            program.runtimeError(command.lino, error);
    428                         } catch (err) {
    429                             program.reportError(err, program);
    430396                        }
    431397                        break;
     
    443409            case `post`:
    444410                const value = program.getValue(command.value);
    445                 console.log(`POST ${value} to ${path}`);
     411                console.log(`POST to ${path}`);
    446412                ajax.open(`POST`, path);
    447                 if (value[0] === `{`) {
     413                if (value.charAt(0) === `{` || !isNaN(value)) {
    448414                    ajax.setRequestHeader(`Content-Type`, `application/json; charset=UTF-8`);
    449415                    //            console.log(`value=${value}`);
    450                     ajax.send(value);
     416                    ajax.send(value.charAt(0) === `{` ? value : value.toString());
    451417                } else {
    452418                    ajax.setRequestHeader(`Content-Type`, `application/text; charset=UTF-8`);
    453                     console.log(`value=${program.encode(value)}`);
     419                    // console.log(`value=${program.encode(value)}`);
    454420                    ajax.send(program.encode(value));
    455421                }
     
    487453            }
    488454            if (compiler.tokenIs(`json`)) {
    489                 if ([`size`, `count`].includes(compiler.nextToken())) {
     455                const type = compiler.nextToken();
     456                if ([`size`, `count`, `keys`].includes(type)) {
    490457                    compiler.skip(`of`);
    491458                    if (compiler.isSymbol()) {
     
    495462                            return {
    496463                                domain: `json`,
    497                                 type: `size`,
     464                                type,
    498465                                name: target.name
    499466                            };
    500467                        }
    501468                    }
     469                } else if (type === `index`) {
     470                    if (compiler.nextTokenIs(`of`)) {
     471                        const item = compiler.getNextValue();
     472                        if (compiler.tokenIs(`in`)) {
     473                            const list = compiler.getNextValue();
     474                            return {
     475                                domain: `json`,
     476                                type,
     477                                item,
     478                                list
     479                            };
     480                        }
     481                    }
    502482                }
    503483            }
     
    506486
    507487        get: (program, value) => {
     488            let symbolRecord;
     489            let data;
     490            let content;
    508491            switch (value.type) {
    509492            case `size`:
    510                 const symbolRecord = program.getSymbolRecord(value.name);
    511                 const data = program.getValue(symbolRecord.value[symbolRecord.index]);
    512                 var array = [];
     493            case `count`:
     494                symbolRecord = program.getSymbolRecord(value.name);
     495                data = program.getValue(symbolRecord.value[symbolRecord.index]);
     496                let array;
    513497                try {
    514498                    array = JSON.parse(data);
    515499                } catch (err) {
    516                     program.runtimeError(0, `Can't parse JSON`);
    517                     return 0;
     500                    array = [];
    518501                }
    519502                return {
     
    521504                    numeric: true,
    522505                    content: array.length
     506                };
     507            case `keys`:
     508                symbolRecord = program.getSymbolRecord(value.name);
     509                data = program.getValue(symbolRecord.value[symbolRecord.index]);
     510                content = data ? JSON.stringify(Object.keys(JSON.parse(data)).sort()) : `[]`;
     511                return {
     512                    type: `constant`,
     513                    numeric: false,
     514                    content
     515                };
     516            case `index`:
     517                const item = program.getValue(value.item);
     518                const list = JSON.parse(program.getValue(value.list));
     519                content = list.findIndex(function(value) {
     520                    return value === item;
     521                });
     522                return {
     523                    type: `constant`,
     524                    numeric: true,
     525                    content
    523526                };
    524527            }
  • easycoder/trunk/plugins/ui.js

    r2016526 r2038748  
    136136                        }
    137137                    }
    138                 } else if (type === `navigator`) {
    139                     if (compiler.nextTokenIs(`in`)) {
    140                         if (compiler.nextIsSymbol()) {
    141                             const symbolRecord = compiler.getSymbolRecord();
    142                             compiler.next();
    143                             if (symbolRecord.keyword === `div`) {
    144                                 compiler.addCommand({
    145                                     domain: `ui`,
    146                                     keyword: `create`,
    147                                     lino,
    148                                     type,
    149                                     parent: symbolRecord.name
    150                                 });
    151                                 return true;
    152                             }
    153                         }
    154                     }
    155138                }
    156139            }
     
    223206                    EasyCoder_UI.renderDate(this.dateRecord);
    224207                };
    225                 break;
    226             case `navigator`:
    227208                break;
    228209            }
     
    288269                const dateRecord = program.getSymbolRecord(command.date);
    289270                dateRecord.timestamp = program.getValue(command.timestamp) * 1000;
    290                 EasyCoder_UI.renderDate(this.dateRecord);
     271                EasyCoder_UI.renderDate(dateRecord);
    291272                break;
    292273            }
  • easycoder/trunk/readme.txt

    r2016526 r2038748  
    66Requires at least: 4.4
    77Requires PHP: 5.2
    8 Tested up to: 5.0
     8Tested up to: 5.1
    99Stable tag: trunk
    1010License: GPLv2 or later
     
    2424* Retrieve content from web services using REST and JSON
    2525* Draw and animate simple graphics
     26* Create a Google Map, put markers on it and interct with the map and the markers
     27* Include the CKEditor rich text editor in your pages
     28* Include the Showdown markdown converter in your pages
    2629
    2730*EasyCoder* provides a simple syntax to do all these things, at about the same level of complexity and readability as SQL or Excel macros. It's a full programming language, though, capable of making complex logical decisions.
     
    2932*EasyCoder* scripts are embedded in your page or post, inside a special "preformatted" tag. When the page loads, *EasyCoder* looks for this element then compiles and runs the script it contains. When it interacts with HTML elements it attaches their IDs to its own variables, so your HTML and its controlling script are in the same file.
    3033
    31 The *EasyCoder* core module is currently about 47k bytes in its minimised form and it downloads its own plugin modules from a growing library. Its performance is good because it precompiles scripts - a process that takes only tens of milliseconds - and the compiled code for each command is only a thin wrapper around the corresponding JavaScript functionality.
     34The *EasyCoder* core module is currently about 50k bytes in its minimised form and it downloads its own plugin modules from a growing library. Its performance is good because it precompiles scripts - a process that takes only tens of milliseconds - and the compiled code for each command is only a thin wrapper around the corresponding JavaScript functionality.
    3235
    3336When *EasyCoder* detects an error, either in compilation or at runtime, it opens a popup window with a friendly error message that tries to tell you what went wrong and where in the script it happened.
     
    5053
    5154== Changelog ==
     55
     56= 2.2.0 25-feb-2019 =
     57* New plugins: gmap and showdown.
    5258
    5359= 2.1.9 21-jan-2019 =
  • easycoder/trunk/rest.php

    r2016526 r2038748  
    11<?php
    2     // REST server
    3 
    4     // This small REST server gives you the ability to manage tables
    5     // in your site database. WordPress provides a full set of API endpoints
    6     // for its own functions but if you need extra tables or extra features
    7     // you have to write your own. This server provides you with access to
    8     // your own tables as long as they fit a specified format. It also lets you
    9     // add an extension of your own to handle special needs.
    10     //
    11     // Don't modify this file; it will be overwritten on the next upgrade
    12     // of EasyCoder. Instead, use the extension script, a skeleton for which
    13     // was set up when you first installed EasyCoder, at
    14     // {root of your WordPress installation}/easycoder/rest-local.php.
    15 
    16     date_default_timezone_set('Europe/London');
    17     logger(substr($_SERVER['PATH_INFO'], 1));
    18     $request = explode("/", substr($_SERVER['PATH_INFO'], 1));
    19     $table = array_shift($request);
    20     $method = $_SERVER['REQUEST_METHOD'];
    21    
    22     // The properties file is best kept above the site root to prevent access by browsers.
    23     // For WordPress, that's 4 levels up from our current location, the 'plugins/easycoder' folder.
    24     $props = array();
    25     $filename = '../../../../' . $_SERVER['HTTP_HOST'] . '.txt';
    26     if (file_exists($filename)) {
    27         $file = fopen($filename, 'r');
    28         while (!feof($file)) {
    29             $ss = trim(fgets($file));
    30             if (!$ss || substr($ss, 0, 1) == '#') {
    31                 continue;
    32             }
    33             $ss = explode('=', $ss, 2);
    34             if (count($ss) > 1) {
    35                 $props[$ss[0]] = $ss[1];
    36             }
    37         }
    38         fclose($file);
    39     }
    40 
    41     // First, the commands that don't require a database connection.
    42     switch ($method) {
    43         case 'GET':
    44             switch ($table) {
    45             case '_list':
    46                 // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_list
    47                 // List the contents of a directory
    48                 if (count($request)) {
    49                     $path = '/' . str_replace('~', '/', $request[0]);
    50                 }
    51                 // Start at the resources folder
    52                 $path = "../../../resources$path";
    53                 $files = scandir($path);
    54                 print '[';
    55                 // First list all the directories
    56                 $flag = false;
    57                 foreach ($files as $file) {
    58                     if (strpos($file, '.') !== 0) {
    59                         if (is_dir("$path/$file")) {
    60                             if ($flag) {
    61                                 print ',';
    62                             } else {
    63                                 $flag = true;
    64                             }
    65                             print "{\"name\":\"$file\",\"type\":\"dir\"}";
    66                         }
    67                     }
    68                 }
    69                 // Now do the ordinary files
    70                 foreach ($files as $file) {
    71                     if (strpos($file, '.') !== 0) {
    72                         if (!is_dir("$path/$file")) {
    73                             if ($flag) {
    74                                 print ',';
    75                             } else {
    76                                 $flag = true;
    77                             }
    78                             $ext = substr($file, strrpos($file, '.') + 1);
    79                             $type = $ext;
    80                             switch (strtolower($ext)) {
    81                             case 'jpg':
    82                             case 'png':
    83                             case 'gif':
    84                                 $type = 'img';
    85                                 break;
    86                             }
    87                             print "{\"name\":\"$file\",\"type\":\"$type\"}";
    88                         }
    89                     }
    90                 }
    91                 print ']';
    92                 exit;
    93             case '_hash':
    94                 // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_hash/{value-to-hash}
    95                 print password_hash($request[0], PASSWORD_DEFAULT);
    96                 exit;
    97             case '_verify':
    98                 // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_verify/{value-to-verify}
    99                 print password_verify($request[0], $props['password']) ? 'yes' : 'no';
    100                 exit;
    101             }
    102             break;
    103         case 'POST':
    104             switch ($table) {
    105                 case '_mkdir':
    106                     // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_mkdir
    107                     header("Content-Type: application/text");
    108                     $path = stripslashes(file_get_contents("php://input"));
    109                     mkdir($path);
    110                     exit;
    111                 case '_upload':
    112                     // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_upload
    113                     // Upload a file (an image) to the current directory
    114                     $path = $_POST['path'];
    115                     $fileName = $_FILES['source']['name'];
    116                     $tempName = $_FILES['source']['tmp_name'];
    117                     $fileType = $_FILES['source']['type'];
    118                     $fileSize = $_FILES['source']['size'];
    119                     $fileError = $_FILES['source']['error'];
    120                     if (!move_uploaded_file($tempName, "$path/$fileName")) {
    121                         unlink($tempName);
    122                         http_response_code(400);
    123                         print "Failed to upload $fileName to $path.<br />tempName: $tempName<br />fileType: $fileType<br />fileSize:$fileSize<br />fileError: $fileError";
    124                     } else {
    125                         print "File $fileName uploaded successfully to $path/$filenName";
    126                     }
    127                     exit;
    128                 case '_delete':
    129                     // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_delete
    130                     header("Content-Type: application/text");
    131                     $path = stripslashes(file_get_contents("php://input"));
    132                     if (is_dir($path)) {
    133                         rmdir($path);
    134                     } else {
    135                         unlink($path);
    136                     }
    137                     exit;
    138             }
    139             break;
    140     }
    141 
    142     // Most further commands require use of the database.
    143     $conn = mysqli_connect($props['sqlhost'], $props['sqluser'],
    144         $props['sqlpassword'], $props['sqldatabase']);
    145     if (!$conn)
    146     {
    147         http_response_code(404);
    148         die("Failed to connect to MySQL: " . mysqli_connect_error());
    149     }
    150     mysqli_set_charset($conn,'utf8');
    151      
    152     if (!count($request)) {
    153         http_response_code(400);
    154         print "{\"message\":\"Incomplete REST query: ".substr($_SERVER['PATH_INFO'], 1).".\"}";
    155         exit;
    156     }
    157    
    158     // You can have a custom extension that deals with special requests.
    159     // These all have '_' as the table name.
    160     switch ($method) {
    161    
    162         case 'GET':
    163             if ($table == '_') {
    164                 // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_/{request-and-arguments}
    165                 include '../../../easycoder/rest-local.php';
    166                 get_local($conn, $request);
    167                 return;
    168             } else {
    169                 // Use the handler below
    170                 get($conn, $table, $request);
    171             }
    172             break;
    173            
    174         case 'POST':
    175             if ($table == '_') {
    176                 // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_/{request-and-arguments}
    177                 include '../../../easycoder/rest-local.php';
    178                 post_local($conn, $request);
    179                 return;
    180             } else {
    181                 // Use the handler below
    182                 post($conn, $table, $request);
    183             }
    184             break;
    185 
    186         default:
    187             http_response_code(400);
    188             break;
    189     }
    190     mysqli_close();
    191     exit;
    192    
    193     /////////////////////////////////////////////////////////////////////////
    194     // All the other commands deal with tables having a specific format, with the following fields:
    195     //
    196     // id int(11)
    197     // name varchar(40)
    198     // value text
    199     // ts int(11)
    200     //
    201     // GET
    202     function get($conn, $table, $request) {
    203         $action = $request[0];
    204         switch ($action) {
    205             case 'count':
    206                 // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/table/count
    207                 // Return the number of items in the table
    208                 $result = $conn->query("SELECT id from $table");
    209                 //print "{\"count\":".mysqli_num_rows($result)."}";
    210                 print mysqli_num_rows($result);
    211                 break;
    212             case 'list':
    213                 // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/table/list/{offset}/{count}
    214                 // List items by ID, with optional offset & count, defaulting to 0 & 10
    215                 switch (count($request)) {
    216                     case 2:
    217                         $offset = 0;
    218                         $count = $request[1];
    219                         break;
    220                     case 3:
    221                         $offset = $request[1];
    222                         $count = $request[2];
    223                         break;
    224                     default:
    225                         $offset = 0;
    226                         $count = 10;
    227                         break;
    228                 }
    229                 $result = $conn->query("SELECT id FROM $table LIMIT $offset, $count");
    230                 $response = '[';
    231                 while ($row = mysqli_fetch_object($result)) {
    232                     if ($response != '[') {
    233                         $response .= ',';
    234                     }
    235                     $response .= $row->id;
    236                 }
    237                 $response .= ']';
    238                 print $response;
    239                 break;
    240             case 'names':
    241                 // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/table/names/{offset}/{count}
    242                 // List items by name in ascending alphabetical order,
    243                 // with optional offset & count, defaulting to 0 & 10
    244                 switch (count($request)) {
    245                     case 2:
    246                         $offset = 0;
    247                         $count = $request[1];
    248                         break;
    249                     case 3:
    250                         $offset = $request[1];
    251                         $count = $request[2];
    252                         break;
    253                     default:
    254                         $offset = 0;
    255                         $count = 10;
    256                         break;
    257                 }
    258                 $result = $conn->query("SELECT name FROM $table ORDER BY name LIMIT $offset, $count");
    259                 $response = '[';
    260                 while ($row = mysqli_fetch_object($result)) {
    261                     if ($response != '[') {
    262                         $response .= ',';
    263                     }
    264                     $response .= "\"$row->name\"";
    265                 }
    266                 $response .= ']';
    267                 print $response;
    268                 break;
    269             case 'id':
    270                 // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/table/id/{id}
    271                 // Get a record given its id
    272                 if (count($request) < 2) {
    273                     http_response_code(400);
    274                     print "Incomplete REST query.";
    275                     exit;
    276                 }
    277                 $id = $request[1];
    278                 $result = $conn->query("SELECT value FROM $table WHERE id='$id'");
    279                 if ($row = mysqli_fetch_object($result)) {
    280                     print $row->value;
    281                 } else {
    282                     http_response_code(404);
    283                     print "Cannot get item id '$id' as it does not exist.";
    284                 }
    285                 break;
    286             case 'name':
    287                 // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/table/name/{name}
    288                 // Get a record given its name
    289                 if (count($request) < 2) {
    290                     http_response_code(400);
    291                     print "Incomplete REST query.";
    292                     exit;
    293                 }
    294                 $name = $request[1];
    295                 $result = $conn->query("SELECT value FROM $table WHERE name='$name'");
    296                 if ($row = mysqli_fetch_object($result)) {
    297                     print $row->value;
    298                 } else {
    299                     http_response_code(404);
    300                     print "Cannot get item named '$name' as it does not exist.";
    301                 }
    302                 break;
    303             default:
    304                 http_response_code(404);
    305                 print "I don't understand this request.";
    306                 break;
    307             }
    308     }
    309    
    310     /////////////////////////////////////////////////////////////////////////
    311     // POST
    312     function post($conn, $table, $request) {
    313         $ts = time();
    314         $action = $request[0];
    315         switch ($action) {
    316             case 'set':
    317                 // Set the value of a record
    318                 if (count($request) > 2) {
    319                     switch ($request[1]) {
    320                         case 'id':
    321                             // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/table/id/{id}
    322                             // Set by id. The record must already exist
    323                             header("Content-Type: application/text");
    324                             $value = stripslashes(file_get_contents("php://input"));
    325                             $id = $request[2];
    326                             // See if there's an item with this id
    327                             $result = $conn->query("SELECT id FROM $table WHERE id=$id");
    328                             if (mysqli_fetch_object($result)) {
    329                                 // It exists, so update it
    330                                 $value = urldecode($value);
    331                                 logger("UPDATE $table SET value='$value',ts=$ts WHERE id=$id");
    332                                 query($conn, "UPDATE $table SET value='$value',ts=$ts WHERE id=$id");
    333                             } else {
    334                                 // Not found
    335                                 http_response_code(404);
    336                                 logger("{\"code\":\"404\",\"message\":\"Cannot set record $id of $table.\"}");
    337                                 print "{\"message\":\"Cannot set record $id of $table.\"}";
    338                             }
    339                             break;
    340                         case 'name':
    341                             // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/table/name/{name}
    342                             // Set by name. If the record does not exist, add it
    343                             header("Content-Type: application/text");
    344                             $value = stripslashes(file_get_contents("php://input"));
    345                             $name = $request[2];
    346                             // See if there's an item with this name
    347                             $result = $conn->query("SELECT id FROM $table WHERE name='$name'");
    348                             if (mysqli_fetch_object($result)) {
    349                                 // It exists, so update it
    350                                 query($conn, "UPDATE $table SET value='$value',ts=$ts WHERE name='$name'");
    351                             } else {
    352                                 // Add a new item
    353                                 query($conn, "INSERT INTO $table (name,value,ts) VALUES ('$name','$value','$ts')");
    354                                 http_response_code(201);
    355                             }
    356                             break;
    357                         default:
    358                             http_response_code(400);
    359                             print "{\"message\":\"Value '".$request[1]."' should be 'id' or 'name'.\"}";
    360                             break;
    361                     }
    362                 } else {
    363                     http_response_code(400);
    364                     print "{\"message\":\"Incomplete REST query.\"}";
    365                 }
    366                 break;
    367             case 'delete':
    368                 // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/table/delete/{id}
    369                 // Or: .../rest.php/table/delete/{name}
    370                 // Delete a record, by id or by name
    371                 if (count($request) > 1) {
    372                     $item = $request[1];
    373                     if (is_int($item)) {
    374                         // Delete the requested id
    375                         query($conn, "DELETE FROM $table WHERE id=$id");
    376                     } else {
    377                         // Delete the named item
    378                         query($conn, "DELETE FROM $table WHERE name='$item'");
    379                     }
    380                 }
    381                 break;
    382             case 'rename':
    383                 // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/table/rename
    384                 // Rename a record
    385                 $value = $_POST['value'];
    386                 $id = $_POST['id'];
    387                 if (!$id && count($request) > 1) {
    388                     $id = $request[1];
    389                 }
    390                 if ($id) {
    391                     query($conn, "UPDATE $table SET name='$name',value='$value' WHERE id=$id");
    392                 } else {
    393                     $name = $_POST['name'];
    394                     $newname = $_POST['newname'];
    395                     // See if there's a data item with the new name
    396                     $result = $conn->query("SELECT id FROM $table WHERE name='$newname'");
    397                     if ($row = mysqli_fetch_object($result)) {
    398                         // Conflict
    399                         http_response_code(409);
    400                         print "{\"message\":\"Cannot rename item '$name' to '$newname' as it already exists.\"}";
    401                     } else {
    402                         // See if there's a data item with this name
    403                         $result = $conn->query("SELECT id FROM $table WHERE name='$name'");
    404                         if ($row = mysqli_fetch_object($result)) {
    405                             // There's a data item to rename
    406                             $id = $row->id;
    407                             query($conn, "UPDATE $table SET name='$newname',value='$value' WHERE id=$id");
    408                         } else {
    409                             // Not found
    410                             http_response_code(404);
    411                             print "{\"message\":\"Cannot rename item '$name' as it does not exist.\"}";
    412                         }
    413                     }
    414                 }
    415                 break;
    416             default:
    417                 http_response_code(404);
    418                 print "{\"message\":\"Unrecognised action '$action' requested.\"}";
    419                 break;
    420         }
    421     }
    422 
    423     /////////////////////////////////////////////////////////////////////////
    424     // Do an SQL query
    425     function query($conn, $sql)
    426     {
    427         $result = mysqli_query($conn, $sql);
    428         if (!$result) {
    429             http_response_code(404);
    430             logger('Error: '.mysqli_error($conn));
    431             die('Error: '.mysqli_error($conn));
    432         }
    433         mysqli_free_result($result);
    434         return $result;
    435     }
    436 
    437     ////////////////////////////////////////////////////////////////////////////
    438     // Log a message.
    439     function logger($message)
    440     {
    441         $timestamp = time();
    442         $date = date("Y/m/d H:i", $timestamp);
    443         if (!file_exists("log")) mkdir("log");
    444         $file = "log/".date("Y", $timestamp);
    445         if (!file_exists($file)) mkdir($file);
    446         $file.= "/".date("Ymd", $timestamp).".txt";
    447         $fp = fopen($file, "a+") or die("Can't open $file");
    448         fwrite($fp, "$date: $message\n");
    449         fclose($fp);
    450     }
     2        // REST server
     3
     4        // This small REST server gives you the ability to manage tables
     5        // in your site database. WordPress provides a full set of API endpoints
     6        // for its own functions but if you need extra tables or extra features
     7        // you have to write your own. This server provides you with access to
     8        // your own tables as long as they fit a specified format. It also lets you
     9        // add an extension of your own to handle special needs.
     10        //
     11        // Don't modify this file; it will be overwritten on the next upgrade
     12        // of EasyCoder. Instead, use the extension script, a skeleton for which
     13        // was set up when you first installed EasyCoder, at
     14        // {root of your WordPress installation}/easycoder/rest-local.php.
     15
     16        date_default_timezone_set('Europe/London');
     17        logger(substr($_SERVER['PATH_INFO'], 1));
     18        $request = explode("/", substr($_SERVER['PATH_INFO'], 1));
     19        $table = array_shift($request);
     20        $method = $_SERVER['REQUEST_METHOD'];
     21       
     22        // The properties file is best kept above the site root to prevent access by browsers.
     23        // For WordPress, that's 4 levels up from our current location, the 'plugins/easycoder' folder.
     24        $props = array();
     25        $filename = '../../../../' . $_SERVER['HTTP_HOST'] . '.txt';
     26        if (file_exists($filename)) {
     27                $file = fopen($filename, 'r');
     28                while (!feof($file)) {
     29                        $ss = trim(fgets($file));
     30                        if (!$ss || substr($ss, 0, 1) == '#') {
     31                                continue;
     32                        }
     33                        $ss = explode('=', $ss, 2);
     34                        if (count($ss) > 1) {
     35                                $props[$ss[0]] = $ss[1];
     36                        }
     37                }
     38                fclose($file);
     39        }
     40
     41        // First, the commands that don't require a database connection.
     42        switch ($method) {
     43                case 'GET':
     44                        switch ($table) {
     45                        case '_list':
     46                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_list/[{path}]
     47                                // List the contents of a directory
     48                                if (count($request)) {
     49                                        $path = '/' . str_replace('~', '/', $request[0]);
     50                                }
     51                                // Start at the resources folder
     52                                $path = "../../../resources$path";
     53                                $files = scandir($path);
     54                                print '[';
     55                                // First list all the directories
     56                                $flag = false;
     57                                foreach ($files as $file) {
     58                                        if (strpos($file, '.') !== 0) {
     59                                                if (is_dir("$path/$file")) {
     60                                                        if ($flag) {
     61                                                                print ',';
     62                                                        } else {
     63                                                                $flag = true;
     64                                                        }
     65                                                        print "{\"name\":\"$file\",\"type\":\"dir\"}";
     66                                                }
     67                                        }
     68                                }
     69                                // Now do the ordinary files
     70                                foreach ($files as $file) {
     71                                        if (strpos($file, '.') !== 0) {
     72                                                if (!is_dir("$path/$file")) {
     73                                                        if ($flag) {
     74                                                                print ',';
     75                                                        } else {
     76                                                                $flag = true;
     77                                                        }
     78                                                        $ext = substr($file, strrpos($file, '.') + 1);
     79                                                        $type = $ext;
     80                                                        switch (strtolower($ext)) {
     81                                                        case 'jpg':
     82                                                        case 'png':
     83                                                        case 'gif':
     84                                                                $type = 'img';
     85                                                                break;
     86                                                        }
     87                                                        print "{\"name\":\"$file\",\"type\":\"$type\"}";
     88                                                }
     89                                        }
     90                                }
     91                                print ']';
     92                                exit;
     93                        case '_hash':
     94                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_hash/{value-to-hash}
     95                                print password_hash($request[0], PASSWORD_DEFAULT);
     96                                exit;
     97                        case '_verify':
     98                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_verify/{value-to-verify}
     99                                print password_verify($request[0], $props['password']) ? 'yes' : 'no';
     100                                exit;
     101                        case '_validate':
     102                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_validate/{value-to-validate}/{encrypted-value}
     103                                print password_verify($request[0], str_replace('~', '/', $request[1])) ? 'yes' : 'no';
     104                                exit;
     105                        case '_load':
     106                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_load/{path}
     107                                $path = '../../../resources/' . str_replace('~', '/', $request[0]);
     108                                print file_get_contents($path);
     109                                exit;
     110                        break;
     111                    }
     112                case 'POST':
     113                        switch ($table) {
     114                                case '_mkdir':
     115                                        // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_mkdir
     116                                        header("Content-Type: application/text");
     117                                        $path = stripslashes(file_get_contents("php://input"));
     118                                        logger("Create directory $path");
     119                                        mkdir($path);
     120                                        exit;
     121                                case '_upload':
     122                                        // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_upload
     123                                        // Upload a file (an image) to the current directory
     124                                        $path = $_POST['path'];
     125                                        $fileName = $_FILES['source']['name'];
     126                                        $tempName = $_FILES['source']['tmp_name'];
     127                                        $fileType = $_FILES['source']['type'];
     128                                        $fileSize = $_FILES['source']['size'];
     129                                        $fileError = $_FILES['source']['error'];
     130                                        if (!move_uploaded_file($tempName, "$path/$fileName")) {
     131                                                unlink($tempName);
     132                                                http_response_code(400);
     133                                                logger("Failed to upload $fileName to $path.\ntempName: $tempName\nfileType: $fileType\nfileSize:$fileSize\nfileError: $fileError");
     134                                        } else {
     135                                                logger("File $fileName uploaded successfully to $path/$fileName");
     136                                                $size = getimagesize("$path/$fileName");
     137                                                logger("$path/$fileName: width:".$size[0].", height:".$size[1]);
     138                                                if ($size[0] > 1024) {
     139                                                        logger("mogrify -resize 1024x1024 $path/$fileName");
     140                                                        system("mogrify -resize 1024x1024 $path/$fileName");
     141                                                }
     142                                        }
     143                                        exit;
     144                                case '_delete':
     145                                        // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_delete
     146                                        header("Content-Type: application/text");
     147                                        $path = stripslashes(file_get_contents("php://input"));
     148                                        if (is_dir($path)) {
     149                                                rmdir($path);
     150                                        } else {
     151                                                unlink($path);
     152                                        }
     153                                        exit;
     154                                case '_email':
     155                                        // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_email
     156                                        header("Content-Type: application/text");
     157                                        $value = stripslashes(file_get_contents("php://input"));
     158                                        $json = json_decode($value);
     159                                        $from = $json->from;
     160                                        $to = $json->to;
     161                                        $subject = $json->subject;
     162                                        $message = $json->message;
     163                                        $headers = "MIME-Version: 1.0\r\n";
     164                                        $headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n";
     165                                        $headers .= "From: $from\r\n";
     166                                        mail($to, $subject, $message, "$headers\r\n");
     167                                        print "$headers\r\n$message";
     168                                        exit;
     169                                case '_save':
     170                                        // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_save/{path}
     171                                        $path = '../../../resources/' . str_replace('~', '/', $request[0]);
     172                                        $p = strrpos($path, '/');
     173                                        $dir = substr($path, 0, $p);
     174                                        mkdir($dir, 0777, true);
     175                                        header("Content-Type: application/text");
     176                                        $content = stripslashes(file_get_contents("php://input"));
     177                                        $p = strrpos($path, '.');
     178                                        $root = substr($path, 0, $p);
     179                                        $ext = substr($path, $p);
     180                                        $backup = "$root-bak$ext";
     181                                        unlink($backup);
     182                                        copy($path, $backup);
     183                                        file_put_contents($path, $content);
     184                                        exit;
     185                        }
     186                        break;
     187        }
     188
     189        // Most further commands require use of the database.
     190        $conn = mysqli_connect($props['sqlhost'], $props['sqluser'],
     191                $props['sqlpassword'], $props['sqldatabase']);
     192        if (!$conn)
     193        {
     194                http_response_code(404);
     195                die("Failed to connect to MySQL: " . mysqli_connect_error());
     196        }
     197        mysqli_set_charset($conn,'utf8');
     198       
     199        if (!count($request)) {
     200                http_response_code(400);
     201                print "{\"message\":\"Incomplete REST query: ".substr($_SERVER['PATH_INFO'], 1).".\"}";
     202                exit;
     203        }
     204       
     205        // You can have a custom extension that deals with special requests.
     206        // These all have '_' as the table name.
     207        switch ($method) {
     208       
     209                case 'GET':
     210                        if ($table == '_') {
     211                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_/{request-and-arguments}
     212                                include '../../../easycoder/rest-local.php';
     213                                get_local($conn, $request);
     214                                return;
     215                        } else {
     216                                // Use the handler below
     217                                get($conn, $table, $request);
     218                        }
     219                        break;
     220                       
     221                case 'POST':
     222                        if ($table == '_') {
     223                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_/{request-and-arguments}
     224                                include '../../../easycoder/rest-local.php';
     225                                post_local($conn, $request);
     226                                return;
     227                        } else {
     228                                // Use the handler below
     229                                post($conn, $table, $request);
     230                        }
     231                        break;
     232
     233                default:
     234                        http_response_code(400);
     235                        break;
     236        }
     237        mysqli_close();
     238        exit;
     239       
     240        /////////////////////////////////////////////////////////////////////////
     241        // All the other commands deal with tables having a specific format, with the following fields:
     242        //
     243        // id int(11)
     244        // name varchar(40)
     245        // value text
     246        // ts int(11)
     247        //
     248        // GET
     249        function get($conn, $table, $request) {
     250                $action = $request[0];
     251                switch ($action) {
     252                        case 'count':
     253                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/table/count
     254                                // Return the number of items in the table
     255                                $result = $conn->query("SELECT id from $table");
     256                                //print "{\"count\":".mysqli_num_rows($result)."}";
     257                                print mysqli_num_rows($result);
     258                                mysqli_free_result($result);
     259                                break;
     260                               
     261                        case 'list':
     262                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/list/{offset}/{count}
     263                                // List items by ID, with optional offset & count, defaulting to 0 & 10
     264                                switch (count($request)) {
     265                                        case 2:
     266                                                $offset = 0;
     267                                                $count = $request[1];
     268                                                break;
     269                                        case 3:
     270                                                $offset = $request[1];
     271                                                $count = $request[2];
     272                                                break;
     273                                        default:
     274                                                $offset = 0;
     275                                                $count = 10;
     276                                                break;
     277                                }
     278                                $result = $conn->query("SELECT id FROM $table LIMIT $offset, $count");
     279                                $response = '[';
     280                                while ($row = mysqli_fetch_object($result)) {
     281                                        if ($response != '[') {
     282                                                $response .= ',';
     283                                        }
     284                                        $response .= $row->id;
     285                                }
     286                                mysqli_free_result($result);
     287                                $response .= ']';
     288                                print $response;
     289                                break;
     290                               
     291                        case 'names':
     292                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/names/{offset}/{count}
     293                                // List items by name in ascending alphabetical order,
     294                                // with optional offset & count, defaulting to 0 & 10
     295                                switch (count($request)) {
     296                                        case 2:
     297                                                $offset = 0;
     298                                                $count = $request[1];
     299                                                break;
     300                                        case 3:
     301                                                $offset = $request[1];
     302                                                $count = $request[2];
     303                                                break;
     304                                        default:
     305                                                $offset = 0;
     306                                                $count = 10;
     307                                                break;
     308                                }
     309                                $result = $conn->query("SELECT name FROM $table ORDER BY name LIMIT $offset, $count");
     310                                $response = '[';
     311                                while ($row = mysqli_fetch_object($result)) {
     312                                        if ($response != '[') {
     313                                                $response .= ',';
     314                                        }
     315                                        $response .= "\"$row->name\"";
     316                                }
     317                                mysqli_free_result($result);
     318                                $response .= ']';
     319                                print $response;
     320                                break;
     321                               
     322                        case 'id':
     323                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/id/{id}
     324                                // Get a record given its id
     325                                if (count($request) < 2) {
     326                                        http_response_code(400);
     327                                        print "Incomplete REST query.";
     328                                        exit;
     329                                }
     330                                $id = $request[1];
     331                                $result = $conn->query("SELECT value FROM $table WHERE id='$id'");
     332                                if ($row = mysqli_fetch_object($result)) {
     333                                        print $row->value;
     334                                } else {
     335                                        http_response_code(404);
     336                                        print "Cannot get item id '$id' as it does not exist.";
     337                                }
     338                                mysqli_free_result($result);
     339                break;
     340                               
     341                        case 'name':
     342                        case 'query':
     343                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/name/{name}
     344                                // Get a record given its name
     345                                if (count($request) < 2) {
     346                                                http_response_code(400);
     347                                                print "Incomplete REST query.";
     348                                                exit;
     349                                }
     350                                $name = $request[1];
     351                                $result = $conn->query("SELECT value FROM $table WHERE name='$name'");
     352                                if ($row = mysqli_fetch_object($result)) {
     353                                        print $row->value;
     354                                } else if ($action == 'name') {
     355                                        http_response_code(404);
     356                                        print "Cannot get item named '$name' as it does not exist.";
     357                                }
     358                break;
     359                               
     360                        default:
     361                                http_response_code(404);
     362                                print "I don't understand this request.";
     363                                break;
     364                        }
     365        }
     366       
     367        /////////////////////////////////////////////////////////////////////////
     368        // POST
     369        function post($conn, $table, $request) {
     370                $ts = time();
     371                $action = $request[0];
     372                switch ($action) {
     373                        case 'set':
     374                                // Set the value of a record
     375                                if (count($request) > 2) {
     376                                        switch ($request[1]) {
     377                                                case 'id':
     378                                                        // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/id/{id}
     379                                                        // Set by id. The record must already exist
     380                                                        header("Content-Type: application/text");
     381                                                        $value = stripslashes(file_get_contents("php://input"));
     382                                                        $id = $request[2];
     383                                                        // See if there's an item with this id
     384                                                        $result = $conn->query("SELECT id FROM $table WHERE id=$id");
     385                                                        if (mysqli_fetch_object($result)) {
     386                                                                // It exists, so update it
     387                                                                $value = urldecode($value);
     388                                                                logger("UPDATE $table SET value='$value',ts=$ts WHERE id=$id");
     389                                                                query($conn, "UPDATE $table SET value='$value',ts=$ts WHERE id=$id");
     390                                                        } else {
     391                                                                // Not found
     392                                                                http_response_code(404);
     393                                                                logger("{\"code\":\"404\",\"message\":\"Cannot set record $id of $table.\"}");
     394                                                                print "{\"message\":\"Cannot set record $id of $table.\"}";
     395                                                        }
     396                            mysqli_free_result($result);
     397                            break;
     398                                                       
     399                                                case 'name':
     400                                                        // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/name/{name}
     401                                                        // Set by name. If the record does not exist, add it
     402                                                        header("Content-Type: application/text");
     403                                                        $value = stripslashes(file_get_contents("php://input"));
     404                                                        $name = $request[2];
     405                                                        // See if there's an item with this name
     406                                                        $result = $conn->query("SELECT id FROM $table WHERE name='$name'");
     407                                                        if (mysqli_fetch_object($result)) {
     408                                                                // It exists, so update it
     409                                                                query($conn, "UPDATE $table SET value='$value',ts=$ts WHERE name='$name'");
     410                                                        } else {
     411                                                                // Add a new item
     412                                                                query($conn, "INSERT INTO $table (name,value,ts) VALUES ('$name','$value','$ts')");
     413                                                                http_response_code(201);
     414                                                        }
     415                            mysqli_free_result($result);
     416                            break;
     417                                                       
     418                                                default:
     419                                                        http_response_code(400);
     420                                                        print "{\"message\":\"Value '".$request[1]."' should be 'id' or 'name'.\"}";
     421                                                        break;
     422                                        }
     423                                } else {
     424                                        http_response_code(400);
     425                                        print "{\"message\":\"Incomplete REST query.\"}";
     426                                }
     427                                break;
     428                               
     429                        case 'delete':
     430                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/delete/{id}
     431                                // Or: .../rest.php/table/delete/{name}
     432                                // Delete a record, by id or by name
     433                                if (count($request) > 1) {
     434                                        $item = $request[1];
     435                                        if (is_int($item)) {
     436                                                // Delete the requested id
     437                                                query($conn, "DELETE FROM $table WHERE id=$id");
     438                                        } else {
     439                                                // Delete the named item
     440                                                query($conn, "DELETE FROM $table WHERE name='$item'");
     441                                        }
     442                                }
     443                                break;
     444                               
     445                        case 'rename':
     446                                // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/rename
     447                                // Rename a record
     448                                $value = $_POST['value'];
     449                                $id = $_POST['id'];
     450                                if (!$id && count($request) > 1) {
     451                                        $id = $request[1];
     452                                }
     453                                if ($id) {
     454                                        query($conn, "UPDATE $table SET name='$name',value='$value' WHERE id=$id");
     455                                } else {
     456                                        $name = $_POST['name'];
     457                                        $newname = $_POST['newname'];
     458                                        // See if there's a data item with the new name
     459                                        $result = $conn->query("SELECT id FROM $table WHERE name='$newname'");
     460                                        if ($row = mysqli_fetch_object($result)) {
     461                                                // Conflict
     462                                                http_response_code(409);
     463                                                print "{\"message\":\"Cannot rename item '$name' to '$newname' as it already exists.\"}";
     464                                        } else {
     465                                                // See if there's a data item with this name
     466                                                $result = $conn->query("SELECT id FROM $table WHERE name='$name'");
     467                                                if ($row = mysqli_fetch_object($result)) {
     468                                                        // There's a data item to rename
     469                                                        $id = $row->id;
     470                                                        query($conn, "UPDATE $table SET name='$newname',value='$value' WHERE id=$id");
     471                                                } else {
     472                                                        // Not found
     473                                                        http_response_code(404);
     474                                                        print "{\"message\":\"Cannot rename item '$name' as it does not exist.\"}";
     475                                                }
     476                                        }
     477                    mysqli_free_result($result);
     478                                }
     479                break;
     480                               
     481                        default:
     482                                http_response_code(404);
     483                                print "{\"message\":\"Unrecognised action '$action' requested.\"}";
     484                                break;
     485                }
     486        }
     487
     488        /////////////////////////////////////////////////////////////////////////
     489        // Do an SQL query
     490        function query($conn, $sql)
     491        {
     492                $result = mysqli_query($conn, $sql);
     493                if (!$result) {
     494                        http_response_code(404);
     495                        logger('Error: '.mysqli_error($conn));
     496                        die('Error: '.mysqli_error($conn));
     497                }
     498                return $result;
     499        }
     500
     501        ////////////////////////////////////////////////////////////////////////////
     502        // Log a message.
     503        function logger($message)
     504        {
     505                $timestamp = time();
     506                $date = date("Y/m/d H:i", $timestamp);
     507                if (!file_exists("log")) mkdir("log");
     508                $file = "log/".date("Y", $timestamp);
     509                if (!file_exists($file)) mkdir($file);
     510                $file.= "/".date("Ymd", $timestamp).".txt";
     511                $fp = fopen($file, "a+") or die("Can't open $file");
     512                fwrite($fp, "$date: $message\n");
     513                fclose($fp);
     514        }
    451515?>
Note: See TracChangeset for help on using the changeset viewer.