public static VerifiedExpression Parse(string expr, CalcEnvironment env) { if (env == null) { throw new ArgumentNullException(nameof(env)); } var tokenExpr = TokenExpression.Parse(expr); if (tokenExpr.Success) { return(Verifier.VerifyExpression(tokenExpr.BackingArray, env)); } else { return(new VerifiedExpression(tokenExpr.ErrorMessage, tokenExpr.ErrorPosition)); } }
public static VerifiedExpression Create(TokenExpression tokenExpr, CalcEnvironment env) { if (tokenExpr == null) { throw new ArgumentNullException(nameof(tokenExpr)); } if (!tokenExpr.Success) { throw new ArgumentException("Expression must be successfully parsed.", nameof(tokenExpr)); } if (env == null) { throw new ArgumentNullException(nameof(env)); } var arr = tokenExpr.Expression.ToArray(); return(Verifier.VerifyExpression(arr, env)); }
public static VerifiedExpression VerifyExpression(CalcToken[] tokens, CalcEnvironment env) { int currentDepth = 0; // Depth/subexpression -> Argument counter & function metadata. // A stack based solution might be better? var funcTracking = new Dictionary <int, FunctionMetadata>(); for (int i = 0; i < tokens.Length; i++) { var token = tokens[i]; // Used as `out` argument in processing types ArgSeperator and ParenClose. // The compiler complains if we define it in both cases, which are counted as the same scope. FunctionMetadata funcMeta; switch (token.Type) { // --- Verify symbol (variables and constants) --- case TokenType.Symbol: double numVal; if (env.VarOrConst(token.Value, out numVal)) { // Resolve symbol to value. var symbol = (CalcSymbolToken)token; tokens[i] = symbol.New(numVal); break; } else { return(new VerifiedExpression("Undefined symbol: " + token.Value, token.OriginIndex)); } // --- Verify function symbol --- case TokenType.Function: CalcFunction func; if (env.Function(token.Value, out func)) { // Resolve function symbol to function. var funcToken = (CalcFuncToken)token; tokens[i] = funcToken.New(func); // Record that the next subexpression (delimited by parentheses) is associated // with a function. funcTracking[currentDepth + 1] = new FunctionMetadata(func, funcToken); break; } else { return(new VerifiedExpression("Undefined function symbol: " + token.Value, token.OriginIndex)); } // --- Keep count of arguments to functions --- case TokenType.ArgSeperator: if (funcTracking.TryGetValue(currentDepth, out funcMeta)) { // Another seperator, another function argument. // The previous stage assures that there will be an operand. funcMeta.ArgCounter++; } break; // --- Record opening and closing of parentheses --- case TokenType.ParenOpen: currentDepth++; break; case TokenType.ParenClose: // If the subexpression we're closing/leaving is associated with a function we need to // verify the function argument count. if (funcTracking.TryGetValue(currentDepth, out funcMeta)) { if (!funcMeta.CountsAreEqual) { // And this is why we kept around that metadata, so we could report the appropriate // information on encountering an discrepancy in argument count. return(new VerifiedExpression( string.Format("Invalid number of arguments for {0} (got {1} expected {2})", funcMeta.FuncSymbol, funcMeta.ArgCounter, funcMeta.RequiredArgs), funcMeta.OriginIndex)); } // We're leaving the current depth, we no longer need this information. funcTracking.Remove(currentDepth); } currentDepth--; break; // --- The rest is not important for this stage --- default: continue; } // switch } // for // We have successfully stepped through the expression without encountering undefined symbols or functions // and the functions were supplied the correct number of arguments. Hurray! return(new VerifiedExpression(tokens)); }