-->
Page 1 of 3

Undefined Variables Bug

PostPosted: Thu Oct 13, 2016 4:39 pm
by Electroguard
When specifying text, quotes are used to differentiate it from variables.
Using UDPport = 5001 as an example, html "UDPport" (in quotes) returns the specified text "UDPport", whereas html UDPport (no quotes) returns the value 5001 which was assigned to the variable UDPport.

But there is a bug when specifying a variable that does not exist, because any reference to an undefined variable turns it into a text variable which contains it's own previously undeclared name as its value.
For instance, if not previously defined, testing if udpport == "" would create udpport as a text variable and assign it the value of "udpport".

This has significance when testing for variables, because the very act of testing for a variable (such as after trying to read variables from flash) will cause a previously non-existing variable to then exist with that name and to have the same text value as the new name.

This prevents any subsequent numeric variable assignment of that name, and causes any subsequent numeric operations with that variable name to fail with 'Comparaison between string and number' error.

For instance, testing for an empty udpport numeric variable using if udpport == "" will not work if the variable has not yet been defined, because it will create a new text variable of that name and assign it the value of "udpport". So it will not then be possible to subsequently define eg: let udpport = 5001, and any subroutines that try to do anything with the numeric port variable will fail with the dreaded 'Comparaison between string and number' error.

I think it would be more appropriate for an undefined non-quotes variable to simply return an empty "" (nul) value rather than echoing back the unrecognised name as a text value.

This could then allow testing variables for empty using: if udpport = "", followed by a subsequent assignment of eg: let udpport = 5001, if needed (I'm fairly sure this was how things used to work).

Re: Undefined Variables Bug

PostPosted: Thu Oct 13, 2016 10:53 pm
by Mmiscool
The best practice is to assign a value or empty string to a variable at the top of your program so that it is defined prior to checking a value as with the uf command or doing any arithmadic operations on it.

Variables in esp basic come in to existence when a value is assigned to it.

Re: Undefined Variables Bug

PostPosted: Fri Oct 14, 2016 8:03 am
by Electroguard
Variables in esp basic come in to existence when a value is assigned to it.

No, that is what should happen, but I am pointing out a bug which brings new unwanted variables into existence without assigning any value to them. The following snippet should help illustrate the problem...

Code: Select allmemclear
print "OK" 'prints the specified quoted text
print BUG 'this is not specified text, nor is it a declared variable with assigned contents, it is a bug!
BUG = BUG 'the bug created an error variable without any assigned data, and populated it with its own confused name
print BUG 'this is now an unwanted bug variable with unwanted and unspecified contents

emptyvar = "" 'This is correct, it creates an empty variable by asigning it nul contents
print emptyvar 'prints empty contents
emptyvar = emptyvar 'still empty, does not confuse contents with the var name
print emptyvar 'prints empty contents again to prove no confusion with var name
emptyvar = 12345 'assign numeric value to empty variable
print emptyvar 'correctly prints contents of the variable


It is not always possible or practical to pre-assign values to all variables - not all optional variable will necessarilly be required, and perhaps not even initially known.

I am starting a re-write of EasyNet for V3. The aim is for a generic network node script which is configurable for a multitude of different purposes even though the main script with all the network framework remains the same and is common for all. The main difference in the huge personality changes between a PIR sensor, an Infra-red Receiver or Transmitter node, a Mains Monitor Receiver and Transmitter etc, is achieved by selecting different uses for the gpio00 input button, and different uses for the optional relay outputs.

The gpio00 button is configurable from a dropdown list, ie: dropdown "MultiMode,BlinkFullIP,BlinkShortIP,ToggleButton,FlipSwitch,Sensor,Monitor,IR", buttonmode.

All those different possible uses require their own sets of parameters which are only relevent when used in the chosen users configuration. The sets of parameters for unused options should remain undefined.

The program also needs to cater for the possibility of 1 relay as with the Sonoff, or 2 relays as with an ElectroDragon, or even possibly 4 or 8 if used with an SPI or I2C multi-IO expansion module.

But each relay potentially requires a set of parameters, ie:
relaypin=12, relaylogic=0, relaystartup=off, relayondelay=0, relayoffdelay=0, relayonduration=9, relayoffduration=0.

It would be completely impractical to pre-define all those sets of parameters for 8 different relays on the off-chance that they may occasionally be required, when the chances are that no relays may be used by a lot of people a lot of the time.

So it makes sense to create a script which has just a few essential pre-defined example variables specified at the top of the script, which then automatically declares any others which are need for that configuration and assigns them defaults, which may be overwritten by any available saved variables if read back in from flash memory. Any new user edits would be saved to flash and subsequently overwrite all pre-defined defaults each time. This would avoid all variables needing to be explicitely pre-declared even if empty, and unnecessarilly and wastefully saved to flash every time... only non-defaults need be saved.


That could be easily achieved if the undeclared variables bug didn't keep mistakingly creating unwanted variables with unwanted contents when tested for empty using if var == "".

But until that is fixed, it will require explicitely pre-defining all required variables, which in practice will probably mean doing individual tailored scripts for each required configuration by ripping out all references to all optional variables which are not used by that specific configuration, but which cannot be tested for.

Just to give a better idea of the scale of things, I've included the snippet below, which doesn't include all required variables, but shows what I was working my way through until coming up against the undeclared variables bug problem, which prevented me from subsequently declaring numeric variables after testing a non-existing var name for empty.
I fleetingly considered avoiding all numeric variables and using all text conversions instead... but I'm not masochistic enough.

Code: Select all[INIT]
localIP = ip()
gosub [IPSPLIT]
if opmode == "" or opmode == "opmode" then opmode = "Node"
if localname == "" or localname == "localname" then localname = opmode & nodeIP
if groupname == "" or groupname == "groupname" then groupname = opmode
if globalname == "" or globalname == "globalname" then globalname = "ALL"
if udpport == "" or udpport == "udpport" then udpport = 5001
if description == "description" then description = ""
if debugmode == "" or debugmode == "debugmode" then debugmode = "normal"
if buttonmode == "" or buttonmode == "buttonmode" then buttonmode = "MultiMode"
if voicemode == "On" then gosub [VOICEINIT]
'if voicemode == "" then voicemode = "Off"
'if userpage == "" then userpage = "Normal"
if relaylogic == "" or relaylogic == "relaylogic" then relaylogic = 1
'if relaylogic == "" or relaylogic == "relaylogic" then relaylogic = 1
if startmode == "" or startmode == "startmode" then startmode = "Off"
if startmode == "On" then
 if relaylogic == 0 then io(po,relaypin,1) else io(po,relaypin,0)
 if ledlogic == 0 then io(po,ledpin,1) else io(po,ledpin,0)
else
 if relaylogic == 0 then io(po,relaypin,0) else io(po,relaypin,1)
 if ledlogic == 0 then io(po,ledpin,0) else io(po,ledpin,1)
endif
interrupt buttonpin, [PRESSED]
return


[READVARS]
tmp = read("Title")
if tmp <> "" then title = tmp
tmp = read("Name")
if tmp <> "" then localname = tmp
tmp = read("Global")
if tmp <> "" then globalname = tmp
tmp = read("Group")
if tmp <> "" then groupname = tmp
tmp = read("Description")
if tmp <> "" then description = tmp
tmp = read("Opmode")
if tmp <> "" then opmode = tmp
tmp = read("Debugmode")
if tmp <> "" then debugmode = tmp
tmp = read("Buttonmode")
if tmp <> "" then buttonmode = tmp
tmp = read("Voicemode")
if tmp <> "" then voicemode = tmp
tmp = read("UDPport")
if tmp <> "" then udpport = val(tmp)
tmp = read("Ondelay")
if tmp <> "" then ondelay = val(tmp)
tmp = read("Offdelay")
if tmp <> "" then offdelay = val(tmp)
tmp = read("Onduration")
if tmp <> "" then onduration = val(tmp)
tmp = read("Offduration")
if tmp <> "" then offduration = val(tmp)
tmp = read("Blinks")
if tmp <> "" then numblinks = val(tmp)
return


[SAVEVARS]
if debugmode <> "silent" then udpreply localname & " Save to Flash acknowledged"
if title <> "" and title <> "title" then write Title title
if localname <> "" and localname <> "localname" then write Name, localname
if groupname <> "" and groupname <> "groupname" then write Group, groupname
if globalname <> "" and globalname <> "globalname" then write Global, globalname
if description <> "" and description <> "description" then write Description, description
if udpport <> "" and udpport <> "udpport" then write UDPport, udpport
if opmode <> "" and opmode <> "opmode" then write Opmode, opmode
if debugmode <> "" then write Debugmode, debugmode
if buttonmode <> "" then write Buttonmode, buttonmode
if voicemode <> "" then write Voicemode, voicemode
if ondelay <> "" then write Ondelay, ondelay
if offdelay <> "" then write Offdelay, offdelay
if onduration <> "" then write Onduration, onduration
if offduration <> "" then write Offduration, offduration
if numblinks <> "" then write Blinks, numblinks
if startmode <> "" then write Startmode, startmode
if userpage <> "" then write Userpage, userpage
return

Re: Undefined Variables Bug

PostPosted: Sun Oct 16, 2016 3:41 am
by Oldbod
I quite agree that best practise would be to define variables at the beginning, but I also understand where electroguard is coming from. Perhaps this would be a case for an "if variable exists" function.

It would be nice (easy to say) if all errors generated a code and line number and were trappable - the "on error goto" approach. But while I was thinking about the problem electroguard has, I played a bit.

And I'm getting a bit confused (easily done). It seems that doing arithmetic on variables which contain a string causes the string contents to be ignored. For example,

newvar = "abc"
newvar = newvar + 1
print newvar

will show newvar as having a value of 1

I would have expected an error - don't think there's anything in the ref to cover this.....

I'm beginning to wonder if making the $ compulsory for strings might have some merit after all....