#A command explaining the math evaluation

1 messages · Page 1 of 1 (latest)

tepid rose
#

It would be convenient if there was a command that would print out the available operators and the current order of operation. Also clarification of which operators work together and which don't would be much welcome (I don't know if it's a bug or a feature, but '&' doesn't work with >> eg '16>>4&6' or '32>>35%6').

soft rain
#

What's your issue with these?
16>>4&6 = (16>>4)&6 = 1&6 = 0
And 16>>(4&6) = 16>>4 = 1

tepid rose
#

For example, it would be nice, if there was a way to know that parentheses even work. Also the fact that the bot simply ignores messages with '>>' and '&' at the same time, seems sloppy.

soft rain
#

Afaik only if they evaluate to 0

#

Maybe negative values, too, but I didn't try that yet iirc

soft rain
#

I experimented in #bot-stuff with /calc and found this binding order:

( )
^
* / %
+ -
<< >>
& | ⊻

Parenthesises obviously have the highest binding power to allow grouping.
Lines with more than one operator mean, that those have the same binding strength and are evaluated left-to-right (which is really weird with the logic stuff, but that's how it is apparently... - as a programmer, I don't like this at all).

#

Tomorrow I'll use that to make my own calculation function and post it here if all lines up with the calc command

#

Thanks for giving me the motiviation to finally check this stuff ^^

soft rain
#

I was a bit busy, but I'm working on it again now

soft rain
#

Okay, I think hope I'm done

#

Code should be done, now it's time to test

soft rain
#

Had a tiny mistake, but easy fix - now it works

#

If you know how to run JS code and want to help testing, here's the code:

var digitsOnlyPattern = '^[0-9]+$';
var simpleOperatorPattern = '^[-+*/^%⊻&|()]$';
var compoundOperatorPattern = '^(<<|>>)$';
var operatorCharsPattern = '^[-+*/^%⊻&|<>()]+$';
var numberPattern = '^[0-9.]+$';

function calculate (calcString) {
    // tokenise, then evaluate separately
    let tokens = [];
    let currentToken = '';
    for (let i=0; i<calcString.length; i++) {
        if (
            currentToken.match(simpleOperatorPattern) || currentToken.match(compoundOperatorPattern) ||
            currentToken.match(numberPattern) && calcString[i].match(operatorCharsPattern) ||
            currentToken.match(operatorCharsPattern) && calcString[i].match(numberPattern)
        ) {
            tokens.push(currentToken);
            currentToken = '';
        }
        currentToken += calcString[i];
    }
    tokens.push(currentToken);
    for (let i=0; i < tokens.length; i++) {
        if (tokens[i].match(digitsOnlyPattern)) {
            tokens[i] = parseInt(tokens[i]);
        } else if (tokens[i].match(numberPattern)) {
            tokens[i] = parseFloat(tokens[i]);
        }
    }
    return calculateTokenised(tokens);
}

function calculateTokenised (tokens) {
    let lastOpeningIndex;
    do {
        lastOpeningIndex = -1
        for (let i=0; i < tokens.length; i++) {
            if (tokens[i] == '(') {
                lastOpeningIndex = i;
            } else if (tokens[i] == ')') {
                if (lastOpeningIndex == -1) {
                    throw 'Malformed parenthesis';
                }
                let slice = tokens.splice(lastOpeningIndex, i - lastOpeningIndex + 1);
                let partialEval = calculateTokenised(slice.splice(1, slice.length - 2));
                tokens.splice(lastOpeningIndex, 0, partialEval);
            }
        }
    } while (lastOpeningIndex != -1);
    let operators = [
        [
            { 'str': '^', 'f': (a,b) => Math.pow(a, b) }
        ],
        [
            { 'str': '*', 'f': (a,b) => a*b },
            { 'str': '/', 'f': (a,b) => a/b },
            { 'str': '%', 'f': (a,b) => a%b }
        ],
        [
            { 'str': '+', 'f': (a,b) => a+b },
            { 'str': '-', 'f': (a,b) => a-b }
        ],
        [
            { 'str': '<<', 'f': (a,b) => a<<b },
            { 'str': '>>', 'f': (a,b) => a>>b }
        ],
        [
            { 'str': '&', 'f': (a,b) => a&b },
            { 'str': '|', 'f': (a,b) => a|b },
            { 'str': '⊻', 'f': (a,b) => a^b }
        ]
    ];
    for (let p=0; p<operators.length; p++) {
        let ops = operators[p];
        let foundOp;
        do {
            foundOp = false;
            for (let t=1; t < tokens.length - 1 && !foundOp; t++) {
                for (let op=0; op < ops.length && !foundOp; op++) {
                    if (tokens[t] == ops[op].str) {
                        foundOp = true;
                        let a = tokens[t-1];
                        let b = tokens[t+1];
                        if (typeof a != 'number' || typeof b != 'number') {
                            console.error(tokens);
                            throw 'Encountered non-number token where number was expected';
                        }
                        let partialEval = ops[op].f(a, b);
                        tokens.splice(t-1, 3, partialEval);
                    }
                }
            }
        } while (foundOp);
    }
    if (tokens.length != 1) {
        console.error(tokens);
        throw 'Evaluation failed';
    }
    return tokens[0];
}

(I know it's not the cleanest and I would've used TS if that was an option)