Inconsistent boolean conversion

What would you expect to be the result of ((“true”=yes)=(yes=“true”)) ?
Well, it can be evaluated and the result is false! Why?

[admin@gw] > :put ("yes"="true")
false
[admin@gw] > :put (yes="true")
false
[admin@gw] > :put ("true"=yes)
true
[admin@gw] > :put ("yes"=true)
false
[admin@gw] > :put (true="yes")
false
[admin@gw] > :put (yes=true)
true
[admin@gw] > :put (!"yes"=!"true")
true

Suppose you have a script like myscript.rsc:

:local me "myscript"
:put "$me start"
:local debug [/system script environment get "debug$me" value]
:if (!$debug) do={ :put "not debugging" }
:if (!!$debug) do={ :put "debugging" }
:if ($debug) do={ :put "debugging" }
:put "$me end"



admin@gw] > /import myscript.rsc 
myscript start
not debugging
myscript end

Script file loaded and executed successfully
[admin@gw] > :global debugmyscript true
[admin@gw] > /import myscript.rsc 
myscript start
debugging
conditional is not boolean
[admin@gw] >

In a more complex script it would be harder to find out which line gives the “conditional is not boolean” error, and the condition does not have to be boolean because very often is converted to boolean:

[admin@gw] > :if (0) do={:put "true"} else={:put "false"}
false
[admin@gw] > :if (1) do={:put "true"} else={:put "false"}
true
[admin@gw] > :if ($undefined) do={:put "true"} else={:put "false"}
false
[admin@gw] >

By the way [/system script environment get “globalvarname” value] is always a string whilst $globalvarname is typed.

(Tested on Ros 6.25 and 7.15.3)

This is a decades old issue. Similar discussion/complaints here: http://forum.mikrotik.com/t/tobool-not-working-as-expected/130860/1

The basic logic is 0 is “false”, and all other numbers make a bool “true”. Provide any other type in a [:tobool] the bool becomes will get you a [:nothing] type (“nil”). e.g.

:put [:typeof [:tobool "anystring"]]
#  nil 
:put [:typeof [:tobool "false"]]    
#  nul

IMO it the “nil” that makes the [:typeof]=“bool” less than useful - sometimes [:nothing] is not same as false.

But since the config uses “yes” and “no” and does NOT take a bool type instead.

IMO you’re better off use the string “yes” or “no” instead of bool — since those can be use in scripts and some :if ($myvar = “yes”) is just as clear. The side-effects of the “nil” add unnecessary complexity in in already complex scripting language.

What I meant with “inconsistency” is that the ! operator can do string to boolean conversion while the :if statement and the = operator cannot.

admin@branch] > :put (!"yes")
false
[admin@branch] > :put ("true"=true)   
true
[admin@branch] > :put (true="true")  
false
[admin@branch] > :if ("true") do={:put "-y-"} else={:put "-n-"}
conditional is not boolean

I admit, my first post was not that clear.

And there are more differences, for example ‘The basic logic is 0 is “false”’ does not apply to the ! operator:

[admin@Mikrotik] > :put (!0)  
Script Error: cannot logically negate time interval
[admin@branch] > :if (0) do={:put "-y-"} else={:put "-n-"}      
-n-

It seems kind of messy/undocumented. Implementing your own boolean could be a workaround, but I think it is not ok if one has to do that.

So do that, or more logically stop using string as true/false values, no mater if the string have the characters “true” or “false” inside and also as yes/no for true / false

:put (true=!!"true")
true

:if (!!"true") do={:put "-y-"} else={:put "-n-"}  
-y-

then leave other considerations aside, such as -1 = True, 0 = False on some cases…
(with only 1 bit, the last bit for “negative” is the only bit available, so the numbers on that bit value can be only 0 and -1, so set = true (-1) and unset = 0 (false) )



RouterOS inconsistency (synthesis: use true/false on script processing, yes/no on config commands)

/system routerboard settings> :put [get]
auto-upgrade=true

/system routerboard settings> export 
/system routerboard settings
set auto-upgrade=yes

/system routerboard settings> set auto-upgrade=true
syntax error (line 1 column 18)

/system routerboard settings> set auto-upgrade=yes

In my opinion the inconsistency is the problem: if the operator !$a works, :if ($a) should also work and vice versa.

Of course, in scripts one avoids using strings as :if statement condition, but sometimes is quite unexpected that a boolean turns into string, for example:

[admin@Mikrotik] > :global myfunc do={:put [:typeof $1]; :if ($1) do={:put "-y-"} else={:put "-n-"}}
[admin@Mikrotik] > $myfunc true
str
conditional is not boolean
[admin@Mikrotik] > $myfunc (true)
bool
-y-

In this case the argument is converted to string unless put into ().
In other cases strings are converted into non strings. One might expect that “$a” is a string, but no, it is $a.

Ome other examples of unexpected type conversions:

[admin@Mikrotik] > :put [:typeof "yes"] 
str
[admin@Mikrotik] > :put [:typeof "true"]   
str
[admin@Mikrotik] > :put [:typeof "$(true)"]
bool
[admin@Mikrotik] > :put [:typeof "$(yes)"]
bool
[admin@Mikrotik] > :global a 0
[admin@Mikrotik] > :put [:typeof "$a"]   
num
[admin@Mikrotik] > :put [:typeof "$($a)"] 
num

It is confusing. At least should be documented. Maybe it is documented, I just did not found it.

These examples show exactly what the result SHOULD look like, so what’s wrong with these examples results, is your expectations.


Just write the scripts properly.
This is just a forcing.
When happening this on one script, if is just made for this case?

:put [:typeof "$(true)"]



[admin@Mikrotik] > :put [:typeof "yes"] 
str
# correct!!! yes is just one string

[admin@Mikrotik] > :put [:typeof "true"]   
str
# correct!!! yes is just one string

[admin@Mikrotik] > :put [:typeof "$(true)"]
bool
# correct!!! "$(true)" is translated into $(true) since is just one $ element, and ( ) provide the result of internal
# operation that is boolean true (not "true"), so $(true) is obviously true and :typeof of true, is obvious that is boolean



[admin@Mikrotik] > :put [:typeof "$(yes)"]
bool
# same as before

[admin@Mikrotik] > :global a 0
[admin@Mikrotik] > :put [:typeof "$a"]   
num
[admin@Mikrotik] > :put [:typeof "$($a)"] 
num
# same as before "$($a)" is translated to $($a), then $() mean put here the result of $a, so $a is a num, and the result is a num....

Correct example

:global a 1
:global b 2
[admin@Mikrotik] > :put [:typeof "$($a + $b)"] 
num
[admin@Mikrotik] > :put "$($a + $b)"
3

Mikrotik+us are in a bit a box... the #1 rules is existing script have to still work - so some inconsistencies" cannot be fixed, since folk may rely on the "wrong" behavior. And also there just are a lot implicit type conversations - since script and config are same system. So as "config language", scripting engine has to be forgiving on someone using ="1" vs =1 in a set. This logic gets you into the current state.

Basically you're better off using :more [:typeof $x] perhaps with [:tonum]/whatever if doing any if/logic with something if you want to be "safe". i.e. convert and check types yourself if it matters in logic...

But boolean logic like ! on something your not SURE of it's type is bad idea. Since you're not wrong – boolean are inconsistent. So the resulting operators are also too! Best just not to do stuff like !$x unless your script created the $x using no-quotes true or false. Stuff like !($x = 1) is okay, since the result of an = expression is a bool type.

At the end of the day, scripts variables get marshaled to/from strings, so type information is always inferred, not "saved". This is especially "true" or true for booleans, since it "upconverts" strings to the booleans IF NEEDED in expression. But the bool variables are fake! Similar things with ip address types, as router, the type inference prefers ip in some cases. (i.e. :global mynum 1.1 - will get you an ip address of 1.0.0.1 since original RFC said that was okay for an ip address). Why [:typeof] is best defense you have.

In terms of scripting docs... it's true do not give a lot of guidance how "how to" or have deeper reference on things like types and script lifecycle. There is a "tip and ticks" section that is helpful – it shows the "worst" gotchas. And @rextended's pinned "snippets" post have more good examples.



And this is real crime here. You should be able to pass a bool to a "/interface/ethernet set enabled=$mybool" but @rextended is right - commands need the "yes" or "no" string. Worse scripting has no casting to convert bool to "yes"/"no" format.