Expressions and LuaXP Functions
-
I see. I hadn't considered the
.find
method only because it wasn't explicitly mentioned in lexpjs docs, but should have assumed it present from JS by extension.Furthermore, I stand corrected about
1 in [1,2,3]
which I swore (should not!) MSR returned as FALSE on first run of test. My bad.Lastly, where do you stand on
[1,2,3] || [3,4,5]
and[1,2,3] && [3,4,5]
? Former is returning TRUE (sorta expected) and latter is returning[3,4,5]
which I kinda get, but don't? -
I see. I hadn't considered the
.find
method only because it wasn't explicitly mentioned in lexpjs docs, but should have assumed it present from JS by extension.Furthermore, I stand corrected about
1 in [1,2,3]
which I swore (should not!) MSR returned as FALSE on first run of test. My bad.Lastly, where do you stand on
[1,2,3] || [3,4,5]
and[1,2,3] && [3,4,5]
? Former is returning TRUE (sorta expected) and latter is returning[3,4,5]
which I kinda get, but don't?@librasun said in Expressions and LuaXP Functions:
I see. I hadn't considered the .find method only because it wasn't explicitly mentioned in lexpjs docs, but should have assumed it present from JS by extension.
[referring to
Array.find()
] That's not a lexpjs function, it's how you do what you are trying to do in JavaScript (find a value in an array). Your Reactor/lexpjs solution isindexOf()
. Also note that dot-function notation is not supported by Reactor/lexpjs -- you can't saysomearrayname.indexOf( lookingfor )
, you have to writeindexof( somearrayname, lookingfor )
.I'm tempted to just leave
in
as-is for arrays, but well-documented, simply because it follows a common-use language pattern, and there is a defined replacement, which happens to be (almost) the same for both.Lastly, where do you stand on [1,2,3] || [3,4,5] and [1,2,3] && [3,4,5]?
That's a bug. The answer for the former should be
[1,2,3]
and for the latter[4,5,6]
. Fixing now. -
MSR SAMPLE EXPRESSIONS: Timestamp
If you want to display a timestamp in a stored variable or an outbound message from MSR, that looks like "
14:24:43 Wed 2021-Mar-17
", allow me to save you some typing. Just include this Expression in your Reaction, and reference${{timeStamp}}
in your workflow.daysList = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"], monthsList = ["","Jan","Feb","Mar","Apr","Jun","Jul","Aug","Sep","Oct","Nov","Dec"], timeNow = dateparts(time()), timeStamp = timeNow.hour + ':' + substr('0' + timeNow.minute,-2,2) + ':' + substr('0' + timeNow.second,-2,2) + ' ' + daysList[timeNow.weekday] + ' ' + timeNow.year + '-' + monthsList[timeNow.month] + '-' + substr('0' + timeNow.day,-2,2)
This construct takes advantage of the multi-statement feature built into MSR, which permits you to string together a series of declarations and evaluations within a single Expression. I've also sprinkled in a little formatting spice, so that single-digit
day
,minute
andsecond
fields display with a leading0
. -
lexpjs is missing some functions that LuaXP had and range from convenience to vital. Seems that date/time formatting is among them, as this should be a simpler operation, and we can take advantage of all the localization that the system itself offers in the process.
Among the functions I know are missing, or I'm pondering, are:
format( template, ...args )
- A C-style formatter for strings (likesprintf()
)
strftime( template, time )
- Also C-style, formatting specifically for date parts.
dateadd( time, yy, mm, dd, hh, MM, ss )
- Modify a timestamp; e.g.dateadd( time(), null, null, 1 )
would add one day to the current time (so the result time would have the same hour, minute, second just a day forward)
dateset( time, yy, mm, dd, hh, MM, ss )
- Another way to modify a timestamp, e.g.dateset( time(), null, null, null, 0, 0, 0 )
would result in midnight for the current date, whiledateset( time(), null, null, null, 24, 0, 0 )
would be exactly midnight tomorrow.
push
,pop
,shift
andunshift
- Array functions to move an element in or out.
concat
- Concatenate two arrays; function, or overload+
( e.g.[1,2,3] + [4,5,6] => [1,2,3,4,5,6]
)
slice
- Return a portion of arrray; pondering whether this should be done as a function, or with python-stylearray[start:end]
syntax (into the whole brevity thing);
splice
- Remove elements from, and insert into, and array;splice()
is the JavaScript function, and it's very powerful but not particularly user-friendly. Maybe better to have separateremove()
andinsert()
?The HubitatController also extends lexpjs with its own private
hsltorgb()
andrgbtohsl()
functions to convert between RGB and HS(L) as part of its entity mappings. I'm considering making these available everywhere (could be useful in some actions in particular on any platform).You may note that map/reduce functions are missing, but they are covered by the
each
operator (e.g.each val in numbers: val**2
produces a new array of the square of the values innumbers
, themap()
function, whilet=0, each val in squares: t=t+val
is a reduce that produces the sum of the squares); theselect()
function from LuaXP is covered by thefirst
option (e.g.first element in array-or-object with match-expression
). And filtering can also be done witheach
because any null result during iteration isn't added to the result array. -
Wonderful!
COMMENTS:
- Yes, brevity counts.
- Did not know LuaXP (much less lexpjs) has exponentiation with
**
, only knewpow( )
- Hadn't used some of the older syntax much like
pop
orconcat
, but other power users sure did - YES to HSL◄►RGB conversion
- I'm new to
each
,first
anddo
but gosh are they powerful!
Guess you'll have your hands full deciding which to implement and then fleshing out the lexpjs docs!
Looking forward to it. Thanks again. -
No equivalent function, but the name is available as
getEntity( .... ).name
-
I'll try that, and encourage you to add that to drop-down.
Also, meanwhile, I'm getting a (null) response with:
getEntity( "vera>device_337" ).attributes.x_vera_device.device_number
which IMHO ought to be(number) 337
, no?
EDIT: Solved with a hard refresh of browser! Weird. -
I'm going to enhance the following functions to work like this. I use the "old school" *nix notation for optional parameters by enclosing in square brackets, so where you see that below, those parameters are optional.
match( string, pattern [ , num [ , flags ] ] )
- Find pattern in string. If num is given and the pattern contains regexp groups, the function will return the numth matched group (e.g.match( "The rain in Spain", "r(ai)n", 1 )
returns "ai"; zero is always the full match, and thus the default). If flags is given as "i", a case-insensitive match will be performed.find( string, pattern [ , flags ] )
- Returns the position (0-based) of the matched pattern in string. Returns -1 if the pattern is not matched. The flags have the same meaning as inmatch()
.replace( string, pattern, replacement [ , flags ] )
- Replaces the first occurrence (by default) of pattern in string with replacement. If specified, flags have the same meaning as inmatch()
, plus the additional flag "g" (for global) modifies the behavior to replace all instances of the pattern in string (if needed, multiple flags can simply be concatenated, as in "ig" for case-insensitive + global). The replacement string uses$
as a special pattern lead-in, so $` yields the portion of string before the matched pattern,$'
yields the portion of string after the matched pattern,$&
is the entire matched substring,$n
inserts the match of the nth group in pattern, and$$
yields "$" (self-escape).
Basically, all of this follows JavaScript semantics, which provides the underlying implementation.
-
In what context?
-
Yes, will happen. It raises priority on another "to-do" item for the parser, which has rather "blunt" string lexing at the moment. I've got a more formal version working now, but I want to play with it a little more before I commit it to MSR.
-
@toggledbits I can't seem to concoct a meaningful example of
DO
..DONE
, since I'm probably not grasping its intended function within the MSR framework.
Does it have a use here, and if so, how would such a statement look? Thanks! -
do...done
is intended to help thefirst
andeach
statements (but can be used anywhere, really).- the
each
andfirst
statements operate only on a single expression; do...done
encapsulates multiple expressions (separated by comma), but looks like a single expression toeach
andfirst
;- The result of a
do...done
statement is the value of the last expression executed within it.
Here from my own configuration is an example:
each detector in leak_detectors: do d=getEntity(detector), d.attributes.arming.state && d.attributes.binary_sensor.state ? d.id : null done
There's a separate expression called
leak_detectors
that provides an array of leak detector entity IDs (canonical, across controllers, of course). Theeach
statement iterates over each of them. Thedo...done
encapsulates two expressions: the fetch of the entity stored to a local variable in the first, and then uses that local variable twice in the second expression. The result of thedo...done
is the result of the last expression, which is the detector's ID if it is armed and tripped, or null otherwise. With aneach
expression, anynull
s do not appear in the final array.The idea should be familiar from RFV, although the construction is a little different: it's a loop over a list of known leak detectors and builds an array of those that are armed and in alarm (if any, hopefully not).
- the
-
For a lark, I defined a test variable as:
testObj = { a:5, b:2, c:"cat"}, each num in values(testObj): r = isNaN(num) ? " That's all!" : "Libra owns " + num + " cats"
which spits out:
["Libra owns 5 cats","Libra owns 2 cats","Libra owns cat cats"]
while I had actually expected (due to the
NaN
:["Libra owns 5 cats","Libra owns 2 cats"," That's all!"]
Also, am noticing that the Expression 'value' windows always contract to their initial (1.25-line tall) height, rather than remember what the user enlarged them to.
Are both of these "expected behaviors" in MSR??
-
The
isNaN()
is a yes... unlike JavaScript, the lexpjsisNaN()
literally tests to see if the argument isNaN
, which"cat"
is not. You'd need to write your test asr = isNaN( int( num ) ) ? ...etc.
I can't store your selected/custom field sizes. I haven't poked harder into getting it to auto-size. I've done that before, but for some reason it isn't working and I haven't bothered to track it down yet.