Community discussions

MUM Europe 2020
 
User avatar
astounding
Member Candidate
Member Candidate
Topic Author
Posts: 121
Joined: Tue Dec 16, 2008 12:17 am

Understanding scripting data types

Fri Nov 21, 2014 2:02 am

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.
 
User avatar
astounding
Member Candidate
Member Candidate
Topic Author
Posts: 121
Joined: Tue Dec 16, 2008 12:17 am

Re: Understanding scripting data types

Fri Nov 21, 2014 2:04 am

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...
 
User avatar
astounding
Member Candidate
Member Candidate
Topic Author
Posts: 121
Joined: Tue Dec 16, 2008 12:17 am

CODE type...

Fri Nov 21, 2014 10:18 pm

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...
 
mmv
Trainer
Trainer
Posts: 64
Joined: Wed Feb 24, 2010 5:03 pm
Location: Moscow, Russia
Contact:

Re: Understanding scripting data types

Tue Dec 16, 2014 3:33 pm

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.
Mikhail Moskalev
 
User avatar
boen_robot
Forum Guru
Forum Guru
Posts: 2411
Joined: Thu Aug 31, 2006 4:43 pm
Location: europe://Bulgaria/Plovdiv

Re: Understanding scripting data types

Tue Dec 16, 2014 3:56 pm

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.
PEAR2_Net_RouterOS(1.0.0b6) - My API client in PHP
(Rate my posts? If you want... no pressure...)
 
mmv
Trainer
Trainer
Posts: 64
Joined: Wed Feb 24, 2010 5:03 pm
Location: Moscow, Russia
Contact:

Re: Understanding scripting data types

Tue Dec 16, 2014 4:51 pm

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
Mikhail Moskalev

Who is online

Users browsing this forum: No registered users and 11 guests