szolDat

Hi!

Is it possible to convert strings to operations in a relatively simple way For example convert this string:

string OperationInString = "3+3+9";

somehow, so I get back "15".

Thanks in advance. ;)



Re: Visual C# General Convert string to operation

John.Doe

You got three options:

1) Parse the string via tokens and write your own evaluator for the string by doing this you can define your own syntax, tokens, functions and so forth. This is of course a lot of work, although, quite interesting, too, if you never programmed something like this with an operator stack, an operand stack and so forth.

2) Going through XPath which is quite easy. You are then stuck with the XPath syntax which is not bad. XPath can be extended to support custom functions if needed, too, if you use your own XSLT context and define some more functions in there, but for basic use that is not even necessary, example:
class Program
{
 
static void Main(string[] args)
  {
   
XPathExpression expr = XPathExpression.Compile("2+4+6");
   
XmlDocument xmlDoc = new XmlDocument();
   
XPathNavigator xPathNavigator = xmlDoc.CreateNavigator();
   
Console.WriteLine(xPathNavigator.Evaluate(expr));
  }
}

3) Similar to two, but you go through the code emitter and assign the string as a result of a calculation to a variable. Kind of like injecting the string into a static function of a class that does nothing more than:
public class Test { public static double Calc() { return 2+4+6; } }
Although dynamically generating code like this is in my optinion much slower than 2) and only worth it if you reuse the calculation over and over again, because the XPathNavigator has some overhead, too.





Re: Visual C# General Convert string to operation

szolDat

The first one would be the best, unfortunately I don't have the time to write that. The second one is good enough but I'm interested in the third the most. So how do you assign the string as a result of a calculation to a variable If I try for example:

int Result = OperationInString;

then there is no way I can assign (or convert) the string so I get a calculated result. I'm confused here, sorry.

And if I use the static method, that wont work with a variable. If I set a double return type, it won't let me return the OperationInString variable. Could you explain to me in detail





Re: Visual C# General Convert string to operation

John.Doe

Three is not as easy as you might think.

It is essentially creating the class I outlined that contains the method with your calculation on the fly and executing it. Here is a complete example:

using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom;
using System.CodeDom.Compiler;

namespace ConsoleApplication1
{
  class Program
  {
    static double Calculate(string formula)
    {
      // create compile unit
      CodeCompileUnit compileUnit = new CodeCompileUnit();
      // add a namespace
      CodeNamespace codeNamespace = new CodeNamespace("MyCalculator");
      compileUnit.Namespaces.Add(codeNamespace);

      // add imports/using
      codeNamespace.Imports.Add(new CodeNamespaceImport("System"));

      // create a class
      CodeTypeDeclaration classType = new CodeTypeDeclaration("Calculator");
      classType.Attributes = MemberAttributes.Public;
      codeNamespace.Types.Add(classType);

      // create a method
      CodeMemberMethod method = new CodeMemberMethod();
      method.Name = "Calc";
      method.Attributes = MemberAttributes.Public;
      method.ReturnType = new CodeTypeReference(typeof(double));
      classType.Members.Add(method);

      // add the return statement with the calculation
      CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement(
        new CodeSnippetExpression(formula));
      method.Statements.Add(returnStatement);

      // add assemblies for linking
      String[] assemblyNames = { 
        typeof(System._AppDomain).Assembly.Location,
        typeof(System.CodeDom.Compiler.GeneratedCodeAttribute).Assembly.Location,
      };

      // compile in memory
      CodeDomProvider provider = CodeDomProvider.CreateProvider("CS");
      CompilerParameters parameters = new CompilerParameters(assemblyNames);
      parameters.GenerateExecutable = false;
      parameters.GenerateInMemory = true;
      parameters.TreatWarningsAsErrors = false;

      // check for errors
      CompilerResults compilerResults = provider.CompileAssemblyFromDom(parameters, compileUnit);
      StringBuilder errors = new StringBuilder();
      string errorText = String.Empty;
      foreach (CompilerError compilerError in compilerResults.Errors)
        errorText += compilerError.ToString() + "\n";

      if (errors.Length == 0)
      {
        Type type = compilerResults.CompiledAssembly.GetType("MyCalculator.Calculator");
        object objInstance = Activator.CreateInstance(type);
        object result = type.GetMethod("Calc").Invoke(objInstance, new object[] { });
        return (double)result;
      }
      else
        throw new Exception(errorText);
    }

    static void Main(string[] args)
    {
      Console.WriteLine(Calculate("3+4+5"));
    }
  }
}
You can of course if you want to reuse the compiled assembly store it in a cache and execute it whenever needed. But if the formula changes it means recreating the dynamically created class.







Re: Visual C# General Convert string to operation

szolDat

Wow you're expert! I guess it takes an amount of infinity^2 time to get to this level of knowledge. :) Thank you for that long piece of code, though I must admit, I get lost hopelessly while interpreting it after the 5th line. :) I hope at least I can implement it into my project. It does what I want it to do in itself  so far, I'll see what bugs show up with my stitched-together project.





Re: Visual C# General Convert string to operation

John.Doe

Code generation is not as difficult as it appears at first.

If you want to customize the code later or extend the class or the method that is generated, e.g. by adding parameters, it also makes sense to take a look at the actual code that it compiled. If you want to do so, just at this peace of code before the comment "//check for errors":

using (System.IO.StringWriter stringWriter = new System.IO.StringWriter())
{
  provider.GenerateCodeFromCompileUnit(compileUnit, stringWriter,
new CodeGeneratorOptions());
 
Console.WriteLine(stringWriter.ToString());
}

// check for errors
...

You will see the following output:

//-----------------------------------------------------------------------------

// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.42
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//-----------------------------------------------------------------------------


namespace MyCalculator {
    using System;


    public class Calculator {

        public virtual double Calc() {
            return 3+4+5;
        }
    }
}





Re: Visual C# General Convert string to operation

Sreevi

This is working fine for most of the operations, except operations like (1280-2)/2^7.

It is replacing ^ with + sign and calculating the result. How to access scientific operations using the same example

Thanks,

Sreevi





Re: Visual C# General Convert string to operation

John.Doe

I doubt, that ^ is replaced by +, it might coincidentally just come out to the same result. If you use the Xslt method, it should actually bomb out with an exception, if you use the C# code generator method, then the ^ is an exclusive "or" operation.

Depending on which of the two solutions you mean, you have these options, to extend them...

For XPath does not support advanced math functions by default, but you can of course write your own XsltContext which includes these functions. Essentially you would just need a XsltContext wrapper for the functions from System.Math, which you need, which is done pretty fast. The functions would then have to be used inside the formula you submit to the XPathExpression and for ease of use, I would demand, that the user uses you defined namespace prefix like "math", so that he would have to write something like 1280-2/math:pow(2, 7)
You can find an example of how to implement your own XsltContext here:
http://msdn2.microsoft.com/en-us/library/ms950806.aspx
http://msdn.microsoft.com/msdnmag/issues/03/02/XMLFiles/

For the code generation method, you do not have to do anything, but to write the right formula, like:
(1280-2)/Math.Pow(2,7)





Re: Visual C# General Convert string to operation

Cosmin Calinescu

The class above it is working fine until you atempt to divide by 0.

Then you get this:

--------------------------------

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.IO.FileNotFoundException: Could not load file or assembly 'file:///C:\Documents and Settings\.....\Local Settings\Temp\k-en2rwj.dll' or one of its dependencies. The system cannot find the file specified.

Source Error:

Line 69:       if (errors.Length == 0)
Line 70:       {
Line 71:         Type type = compilerResults.CompiledAssembly.GetType("MyCalculator.Calculator");
Line 72:         object objInstance = Activator.CreateInstance(type);
Line 73:         object result = type.GetMethod("Calc").Invoke(objInstance, new object[] { });

Source File: d:\.....t\App_Code\Class1.cs Line: 71

Assembly Load Trace: The following information can be helpful to determine why the assembly 'file:///C:\Documents and Settings\......\Local Settings\Temp\k-en2rwj.dll' could not be loaded.

WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

--------------------------------------

How can you handle this exception