Skip to content

Instantly share code, notes, and snippets.

@jdavidw13
Forked from Zirak/gist:1761880
Created March 22, 2012 06:03
Show Gist options
  • Save jdavidw13/2156565 to your computer and use it in GitHub Desktop.
Save jdavidw13/2156565 to your computer and use it in GitHub Desktop.

Revisions

  1. Josiah Wilkerson revised this gist Mar 22, 2012. 1 changed file with 23 additions and 6 deletions.
    29 changes: 23 additions & 6 deletions gistfile1.js
    Original file line number Diff line number Diff line change
    @@ -39,14 +39,23 @@ var operators = {
    },
    'd' : {
    precedence : 3,
    exec : function ( rolls, sides, rollsSoFar ) {
    exec : function ( rolls, sides, rollsSoFar, range ) {
    if ( rolls > 100 ) {
    throw new Error( 'Maximum roll count is 100' );
    }

    var ret = 0, roll;
    while ( rolls-- ) {
    roll = Math.floor( Math.random() * sides ) + 1;
    roll = Math.floor( Math.random() * sides ) + 1;

    if (range != null && range.toLowerCase) {
    if ('min' == range.toLowerCase()) {
    roll = 1;
    }
    else if ('max' == range.toLowerCase()) {
    roll = sides;
    }
    }

    ret += roll;
    rollsSoFar.push( roll );
    @@ -68,11 +77,13 @@ var parser = {
    pos : 0,
    len : 0,
    lookahead : '',
    range : '',

    parse : function ( source ) {
    parse : function ( source, range ) {
    this.source = source;
    this.pos = 0;
    this.len = source.length;
    this.range = range;

    this.numberStack = [];
    this.operatorStack = [];
    @@ -212,7 +223,13 @@ var parser = {
    //we remove the index-th item from the operatorStack and grab its
    // "value", which is the operator symbol (+, * etc)
    //when we have that value, we grab the corresponding operator object
    var op = operators[ this.operatorStack.splice(index, 1)[0].value ];
    var operator = this.operatorStack.splice(index, 1)[0].value;
    var op = operators[ operator ];

    //need to tell the dice operator to use the min or max values
    if ( 'd' == operator ) {
    couplet.push(this.range);
    }

    //arr.splice, as well as removing items, can also add items
    //so, we slice-n-dice at the two numbers, grab the result of executing
    @@ -250,7 +267,7 @@ var parser = {
    //returns an object:
    // total => result of all dice rolls and arithmetic operations
    // rolls => array of results of each individual dice roll
    return function ( source ) {
    return parser.parse( source );
    return function ( source, range ) {
    return parser.parse( source, range );
    };
    }());
  2. Titani revised this gist Mar 17, 2012. 1 changed file with 21 additions and 8 deletions.
    29 changes: 21 additions & 8 deletions gistfile1.js
    Original file line number Diff line number Diff line change
    @@ -140,7 +140,6 @@ var parser = {

    function execute ( token, index ) {
    var last = this.operatorStack[ index + 1 ];
    console.log( token, last, index );

    //last one is more important than we are
    if ( last && last.precedence > token.precedence ) {
    @@ -198,16 +197,30 @@ var parser = {
    },

    operate : function ( index ) {
    if ( typeof index === 'undefined' ) {
    index = this.operatorStack.length - 1;
    }

    //grab the two numbers we care about
    //since the source string looks like: 2 + 1
    // and the index param is actually the index of the operator to use,
    // we grab the index-th number and the index-th+1 number
    //in the above example, index = 0, we grab numberStack[0] and
    // numberStack[1]
    var couplet = this.numberStack.slice( index, index + 2 );
    console.log( couplet.slice(), index, this.numberStack.slice() );

    //in addition to the numbers we operate on, there's also a dice-roll
    // operator, so we take it into consideration
    couplet.push( this.rolls );

    //arr.splice removes items and returns the removed items as an array
    //we remove the index-th item from the operatorStack and grab its
    // "value", which is the operator symbol (+, * etc)
    //when we have that value, we grab the corresponding operator object
    var op = operators[ this.operatorStack.splice(index, 1)[0].value ];

    //arr.splice, as well as removing items, can also add items
    //so, we slice-n-dice at the two numbers, grab the result of executing
    // the operator, and add that result where we finished slicing
    //for example:
    // [0, 1, 2].splice( 0, 2, 42 )
    //will make the array look like
    // [42, 2]
    this.numberStack.splice( index, 2, op.exec.apply(null, couplet) );
    },

    @@ -240,4 +253,4 @@ var parser = {
    return function ( source ) {
    return parser.parse( source );
    };
    }());
    }());
  3. Titani revised this gist Mar 17, 2012. 1 changed file with 161 additions and 95 deletions.
    256 changes: 161 additions & 95 deletions gistfile1.js
    Original file line number Diff line number Diff line change
    @@ -1,158 +1,228 @@
    //infix operator-precedence parser
    //also supports a d operator - a dice roll
    var parse = (function () {
    var parsePrecedence = (function () {

    //we don't care about whitespace. well, most whitespace
    var whitespace = {
    ' ' : true,
    '\t' : true
    };

    //the operators we deal with and their precedence
    //the operators we deal with
    var operators = {
    '+' : 1,
    '-' : 1,
    '*' : 2,
    '/' : 2,
    'd' : 3
    };

    var callbacks = {
    '+' : function ( a, b ) {
    return a + b;
    },
    '-' : function ( a, b ) {
    return a - b;
    '+' : {
    precedence : 1,
    exec : function ( a, b ) {
    return a + b;
    }
    },
    '*' : function ( a, b ) {
    return a * b;
    '-' : {
    precedence : 1,
    exec : function ( a, b ) {
    return a - b;
    }
    },
    '/' : function ( a, b ) {
    return a / b;
    '*' : {
    precedence : 2,
    exec : function ( a, b ) {
    return a * b;
    }
    },
    'd' : function ( rolls, sides, rollsSoFar ) {
    if ( rolls > 100 || sides > 100 ) {
    throw new Error( 'Maximum roll and side count is 100' );
    '/' : {
    precedence : 2,
    exec : function ( a, b ) {
    if ( b === 0 ) {
    throw new Error( 'Division by 0' );
    }
    return a / b;
    }
    },
    'd' : {
    precedence : 3,
    exec : function ( rolls, sides, rollsSoFar ) {
    if ( rolls > 100 ) {
    throw new Error( 'Maximum roll count is 100' );
    }

    var ret = 0, roll;
    while ( rolls-- ) {
    roll = Math.floor( Math.random() * sides ) + 1;
    var ret = 0, roll;
    while ( rolls-- ) {
    roll = Math.floor( Math.random() * sides ) + 1;

    ret += roll;
    rollsSoFar.push( roll );
    }
    ret += roll;
    rollsSoFar.push( roll );
    }

    return ret;
    return ret;
    }
    }
    };

    //returns an object:
    // total => result of all dice rolls and arithmetic operations
    // rolls => array of results of each individual dice roll
    return function ( source ) {
    var numberStack = [],
    operatorStack = [],
    rolls = [];

    //the tokenizer
    var token, last;
    for ( var pos = 0, len = source.length; pos < len; ++pos ) {
    //skip teh noobz whitespace
    if ( whitespace.hasOwnProperty(source[pos]) ) {
    continue;
    }
    var parser = {
    //not exactly stacks, but meh
    numberStack : null,
    operatorStack : null,
    rolls : null,

    token = nextToken();
    //the source string and some metadata
    source : null,
    pos : 0,
    len : 0,
    lookahead : '',

    if ( token.type === 'number' ) {
    numberStack.push( token.value );
    }
    parse : function ( source ) {
    this.source = source;
    this.pos = 0;
    this.len = source.length;

    this.numberStack = [];
    this.operatorStack = [];
    this.rolls = [];

    this.tokenize();
    this.execute();

    //garbage collection, important for gianormo strings
    this.source = source = null;

    return {
    //the remaining number on the "stack" is the result
    total : this.numberStack[ 0 ],
    //we execute right->left, so the rolls array will be "backwards"
    rolls : this.rolls.reverse()
    };
    },

    //take the source string, and break it down into tokens
    tokenize : function () {
    var token, last;

    for ( ; this.pos < this.len; this.pos++ ) {
    this.lookahead = this.source[ this.pos ];

    if ( whitespace.hasOwnProperty(this.lookahead) ) {
    continue;
    }

    token = this.nextToken();

    if ( token.type === 'number' ) {
    this.numberStack.push( token.value );
    }

    else if ( token.type === 'operator' ) {
    last = operatorStack[ operatorStack.length - 1 ];
    else if ( token.type === 'operator' ) {
    last = this.operatorStack[ this.operatorStack.length - 1 ];

    //check for things like 1d2d3, which aren't valid
    if ( last && token.value === 'd' && last.value === 'd' ) {
    throw new Error(
    'Unexpected unchainable operator d at ' + pos
    );
    //check for things like 1d2d3, which aren't valid
    if ( last && token.value === 'd' && last.value === 'd' ) {
    var itOnTheGround = new Error(
    'Unexpected unchainable operator d'
    );
    itOnTheGround.column = this.pos;

    throw itOnTheGround; //I'M AN ADULT!
    }

    this.operatorStack.push( token );
    }
    }

    },

    operatorStack.push( token );
    execute : function () {
    var idx;

    while ( (idx = this.operatorStack.length) ) {
    //execute, BACKWARDS! OH THE INSANITY
    while ( 0 <=-- idx ) {
    execute.call( this, this.operatorStack[idx], idx );
    }
    }
    }

    //the "executer"
    while ( operatorStack.length ) {
    operatorStack.forEach(function ( token, index ) {
    last = operatorStack[ index - 1 ];
    function execute ( token, index ) {
    var last = this.operatorStack[ index + 1 ];
    console.log( token, last, index );

    //last one is more important than we are
    if ( last && last.precedence > token.precedence ) {
    //execute it
    operate( index - 1 );
    this.operate( index + 1 );
    }
    //we're about to finish and the last one isn't as all-mighty as we
    // thought
    else if ( index + 1 === operatorStack.length ) {
    else if ( !index ) {
    //execute za operator!
    operate( index );
    this.operate( index );
    }
    });
    }

    //the last number in the stack is the result
    return {
    total : numberStack[ 0 ],
    rolls : rolls
    };
    }
    },

    //get the next token
    function nextToken () {
    var ch = source[ pos ];
    //fetch le token!
    nextToken : function () {
    var ch = this.lookahead;
    var ret = {
    type : null,
    value : ch
    },
    res;
    res;

    //have we overflowed, while looking for something else?
    if ( pos >= len ) {
    if ( this.pos >= this.len ) {
    throw new Error( 'Unexpected end of input' );
    }

    //is it a digit?
    else if ( ch >= 0 && ch < 10 ) {
    ret.type = 'number';
    res = getNumber();
    res = this.fetchNumber();

    pos += res.length - 1;
    this.pos += res.length - 1;
    ret.value = res.value;
    }

    //is it an operator?
    else if ( operators.hasOwnProperty(ch) ) {
    ret.type = 'operator';
    ret.precedence = operators[ ch ];
    ret.precedence = operators[ ch ].precedence;
    }

    //Y U TROLLZ!?!?
    else {
    var chuckNorris = new Error( 'Invalid character ' + ch );
    chuckNorris.column = this.pos;

    throw chuckNorris;
    }


    return ret;
    }
    },

    function getNumber () {
    operate : function ( index ) {
    if ( typeof index === 'undefined' ) {
    index = this.operatorStack.length - 1;
    }

    var couplet = this.numberStack.slice( index, index + 2 );
    console.log( couplet.slice(), index, this.numberStack.slice() );

    couplet.push( this.rolls );

    var op = operators[ this.operatorStack.splice(index, 1)[0].value ];
    this.numberStack.splice( index, 2, op.exec.apply(null, couplet) );
    },

    fetchNumber : function () {
    var offset = 0, num = '', ch;

    //keep eating digits until we find a non-digit
    while ( (ch = source[pos+offset]) >= 0 && ch < 10 ) {
    while ( (ch = this.source[this.pos+offset]) >= 0 && ch < 10 ) {
    num += ch;
    offset++;
    }

    if ( num.length === 0 ) {
    throw new Error(
    'Incomplete operation: Expected number at ' + pos
    'Incomplete operation: Expected number at ' + this.pos
    );
    }

    @@ -162,16 +232,12 @@ return function ( source ) {
    };
    }

    function operate ( index ) {
    if ( typeof index === 'undefined' ) {
    index = operatorStack.length - 1;
    }

    var couplet = numberStack.slice( index, index + 2 );
    couplet.push( rolls );
    };

    var method = callbacks[ operatorStack.splice(index, 1)[0].value ];
    numberStack.splice( index, 2, method.apply(null, couplet) );
    }
    //returns an object:
    // total => result of all dice rolls and arithmetic operations
    // rolls => array of results of each individual dice roll
    return function ( source ) {
    return parser.parse( source );
    };
    }());
    }());
  4. Titani revised this gist Feb 10, 2012. 1 changed file with 55 additions and 40 deletions.
    95 changes: 55 additions & 40 deletions gistfile1.js
    Original file line number Diff line number Diff line change
    @@ -1,28 +1,40 @@
    //infix operator-precedence parser, supporting d, + and -, where d is a dice
    // roll
    //infix operator-precedence parser
    //also supports a d operator - a dice roll
    var parse = (function () {

    //the operators we deal with and their precedence
    var operators = {
    '+' : 1,
    '-' : 1,
    'd' : 2
    };

    //we don't care about whitespace. well, most whitespace
    var whitespace = {
    ' ' : true,
    '\t' : true
    };

    //the operators we deal with and their precedence
    var operators = {
    '+' : 1,
    '-' : 1,
    '*' : 2,
    '/' : 2,
    'd' : 3
    };

    var callbacks = {
    '+' : function ( a, b ) {
    return a + b;
    },
    '-' : function ( a, b ) {
    return a - b;
    },
    '*' : function ( a, b ) {
    return a * b;
    },
    '/' : function ( a, b ) {
    return a / b;
    },
    'd' : function ( rolls, sides, rollsSoFar ) {
    if ( rolls > 100 || sides > 100 ) {
    throw new Error( 'Maximum roll and side count is 100' );
    }

    var ret = 0, roll;
    while ( rolls-- ) {
    roll = Math.floor( Math.random() * sides ) + 1;
    @@ -43,6 +55,7 @@ return function ( source ) {
    operatorStack = [],
    rolls = [];

    //the tokenizer
    var token, last;
    for ( var pos = 0, len = source.length; pos < len; ++pos ) {
    //skip teh noobz whitespace
    @@ -61,26 +74,32 @@ return function ( source ) {

    //check for things like 1d2d3, which aren't valid
    if ( last && token.value === 'd' && last.value === 'd' ) {
    throw new Error( 'Unexpected unchainable-operator d' );
    }

    if (
    //previous operator is more important than us
    (last && token.precedence < last.precedence) ||
    //or, we're about to finish
    pos + 1 === len
    ) {
    operate();
    throw new Error(
    'Unexpected unchainable operator d at ' + pos
    );
    }

    operatorStack.push( token );
    }
    }

    //by now, operatorStack will only have operators of equal, lowest
    // precedence, so we just need to go over the operator stack and execute
    //the "executer"
    while ( operatorStack.length ) {
    operate();
    operatorStack.forEach(function ( token, index ) {
    last = operatorStack[ index - 1 ];

    //last one is more important than we are
    if ( last && last.precedence > token.precedence ) {
    //execute it
    operate( index - 1 );
    }
    //we're about to finish and the last one isn't as all-mighty as we
    // thought
    else if ( index + 1 === operatorStack.length ) {
    //execute za operator!
    operate( index );
    }
    });
    }

    //the last number in the stack is the result
    @@ -127,36 +146,32 @@ return function ( source ) {

    //keep eating digits until we find a non-digit
    while ( (ch = source[pos+offset]) >= 0 && ch < 10 ) {
    num += source[ pos+offset ];
    num += ch;
    offset++;
    }

    if ( num.length === 0 ) {
    throw new Error(
    'Incomplete operation: Expected number at ' + pos
    );
    }

    return {
    value : Number( num ),
    length : offset
    };
    }

    function operate () {
    var couplet = popTwo();
    couplet.push( rolls );

    if ( couplet.indexOf(undefined) > -1 ) {
    throw new Error( 'Incomplete expression - expected number' );
    function operate ( index ) {
    if ( typeof index === 'undefined' ) {
    index = operatorStack.length - 1;
    }

    numberStack.push(
    callbacks[ operatorStack.pop().value ].apply( null, couplet )
    );
    }
    var couplet = numberStack.slice( index, index + 2 );
    couplet.push( rolls );

    function popTwo () {
    //because we're going left->right, something like:
    // 4 + 1
    //will be displayed in the stack like:
    // [1, 4]
    //so after grabbing the topmost numbers, we need to flip them
    return [ numberStack.pop(), numberStack.pop() ].reverse();
    var method = callbacks[ operatorStack.splice(index, 1)[0].value ];
    numberStack.splice( index, 2, method.apply(null, couplet) );
    }
    };
    }());
  5. Titani revised this gist Feb 8, 2012. 1 changed file with 9 additions and 17 deletions.
    26 changes: 9 additions & 17 deletions gistfile1.js
    Original file line number Diff line number Diff line change
    @@ -8,11 +8,7 @@ var operators = {
    '-' : 1,
    'd' : 2
    };
    //object with keys 0-9 set to true
    var digits = [
    true, true, true, true, true,
    true, true, true, true, true
    ];

    //we don't care about whitespace. well, most whitespace
    var whitespace = {
    ' ' : true,
    @@ -47,17 +43,13 @@ return function ( source ) {
    operatorStack = [],
    rolls = [];

    //remove ALL whitespace!
    //could be achieved by:
    // source = source.replace( /\s/g, '' )
    //but who cares?
    Object.keys( whitespace ).forEach(function ( whitey ) {
    source = source.replace( whitey, '' );
    });

    var token, last;
    for ( var pos = 0, len = source.length; pos < len; ++pos ) {
    console.log( operatorStack.slice(), numberStack.slice() );
    //skip teh noobz whitespace
    if ( whitespace.hasOwnProperty(source[pos]) ) {
    continue;
    }

    token = nextToken();

    if ( token.type === 'number' ) {
    @@ -112,7 +104,7 @@ return function ( source ) {
    }

    //is it a digit?
    else if ( digits.hasOwnProperty(ch) ) {
    else if ( ch >= 0 && ch < 10 ) {
    ret.type = 'number';
    res = getNumber();

    @@ -131,10 +123,10 @@ return function ( source ) {
    }

    function getNumber () {
    var offset = 0, num = '';
    var offset = 0, num = '', ch;

    //keep eating digits until we find a non-digit
    while ( digits.hasOwnProperty(source[pos+offset]) ) {
    while ( (ch = source[pos+offset]) >= 0 && ch < 10 ) {
    num += source[ pos+offset ];
    offset++;
    }
  6. Titani revised this gist Feb 8, 2012. 1 changed file with 21 additions and 5 deletions.
    26 changes: 21 additions & 5 deletions gistfile1.js
    Original file line number Diff line number Diff line change
    @@ -26,19 +26,26 @@ var callbacks = {
    '-' : function ( a, b ) {
    return a - b;
    },
    'd' : function ( rolls, sides ) {
    var ret = 0;
    'd' : function ( rolls, sides, rollsSoFar ) {
    var ret = 0, roll;
    while ( rolls-- ) {
    ret += Math.floor( Math.random() * sides ) + 1;
    roll = Math.floor( Math.random() * sides ) + 1;

    ret += roll;
    rollsSoFar.push( roll );
    }

    return ret;
    }
    };

    //returns an object:
    // total => result of all dice rolls and arithmetic operations
    // rolls => array of results of each individual dice roll
    return function ( source ) {
    var numberStack = [],
    operatorStack = [];
    operatorStack = [],
    rolls = [];

    //remove ALL whitespace!
    //could be achieved by:
    @@ -60,6 +67,11 @@ return function ( source ) {
    else if ( token.type === 'operator' ) {
    last = operatorStack[ operatorStack.length - 1 ];

    //check for things like 1d2d3, which aren't valid
    if ( last && token.value === 'd' && last.value === 'd' ) {
    throw new Error( 'Unexpected unchainable-operator d' );
    }

    if (
    //previous operator is more important than us
    (last && token.precedence < last.precedence) ||
    @@ -80,7 +92,10 @@ return function ( source ) {
    }

    //the last number in the stack is the result
    return numberStack[ 0 ];
    return {
    total : numberStack[ 0 ],
    rolls : rolls
    };

    //get the next token
    function nextToken () {
    @@ -132,6 +147,7 @@ return function ( source ) {

    function operate () {
    var couplet = popTwo();
    couplet.push( rolls );

    if ( couplet.indexOf(undefined) > -1 ) {
    throw new Error( 'Incomplete expression - expected number' );
  7. Titani revised this gist Feb 8, 2012. 1 changed file with 7 additions and 9 deletions.
    16 changes: 7 additions & 9 deletions gistfile1.js
    Original file line number Diff line number Diff line change
    @@ -50,6 +50,7 @@ return function ( source ) {

    var token, last;
    for ( var pos = 0, len = source.length; pos < len; ++pos ) {
    console.log( operatorStack.slice(), numberStack.slice() );
    token = nextToken();

    if ( token.type === 'number' ) {
    @@ -100,7 +101,7 @@ return function ( source ) {
    ret.type = 'number';
    res = getNumber();

    pos += res.length;
    pos += res.length - 1;
    ret.value = res.value;
    }

    @@ -115,25 +116,22 @@ return function ( source ) {
    }

    function getNumber () {
    var offset = 0, num = 0;
    var offset = 0, num = '';

    //keep eating digits until we find a non-digit
    while ( digits.hasOwnProperty(source[pos+offset]) ) {
    num *= 10;
    num += Number( source[pos+offset] );
    num += source[ pos+offset ];
    offset++;
    }

    return {
    value : num,
    length : offset - 1
    value : Number( num ),
    length : offset
    };
    }

    function operate () {
    var operator = operatorStack.pop().value,
    method = callbacks[ operator ],
    couplet = popTwo();
    var couplet = popTwo();

    if ( couplet.indexOf(undefined) > -1 ) {
    throw new Error( 'Incomplete expression - expected number' );
  8. Titani revised this gist Feb 7, 2012. 1 changed file with 1 addition and 3 deletions.
    4 changes: 1 addition & 3 deletions gistfile1.js
    Original file line number Diff line number Diff line change
    @@ -78,7 +78,6 @@ return function ( source ) {
    operate();
    }

    console.log( numberStack );
    //the last number in the stack is the result
    return numberStack[ 0 ];

    @@ -111,7 +110,6 @@ return function ( source ) {
    ret.precedence = operators[ ch ];
    }

    console.log( )

    return ret;
    }
    @@ -142,7 +140,7 @@ return function ( source ) {
    }

    numberStack.push(
    callback[ operatorStack.pop().value ].apply( null, couplet )
    callbacks[ operatorStack.pop().value ].apply( null, couplet )
    );
    }

  9. Titani created this gist Feb 7, 2012.
    158 changes: 158 additions & 0 deletions gistfile1.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,158 @@
    //infix operator-precedence parser, supporting d, + and -, where d is a dice
    // roll
    var parse = (function () {

    //the operators we deal with and their precedence
    var operators = {
    '+' : 1,
    '-' : 1,
    'd' : 2
    };
    //object with keys 0-9 set to true
    var digits = [
    true, true, true, true, true,
    true, true, true, true, true
    ];
    //we don't care about whitespace. well, most whitespace
    var whitespace = {
    ' ' : true,
    '\t' : true
    };

    var callbacks = {
    '+' : function ( a, b ) {
    return a + b;
    },
    '-' : function ( a, b ) {
    return a - b;
    },
    'd' : function ( rolls, sides ) {
    var ret = 0;
    while ( rolls-- ) {
    ret += Math.floor( Math.random() * sides ) + 1;
    }

    return ret;
    }
    };

    return function ( source ) {
    var numberStack = [],
    operatorStack = [];

    //remove ALL whitespace!
    //could be achieved by:
    // source = source.replace( /\s/g, '' )
    //but who cares?
    Object.keys( whitespace ).forEach(function ( whitey ) {
    source = source.replace( whitey, '' );
    });

    var token, last;
    for ( var pos = 0, len = source.length; pos < len; ++pos ) {
    token = nextToken();

    if ( token.type === 'number' ) {
    numberStack.push( token.value );
    }

    else if ( token.type === 'operator' ) {
    last = operatorStack[ operatorStack.length - 1 ];

    if (
    //previous operator is more important than us
    (last && token.precedence < last.precedence) ||
    //or, we're about to finish
    pos + 1 === len
    ) {
    operate();
    }

    operatorStack.push( token );
    }
    }

    //by now, operatorStack will only have operators of equal, lowest
    // precedence, so we just need to go over the operator stack and execute
    while ( operatorStack.length ) {
    operate();
    }

    console.log( numberStack );
    //the last number in the stack is the result
    return numberStack[ 0 ];

    //get the next token
    function nextToken () {
    var ch = source[ pos ];
    var ret = {
    type : null,
    value : ch
    },
    res;

    //have we overflowed, while looking for something else?
    if ( pos >= len ) {
    throw new Error( 'Unexpected end of input' );
    }

    //is it a digit?
    else if ( digits.hasOwnProperty(ch) ) {
    ret.type = 'number';
    res = getNumber();

    pos += res.length;
    ret.value = res.value;
    }

    //is it an operator?
    else if ( operators.hasOwnProperty(ch) ) {
    ret.type = 'operator';
    ret.precedence = operators[ ch ];
    }

    console.log( )

    return ret;
    }

    function getNumber () {
    var offset = 0, num = 0;

    //keep eating digits until we find a non-digit
    while ( digits.hasOwnProperty(source[pos+offset]) ) {
    num *= 10;
    num += Number( source[pos+offset] );
    offset++;
    }

    return {
    value : num,
    length : offset - 1
    };
    }

    function operate () {
    var operator = operatorStack.pop().value,
    method = callbacks[ operator ],
    couplet = popTwo();

    if ( couplet.indexOf(undefined) > -1 ) {
    throw new Error( 'Incomplete expression - expected number' );
    }

    numberStack.push(
    callback[ operatorStack.pop().value ].apply( null, couplet )
    );
    }

    function popTwo () {
    //because we're going left->right, something like:
    // 4 + 1
    //will be displayed in the stack like:
    // [1, 4]
    //so after grabbing the topmost numbers, we need to flip them
    return [ numberStack.pop(), numberStack.pop() ].reverse();
    }
    };
    }());