Page 1 of 1

Understanding scripting data types

Posted: Fri Nov 21, 2014 2:02 am
by astounding
In an effort to better understand MikroTik scripting data types (running RouterOS 6.22) and understand what happens when doing boolean comparisons or passing data around, I've been experimenting. I've encountered some "interesting" things. Here are some of them (some may be bugs):

nil is not the same as nothing:
Wiki docs currently state:
nil - default variable type if no value is assigned;
Apparently that's not true exactly, at least with RouterOS 6.22:
{:local noValAssigned; :put "noValAssigned='$noValAssigned' ($[:typeof $noValAssigned],$[:len $noValAssigned])"}
Resulting output:
noValAssigned='' (nothing,0)
I learned that the not-nothing special nil value is returned by the :pick and :find global commands:
{
  :local a [:pick "" -1];
  :put ":pick example: a='$a' ($[:typeof $a],$[:len $a])";
  :set a [:find [/ip dns get servers] "10.0.0.1" -1];
  :put ":find example: a='$a' ($[:typeof $a],$[:len $a])";
}
Resulting output:
pick example: a='' (nil,0)
:find example: a='' (nil,0)
This brings up testing for that special nil value. Several ways spring to mind:
{
  :local weKnowThisIsNil [:pick "" -1];
  :local isItNil [:find {"foo";"bar"} "baz" -1];
  :if ([:typeof $isItNil]="nil") do={
    :put ":typeof test: It IS nil!";
  };
  :if ([:len $isItNil]=0) do={
    :put ":len test: It IS nil! (WARNING: Method NOT recommended!)";
  };
  :if ($isItNil=$weKnowThisIsNil) do={
    :put "compare against known nil value test: It IS nil! (I prefer this method.)";
  };
}
Output:
:typeof test: It IS nil!
:len test: It IS nil! (WARNING: Method NOT recommended!)
compare against known nil value test: It IS nil! (I prefer this method.)
The :len method will work in cases where you KNOW with ABSOLUTE certainty that you are comparing something that can ONLY be non-zero-length or else nil. I'd avoid the length test. I like to define a global variable $NIL and do my comparisons that way, personally. My preferred method could run into trouble if other code I'm unaware of sets $NIL to something else, however.

The same techniques work to test for undefined variables (the special nothing value):
{
  :local knownNothing;
  :local isItNothing;
  :if ([:typeof $isItNothing]="nothing") do={
    :put ":typeof test: It IS nothing!";
  };
  :if ([:len $isItNothing]=0) do={
    :put ":len test: It IS nothing! (WARNING: Method NOT recommended!)";
  };
  :if ($isItNothing=$knownNothing) do={
    :put "compare against known nothing value test: It IS nothing! (I prefer this method.)";
  };
}
Output:
:typeof test: It IS nothing!
:len test: It IS nothing! (WARNING: Method NOT recommended!)
compare against known nothing value test: It IS nothing! (I prefer this method.)
FEATURE REQUEST TO MIKROTIK:
For scripting, could you make global constants named NOTHING and NIL available for doing comparisons with? So code like this would work:
:if ($var=NOTHING) do={ ... } else={:if ($var=NIL) do={ ... } }
Alternatively, define :isnil and :isnothing global commands that take one parameter and return a boolean true or false.

Re: Understanding scripting data types

Posted: Fri Nov 21, 2014 2:04 am
by astounding
Arrays, especially now that there are key/value associative variations are proving interesting too me. I hope to post some quirks I'm encountering in the future...

CODE type...

Posted: Fri Nov 21, 2014 10:18 pm
by astounding
Messing around more on RouterOS 6.22, I note that functions (the special code data type) can be stored in both global and local variables. But I notice there seems to be two different ways code can be stored:
  • The older :parse global command way:
    {
      :local x [:parse ":return 1;"];
      :put [$x];
    }
    
    Output:
    1
  • The more recent do={} block style:
    {
      :local x do={:return 2;};
      :put [$x];
    }
    
    Output:
    2
My initial thought was that these two code types probably stored the executable code exactly the same way. WRONG!

More experimentation:
  • The older :parse global command way:
    {
      :local x [:parse ":return 1;"];
      :put [$x];
      :put "type=$[:typeof $x] len=$[:len $x]";
      :put $x;
    }
    
    Output:
    1
    type=code len=21
    (eval /returnvalue=1)
    Pretty much as expected...
  • The more recent do={} block style:
    {
      :local x do={:return 2;};
      :put [$x];
      :put "type=$[:typeof $x] len=$[:len $x]";
      :put $x;
    }
    
    Output:
    2
    type=array len=2
    ;(eval (eval /returnvalue=2))
Whoa! The second form is stored in an array of length 2, the first array item is the special nil value, the second is the special code type. And notice there's a double-eval going on compared to the other way (perhaps because it's in a curly-brace block).

Sure enough, if I enclose the first version in its own curly-braced block, it gets the double-eval treatment too. I still don't know why the do={} version gets stored in an array.

A quick tests shows that I can create my own array and store a nil first value and a parsed code block value and it's essentially identical:
{
  :local a {[:pick "" -1];[:parse "{:put \"Hi Mom!\";};"]};
  :put $a;
  $a;
  :local b do={:put "Hi Mom!";};
  :put $b;
  $b;
  :put ($a=$b);
} 
Output:
;(eval (eval /putmessage=Hi Mom!))
Hi Mom!
;(eval (eval /putmessage=Hi Mom!))
Hi Mom!
true
Interesting...

Re: Understanding scripting data types

Posted: Tue Dec 16, 2014 3:33 pm
by mmv
More interesting. Simple benchmark show difference in performance
:global benchmarkloopcount 1000000

:local fold [:parse ":return 1"]

:local fnew do={:return 1}

:local time1 [:system clock get time]
:for i from=0 to=$benchmarkloopcount do={
  $fold
}
:local time2 [:system clock get time]
:for i from=0 to=$benchmarkloopcount do={
  $fnew
}
:local time3 [:system clock get time]
:put "1: $($time2-$time1)"
:put "2: $($time3-$time2)"
Old style function outperform new style. At least on CCR1036-12G-4S and RB450G
> /system script run bencmark-functionking 
1: 00:01:02
2: 00:01:23
But new style avoid excessive quoting, so in common case it look better.

Re: Understanding scripting data types

Posted: Tue Dec 16, 2014 3:56 pm
by boen_robot
I'm guessing the difference in performance there is because the new style may also accept parameters, and the parameter handling is what's slowing things down.

Or does the old syntax now accept parameters too? If it does, and yet the difference persists, that's more troubling.

Re: Understanding scripting data types

Posted: Tue Dec 16, 2014 4:51 pm
by mmv
Really! Old syntax don't support parameters
:global fold [:parse ":put \"$param\""]
:global fnew do={:put "$param"}

:put "Old"
$fold param="params work"
:put "New"
$fnew param="params work"
 > /system script run test-params 
Old

New
params work