{"id":388740,"date":"2024-06-29T08:08:24","date_gmt":"2024-06-29T08:08:24","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=388740"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-06-29T08:08:24","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=388740","title":{"rendered":"<span>NFun \u2014 expression evaluator for .Net<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><a href=\"https:\/\/github.com\/tmteam\/NFun\" rel=\"noopener noreferrer nofollow\">Git hub repository<\/a><\/p>\n<p><a href=\"https:\/\/github.com\/tmteam\/NFun\/blob\/master\/README.md\" rel=\"noopener noreferrer nofollow\">Examples and specification.<\/a><\/p>\n<h2>What is an &#171;Expression evaluator&#187;?<\/h2>\n<p>Expression evaluator allows you to evaluate specified expressions, for example:<\/p>\n<ul>\n<li>\n<p><code>12*3<\/code> is 36<\/p>\n<\/li>\n<li>\n<p><code>[1,2,3].reverse()<\/code> is an array of [3,2,1]<\/p>\n<\/li>\n<li>\n<p><code>'Kate'.reverse()<\/code> is &#171;etaK&#187;<\/p>\n<\/li>\n<\/ul>\n<p>Expressions may depend on input variables:<\/p>\n<ul>\n<li>\n<p><code>10*x + 4<\/code> depends on the value of <code>x<\/code>.<\/p>\n<\/li>\n<li>\n<p><code>'My name is {userName}. Age is {2022-birthYear}'<\/code> depends on the <code>userName<\/code> and <code>birthYear<\/code> values.<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/51e\/4c6\/46d\/51e4c646d6a7dd04329443f32a9c6566.png\" alt=\"Nfun script in Sonica SCADA\" title=\"Nfun script in Sonica SCADA\" width=\"1092\" height=\"312\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/51e\/4c6\/46d\/51e4c646d6a7dd04329443f32a9c6566.png\"\/><figcaption>Nfun script in Sonica SCADA<\/figcaption><\/figure>\n<p>A script can contain several such expressions:<\/p>\n<pre><code class=\"python\">x = Vx*t y = Vy*t  distance = sqrt(x**2 + y**2) average = (x+y)\/2<\/code><\/pre>\n<p>In general, you can use Nfun wherever you have previously stored, passed, or configured constants. In the example below, we formally set the rules for calculating bonuses<\/p>\n<pre><code class=\"json\">\/\/ ...`settings.json`... {     \"offset\": \"25\",     \"timeOffset\": \"3* 60 * 60 * 24 #sec\",     \"condition\": \"if (age>18) isEmpty(orders) else isEmpty(parent.orders)\",     \"bonus\": \"(min(max(order.price, 20.0), 100) + prevBonus)\/ordersCount\" }<\/code><\/pre>\n<p>Here are some usage examples:<\/p>\n<ul>\n<li>\n<p>Backend: Incoming request filters;<\/p>\n<\/li>\n<li>\n<p>Embedded: configuring signal processing;<\/p>\n<\/li>\n<li>\n<p>Loyalty system: bonus program settings;<\/p>\n<\/li>\n<li>\n<p>Robotics: kinematic model. Trajectory Descriptions;<\/p>\n<\/li>\n<li>\n<p>Low-code solutions.<\/p>\n<\/li>\n<\/ul>\n<h2>What can NFun do<\/h2>\n<p>Nfun continues the idea of the <a href=\"https:\/\/github.com\/sklose\/NCalc2\" rel=\"noopener noreferrer nofollow\">Ncalc<\/a> library, but for a rich type system<\/p>\n<ul>\n<li>\n<p>Primitive types &#8212; <code>byte<\/code>, <code>u16<\/code>, <code>u32<\/code>, <code>u64<\/code>, <code>i16<\/code>, <code>i32<\/code>, <code>i64<\/code>, <code>real<\/code>, <code>bool<\/code>, <code>ip<\/code>, <code>char<\/code>, <code>text<\/code>, <code>any<\/code><\/p>\n<\/li>\n<li>\n<p>Arrays, structures, Lambda expressions and Linq.<\/p>\n<\/li>\n<li>\n<p>Arithmetic, binary, discrete operators, array operators<\/p>\n<\/li>\n<li>\n<p>Conditional if expression.<\/p>\n<\/li>\n<li>\n<p>String interpolation.<\/p>\n<\/li>\n<li>\n<p>Named expressions and user-defined functions.<\/p>\n<\/li>\n<li>\n<p>Strict type system with type inference.<\/p>\n<\/li>\n<li>\n<p>Built-in functions.<\/p>\n<\/li>\n<li>\n<p>semantics customization<\/p>\n<\/li>\n<\/ul>\n<h2>Playground<\/h2>\n<p>Install the nuget package NFun<\/p>\n<p><code>PM> Install-Package NFun<\/code><\/p>\n<p>Let&#8217;s start with the classics!<\/p>\n<pre><code class=\"cs\">var a = Funny.Calc(\"'Hello world'\"); Console.WriteLine(a);<\/code><\/pre>\n<p>Let &#8216;s calculate the constants<\/p>\n<pre><code class=\"cs\">bool   b = Funny.Calc&lt;bool>(\"false and (2 > 1)\"); \/\/ false of bool   double d = Funny.Calc&lt;double>(\" 2 * 10 + 1 \"); \/\/ 21  of double   int    i = Funny.Calc&lt;int>(\" 2 * 10 + 1 \"); \/\/ 21 of int<\/code><\/pre>\n<p>Let&#8217;s calculate the output data<\/p>\n<pre><code class=\"cs\">class User { public string Age {get;set;} public string Name {get;set;} }  var inputUser = new User{ Age = 42; Name = \"Ivan\"; }  string userAlias =      Funny.Calc&lt;User,string> (          \"if(age &lt; 18) name else 'Mr. {name}' \",          inputUser);<\/code><\/pre>\n<p>And now let&#8217;s switch to hardcore mode. This mode provides access to all variables, and to low-level execution control.<\/p>\n<pre><code class=\"cs\">var runtime = Funny.Hardcore.Build(     \"y = a-b; \" +     \"out = 2*y\/(a+b)\" ); \/\/ Set inputs runtime[\"a\"].Value = 30; runtime[\"b\"].Value = 20; \/\/ Run script runtime.Run(); \/\/ Get outputs Assert.AreEqual(0.4, runtime[\"out\"].Value); <\/code><\/pre>\n<details class=\"spoiler\">\n<summary>We can continue&#8230;<\/summary>\n<div class=\"spoiler__content\">\n<p>Calculating multiple values based on input variable<\/p>\n<pre><code class=\"cs\">\/\/ Assume we have some \u0421# model \/*     class SomeModel {         public SomeModel(int age, Car[] cars) {             Age = age;             Cars = cars;         }         public int Age { get; }    \/\/Used as input         public Car[] Cars { get; } \/\/Used as input         public bool Adult { get; set; }   \/\/Used as output         public double Price { get; set; } \/\/Used as output     } *\/  var context =  new SomeModel(   age:42,    cars: new []{ new Car{ Price = 6000 }, new Car{ Price = 6200 }} );  \/\/ then we can set the 'Adult' and 'Price' properties based on the value of the 'Age' and 'Cars' properties Funny.CalcContext(     @\"           adult = age>18         price = cars.sum(rule it.price)     \",     context); Assert.AreEqual(true, context.Adult); Assert.AreEqual(12200, context.Price); \/\/ So input values and output values are properties of the same object<\/code><\/pre>\n<p>Customization<\/p>\n<p>Nfun provides customization of syntax and semantics for your needs <\/p>\n<ul>\n<li>\n<p> Prohibition or permission of if-expressions <\/p>\n<\/li>\n<li>\n<p> Decimal or Double arithmetic <\/p>\n<\/li>\n<li>\n<p> Integer overflow behavior <\/p>\n<\/li>\n<li>\n<p>Prohibition or permission of custom functions <\/p>\n<\/li>\n<li>\n<p>Default type for integer constants<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"cs\">var uintResult = Funny         .WithDialect(integerOverflow: IntegerOverflow.Unchecked)         .Calc&lt;uint>(\"0xFFFF_FFFF + 1\");     Assert.AreEqual((uint)0, uintResult);   \/\/now you cannot launch script with such an expression var builder = Funny.WithDialect(IfExpressionSetup.Deny); Assert.Throws&lt;FunnyParseException>(   () => builder.Calc(\"if(2&lt;1) true else false\"));<\/code><\/pre>\n<p>Add functions and constants<\/p>\n<pre><code class=\"cs\">\/\/assume we have custom function (method or Func&lt;...>) Func&lt;int, int, int> myFunctionMin = (i1, i2) => Math.Min(i1, i2);  object a = Funny             .WithConstant(\"foo\", 42)             .WithFunction(\"myMin\", myFunctionMin)             \/\/ now you can use 'myMin' function and 'foo' constant in script!             .Calc(\"myMin(foo,123) == foo\"); Assert.AreEqual(true, a);<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h2>Syntax<\/h2>\n<p>Nfun supports single-line expressions:<\/p>\n<pre><code class=\"python\">12 * x**3 - 3<\/code><\/pre>\n<p>Multiline named expressions:<\/p>\n<pre><code class=\"python\">nameStr = 'My name is: \"{name}\"' ageStr = 'My age is {age}' result = '{nameStr}. {ageStr}'<\/code><\/pre>\n<p>And custom functions:<\/p>\n<pre><code class=\"python\">maxOf3(a,b,c) = max(max(a,b),c)  y = maxOf3(1,2,3) # 3<\/code><\/pre>\n<p>Depending on the task, you can enable and disable these features.<\/p>\n<details class=\"spoiler\">\n<summary>More about the syntax<\/summary>\n<div class=\"spoiler__content\">\n<h3>Operators<\/h3>\n<pre><code class=\"cs\"># Arithmetic operators: + - * \/ % \/\/ **  y1 = 2*(x\/\/2 + 1) \/ (x % 3 -1)**0.5  # Bitwise:     ~ | &amp; ^ &lt;&lt; >> y2 = (x | y &amp; 0xF0FF &lt;&lt; 2) ^ 0x1234  # Discreet:    and or not > >= &lt; &lt;= == != y3 = x and false or not (y>0)<\/code><\/pre>\n<p>if-expression<\/p>\n<pre><code class=\"python\">simple  = if (x>0) x else if (x==0) 0 else -1 complex = if (age>18)             if (weight>100) 1             if (weight>50)  2             else 3         if (age>16) 0         else       -1<\/code><\/pre>\n<p>User functions and generic arithmetic<\/p>\n<pre><code class=\"cs\">sum3(a,b,c) = a+b+c #define generic user function sum3  r:real = sum3(1,2,3)  i:int  = sum3(1,2,3)<\/code><\/pre>\n<p>Arrays<\/p>\n<pre><code class=\"python\"># Initialization a:int[] = [1,2,3,4]      # [1,2,3,4]  type: int[] b = ['a','b','foo']# ['a','b','foo'] type: text[] c = [1..4]    # [1,2,3,4]  type: int[] d = [1..7 step 2]      # [1,3,5,7]  type: int[]  # Operator in a = 1 in [1,2,3,4]# true  # Get values c = (x[5]+ x[4])\/3  # Slices y = [0..10][1:3] #[1,2,3] y = [0..10][7:]  #[7,8,9,10] y = [0..10][:2]  #[0,1,2] y = [0..10][1:5 step 2] #[1,3,5]  # Functions # concat, intersect, except, unite, unique, find, max, min, avg, median, sum, count, any, sort, reverse, chunk, fold, repeat<\/code><\/pre>\n<p>Structures<\/p>\n<pre><code class=\"python\"># initialization user = {     age = 12,      name = 'Kate'     cars = [ # array of structures         { name = 'Creta',   id = 112, power = 140, price = 5000},         { name = 'Camaro', id = 113, power = 353, price = 10000}      ] } userName = user.name # field access<\/code><\/pre>\n<p>Strings<\/p>\n<pre><code class=\"python\">a =  ['one', 'two', 'three'].join(', ') # \"one, two, three\" of String  # Interpolation: x = 42 out = '21*2= {x}, arr = {[1,2,x]}'  #\"21*2= 42, arr = [1,2,42]\" of String<\/code><\/pre>\n<p>Anonymous-functions<\/p>\n<pre><code class=\"python\">[1,2,3,4]     .filter(rule it>2)     .map(rule it**3)     .max() # 64   <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h2>Semantics<\/h2>\n<p>Nfun is strictly typed &#8212; this was the main challenge, and a key feature for integration into C#, as well as error protection. However, the syntax of strongly typed languages is always more complicated.<\/p>\n<p>To solve this problem, I relied on the postulate:<\/p>\n<blockquote>\n<p>Anything that looks like a proper script (within the framework of syntax\/semantics) should run<\/p>\n<\/blockquote>\n<p>Or, more formalized:<\/p>\n<blockquote>\n<p>If the code looks like a weakly typed script, but it runs without errors, then it can be unambiguously output types for it.<\/p>\n<\/p>\n<p>Or to show that such code cannot be executed without errors.<\/p>\n<\/blockquote>\n<p>This required the development of a complex type inference system, from which the entire semantics of the language is based. The result of this is the widespread use of Generic-types.<\/p>\n<p>In the example below, functions, calculations, and even constants are generalized types (from the list <code>int32<\/code>, <code>uint32<\/code>, <code>int64<\/code>, <code>uint64<\/code>, <code>real<\/code>). However, the user should not think about it:<\/p>\n<pre><code class=\"cs\">var expr = @\"   # generic function   inc(a) = a + 1      # generic calculation   out = 42.inc().inc() \"; double d = Funny.Calc&lt;double>(expr); \/\/ 44  of double   int    i = Funny.Calc&lt;int>(expr); \/\/ 44 of int<\/code><\/pre>\n<p>Thus, it was possible to collect all the advantages of strict typing:<\/p>\n<ul>\n<li>\n<p>If the expression types do not converge, you get an error at the interpretation stage.<\/p>\n<\/li>\n<li>\n<p>If the types do not converge with the expected C# types, you get an error at the interpretation stage.<\/p>\n<\/li>\n<li>\n<p>Perfomance<\/p>\n<\/li>\n<\/ul>\n<p>At the same time, the syntax remained as simple as possible for an usual user!<\/p>\n<h2>Technical Details<\/h2>\n<p>Since Nfun is &#171;almost a programming language&#187; under the hood, its architecture is fairly standard. I will not describe it in detail here, there are already many cool articles about it.<\/p>\n<p>Code interpretation can be divided into several stages:<\/p>\n<ol>\n<li>\n<p>Tokenization (lexer).<\/p>\n<\/li>\n<\/ol>\n<p>A self-written lexer parses the input string into tokens, taking into account the possibility of string interpolation.<\/p>\n<ol start=\"2\">\n<li>\n<p>Parsing.<\/p>\n<\/li>\n<\/ol>\n<p>Parsing tokens into the AST tree. A self-written parser is used. The syntax specification relies on the language specification (and a lot of tests). The formal grammar is not described.<\/p>\n<ol start=\"3\">\n<li>\n<p>Type inference.<\/p>\n<\/li>\n<\/ol>\n<p>A custom, graph-based type inference algorithm, with support for implicit conversions between types.<\/p>\n<p>Type inference nuances:<\/p>\n<ul>\n<li>\n<p>Integer constants are &#171;generalized constants&#187;<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">one() = 1 # returns T, where byte -> T -> real   y:int  = one() # 1 of int z:real = one() # 1.0 of real<\/code><\/pre>\n<ul>\n<li>\n<p>The node type may depend on both the previous and subsequent code<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">y = 1 # y:real, \u0442\u0430\u043a \u043a\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u0437\u043d\u0430\u043c\u0435\u043d\u0430\u0442\u0435\u043b\u0435 \u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0440\u043e\u0447\u043a\u0435 x:real = 1\/y<\/code><\/pre>\n<ul>\n<li>\n<p>Restrictions on generic variables can be set both from above (inheritance, the type to which this can be reduced) and from below (descendant, the type that can be reduced to this)<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">sum(a,b) = a+b  x:int  = 1 + 2  # x = 3 of int y:real = 1 + 2 # y = 3.0 of real z:byte = 1 + 2 # error! operator '+' is defined for (uint16|int16)-> T -> real                # so it cannot return 'byte'<\/code><\/pre>\n<ol start=\"4\">\n<li>\n<p>Assembly of expressions (construction of runtime)<\/p>\n<\/li>\n<\/ol>\n<p>A self-written tree of calculated expressions is assembled from the results of solving types and the Ast tree.<\/p>\n<ol start=\"5\">\n<li>\n<p>Conversion of CLR values to Nfun.<\/p>\n<\/li>\n<li>\n<p>Execution of expressions.<\/p>\n<\/li>\n<li>\n<p>Conversion of results to CLR values.<\/p>\n<\/li>\n<\/ol>\n<p>I deliberately did not use the Csharp-expression-tree, since one of the most important criteria was the &#171;one-time-shot&#187; speed. That is, the launch, from the moment the script line is received until the execution results are received.<\/p>\n<h2>Project status<\/h2>\n<p>The project is ready for use in production.<\/p>\n<p>It has been used for a year as part of a scada system, covered with 6000+ tests, a specification has been written for it, and .. there are even a few stars on the github (yes, it&#8217;s a call to action!). Almost a success!<\/p>\n<h2>Conclusion<\/h2>\n<p>When I started Nfun, I dreamed of creating a simple, reliable and intuitive open source tool. I wanted to implement syntactic ideas and experiment with type inference systems, try something new for myself&#8230;<\/p>\n<p>As a result, Nfun cost a huge amount of effort, time and investment. This is the first time I have faced such a task. Now I want people to use this tool! And they wrote tickets and requests for github. Well, or comments under this post \ud83d\ude09<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/711864\/\"> https:\/\/habr.com\/ru\/articles\/711864\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><a href=\"https:\/\/github.com\/tmteam\/NFun\" rel=\"noopener noreferrer nofollow\">Git hub repository<\/a><\/p>\n<p><a href=\"https:\/\/github.com\/tmteam\/NFun\/blob\/master\/README.md\" rel=\"noopener noreferrer nofollow\">Examples and specification.<\/a><\/p>\n<h2>What is an &#171;Expression evaluator&#187;?<\/h2>\n<p>Expression evaluator allows you to evaluate specified expressions, for example:<\/p>\n<ul>\n<li>\n<p><code>12*3<\/code> is 36<\/p>\n<\/li>\n<li>\n<p><code>[1,2,3].reverse()<\/code> is an array of [3,2,1]<\/p>\n<\/li>\n<li>\n<p><code>'Kate'.reverse()<\/code> is &#171;etaK&#187;<\/p>\n<\/li>\n<\/ul>\n<p>Expressions may depend on input variables:<\/p>\n<ul>\n<li>\n<p><code>10*x + 4<\/code> depends on the value of <code>x<\/code>.<\/p>\n<\/li>\n<li>\n<p><code>'My name is {userName}. Age is {2022-birthYear}'<\/code> depends on the <code>userName<\/code> and <code>birthYear<\/code> values.<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><figcaption>Nfun script in Sonica SCADA<\/figcaption><\/figure>\n<p>A script can contain several such expressions:<\/p>\n<pre><code class=\"python\">x = Vx*t y = Vy*t  distance = sqrt(x**2 + y**2) average = (x+y)\/2<\/code><\/pre>\n<p>In general, you can use Nfun wherever you have previously stored, passed, or configured constants. In the example below, we formally set the rules for calculating bonuses<\/p>\n<pre><code class=\"json\">\/\/ ...`settings.json`... {     \"offset\": \"25\",     \"timeOffset\": \"3* 60 * 60 * 24 #sec\",     \"condition\": \"if (age>18) isEmpty(orders) else isEmpty(parent.orders)\",     \"bonus\": \"(min(max(order.price, 20.0), 100) + prevBonus)\/ordersCount\" }<\/code><\/pre>\n<p>Here are some usage examples:<\/p>\n<ul>\n<li>\n<p>Backend: Incoming request filters;<\/p>\n<\/li>\n<li>\n<p>Embedded: configuring signal processing;<\/p>\n<\/li>\n<li>\n<p>Loyalty system: bonus program settings;<\/p>\n<\/li>\n<li>\n<p>Robotics: kinematic model. Trajectory Descriptions;<\/p>\n<\/li>\n<li>\n<p>Low-code solutions.<\/p>\n<\/li>\n<\/ul>\n<h2>What can NFun do<\/h2>\n<p>Nfun continues the idea of the <a href=\"https:\/\/github.com\/sklose\/NCalc2\" rel=\"noopener noreferrer nofollow\">Ncalc<\/a> library, but for a rich type system<\/p>\n<ul>\n<li>\n<p>Primitive types &#8212; <code>byte<\/code>, <code>u16<\/code>, <code>u32<\/code>, <code>u64<\/code>, <code>i16<\/code>, <code>i32<\/code>, <code>i64<\/code>, <code>real<\/code>, <code>bool<\/code>, <code>ip<\/code>, <code>char<\/code>, <code>text<\/code>, <code>any<\/code><\/p>\n<\/li>\n<li>\n<p>Arrays, structures, Lambda expressions and Linq.<\/p>\n<\/li>\n<li>\n<p>Arithmetic, binary, discrete operators, array operators<\/p>\n<\/li>\n<li>\n<p>Conditional if expression.<\/p>\n<\/li>\n<li>\n<p>String interpolation.<\/p>\n<\/li>\n<li>\n<p>Named expressions and user-defined functions.<\/p>\n<\/li>\n<li>\n<p>Strict type system with type inference.<\/p>\n<\/li>\n<li>\n<p>Built-in functions.<\/p>\n<\/li>\n<li>\n<p>semantics customization<\/p>\n<\/li>\n<\/ul>\n<h2>Playground<\/h2>\n<p>Install the nuget package NFun<\/p>\n<p><code>PM> Install-Package NFun<\/code><\/p>\n<p>Let&#8217;s start with the classics!<\/p>\n<pre><code class=\"cs\">var a = Funny.Calc(\"'Hello world'\"); Console.WriteLine(a);<\/code><\/pre>\n<p>Let &#8216;s calculate the constants<\/p>\n<pre><code class=\"cs\">bool   b = Funny.Calc&lt;bool>(\"false and (2 > 1)\"); \/\/ false of bool   double d = Funny.Calc&lt;double>(\" 2 * 10 + 1 \"); \/\/ 21  of double   int    i = Funny.Calc&lt;int>(\" 2 * 10 + 1 \"); \/\/ 21 of int<\/code><\/pre>\n<p>Let&#8217;s calculate the output data<\/p>\n<pre><code class=\"cs\">class User { public string Age {get;set;} public string Name {get;set;} }  var inputUser = new User{ Age = 42; Name = \"Ivan\"; }  string userAlias =      Funny.Calc&lt;User,string> (          \"if(age &lt; 18) name else 'Mr. {name}' \",          inputUser);<\/code><\/pre>\n<p>And now let&#8217;s switch to hardcore mode. This mode provides access to all variables, and to low-level execution control.<\/p>\n<pre><code class=\"cs\">var runtime = Funny.Hardcore.Build(     \"y = a-b; \" +     \"out = 2*y\/(a+b)\" ); \/\/ Set inputs runtime[\"a\"].Value = 30; runtime[\"b\"].Value = 20; \/\/ Run script runtime.Run(); \/\/ Get outputs Assert.AreEqual(0.4, runtime[\"out\"].Value); <\/code><\/pre>\n<details class=\"spoiler\">\n<summary>We can continue&#8230;<\/summary>\n<div class=\"spoiler__content\">\n<p>Calculating multiple values based on input variable<\/p>\n<pre><code class=\"cs\">\/\/ Assume we have some \u0421# model \/*     class SomeModel {         public SomeModel(int age, Car[] cars) {             Age = age;             Cars = cars;         }         public int Age { get; }    \/\/Used as input         public Car[] Cars { get; } \/\/Used as input         public bool Adult { get; set; }   \/\/Used as output         public double Price { get; set; } \/\/Used as output     } *\/  var context =  new SomeModel(   age:42,    cars: new []{ new Car{ Price = 6000 }, new Car{ Price = 6200 }} );  \/\/ then we can set the 'Adult' and 'Price' properties based on the value of the 'Age' and 'Cars' properties Funny.CalcContext(     @\"           adult = age>18         price = cars.sum(rule it.price)     \",     context); Assert.AreEqual(true, context.Adult); Assert.AreEqual(12200, context.Price); \/\/ So input values and output values are properties of the same object<\/code><\/pre>\n<p>Customization<\/p>\n<p>Nfun provides customization of syntax and semantics for your needs <\/p>\n<ul>\n<li>\n<p> Prohibition or permission of if-expressions <\/p>\n<\/li>\n<li>\n<p> Decimal or Double arithmetic <\/p>\n<\/li>\n<li>\n<p> Integer overflow behavior <\/p>\n<\/li>\n<li>\n<p>Prohibition or permission of custom functions <\/p>\n<\/li>\n<li>\n<p>Default type for integer constants<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"cs\">var uintResult = Funny         .WithDialect(integerOverflow: IntegerOverflow.Unchecked)         .Calc&lt;uint>(\"0xFFFF_FFFF + 1\");     Assert.AreEqual((uint)0, uintResult);   \/\/now you cannot launch script with such an expression var builder = Funny.WithDialect(IfExpressionSetup.Deny); Assert.Throws&lt;FunnyParseException>(   () => builder.Calc(\"if(2&lt;1) true else false\"));<\/code><\/pre>\n<p>Add functions and constants<\/p>\n<pre><code class=\"cs\">\/\/assume we have custom function (method or Func&lt;...>) Func&lt;int, int, int> myFunctionMin = (i1, i2) => Math.Min(i1, i2);  object a = Funny             .WithConstant(\"foo\", 42)             .WithFunction(\"myMin\", myFunctionMin)             \/\/ now you can use 'myMin' function and 'foo' constant in script!             .Calc(\"myMin(foo,123) == foo\"); Assert.AreEqual(true, a);<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h2>Syntax<\/h2>\n<p>Nfun supports single-line expressions:<\/p>\n<pre><code class=\"python\">12 * x**3 - 3<\/code><\/pre>\n<p>Multiline named expressions:<\/p>\n<pre><code class=\"python\">nameStr = 'My name is: \"{name}\"' ageStr = 'My age is {age}' result = '{nameStr}. {ageStr}'<\/code><\/pre>\n<p>And custom functions:<\/p>\n<pre><code class=\"python\">maxOf3(a,b,c) = max(max(a,b),c)  y = maxOf3(1,2,3) # 3<\/code><\/pre>\n<p>Depending on the task, you can enable and disable these features.<\/p>\n<details class=\"spoiler\">\n<summary>More about the syntax<\/summary>\n<div class=\"spoiler__content\">\n<h3>Operators<\/h3>\n<pre><code class=\"cs\"># Arithmetic operators: + - * \/ % \/\/ **  y1 = 2*(x\/\/2 + 1) \/ (x % 3 -1)**0.5  # Bitwise:     ~ | &amp; ^ &lt;&lt; >> y2 = (x | y &amp; 0xF0FF &lt;&lt; 2) ^ 0x1234  # Discreet:    and or not > >= &lt; &lt;= == != y3 = x and false or not (y>0)<\/code><\/pre>\n<p>if-expression<\/p>\n<pre><code class=\"python\">simple  = if (x>0) x else if (x==0) 0 else -1 complex = if (age>18)             if (weight>100) 1             if (weight>50)  2             else 3         if (age>16) 0         else       -1<\/code><\/pre>\n<p>User functions and generic arithmetic<\/p>\n<pre><code class=\"cs\">sum3(a,b,c) = a+b+c #define generic user function sum3  r:real = sum3(1,2,3)  i:int  = sum3(1,2,3)<\/code><\/pre>\n<p>Arrays<\/p>\n<pre><code class=\"python\"># Initialization a:int[] = [1,2,3,4]      # [1,2,3,4]  type: int[] b = ['a','b','foo']# ['a','b','foo'] type: text[] c = [1..4]    # [1,2,3,4]  type: int[] d = [1..7 step 2]      # [1,3,5,7]  type: int[]  # Operator in a = 1 in [1,2,3,4]# true  # Get values c = (x[5]+ x[4])\/3  # Slices y = [0..10][1:3] #[1,2,3] y = [0..10][7:]  #[7,8,9,10] y = [0..10][:2]  #[0,1,2] y = [0..10][1:5 step 2] #[1,3,5]  # Functions # concat, intersect, except, unite, unique, find, max, min, avg, median, sum, count, any, sort, reverse, chunk, fold, repeat<\/code><\/pre>\n<p>Structures<\/p>\n<pre><code class=\"python\"># initialization user = {     age = 12,      name = 'Kate'     cars = [ # array of structures         { name = 'Creta',   id = 112, power = 140, price = 5000},         { name = 'Camaro', id = 113, power = 353, price = 10000}      ] } userName = user.name # field access<\/code><\/pre>\n<p>Strings<\/p>\n<pre><code class=\"python\">a =  ['one', 'two', 'three'].join(', ') # \"one, two, three\" of String  # Interpolation: x = 42 out = '21*2= {x}, arr = {[1,2,x]}'  #\"21*2= 42, arr = [1,2,42]\" of String<\/code><\/pre>\n<p>Anonymous-functions<\/p>\n<pre><code class=\"python\">[1,2,3,4]     .filter(rule it>2)     .map(rule it**3)     .max() # 64   <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h2>Semantics<\/h2>\n<p>Nfun is strictly typed &#8212; this was the main challenge, and a key feature for integration into C#, as well as error protection. However, the syntax of strongly typed languages is always more complicated.<\/p>\n<p>To solve this problem, I relied on the postulate:<\/p>\n<blockquote>\n<p>Anything that looks like a proper script (within the framework of syntax\/semantics) should run<\/p>\n<\/blockquote>\n<p>Or, more formalized:<\/p>\n<blockquote>\n<p>If the code looks like a weakly typed script, but it runs without errors, then it can be unambiguously output types for it.<\/p>\n<\/p>\n<p>Or to show that such code cannot be executed without errors.<\/p>\n<\/blockquote>\n<p>This required the development of a complex type inference system, from which the entire semantics of the language is based. The result of this is the widespread use of Generic-types.<\/p>\n<p>In the example below, functions, calculations, and even constants are generalized types (from the list <code>int32<\/code>, <code>uint32<\/code>, <code>int64<\/code>, <code>uint64<\/code>, <code>real<\/code>). However, the user should not think about it:<\/p>\n<pre><code class=\"cs\">var expr = @\"   # generic function   inc(a) = a + 1      # generic calculation   out = 42.inc().inc() \"; double d = Funny.Calc&lt;double>(expr); \/\/ 44  of double   int    i = Funny.Calc&lt;int>(expr); \/\/ 44 of int<\/code><\/pre>\n<p>Thus, it was possible to collect all the advantages of strict typing:<\/p>\n<ul>\n<li>\n<p>If the expression types do not converge, you get an error at the interpretation stage.<\/p>\n<\/li>\n<li>\n<p>If the types do not converge with the expected C# types, you get an error at the interpretation stage.<\/p>\n<\/li>\n<li>\n<p>Perfomance<\/p>\n<\/li>\n<\/ul>\n<p>At the same time, the syntax remained as simple as possible for an usual user!<\/p>\n<h2>Technical Details<\/h2>\n<p>Since Nfun is &#171;almost a programming language&#187; under the hood, its architecture is fairly standard. I will not describe it in detail here, there are already many cool articles about it.<\/p>\n<p>Code interpretation can be divided into several stages:<\/p>\n<ol>\n<li>\n<p>Tokenization (lexer).<\/p>\n<\/li>\n<\/ol>\n<p>A self-written lexer parses the input string into tokens, taking into account the possibility of string interpolation.<\/p>\n<ol start=\"2\">\n<li>\n<p>Parsing.<\/p>\n<\/li>\n<\/ol>\n<p>Parsing tokens into the AST tree. A self-written parser is used. The syntax specification relies on the language specification (and a lot of tests). The formal grammar is not described.<\/p>\n<ol start=\"3\">\n<li>\n<p>Type inference.<\/p>\n<\/li>\n<\/ol>\n<p>A custom, graph-based type inference algorithm, with support for implicit conversions between types.<\/p>\n<p>Type inference nuances:<\/p>\n<ul>\n<li>\n<p>Integer constants are &#171;generalized constants&#187;<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">one() = 1 # returns T, where byte -> T -> real   y:int  = one() # 1 of int z:real = one() # 1.0 of real<\/code><\/pre>\n<ul>\n<li>\n<p>The node type may depend on both the previous and subsequent code<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">y = 1 # y:real, \u0442\u0430\u043a \u043a\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u0437\u043d\u0430\u043c\u0435\u043d\u0430\u0442\u0435\u043b\u0435 \u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0440\u043e\u0447\u043a\u0435 x:real = 1\/y<\/code><\/pre>\n<ul>\n<li>\n<p>Restrictions on generic variables can be set both from above (inheritance, the type to which this can be reduced) and from below (descendant, the type that can be reduced to this)<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">sum(a,b) = a+b  x:int  = 1 + 2  # x = 3 of int y:real = 1 + 2 # y = 3.0 of real z:byte = 1 + 2 # error! operator '+' is defined for (uint16|int16)-> T -> real                # so it cannot return 'byte'<\/code><\/pre>\n<ol start=\"4\">\n<li>\n<p>Assembly of expressions (construction of runtime)<\/p>\n<\/li>\n<\/ol>\n<p>A self-written tree of calculated expressions is assembled from the results of solving types and the Ast tree.<\/p>\n<ol start=\"5\">\n<li>\n<p>Conversion of CLR values to Nfun.<\/p>\n<\/li>\n<li>\n<p>Execution of expressions.<\/p>\n<\/li>\n<li>\n<p>Conversion of results to CLR values.<\/p>\n<\/li>\n<\/ol>\n<p>I deliberately did not use the Csharp-expression-tree, since one of the most important criteria was the &#171;one-time-shot&#187; speed. That is, the launch, from the moment the script line is received until the execution results are received.<\/p>\n<h2>Project status<\/h2>\n<p>The project is ready for use in<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-388740","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/388740","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=388740"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/388740\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=388740"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=388740"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=388740"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}