diff --git a/src/js/betterRA.tw b/src/js/betterRA.tw new file mode 100644 index 0000000000000000000000000000000000000000..18c6f084a28501a3e6443347ceda205108d805b9 --- /dev/null +++ b/src/js/betterRA.tw @@ -0,0 +1,318 @@ +:: BetterRA_JS [script] +//Imported to pregmod from unknown author added this to original FC at some point (0.9.5.4). I don't write this by myself (pregmodfan). + +// Implements a Top Down Operator Precedence parser, also know as a Pratt +// parser, after its "inventor", Vaughan Pratt. The one implemented here +// closely follows what's presented here, +// * http://javascript.crockford.com/tdop/tdop.html +// by Douglas Crockford. He uses that parser in JSLint. Other relevant +// resources on the interweb +// * http://effbot.org/zone/simple-top-down-parsing.htm +// * http://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing +// * http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ +// * https://higherlogics.blogspot.gr/2009/11/extensible-statically-typed-pratt.html +// * https://github.com/fholm/Vaughan +// included here mostly as bookmarks for potential future reference. +// +// With regards to the lexer, I mostly copied +// * http://eli.thegreenplace.net/2013/06/25/regex-based-lexical-analysis-in-python-and-javascript/ +// not without changes though. +// +// Other useful things, to complement my lacking JavaScript knowledge were +// * https://plainjs.com/javascript/utilities/merge-two-javascript-objects-19/ +// * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions +// Some I actually use, some I do not + + +function panic(index, msg) { + throw {index: index, message: msg}; +} + +function escapeRegExp(s){ + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + + +var Lexer = function(skipWhitespace) { + this.rules = []; + this.index = 0; + this.buffer = ""; + this.skipWhitespace = skipWhitespace ? /\S/ : null; +} + +Lexer.prototype.addRule = function(id, defn) { + var pattern = (defn && defn.pattern) || escapeRegExp(id); + this.rules.push({ + id: id, + pattern: new RegExp('^' + pattern) + }); +} + +Lexer.prototype.feed = function(buffer) { + this.buffer = buffer; + this.index = 0; +} + +Lexer.prototype.nextInterestingChar = function() { + if (this.skipWhitespace) { + var match = this.skipWhitespace.exec(this.buffer.substr(this.index)); + return match ? this.index + match.index + : this.buffer.length; + } + return this.index; +} + +Lexer.prototype.next = function() { + this.index = this.nextInterestingChar(); + + if (this.index >= this.buffer.length) + return { done: true }; + + for (var i = 0; i < this.rules.length; ++i) { + var rule = this.rules[i], + match = rule.pattern.exec(this.buffer.substr(this.index)); + if (match) { + var token = { + id: rule.id, + value: match[0], + index: this.index, + }; + this.index += token.value.length; + return { done: false, value: token }; + } + } + + panic(this.index, "illegal character"); +} + + + +var BaseSymbol = { + lbp: 0, + nud: function() { panic(this.index, "Undefined"); }, + led: function() { panic(this.index, "Missing operator"); } +}; + +var Parser = function(eofToken) { + this.lexer = new Lexer(true); + this.currentSymbol = null; + + this.eofToken = eofToken; + this.symbolTable = { + [this.eofToken]: Object.create(BaseSymbol, {id: {value: this.eofToken}}) + }; +} + +Parser.prototype.addSymbol = function(id, defn) { + var s = this.symbolTable[id]; + if (s) { + if (defn && defn.lbp !== undefined && defn.lbp >= s.lbp) s.lbp = defn.lbp; + if (defn && defn.nud !== undefined) s.nud = defn.nud; + if (defn && defn.led !== undefined) s.led = defn.led; + } else { + s = Object.create(BaseSymbol); + s.id = id; + if (defn && defn.lbp !== undefined) s.lbp = defn.lbp; + if (defn && defn.nud) s.nud = defn.nud; + if (defn && defn.led) s.led = defn.led; + this.symbolTable[id] = s; + this.lexer.addRule(id, defn); + } + return this; +} + +Parser.prototype.addInfix = function(id, lbp, callback) { + this.addSymbol(id, { + lbp: lbp, + led: function(p, left) { return callback(this, left, p.parse(lbp)); } + }); + return this; +} + +Parser.prototype.addInfixR = function(id, lbp, callback) { + this.addSymbol(id, { + lbp: lbp, + led: function(p, left) { return callback(this, left, p.parse(lbp-1)); } + }); + return this; +} + +Parser.prototype.addPrefix = function(id, callback) { + this.addSymbol(id, { + // FIXME: is the rbp (in parse) always 70? + nud: function (p) { return callback(p.parse(70)); } + }); + return this; +} + +Parser.prototype.advance = function(id) { + if (id && this.currentSymbol.id !== id) + panic(this.currentSymbol.index, "expected '" + id + "'"); + + var iter = this.lexer.next(), + token = iter.value; + if (iter.done) + token = { + id: this.eofToken, + value: "", + index: this.lexer.buffer.length + }; + + var symbol = this.symbolTable[iter.done ? this.eofToken : token.id]; + if (!symbol) + panic(token.index, "unknown token " + token.id); + + var newSymbol = Object.create(symbol); + newSymbol.value = token.value; + newSymbol.index = token.index; + return this.currentSymbol = newSymbol; +} + +Parser.prototype.parse = function(rbp) { + var symbol = this.currentSymbol; + this.advance(); + var left = symbol.nud(this); + + rbp = rbp || 0; + while (rbp < this.currentSymbol.lbp) { + symbol = this.currentSymbol; + this.advance(); + left = symbol.led(this, left); + } + return left; +} + +Parser.prototype.parseString = function(string) { + this.lexer.feed(string); + this.advance(); // "kickstart" the lexer + return this.parse(); +} + + + +var BaseExpression = { + check: function(env) { panic("Wrong!"); }, + eval: function(env) { panic("Do no know how to eval " + this); } +}; + +var DeferredEvalParserBuilder = function(eofToken) { + this.parser = new Parser(eofToken); +} + +DeferredEvalParserBuilder.prototype.addSymbol = function(id, extra) { + this.parser.addSymbol(id, extra); + return this; +} + +DeferredEvalParserBuilder.prototype.addInfix = function(id, lbp, func) { + this.parser.addInfix(id, lbp, function(symbol, left, right) { + var expr = Object.create(BaseExpression); + expr.first = left; + expr.second = right; + expr.eval = function(env) { + return func(this.first.eval(env), this.second.eval(env)); + }; + return expr; + }); + return this; +} + +DeferredEvalParserBuilder.prototype.addInfixR = function(id, lbp, func) { + this.parser.addInfixR(id, lbp, function(symbol, left, right) { + var expr = Object.create(BaseExpression); + expr.first = left; + expr.second = right; + expr.eval = function(env) { + return func(this.first.eval(env), this.second.eval(env)); + }; + return expr; + }); + return this; +} + +DeferredEvalParserBuilder.prototype.addPrefix = function(id, func) { + this.parser.addPrefix(id, function (symbol) { + var expr = Object.create(BaseExpression); + expr.val = symbol; + expr.eval = function(env) { return func(this.val.eval(env)); }; + return expr; + }); + return this; +} + + +var op = { + add: function(a, b) { return a + b; }, + sub: function(a, b) { return a - b; }, + mul: function(a, b) { return a * b; }, + div: function(a, b) { return a / b; }, + pow: function(a, b) { return Math.pow(a, b); }, // for completeness + + neg: function(a) { return -a; }, + + lt: function(a, b) { return a < b; }, + le: function(a, b) { return a <= b; }, + gt: function(a, b) { return a > b; }, + ge: function(a, b) { return a >= b; }, + eq: function(a, b) { return a === b; }, + neq: function(a, b) { return a !== b; }, + + not: function(a) { return !a; }, + or: function(a, b) { return a || b; }, + and: function(a, b) { return a && b; }, +}; + +var parserBuilder = new DeferredEvalParserBuilder('(end)') + .addSymbol('(int)', { + pattern: '\\d+', + nud: function() { + var expr = Object.create(BaseExpression); + expr.val = parseInt(this.value); + expr.eval = function() { return this.val; }; + return expr; + } + }) + .addSymbol('(name)', { + pattern: '[a-zA-Z]\\w*', + nud: function(p) { + var expr = Object.create(BaseExpression); + expr.name = this.value; + expr.eval = function(env) { return env[this.name]; }; + return expr; + } + }) + + .addInfix("+", 50, op.add) + .addInfix("-", 50, op.sub) + .addInfix("*", 60, op.mul) + .addInfix("/", 60, op.div) + .addInfixR("^", 70, op.pow) + + .addPrefix("-", op.neg) + + .addInfix("<=", 30, op.le) + .addInfix("<", 30, op.lt) + .addInfix(">=", 30, op.ge) + .addInfix(">", 30, op.gt) + .addInfix("!=", 30, op.neq) + .addInfix('=', 30, op.eq) + + .addPrefix("!", op.not) + .addInfix('||', 30, op.or) + .addInfix('&&', 30, op.and) + + .addSymbol(")") + .addSymbol("(", { + nud: function(p) { + var expr = p.parse(0); + p.advance(")"); + return expr; + } + }); + +window.parser = parserBuilder.parser; +window.parseAndEvaluate = function(text, env) { + var expr = parser.parseString(text); + return expr.eval(env); +} + diff --git a/src/uncategorized/checkAutoRulesActivate.tw b/src/uncategorized/checkAutoRulesActivate.tw index 8b441789d5d8fa54e3e45b76b578cee0b1f777b7..66751631a6936fc344005e3d4b56bb4a587346d0 100644 --- a/src/uncategorized/checkAutoRulesActivate.tw +++ b/src/uncategorized/checkAutoRulesActivate.tw @@ -64,6 +64,18 @@ <</if>> <<switch _currentRule.activation>> +<<case "custom">> /* for betterRA */ + <<set _isActive to parseAndEvaluate(_currentRule.customActivationText, $activeSlave)>> + <<if _isActive>> + <<if !ruleApplied($activeSlave, _currentRule.ID)>> + <<set $activeSlave.currentRules.push(_currentRule.ID)>> + <br>Rule _rule (_currentRule.name) is now applying to $activeSlave.slaveName. + <</if>> + <<else>> + <<if ruleApplied($activeSlave, _currentRule.ID)>> + <<RARemoveRule>> + <</if>> + <</if>> <<case "always">> <<if !ruleApplied($activeSlave, _currentRule.ID)>> <<set $activeSlave.currentRules.push(_currentRule.ID)>> diff --git a/src/uncategorized/rulesAssistant.tw b/src/uncategorized/rulesAssistant.tw index 066f622f02388463d71e5328bc4eb07eb6f2e443..5f295827e61f88eb12f7f97e35089e2caee8f331 100644 --- a/src/uncategorized/rulesAssistant.tw +++ b/src/uncategorized/rulesAssistant.tw @@ -242,6 +242,13 @@ __Rule $r Automatic Activation__ <<RAChangeApply>> <</link>> <</if>> +| +<<link "Custom">> + <<set $currentRule.activation to "custom">> + <<RAChangeActivation>> + <<RAChangeSave>> + <<RAChangeApply>> +<</link>> <br><br> diff --git a/src/utility/raWidgets.tw b/src/utility/raWidgets.tw index 71b98e55829944227728f5fc74363355ab4de1e6..573d4fe116a2cf479e72fc11d0aff17f1ed8e083 100644 --- a/src/utility/raWidgets.tw +++ b/src/utility/raWidgets.tw @@ -11,7 +11,13 @@ <<replace #activation>> __($currentRule.activation):__ <br> -<<if ($currentRule.activation != "none") && ($currentRule.activation != "always")>> +<<if $currentRule.activation == "custom" >> + <<textarea "$currentRule.customActivationText" $currentRule.customActivationText>> + <<link "Save activation">> + <<RAChangeSave>> + <<RAChangeApply>> + <</link>> +<<elseif ($currentRule.activation != "none") && ($currentRule.activation != "always")>> <span id="lower"> <<if ($currentRule.thresholdLower is "none")>> (no lower limit) @@ -119,6 +125,8 @@ When ''$currentRule.activation'' is <br>// Fetus count, rule can be applied only after week 10 of pregnancy. // <<case "belly implant volume">> <br>// Volume of belly implant in ccs. 0 - none. // +<<case "custom">> +<br>// Enter custom condition. // <</switch>> <</replace>> <</widget>>