public StringScanner(StringScanner other) { this.s = other.s; this.Cursor = other.Cursor; }
private void ProcessOperator(StringScanner steve, Stack<string> opStack, List<Token> output, bool lastTokenWasNumber, ref bool tokenIsNumber) { string op = steve.Read ().ToString(); if (lastTokenWasNumber) { PushOperatorToStack (op, opStack, output); } else if (op.Equals ("-")) { // The last token wasn't a number, but we encountered an operator, so this must be a negative sign steve.skipWhitespace (); if (!steve.HasNext ()) { throw new InvalidExpressionException("misplaced operator: " + op); } BigInteger num = 1; // if there's an expression after the minus sign, just process it and negate it if (steve.TryReadLong (ref num) || Char.ToLower (steve.Peek ()) == 'd' || steve.Peek() == '(' || steve.Peek() == '[') { output.Add (new NumToken(-num)); // if the next token is a diedef, paren, or formula, iterativelyAdd will detect the -1 and use it to negate the expression tokenIsNumber = true; } else { // there is no number after the minus sign, so it can't be negating a number, and it can't be doing subtraction throw new InvalidExpressionException ("misplaced operator: " + op); } } else { throw new InvalidExpressionException ("misplaced operator" + op); } }
private void ProcessParentheses(StringScanner scanner, List<Token> output, bool lastTokenWasNumber) { // If the last token was a number, it's a count of how many times to execute the parenthetic expression. // in normal non-random-number math, this is just multiplication, but since we use random numbers, // it is implemented as iterative addition, allowing us to re-roll the dice every iteration. // If there are no dice defs in the parentheses, then this will yield the same result as normal multiplication. string expr = ExtractParentheses (scanner); List<Token> multiplier = LookForMultiplier (output, lastTokenWasNumber); ParenToken toke = new ParenToken (multiplier, InfixToPostfix (expr, 0, null), this); output.Add (toke); }
private void ProcessFormula(StringScanner scanner, List<Token> output, bool lastTokenWasNumber) { Formula f = ExtractFormula (scanner); List<Token> multiplier = LookForMultiplier (output, lastTokenWasNumber); ParenToken toke = new ParenToken (multiplier, InfixToPostfix (f.Expression, 0, f.Name), this); output.Add (toke); }
private Token ProcessDieDef( StringScanner steve, Stack<string> opStack, List<Token> output, bool lastTokenWasNumber) { steve.TrySkip(); // move past the d\ if (!steve.HasNext()) throw new InvalidExpressionException("no die type given"); if (Char.IsDigit (steve.Peek ())) { // check that the syntax is valid before just trying to read it Token dieCount = new NumToken(1); if (lastTokenWasNumber) { // the last number was the die count, because it was followed by a 'd' dieCount = output.Last(); output.RemoveAt (output.Count - 1); } BigInteger dieType = steve.ReadLong(); // this is safe because we checked that the next char is a digit // we now know that die type and the die count, now we need to see if there are extra instructions for the roll long keepCount = 1; KeepStrategy keepstrat = KeepStrategy.ALL; if (steve.HasNext () && char.IsLetter (steve.Peek ()) && Char.ToLower (steve.Peek ()) != 'd') { char extension = Char.ToLower (steve.Read ()); if (extension == 'h') { keepstrat = KeepStrategy.HIGHEST; BigInteger temp = null; if (steve.TryReadLong (ref temp)) { if (temp > MAX_DIECOUNT) { throw new InvalidExpressionException (temp.ToString () + " is too many dice"); } else { keepCount = temp.LongValue (); } } } else if (extension == 'l') { keepstrat = KeepStrategy.LOWEST; BigInteger temp = null; if (steve.TryReadLong (ref temp)) { if (temp > MAX_DIECOUNT) { throw new InvalidExpressionException (temp.ToString () + " is too many dice"); } else { keepCount = temp.LongValue (); } } } else { throw new InvalidExpressionException("invalid die extension " + extension); } } var countList = new List<Token> (); countList.Add (dieCount); return new DiceToken (countList, dieType, keepCount, keepstrat, this); } else { throw new InvalidExpressionException("no die type given"); } }
// converts an infix dice formula to a postfix list of tokens. Can return throw InvalidExpressionException if the expression is invalid. private List<Token> InfixToPostfix(string input, int index, string formulaName) { if (formulaName != null) { // we are evaluating a formula, and will push it to the formulaNest so that we can prevent self-referential formulas Assert(!formulaNest.Contains (formulaName), "[" + formulaName + "] is self-referential"); formulaNest.Push(formulaName); } StringScanner steve = new StringScanner (input, index); Stack<string> operatorStack = new Stack<string> (); List<Token> output = new List<Token>(); steve.skipWhitespace (); bool lastTokenWasNumber = false; while (steve.HasNext()) { bool tokenIsNumber = false; if (Char.ToLower (steve.Peek ()) == 'd') { output.Add (ProcessDieDef (steve, operatorStack, output, lastTokenWasNumber)); tokenIsNumber = true; } else if (IsCharOperator (steve.Peek ())) { ProcessOperator (steve, operatorStack, output, lastTokenWasNumber, ref tokenIsNumber); } else if (Char.IsDigit (steve.Peek ())) { output.Add (new NumToken(steve.ReadLong ())); tokenIsNumber = true; } else if (steve.Peek () == '(') { ProcessParentheses(steve, output, lastTokenWasNumber); tokenIsNumber = true; } else if (steve.Peek() == '[') { ProcessFormula(steve, output, lastTokenWasNumber); tokenIsNumber = true; } else if (steve.Peek () == ')') { // processParentheses reads all the valid close-parens, so if we find one here it must be mismatched throw new InvalidExpressionException ("mismatched parentheses"); } else { throw new InvalidExpressionException("invalid symbol: " + steve.Peek()); } steve.skipWhitespace (); lastTokenWasNumber = tokenIsNumber; } while (operatorStack.Count > 0) { Assert (operatorStack.Peek () == "(", "mismatched parentheses"); output.Add(OpToken.Get(operatorStack.Pop ())); } if (formulaName != null) { formulaNest.Pop (); } return output; }
// computes and returns the value of an expression in parentheses. // The scanner cursor should be pointing at the opening paren when this is called. // When this method returns, the scanner cursor will be pointing at the character directly after the closing paren. // Returns the expression in parentheses private string ExtractParentheses(StringScanner scanner) { StringBuilder steve = new StringBuilder (); int nestCount = 1; // track nested parentheses scanner.TrySkip (); // move past open-paren if (!scanner.HasNext ()) throw new InvalidExpressionException ("mismatched parentheses"); while (!(scanner.Peek () == ')' && nestCount == 1)) { if (scanner.Peek () == '(') { nestCount++; } else if (scanner.Peek () == ')') { nestCount--; } steve.Append (scanner.Read ()); if (!scanner.HasNext ()) throw new InvalidExpressionException ("mismatched parentheses"); } scanner.Read (); // move past close-paren DebugMessage ("evaluating \"" + steve.ToString () + "\" in parentheses"); return steve.ToString (); }
// reads the name of a formula, evaluates its expression, and returns the result. // the scanner cursor should be pointing at the opening bracket when this is called. // When this method returns, the cursor will be pointing at the character after the closing bracket. private Formula ExtractFormula(StringScanner scanner) { StringBuilder steve = new StringBuilder (); scanner.TrySkip(); if (!scanner.HasNext ()) throw new InvalidExpressionException ("mismatched brackets"); while (scanner.Peek () != ']') { steve.Append (scanner.Read ()); if (!scanner.HasNext ()) throw new InvalidExpressionException ("mismatched brackets"); } scanner.Read (); Formula f = formulas [steve.ToString ()]; if (f == null) throw new InvalidExpressionException ("No formula " + steve.ToString ()); return f; }