Source: texParser.js

/** 
 * Code for traversing: 
 * https://code.tutsplus.com/articles/data-structures-with-javascript-tree--cms-23393 
 * https://www.geeksforgeeks.org/what-is-export-default-in-javascript/
 * **/


"use strict";
import config from "./config.json";

// Klasse als Funktion in JS
function node() {
    this.id = -1;
    this.parent = 0;
    this.children = [];
    this.type = 'space';
    this.content = '';
    this.value = 'u';
}

node.prototype.addBracket = function (tree) {
    var temp = findLeftmostBracket(this.content);
    var leftPos = temp[0];
    var bra = temp[1];
    bracket = findCorrespondingRightBracket(this.content, bra);
    var leftPos2 = bracket.leftPos;
    var bra_len = bracket.bracketLength;
    var rightPos = bracket.rightPos;
    var rightbra_len = bracket.rightBracketLength;
    // this should not happen
    if (leftPos !== leftPos2) {
        throw 'Inconsistent left positions ';
    }
    if (leftPos > -1 && rightPos > -1) {
        var leftpart = this.content.substring(0, leftPos);
        var middlepart = this.content.substring(leftPos + bra_len, rightPos);
        var rightpart = this.content.substring(rightPos + rightbra_len);
        this.content = leftpart + '§' + rightpart;
        var bracket = createNode('bracket-' + bra, '', tree);
        var middle = createNode('leaf', middlepart, tree);
        if (middlepart == ' ') { // e.g. indefinite integral
            middle.type = 'empty';
        }
        // first connection
        this.children.push(bracket.id);

        bracket.parent = this.id;
        // second connection
        bracket.children.push(middle.id);
        middle.parent = bracket.id;
    } else {
        // else no pair of brackets found
        leftPos = -1;
    }
    return leftPos;
};

node.prototype.debug = function () {
    var text = this.id + ': parent=' + this.parent;
    text += ' children=' + this.children;
    text += ' type=' + this.type;
    text += ' content=' + this.content;
    return text;
};

function createNode(type, content, tree) {
    var nodelist = tree.nodelist;
    var lof = tree.listOfFree || [];
    var temp = '';
    if (lof.length === 0) {
        temp = new node();
        temp.type = type;
        temp.content = content;
        nodelist.push(temp);
        temp.id = nodelist.length - 1;
        return temp;
    } else {
        var lastFree = lof.pop();
        temp = nodelist[lastFree];
        temp.type = type;
        temp.content = content;
        temp.children = [];
        return temp;
    }
}

// define class FaTree using function syntax
export function FaTree() {
    this.listOfFree = [];
    this.nodelist = [];
    this.nodelist[0] = new node();
    this.root = this.nodelist[0];
    this.root.type = 'root';
    this.root.id = 0;
    this.root.parent = -1;
    this.leaf = new node();
    this.leaf.type = 'leaf';
    this.leaf.content = 'my first leaf';
    this.nodelist[1] = this.leaf;
    this.leaf.id = 1;
    this.leaf.parent = this.root.id;
    this.root.children = [this.leaf.id];
    this.hasValue = false;
    this.variableValueList = [];
}

function withEachNode(tree, f) {
    var i = 0;
    var stop = false;
    var listOfNodes = tree.nodelist;
    do {
        var node = listOfNodes[i];
        // doThis may add or delete nodes!
        f(node);
        i++;
        if (i === tree.nodelist.length) {
            stop = true;
        }
    } while (stop === false)
}

function withEachLeaf(tree, f) {
    withEachNode(tree, function (node) {
        if (node.type == 'leaf') {
            f(node);
        }
    })
}

function withEachLeafOrGreek(tree, f) {
    withEachNode(tree, function (node) {
        if (node.type == 'leaf' || node.type == 'greek') {
            f(node);
        }
    })
}

export function isInUnit(tree, node) {
    var result = false;
    var stop = false;
    do {
        if (node.type == 'unit') {
            result = true;
            stop = true;
        } else {
            node = tree.nodelist[node.parent];
            if (node.type == 'root') {
                stop = true;
            }
        }
    } while (stop === false);
    return result;
}

function deleteSingleNodes(tree) {
    // delete § nodes
    // nodes with type='free' may not be deleted a second time
    withEachNode(tree, function (node) {
        if (node.content === '§' && node.children.length === 1 && node.type !== 'free') {
            var siblings = tree.nodelist[node.parent].children;
            var position = siblings.indexOf(node.id);
            // short circuit
            siblings[position] = node.children[0];
            tree.nodelist[node.children[0]].parent = node.parent;
            node.type = 'free';
            tree.listOfFree.push(node.id);
        }
    });
    // return tree.listOfFree;
}

node.prototype.isRightmostChild = function (nodelist) {
    if (this.id === 0) {
        return false;
    } else {
        var siblings = nodelist[this.parent].children;
        var rightmost = siblings[siblings.length - 1];
        var isRightmost = (this.id === rightmost);
        return isRightmost;
    }
};

function findLeftBracket(content, bra) {
    var pos;
    var long = '\\left' + bra;
    if (bra === '{') {
        long = '\\left\\{';
    }
    var minPos = -1;
    var braKind = 'nothing';
    pos = content.indexOf(long);
    var masked = content;
    if (pos >= 0) {
        minPos = pos;
        braKind = long;
        // mask all occurrencies of long
        var stop = false;
        pos = -1;
        do {
            pos = masked.indexOf(long, pos + 1);
            if (pos === -1) {
                stop = true;
            } else {
                var part1;
                if (pos > 0) {
                    part1 = masked.substring(0, pos - 1);
                } else {
                    part1 = '';
                }
                var part2 = '\\left@';
                if (bra === '\\{') {
                    part2 = '\\left\\@';
                }
                var part3 = masked.substring(pos + long.length);
                masked = part1 + part2 + part3;
            }
        } while (stop === false);
    }
    // All occurrencies of long are masked
    // Look for short bracket
    pos = masked.indexOf(bra);
    if (pos >= 0) {
        if (minPos === -1) {
            minPos = pos;
            braKind = bra;
        } else {
            if (pos < minPos) {
                minPos = pos;
                braKind = bra;
            }
        }
    }
    return [minPos, braKind];
}

function findLeftmostBracket(content) {
    var pos;
    var leftPos = -1;
    var braKind = 'nothing';
    var result = findLeftBracket(content, "(");
    pos = result[0];
    if (pos > -1) {
        if (leftPos === -1) {
            leftPos = pos;
            braKind = result[1];
        } else {
            if (pos < leftPos) {
                leftPos = pos;
                braKind = result[1];
            }
        }
    }
    // maybe there is a better (smaller) pos for a [ bracket
    result = findLeftBracket(content, '[');
    pos = result[0];
    if (pos > -1) {
        if (leftPos === -1) {
            leftPos = pos;
            braKind = result[1];
        } else {
            if (pos < leftPos) {
                leftPos = pos;
                braKind = result[1];
            }
        }
    }
    // maybe there is a better (smaller) pos for a { bracket
    result = findLeftBracket(content, '{');
    pos = result[0];
    if (pos > -1) {
        if (leftPos === -1) {
            leftPos = pos;
            braKind = result[1];
        } else {
            if (pos < leftPos) {
                leftPos = pos;
                braKind = result[1];
            }
        }
    }
    // maybe there is a better (smaller) pos for a | bracket
    result = findLeftBracket(content, '|');
    pos = result[0];
    if (pos > -1) {
        if (leftPos === -1) {
            leftPos = pos;
            braKind = result[1];
        } else {
            if (pos < leftPos) {
                leftPos = pos;
                braKind = result[1];
            }
        }
    }
    return [leftPos, braKind];
}

export function findCorrespondingRightBracket(content, bra) {
    var pos;
    pos = ['(', '[', '{', '|', '\\left(', '\\left[', '\\left\\{', '\\left|'].indexOf(bra);
    var rightbra;
    if (pos === -1) {
        rightbra = 'no bracket found error';
    } else {
        rightbra = [')', ']', '}', '|', '\\right)', '\\right]', '\\right\\}', '\\right|'][pos];
    }
    var stop = false;
    var mass = [];
    for (var i = 0; i < content.length; i++) {
        mass[i] = 0;
    }
    var leftPos = -1;
    var rightPos = -1;
    pos = -1;
    do {
        pos = content.indexOf(bra, pos + 1);
        if (pos === -1) {
            stop = true;
        } else {
            mass[pos] = 1;
            if (leftPos === -1) {
                leftPos = pos;
            }
        }
    } while (stop === false);
    pos = -1;
    stop = false;
    do {
        pos = content.indexOf(rightbra, pos + 1);
        if (pos === -1) {
            stop = true;
        } else {
            mass[pos] = -1;
        }
    } while (stop === false);
    // sum of masses
    for (i = 1; i < content.length; i++) {
        var sum = mass[i - 1] + mass[i];
        if (mass[i] === -1 && sum === 0) {
            rightPos = i;
            break;
        }
        mass[i] = sum;
    }
    return {
        leftPos: leftPos,
        bracketLength: bra.length,
        rightPos: rightPos,
        rightBracketLength: rightbra.length
    };
}

function removeOperators(tree, kindOfOperators) {
    var pos = -1;
    var opOne = '+';
    var opTwo = '-';
    if (kindOfOperators === 'equal') {
        opOne = '=';
        opTwo = '@%';
    }
    if (kindOfOperators === 'timesdivided') {
        opOne = '\\cdot';
        opTwo = ':';
    }
    if (kindOfOperators === 'invisibleTimes') {
        opOne = '*';
        opTwo = '@%';
    }
    // before power, \int has to be parsed
    if (kindOfOperators === 'power') {
        opOne = '^';
        opTwo = '@%';
    }
    // before sub, \int has to be parsed
    if (kindOfOperators === 'sub') {
        opOne = '_';
        opTwo = '@%';
    }
    var opOneLength = opOne.length;
    var opTwoLength = opTwo.length;
    withEachLeaf(tree, function (node) {
        var loop = true;
        do {
            var posOne = node.content.lastIndexOf(opOne);
            var posTwo = node.content.lastIndexOf(opTwo);
            var posOneFlag = false;
            if (posOne === -1 && posTwo === -1) {
                pos = -1;
            } else {
                if (posOne === -1) {
                    pos = posTwo;
                }
                if (posTwo === -1) {
                    pos = posOne;
                    posOneFlag = true;
                }
                if (posOne > -1 && posTwo > -1) {
                    pos = posOne;
                    posOneFlag = true;
                    if (posTwo > pos) {
                        pos = posTwo;
                        posOneFlag = false;
                    }
                }
            }

            if (pos === -1) {
                loop = false;
            } else {
                // found an operator opOne or opTwo in node[index]
                var leftpart, middlepart, rightpart;
                leftpart = node.content.substring(0, pos);
                if (posOneFlag) {
                    middlepart = node.content.substring(pos, pos + opOneLength);
                    rightpart = node.content.substring(pos + opOneLength);
                } else {
                    middlepart = node.content.substring(pos, pos + opTwoLength);
                    rightpart = node.content.substring(pos + opTwoLength);
                }
                // number of § markers
                var leftcount = (leftpart.match(/§/g) || []).length;
                var rightcount = (rightpart.match(/§/g) || []).length;
                var check = ((leftcount + rightcount) === node.children.length);
                if (node.type.startsWith('definite')) {
                    // children[0] = lowerBoundary, children[1] = upperBoundary
                    check = ((leftcount + rightcount) === node.children.length - 2);
                }
                if (check === false) {
                    ///throw('(remove operators) Wrong number of bracket markers');
                    console.warn('(remove operators) Wrong number of bracket markers');
                }
                var rememberchildren = node.children;
                var leftchildren, rightchildren;
                if (leftcount > 0) {
                    leftchildren = rememberchildren.slice(0, leftcount);
                } else {
                    leftchildren = [];
                }
                if (rightcount > 0) {
                    rightchildren = rememberchildren.slice(leftcount, rememberchildren.length);
                } else {
                    rightchildren = [];
                }
                var operator = createNode('plusminus', middlepart, tree);
                if (kindOfOperators === 'equal') {
                    operator.type = 'equal';
                }
                if (kindOfOperators === 'timesdivided') {
                    operator.type = 'timesdivided';
                }
                if (kindOfOperators === 'invisibleTimes') {
                    operator.type = '*';
                    operator.content = '';
                }
                if (kindOfOperators === 'power') {
                    operator.type = 'power';
                }
                if (kindOfOperators === 'sub') {
                    operator.type = 'sub';
                }
                var rest = createNode('leaf', rightpart, tree);
                if (rest.content == "") {
                    rest.content = "0";
                    rest.type = "invisible_zero";
                }
                var siblings = tree.nodelist[node.parent].children;
                var position = siblings.indexOf(node.id);

                // Upper connection: connect new node operator with former parent of node
                tree.nodelist[node.parent].children[position] = operator.id;
                operator.parent = node.parent;
                // Left and right connection: 
                // connect new node operator at left side with old node, but left part only
                // connect new node operator at right side with new node rest
                // Direction "up"
                node.content = leftpart;
                if (node.content == "") {
                    node.content = "0";
                    node.type = "invisible_zero";
                }
                node.parent = operator.id;
                rest.parent = operator.id;
                // Direction "down"
                operator.children = [node.id, rest.id];
                // children of node and rest have to be adjusted
                node.children = leftchildren;
                // node stays parent of left children: nothing to do
                rest.children = rightchildren;
                // node "rest" becomes parent of right children
                for (var i = 0; i < rightchildren.length; i++) {
                    tree.nodelist[rightchildren[i]].parent = rest.id;
                }
            }
        } while (loop == true);
    });
    // return tree.nodelist;
}

let parseTreeCounter = {
    counter: 0,

    getCounter() {
        return this.counter;
    },

    setCounter(value) {
        this.counter = value;
    },

    inc() {
        this.counter++;
    }
}

export function parseTreeByIndex(tree) {
    parseTreeCounter.inc();
    var endParse = false;
    var message = '';
    // var listOfFree;
    switch (parseTreeCounter.getCounter()) {
        case 1:
            message = 'delete spaces and remove backslash at \\min';
            tree.leaf.content = deleteSpaceAndRemoveBackslash(tree.leaf.content);
            tree.leaf.content = makeDegreeUnit(tree.leaf.content);
            break;
        case 2:
            message = 'parse brackets';
            parseBrackets(tree);
            break;
        case 3:
            message = 'parse equal';
            removeOperators(tree, 'equal');
            message = 'parse plusminus';
            removeOperators(tree, 'plusminus');
            break;
        case 4:
            message = 'parse timesdivided';
            removeOperators(tree, 'timesdivided');
            break;
        case 5:
            message = 'unify subscript and exponent (part 1)';
            unifySubExponent(tree);
            break;
        case 6:
            message = 'parse integral';
            parseIntegral(tree);
            break;
        case 7:
            message = 'parse square root / nth root';
            parseNthRoot(tree);
            parseSqrt(tree);
            break;
        case 8:
            message = 'parse log_base';
            parseLogLim(tree, 'log'); //log
            break;
        case 9:
            message = 'parse lim';
            parseLogLim(tree, 'lim'); //lim
            break;
        case 10:
            message = 'parse functions';
            parseFunction(tree);
            break;
        case 11:
            message = 'parse fractions';
            parseFrac(tree);
            break;
        case 12:
            message = 'parse textcolor (unit)';
            //check_children(tree);
            parseTextColor(tree);
            //check_children(tree);
            break;
        case 13:
            message = 'delete single § nodes'
            deleteSingleNodes(tree);
            break;
        case 14:
            message = 'parse greek';
            parseGreek(tree);
            break;
        case 15:
            message = 'parse numbers';
            parseNumbers(tree);
            break;
        case 16:
            message = 'delete single § nodes';
            deleteSingleNodes(tree);
            break;
        case 17:
            message = 'parse mixed numbers ';
            parseMixedNumbers(tree);
            break;
        case 18:
            message = 'unify subscript (part 2) '
            unifySubOrPower(tree, false);
            break;
        case 19:
            message = 'parse subscript'
            parseSubPower(tree, false);
            break;
        case 20:
            message = 'unify power (part 2) '
            unifySubOrPower(tree, true);
            break;
        case 21:
            message = 'parse power'
            parseSubPower(tree, true);
            break;
        case 22:
            message = 'delete single § nodes'
            deleteSingleNodes(tree);
            break;
        case 23:
            message = 'parse unit'
            parseUnit(tree);
            //check_children(tree);
            break;
        case 24:
            message = 'parse factors';
            parseFactors(tree);
            //check_children(tree);
            break;
        case 25:
            message = 'delete single § nodes';
            deleteSingleNodes(tree);
            break;
        default:
            message = 'end of parse';
            endParse = true;
    }
    return {
        message: message,
        endParse: endParse
    };
}

export default function parse(texstring) {
    var myTree = new FaTree();
    myTree.leaf.content = texstring;
    var endParse = false;
    parseTreeCounter.setCounter(0);
    while (!endParse) {
        var parseResult = parseTreeByIndex(myTree);
        endParse = parseResult.endParse;
        //paint_tree(tree, canvas, parseResult.message);
    }
    return myTree;
}

/**
 * deletes spaces and removes backslashes before 'min' or 'max'<br>
 * 
 * @param {string} text latex string to be parsed 
 * @returns {string} beautified latex string
 * @example text = 'abc  def' returns 'abc def'
 * @example A backslash \ has to be escaped as \\ in regex expressions and javascript strings
 * @example text = '\min' returns 'min'
 * @example text = '\cdot' (no trailing space) returns '\cdot ' (1 space)
 * @example text = '\cdot  ' (2 spaces) returns '\cdot ' (1 space)
 */
function deleteSpaceAndRemoveBackslash(text) {
    // https://stackoverflow.com/questions/4025482/cant-escape-the-backslash-with-regex#4025505
    // http://www.javascripter.net/faq/backslashinregularexpressions.htm
    text = String(text);
    var temp = text.replace(/\\\s/g, '');
    temp = temp.replace(/\\min/g, 'min');
    temp = temp.replace(/\\max/g, 'max');
    // unify spaces after \\cdot
    temp = temp.replace(/\\cdot/g, '\\cdot '); // no space -> one space, but one space -> two spaces
    temp = temp.replace(/\\cdot {2}/g, '\\cdot '); // two spaces -> one space

    // temp = temp.replace(/\\Ohm/g, '\\Omega'); // transform unit Ohm to greek Omega. Done in preparePage.js
    return temp;
}

function makeDegreeUnit(text) {
    text = text.replace(/''/g, "↟");
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec
    // https://regex101.com/ online regex tester
    var regex = RegExp('((\\d+(\\.|\\,))?\\d+)([°\'↟]+)', 'g');
    // regex matches for example .45° 3,89' 17.5↟
    let result;
    let lastlastIndex = 0;
    let separator = '∱';
    let gaps = [separator];
    let number = [separator];
    let degree = [separator];

    var stop = false;
    do {
        if ((result = regex.exec(text)) !== null) {
            var gap = text.substring(lastlastIndex, result.index);
            if (gap !== "") {
                gaps.push(separator);
                number.push(separator);
                degree.push(separator);
            }
            gaps.push(gap);
            number.push(result[1]);
            degree.push(result[4]);
            lastlastIndex = regex.lastIndex;
        } else { // result == null
            stop = true;
        }
    } while (stop == false);

    // handle the end of text string
    gap = text.substring(lastlastIndex);
    gaps.push(gap);
    number.push('');
    degree.push('');

    var unitchain = degree.join('') + separator;


    var regex2 = []
    regex2.push(RegExp("∱°'↟∱", 'g'));
    regex2.push(RegExp("∱'↟∱", 'g'));
    regex2.push(RegExp("∱°↟∱", 'g'));
    regex2.push(RegExp("∱°'∱", 'g'));
    regex2.push(RegExp("∱°∱", 'g'));
    regex2.push(RegExp("∱'∱", 'g'));
    regex2.push(RegExp("∱↟∱", 'g'));

    for (var reg = 0; reg < regex2.length; reg++) {
        regex = regex2[reg];
        stop = false;
        do {
            if ((result = regex.exec(unitchain)) !== null) {
                var index = result.index + 1;
                switch (result[0].length) {
                    case 5:
                        // code block
                        gaps[index] += '{';
                        degree[index] += '+';
                        degree[index + 1] += '+';
                        degree[index + 2] += '}';
                        // var test = gaps[index] + '{' + number[index] + degree[index] + '+';
                        // test += gaps[index + 1] + number[index + 1] + degree[index + 1] + '+';
                        // test += gaps[index + 2] + number[index + 2] + degree[index + 2] + '}';
                        break;
                    case 4:
                        gaps[index] += '{';
                        degree[index] += '+';
                        degree[index + 1] += '}';
                        // var test = gaps[index] + '{' + number[index] + degree[index] + '+';
                        // test += gaps[index + 1] + number[index + 1] + degree[index + 1] + '}';
                        break;
                    case 3:
                        // var test = gaps[index] + number[index] + degree[index];
                        break;
                    default:
                        console.error('Error in texParser makeDegreeUnit');
                }
            } else { // result == null
                stop = true;
            }
        } while (stop == false);
    }

    //test
    var textWithBracketsAndPlus = '';
    for (var i = 0; i < degree.length; i++) {
        gap = gaps[i];
        if (gap !== separator) {
            textWithBracketsAndPlus += gap;
            textWithBracketsAndPlus += number[i];
            textWithBracketsAndPlus += degree[i];
        }
    }
    // var unit = "\\unit{";
    // var unit = "\\textcolor{blue}{";
    var unit = config.unit_replacement;

    var temp = textWithBracketsAndPlus.replace(/'/g, unit + "'}");
    temp = temp.replace(/°/g, unit + "°}");
    temp = temp.replace(/↟/g, unit + "''}");
    return temp;
}

function parseBrackets(tree) {
    withEachLeaf(tree, function (node) {
        var stop = false;
        do {
            var leftPos = node.addBracket(tree);
            if (leftPos == -1) {
                stop = true;
            }
        } while (stop === false)
    });
    return tree.nodelist;
}

function unifySubExponent(tree) {
    for (var needle of ['_', '^']) {
        withEachLeaf(tree, function (node) {
            var stop = false;
            var start = 0;
            do {
                var pos = node.content.indexOf(needle, start);
                if (pos < 0) {
                    stop = true;
                } else {
                    start = pos + 1;
                    var leftpart = node.content.substring(0, pos);
                    var leftCount = (leftpart.match(/§/g) || []).length;
                    var rest = node.content.substr(pos + 2);
                    // var predecessor = node.content.substr(pos - 1, 1);
                    var exponentOrSubScript = node.content.substr(pos + 1, 1);

                    // if (predecessor !== '§') {
                    //     newNode = createNode('leaf', predecessor, tree);
                    //     newNode.parent = node.id;
                    //     node.children.splice(leftCount, 0, newNode.id);
                    //     // for (var i = 0; i < node.children.length; i++) {
                    //                    //     // }
                    // }
                    // Now in any case predecessor equals '§'. 
                    // Number of § in leftpart+predecessor is one higher al old leftCount
                    // leftCount++;

                    if (exponentOrSubScript !== '§') {
                        var newNode = createNode('leaf', exponentOrSubScript, tree);
                        newNode.parent = node.id;
                        node.children.splice(leftCount, 0, newNode.id);
                        // for (var i = 0; i < node.children.length; i++) {
                        // }
                    }
                    node.content = leftpart + needle + '§' + rest;
                }
            } while (stop === false)
        });
    }
}

function parseIntegral(tree) {
    // for (var i = 0; i < listOfNodes.length; i++) {
    // does not fit because length of list changes
    withEachLeaf(tree, function (node) {
        var content = node.content;
        const needle = '\\int_§^§';
        var pos = content.indexOf(needle);
        if (pos > -1) {
            var left = node.content.substring(0, pos);
            var right = node.content.substring(pos + needle.length);
            var leftCount = (left.match(/§/g) || []).length;
            var rightCount = (right.match(/§/g) || []).length;
            // if there is no § in left, then leftCount = 0
            var newcontent = left + '§';
            // node has one § less! 
            node.content = newcontent;
            //check
            var lowerBound = tree.nodelist[node.children[leftCount]];
            var upperBound = tree.nodelist[node.children[leftCount + 1]];
            var integral = createNode('integral', '', tree);
            var integrand = createNode('leaf', right, tree);
            // last two characters
            var differential = right.substring(right.length - 2);
            if (differential.startsWith('d')) {
                // repair if differential is too short
                if (differential.length == 1) {
                    differential += 'x';
                }
                integrand.content = right.substr(0, right.length - 2);
                var diff = createNode('differential', differential, tree);
                //integral has four children 
                integral.children = [lowerBound.id, upperBound.id, integrand.id, diff.id];
                diff.parent = integral.id;
            } else {
                //integral has three children 
                integral.children = [lowerBound.id, upperBound.id, integrand.id];
            }

            // link integral
            integral.parent = node.id;
            // now the other directions
            lowerBound.parent = integral.id;
            upperBound.parent = integral.id;
            integrand.parent = integral.id;
            node.children[leftCount] = integral.id;
            node.children.splice(leftCount + 1, 1);
            for (var i = leftCount + 1; i <= leftCount + rightCount; i++) {
                var id = node.children[i];
                integrand.children.push(id);
                tree.nodelist[id].parent = integrand.id;
            }
            node.children.splice(leftCount + 1, rightCount);
        }
    });
}

function parseNthRoot(tree) {
    parseRadix(tree, true);
}

function parseSqrt(tree) {
    parseRadix(tree, false);
}

function parseRadix(tree, nthroot) {
    var needle = '\\sqrt§';
    if (nthroot === true) {
        needle = '\\sqrt§§';
    }

    withEachLeaf(tree, function (node) {
        var stop = false;
        do {
            var pos = node.content.indexOf(needle);
            if (pos > -1) {
                var left = node.content.substring(0, pos);
                var right = node.content.substring(pos + needle.length);
                var radIndex = (left.match(/§/g) || []).length;
                // if there is no § in left, then radIndex = 0
                var newcontent, radix;
                if (nthroot === true) {
                    newcontent = left + '§' + right;
                    // node has one § less! 
                    node.content = newcontent;
                    //check
                    // test = tree.nodelist[node.children[radIndex]].type;
                    // test = tree.nodelist[node.children[radIndex + 1]].type;
                    radix = createNode('nthroot', '', tree);
                    // link radix
                    radix.parent = node.id;
                    //radix has two children 
                    radix.children = [node.children[radIndex], node.children[radIndex + 1]];
                    // now the other directions
                    tree.nodelist[node.children[radIndex]].parent = radix.id;
                    tree.nodelist[node.children[radIndex + 1]].parent = radix.id;
                    node.children[radIndex] = radix.id;
                    node.children.splice(radIndex + 1, 1);
                } else {
                    newcontent = left + '§' + right;
                    //check
                    // test = tree.nodelist[node.children[radIndex]].type;
                    node.content = newcontent;
                    radix = createNode('sqrt', '', tree);
                    // link radix
                    radix.parent = node.id;
                    //radix has only one child
                    radix.children = [node.children[radIndex]];
                    // now the other directions
                    tree.nodelist[node.children[radIndex]].parent = radix.id;
                    node.children[radIndex] = radix.id;
                }
            } else {
                stop = true;
            }
        } while (stop === false)
    });
}

function parseLogLim(tree, kind) {
    var needle = '\\' + kind + '_§';
    withEachLeaf(tree, function (node) {
        var stop = false;
        do {
            var pos = node.content.indexOf(needle);
            if (pos > -1) {
                var left = node.content.substring(0, pos);
                var right = node.content.substring(pos + needle.length);
                var leftCount = (left.match(/§/g) || []).length;
                var rightCount = (right.match(/§/g) || []).length;
                // if there is no § in left, then leftCount = 0
                var newcontent = left + '§'; //right is moved to arg
                // node has one § less!
                node.content = newcontent;
                //check
                var base = tree.nodelist[node.children[leftCount]];
                var log = createNode('fu-' + kind, '', tree);
                var arg = createNode('leaf', right, tree);
                // link log
                log.parent = node.id;
                //log has two children 
                log.children = [base.id, arg.id];
                // now the other directions
                base.parent = log.id;
                arg.parent = log.id;
                node.children[leftCount] = log.id;
                for (var i = leftCount + 1; i < leftCount + 1 + rightCount; i++) {
                    arg.children.push(node.children[i]);
                    tree.nodelist[node.children[i]].parent = arg.id;
                }
                node.children.splice(leftCount + 1, rightCount);
            } else {
                stop = true;
            }
        } while (!stop)
    });
}

function functionList() {
    var result = ['sinh', 'cosh', 'tanh', 'sin', 'cos', 'tan', 'ln', 'lg', 'log', 'exp', 'abs', 'arcsin', 'arccos', 'arctan'];
    return result;
}

function parseFunction(tree) {
    // including function^exponent syntax, e.g. sin^2(x)
    withEachLeaf(tree, function (node) {
        var stop = false;
        var k = 0;
        var fu;
        do {
            fu = functionList()[k];
            var type = 'fu-' + fu;
            fu = '\\' + fu;
            var pos = node.content.indexOf(fu);
            if (pos > -1) {
                var leftpart = node.content.substring(0, pos);
                var leftCount = (leftpart.match(/§/g) || []).length;
                var rest = node.content.substring(pos + fu.length);
                var rightCount = (rest.match(/§/g) || []).length;
                var fuNode = createNode(type, '', tree);
                // link node <-> fuNode
                fuNode.parent = node.id;
                var remember = node.children[leftCount] || 0;
                node.children[leftCount] = fuNode.id;
                if (rest.startsWith('^§')) {
                    //fu-power
                    fuNode.content = 'power';
                    rest = rest.substring(2);
                    var arg = createNode('leaf', rest, tree);
                    fuNode.children[0] = remember;
                    tree.nodelist[remember].parent = fuNode.id;
                    fuNode.children[1] = arg.id;
                    arg.parent = fuNode.id;
                } else {
                    // no power:", "\\sin...
                    if (rest == '§') {
                        // \\sin§
                        fuNode.children[0] = remember;
                        tree.nodelist[remember].parent = fuNode.id;
                    } else {
                        //", "\\sin2\alpha
                        rest = rest.trim();
                        arg = createNode('leaf', rest, tree);
                        arg.parent = fuNode.id;
                        //fuNode.children[0] = remember;
                        fuNode.children[0] = arg.id;

                        for (var i = leftCount + 1; i <= leftCount + rightCount; i++) {
                            var id = node.children[i];
                            arg.children.push(id);
                            tree.nodelist[id].parent = arg.id;
                        }
                        node.children.splice(leftCount, rightCount);

                        //tree.nodelist[remember].parent = fuNode.id;
                        //arg.children[0] = remember;
                        //tree.nodelist[remember].parent = arg.id;
                    }
                }
                node.content = leftpart + '§';
            } else {
                // fu not found. Try next fu
                k++;
            }
            if (k >= functionList().length) {
                stop = true;
            }
        }
        while (stop === false);
    });

}

function parseFrac(tree) {
    const needle = '\\frac§§';
    withEachLeaf(tree, function (node) {
        // eslint-disable-next-line no-constant-condition
        while (true) {
            var pos = node.content.indexOf(needle);
            if (pos <= -1) break;
            var left = node.content.substring(0, pos);
            var right = node.content.substring(pos + needle.length);
            var fracIndex = (left.match(/§/g) || []).length; // = leftCount
            // if there is no § in left, then fracIndex = 0
            // node has one § less! 
            node.content = left + '§' + right;
            // check
            var test = tree.nodelist[node.children[fracIndex]].type;
            // eslint-disable-next-line no-unused-vars
            test = tree.nodelist[node.children[fracIndex + 1]].type;

            var fraction = createNode('frac', '', tree);
            // link fraction
            fraction.parent = node.id;
            //radix has two children 
            fraction.children = [node.children[fracIndex], node.children[fracIndex + 1]];
            // now the other directions
            tree.nodelist[node.children[fracIndex]].parent = fraction.id;
            tree.nodelist[node.children[fracIndex + 1]].parent = fraction.id;
            node.children[fracIndex] = fraction.id;
            node.children.splice(fracIndex + 1, 1);
        }
    });
}

function parseTextColor(tree) {
    const needle = '\\textcolor§§';
    withEachLeaf(tree, function (node) {
        // eslint-disable-next-line no-constant-condition
        while (true) {
            var pos = node.content.indexOf(needle);
            if (pos <= -1) break;
            var left = node.content.substring(0, pos);
            var right = node.content.substring(pos + needle.length);
            var unit_index = (left.match(/§/g) || []).length; // = leftCount
            // if there is no § in left, then unit_index = 0
            // node has one § less! 
            node.content = left + '§' + right;
            var bracket = tree.nodelist[node.children[unit_index]];
            // var test = tree.nodelist[node.children[unit_index + 1]].type;
            //check
            // fetch the color
            var colornode = tree.nodelist[bracket.children[0]];
            var color = colornode.content;
            var unit = createNode('unit', color, tree);
            // link unit
            unit.parent = node.id;
            //unit has one child 
            unit.children[0] = node.children[unit_index + 1];
            // now the other directions
            tree.nodelist[node.children[unit_index + 1]].parent = unit.id;
            node.children[unit_index] = unit.id;
            node.children.splice(unit_index + 1, 1);
            // delete two nodes
            bracket.type = 'free';
            tree.listOfFree.push(bracket.id);
            colornode.type = 'free';
            tree.listOfFree.push(colornode.id);
        }
    })
}

function greekList() {
    return ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta",
        "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi",
        "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega",
        "varepsilon", "vartheta", "varkappa", "varpi", "varrho", "varsigma", "varphi",
        "Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta", "Eta", "Theta",
        "Iota", "Kappa", "Lambda", "Mu", "Nu", "Xi", "Omicron", "Pi",
        "Rho", "Sigma", "Tau", "Upsilon", "Phi", "Chi", "Psi", "Omega",
        "to", "infty"
    ];
}

function parseGreek(tree) {
    withEachLeaf(tree, function (node) {
        var k = 0;
        var pos = -1;
        do {
            var greek = '\\' + greekList()[k];
            pos = node.content.indexOf(greek);
            if (pos > -1) {
                var leftpart = node.content.substring(0, pos);
                var leftCount = (leftpart.match(/§/g) || []).length;
                var rest = node.content.substring(pos + greek.length);
                var greekNode = createNode('greek', greekList()[k], tree);
                // link node <-> greekNode
                greekNode.parent = node.id;
                node.children.splice(leftCount, 0, greekNode.id);
                node.content = leftpart + '§' + rest;
                // maybe use same k again 
            } else {
                k++;
            }
        } while (k <= greekList().length);
    });
}

function parseNumbers(tree) {
    withEachLeaf(tree, function (node) {
        var content = node.content.trim();
        // var regex = '\\d+((\\.|\\,)\\d+)?';
        var regex = '(\\d+(\\.|\\,))?\\d+';
        // backslash must be masked: \\
        var pos = content.search(regex);
        // if no number in content, pos=-1
        if (pos == 0) { //content starts with number
            var match = content.match(regex);
            var num = content.substr(0, match[0].length);
            var rest = content.substring(match[0].length);
            node.content = "§" + rest;
            var number = createNode("number", num, tree);
            number.value = num;
            number.parent = node.id;
            node.children.splice(0, 0, number.id)
        }
    })
}

function parseMixedNumbers(tree) {
    withEachLeaf(tree, function (node) {
        var content = node.content.trim();
        if (content.startsWith('§§')) {
            var child0 = tree.nodelist[node.children[0]];
            if (child0.type == 'number') {
                var child1 = tree.nodelist[node.children[1]];
                if (child1.type == 'frac') {
                    var nomBracket = tree.nodelist[child1.children[0]];
                    if (nomBracket.type == 'bracket-{') {
                        var nom = tree.nodelist[nomBracket.children[0]];
                    }
                    var denomBracket = tree.nodelist[child1.children[1]];
                    if (denomBracket.type == 'bracket-{') {
                        var denom = tree.nodelist[denomBracket.children[0]];
                    }
                    try {
                        if (nom.type == 'number' && denom.type == 'number') {
                            var mixedNum = createNode('mixedNumber', '', tree);
                            // leaf node has one child less and is parent of mixedNum
                            node.content = node.content.substr(1);
                            node.children.shift();
                            node.children[0] = mixedNum.id;
                            mixedNum.parent = node.id;
                            // children of mixedNum are old child0 and child1 of node
                            mixedNum.children.push(child0.id);
                            child0.parent = mixedNum.id;
                            mixedNum.children.push(child1.id);
                            child1.parent = mixedNum.id;
                        }
                    } catch (error) {
                        console.log(error);
                    }
                }
            }
        }
    })
}

function unifySubOrPower(tree, power) {
    var needle = '_§';
    if (power) {
        needle = '^§';
    }
    withEachLeaf(tree, function (node) {
        var start = 0;
        var nextnode = false;
        do {
            var pos = node.content.indexOf(needle, start);
            if (pos < 0) {
                nextnode = true;
            } else {
                start = pos + 1;
                var leftpart = node.content.substring(0, pos - 1);
                var leftCount = (leftpart.match(/§/g) || []).length;
                var base = node.content.substr(pos - 1, 1);
                var rest = node.content.substr(pos + 2);
                if (isInUnit(tree, node)) {
                    leftpart = '';
                    leftCount = 0;
                    base = node.content.substr(0, pos);
                }
                if (base !== '§') {
                    var newNode = createNode('leaf', base, tree);
                    newNode.parent = node.id;
                    node.children.splice(leftCount, 0, newNode.id);
                }
                node.content = leftpart + '§' + needle + rest;
            }
        } while (!nextnode)
    });
}

function parseSubPower(tree, power) {
    var needle = '§_§';
    var type = 'sub';
    if (power) {
        needle = '§^§';
        type = 'power';
    }
    withEachLeaf(tree, function (node) {
        var pos = -1;
        // eslint-disable-next-line no-constant-condition
        while (true) {
            pos = node.content.indexOf(needle);
            if (pos <= -1) break;
            var leftpart = node.content.substring(0, pos);
            var middlepart = node.content.substr(pos, 3);
            var rest = node.content.substr(pos + 3);
            var leftCount = (leftpart.match(/§/g) || []).length; //same for ^ and _
            var base = tree.nodelist[node.children[leftCount]];
            var exponentOrSubScript = tree.nodelist[node.children[leftCount + 1]];
            if (!power) {
                if (exponentOrSubScript.type.startsWith('bracket')) {
                    var child = tree.nodelist[exponentOrSubScript.children[0]];
                    if (child.type == 'leaf') {
                        child.type = 'text'; // avoid later "timification"
                    }
                }
            }
            if (middlepart !== needle) {
                console.error('Error in parseSubPower: ' + needle + ' not found');
            }
            var newNode = createNode(type, '', tree);
            newNode.parent = node.id;
            node.children.splice(leftCount, 2, newNode.id);
            node.content = leftpart + '§' + rest;
            newNode.children.push(base.id);
            base.parent = newNode.id;
            newNode.children.push(exponentOrSubScript.id);
            exponentOrSubScript.parent = newNode.id;
        }
    });
}

function parseUnit(tree) {
    withEachNode(tree, function (node) {
        node.value = 'u';
    });
    withEachLeaf(tree, function (node) {
        if (isInUnit(tree, node)) {
            var temp = decomposeUnit(node.content);
            node.value = temp[3];
        }
    });
}

function parseFactors(tree) {
    withEachLeaf(tree, function (node) {
        if (!isInUnit(tree, node)) {
            // no unit
            var content = node.content.trim();
            node.content = content;
            if (content == "") {
                content = "?";
            }
            if (content.length !== 1) {
                // abc -> a*b*c
                var contentWithTimes = content[0];
                for (var k = 1; k < content.length; k++) {
                    contentWithTimes += '*' + content[k];
                }
                node.content = contentWithTimes;
            }
        } else {
            // unit
            content = node.content.trim();
            if (decomposeUnit(content)[0] == false) {
                // try to separate rightmost (youngest) character
                var left = content.substr(0, content.length - 1);
                var right = content.substr(content.length - 1);
                if (decomposeUnit(left)[0] == true) { //left is Unit
                    if (decomposeUnit(right)[0] == true) { // right isUnit
                        node.content = left + "*" + right;
                    }
                }
            }
        }
    });
    //check_children(tree);
    removeOperators(tree, 'invisibleTimes');
    //check_children(tree);

    //check_children(tree);
}

function decomposeUnit(unitstring) {
    unitstring = unitstring.trim();
    var isUnit = false;
    // default 
    var prefix = '';
    var unit = 'dummy';
    var value = unit2value(unitstring);
    if (value == 'u') {
        if (unitstring.length > 1) {
            // attempt to separate prefix and unit
            prefix = unitstring.substr(0, 1);
            // preserve default value of var unit
            var rest = unitstring.substr(1);
            var power = prefix2power(prefix);
            if (power == 'u') {
                isUnit = false;
            } else {
                var temp = unit2value(rest);
                if (temp == 'u') {
                    isUnit = false;
                } else {
                    // success of separation
                    value = power * temp;
                    unit = rest;
                    isUnit = true;
                }
            }
        }
    } else {
        // length= 1. value exists. No separation necessary.
        // e.g. m, s, A,...
        unit = unitstring;
        isUnit = true;
    }
    if (isUnit == false) {
        prefix = '';
        value = 1;
        unit = '<unknown unit>';
    }
    return [isUnit, prefix, unit, value];
}

function prefix2power(needle) {
    let prefixes = "y__z__a__f__p__n__µ__mcd__hk__M__G__T__P__E__Z__Y";
    // let Mu = String.fromCharCode(956);
    var pos = prefixes.indexOf(needle);
    var power = 0;
    if (pos > -1) {
        power = Math.pow(10, pos - 24);
    } else {
        power = 'u';
    }
    return power;
}

function unit2value(unitname) {
    var valueOf = {
        // dummy values, phantasy 
        // do not matter for purpose of comparison
        "g": 7.003725611783e-2,
        "m": 5.933875512401e-1,
        "A": 8.049921777482e1,
        "s": 9.066344172904e-3,
        "mol": 3.904471947388e-4,
        "Celsius": 7.2209518210337e-3,
        "Kelvin": 8.573310992341e2,
        "one": 1
    }
    valueOf["min"] = 60 * valueOf["s"];
    valueOf["h"] = 60 * valueOf["min"];
    valueOf["d"] = 24 * valueOf["h"];
    valueOf["C"] = valueOf["A"] * valueOf["s"];
    valueOf["e"] = 1.60217648740e-19 * valueOf["C"];
    valueOf["N"] = 1000 * valueOf["g"] * valueOf["m"] / (valueOf["s"] * valueOf["s"]);
    valueOf["J"] = valueOf["N"] * valueOf["m"];
    valueOf["W"] = valueOf["J"] / valueOf["s"];
    valueOf["V"] = valueOf["W"] * valueOf["A"];
    valueOf["\\Omega"] = valueOf["V"] / valueOf["A"];
    valueOf["Pa"] = valueOf["N"] / (valueOf["m"] * valueOf["m"]);
    valueOf["bar"] = 100000 * valueOf["Pa"];
    valueOf["Liter"] = 0.001 * valueOf["m"] * valueOf["m"] * valueOf["m"];
    valueOf["l"] = valueOf["Liter"];
    valueOf["Ar"] = 100 * valueOf["m"] * valueOf["m"];
    valueOf["°C"] = valueOf["Celsius"];
    valueOf["°"] = valueOf["one"] * Math.PI / 180;
    valueOf["''"] = valueOf["°"] / 3600;
    valueOf["'"] = valueOf["°"] / 60;
    valueOf["K"] = valueOf["Kelvin"];
    valueOf["dag"] = 10 * valueOf["g"];
    var result = valueOf[unitname];
    if (typeof result == 'undefined') {
        result = 'u';
    }
    return result;
}


export function value(tree) {
    fillWithValues(tree);
    // temp = {temp.hasValue, temp.variableValueList}
    return evaluateTree(tree);
}

export function evaluateTree(filledTree) {
    // temp = {temp.hasValue, temp.variableValueList}
    // var hasValue = temp[0];
    // var hasValue = temp.hasValue;
    if (filledTree.hasValue) {
        return val(filledTree.root, filledTree);
    } else {
        return undefined;
    }
}

function val(node, tree) { // TODO: different name, too similar to function value?
    //recursive
    var fu, child0, child1, child2, temp;
    var children = node.children;
    var numberOfChildren = children.length;
    if (numberOfChildren == 0) {
        if (node.type == 'number') {
            temp = node.content.replace(',', '.');
            node.value = temp;
        }
        if (node.type == 'invisible_zero') {
            node.value = 0;
        }
        if (isInUnit(tree, node)) {
            temp = decomposeUnit(node.content);
            if (temp[0] == true) {
                node.value = temp[3];
            }
        }
    }
    if (numberOfChildren == 1) {
        child0 = tree.nodelist[children[0]];
        var arg = val(child0, tree);
        if (node.type.startsWith('bracket-') || node.type == 'root' || node.type == 'unit') {
            if (node.type == 'bracket-\\left|') {
                // absolute value
                node.value = Math.abs(arg);
            } else {
                // bracket
                node.value = arg;
            }
        } else {
            if (node.type.startsWith('fu-')) {
                fu = node.type.substr(3)
                node.value = trigonometry(fu, arg);
            }
        }
        if (node.type == 'sqrt') {
            node.value = Math.sqrt(arg);
        }
    }

    if (numberOfChildren == 2) {
        child0 = tree.nodelist[children[0]];
        child1 = tree.nodelist[children[1]];
        var ch0 = val(child0, tree);
        var ch1 = val(child1, tree);
        if (node.type == '*') {
            node.value = Number(ch0) * Number(ch1);
        }
        if (node.type == 'timesdivided') {
            if (node.content == '\\cdot') {
                node.value = Number(ch0) * Number(ch1);
            }
            if (node.content == ':') {
                node.value = Number(ch0) / Number(ch1);
            }
        }
        if (node.type == 'nthroot') {
            node.value = Math.pow(Number(ch1), (1 / Number(ch0)));
        }
        if (node.type == 'power') {
            node.value = Math.pow(Number(ch0), Number(ch1));
        }
        if (node.type == 'fu-log') {
            node.value = Math.log(Number(ch1)) / Math.log(Number(ch0));
        }
        if (node.type.startsWith('fu-') && node.content == 'power') {
            fu = node.type.substr(3)
            var base = trigonometry(fu, ch1);
            node.value = Math.pow(base, ch0)
        }
        if (node.type == 'mixedNumber') {
            node.value = Number(ch0) + Number(ch1);
        }
        if (node.type == 'plusminus') {
            if (node.content == '+') {
                node.value = Number(ch0) + Number(ch1);
            }
            if (node.content == '-') {
                node.value = Number(ch0) - Number(ch1);
            }
        }
        if (node.type == 'frac') {
            node.value = Number(ch0) / Number(ch1);
        }
        if (node.type == 'equal') {
            if (Number(ch1) !== 0) {
                node.value = Number(ch0) / Number(ch1);
            } else {
                node.value = (Number(ch0) + Math.PI) / (Number(ch1) + Math.PI);
            }
        }
    }
    if (numberOfChildren > 2) {
        child0 = tree.nodelist[children[0]];
        child1 = tree.nodelist[children[1]];
        child2 = tree.nodelist[children[2]];
        var dummy = val(child0, tree);
        dummy = val(child1, tree);
        // eslint-disable-next-line no-unused-vars
        dummy = val(child2, tree);
    }
    return node.value;
}

function trigonometry(fu, arg) {
    //'sinh', 'cosh', 'tanh', 'sin', 'cos', 'tan', 'ln', 'lg', 'log', 'exp', 'abs'
    var result = 'u';
    if (fu == 'sinh') {
        result = Math.sinh(arg);
    }
    if (fu == 'cosh') {
        result = Math.cosh(arg);
    }
    if (fu == 'tanh') {
        result = Math.tanh(arg);
    }
    if (fu == 'sin') {
        result = Math.sin(arg);
    }
    if (fu == 'cos') {
        result = Math.cos(arg);
    }
    if (fu == 'tan') {
        result = Math.tan(arg);
    }
    // if (fu == 'ln' || fu == 'log') {
    if (fu == 'ln') {
        result = Math.log(arg);
    }
    if (fu == 'lg') {
        result = Math.log10(arg);
    }
    if (fu == 'exp') {
        result = Math.exp(arg);
    }
    if (fu == 'abs') {
        result = Math.abs(arg);
    }
    if (fu == 'arcsin') {
        result = Math.asin(arg);
    }
    if (fu == 'arccos') {
        result = Math.acos(arg);
    }
    if (fu == 'arctan') {
        result = Math.atan(arg);
    }
    return result;
}

export function fillWithValues(treeVar, list) {
    var random = (arguments.length == 1);
    // random = true: fillWithRandomValues
    // random = false: fill with values of variableValueList
    var valueList = [];
    var hasValue = true;
    treeVar.withEachNode = function (node) {
        if (node.type == 'integral') hasValue = false;
        if (node.type == 'lim') hasValue = false;
        if (node.type == 'text') hasValue = false;
    };
    if (hasValue) {
        withEachLeafOrGreek(treeVar, function (node) {
            if (isInUnit(treeVar, node)) {
                var temp = decomposeUnit(node.content);
                node.value = temp[3];
                //node.type = 'unit';
            }
        });
        var i = 0;
        do {
            var stop = false;
            var found = false;
            var nodelist = treeVar.nodelist;
            do {
                var node = nodelist[i];
                // doThis may add or delete nodes!
                if ((node.type == 'leaf' || node.type == 'greek') && (node.value == 'u')) {
                    // found leaf or greek with value undefined ('u')
                    found = true;
                    stop = true; //short circuit
                } else {
                    i++;
                }
                if (i === nodelist.length) {
                    stop = true;
                }
                if (found) {
                    var content = node.content;
                    if (random == true) {
                        // random = true -> fill with random value
                        // Box-Muller
                        var u1 = 2 * Math.PI * Math.random();
                        var u2 = -2 * Math.log(Math.random());
                        var value = 1000 * Math.cos(u1) * Math.sqrt(u2);
                    } else {
                        value = list[content];
                        if (typeof value == 'undefined') {
                            console.error('Variable in definition set but not in applet: ' + content);
                            stop = true;
                            i++;
                            hasValue = false;
                            found = false;
                        }
                    }
                    if (typeof value !== 'undefined') {
                        withEachLeafOrGreek(treeVar, function (node) {
                            if (node.value == 'u') {
                                if (node.content == content) {
                                    node.value = value;
                                }
                                if (node.content == '\\pi') {
                                    node.value = Math.PI;
                                    value = Math.PI;
                                }
                                if (node.content == 'e') {
                                    node.value = Math.E;
                                    value = Math.E;
                                }
                            }
                        });
                        valueList[content] = value;
                    }
                }
            } while (!stop);
        } while (found);
    }
    treeVar.hasValue = hasValue;
    treeVar.variableValueList = valueList;
}

export function checkScientificNotation(texstring) {
    var isScientific = false;
    // var regex =  RegExp('\\.?', 'g');
    var repl = texstring.replace(".", ",");
    repl = repl.replace("e", "*10^");
    repl = repl.replace("E", "*10^");
    repl = repl.replace("\\cdot", "*");
    repl = repl.replace(/\\ /g, '');
    // accept 'almost scientific' strings like 23, 23,4* 23,4*10^ 
    if (repl.endsWith(',')) {
        repl = repl.substr(0, repl.length - 1);
    }
    if (repl.endsWith('*')) {
        repl = repl.substr(0, repl.length - 1);
    }
    if (repl.endsWith('*1')) {
        repl = repl.substr(0, repl.length - 2);
    }
    if (repl.endsWith('*10')) {
        repl = repl.substr(0, repl.length - 3);
    }
    // repl was used by preparePage.makeAutoUnitstring
    // repl is used by autoUnit.makeAutoUnitstring
    var mantissa = repl;
    var exponent = ''; // default
    var pos = repl.indexOf('*10^');
    if (pos > -1) {
        mantissa = repl.substr(0, pos);
        exponent = repl.substr(pos + 4);
    } else {
        if (mantissa.startsWith('10^')) {
            exponent = mantissa.substr(3);
            mantissa = '';
        }
    }
    exponent = exponent.replace("{", "");
    exponent = exponent.replace("}", "");
    exponent = exponent.toString();
    // https://regex101.com/
    // var regex = RegExp('((\\d+\\,)?\\d+)', 'g');
    var regex = RegExp('((\\-)?((\\d+)?\\,)?(\\d+))');
    // https://stackoverflow.com/questions/6003884/how-do-i-check-for-null-values-in-javascript
    var leftOk = false;
    var left = regex.exec(mantissa);
    if (left !== null) {
        if (mantissa == left[0]) {
            leftOk = true;
        }
    }
    var rightOk = false;
    var right = regex.exec(exponent);
    if (right !== null) {
        if (exponent == right[0]) {
            rightOk = true;
        }
    } else {
        // not existing exponent is always ok
        rightOk = true;
    }
    isScientific = (leftOk && rightOk);
    return {
        isScientific,
        mantissa,
        exponent
    };
}