Refactored Berry rule engine and support for arrays (#18121)

This commit is contained in:
s-hadinger 2023-03-05 22:50:32 +02:00 committed by GitHub
parent 83f039cdf7
commit 6bd73fc883
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1682 additions and 356 deletions

View File

@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.
- Shelly Pro 4PM using standard MCP23xxx driver and needs one time Auto-Configuration
### Changed
- Refactored Berry rule engine and support for arrays
### Fixed
- TuyaMcu v1 sequence fix (#17625)

View File

@ -60,25 +60,26 @@ extern int l_i2cenabled(bvm *vm);
extern int tasm_find_op(bvm *vm);
#include "solidify/solidified_tasmota_class.h"
#include "solidify/solidified_rule_matcher.h"
#include "solidify/solidified_trigger_class.h"
#include "be_fixed_be_class_tasmota.h"
/* @const_object_info_begin
class be_class_tasmota (scope: global, name: Tasmota) {
_fl, var
_rules, var
_timers, var
_crons, var
_ccmd, var
_drivers, var
wire1, var
wire2, var
cmd_res, var
global, var
settings, var
wd, var
_debug_present, var
_fl, var // list of active fast-loop object (faster than drivers)
_rules, var // list of active rules
_timers, var // list of active timers
_crons, var // list of active crons
_ccmd, var // list of active Tasmota commands implemented in Berry
_drivers, var // list of active drivers
wire1, var // Tasmota I2C Wire1
wire2, var // Tasmota I2C Wire2
cmd_res, var // store the command result, nil if disables, true if capture enabled, contains return value
global, var // mapping to TasmotaGlobal
settings, var // mapping to Tasmota Settings
wd, var // working dir
_debug_present, var // is `import debug` present?
_global_def, comptr(&be_tasmota_global_struct)
_settings_def, comptr(&be_tasmota_settings_struct)
@ -173,5 +174,7 @@ class be_class_tasmota (scope: global, name: Tasmota) {
get_light, closure(Tasmota_get_light_closure)
set_light, closure(Tasmota_set_light_closure)
Rule_Matcher, class(be_class_Rule_Matcher)
}
@const_object_info_end */

View File

@ -0,0 +1,333 @@
#- Native code used for testing and code solidification -#
#- Do not use it directly -#
#@ solidify:Rule_Matcher_Key
#@ solidify:Rule_Matcher_Wildcard
#@ solidify:Rule_Matcher_Operator
#@ solidify:Rule_Matcher_Array
#@ solidify:Rule_Matcher
#-
# tests
tasmota.Rule_Matcher.parse("AA#BB#CC")
# [<Matcher key='AA'>, <Matcher key='BB'>, <Matcher key='CC'>]
tasmota.Rule_Matcher.parse("AA")
# [<Matcher key='AA'>]
tasmota.Rule_Matcher.parse("AA#BB#CC=2")
# [<Matcher key='AA'>, <Matcher key='BB'>, <Matcher key='CC'>, <Matcher op '=' val='2'>]
tasmota.Rule_Matcher.parse("AA#BB#CC>=3.5")
# [<Matcher key='AA'>, <Matcher key='BB'>, <Matcher key='CC'>, <Matcher op '>=' val='3.5'>]
tasmota.Rule_Matcher.parse("AA#BB#CC!3.5")
# [<Matcher key='AA'>, <Matcher key='BB'>, <Matcher key='CC!3.5'>]
tasmota.Rule_Matcher.parse("AA#BB#CC==3=5")
# [<Matcher key='AA'>, <Matcher key='BB'>, <Matcher key='CC'>, <Matcher op '==' val='3=5'>]
tasmota.Rule_Matcher.parse("AA#BB#!CC!==3=5")
# [<Matcher key='AA'>, <Matcher key='BB'>, <Matcher key='!CC'>, <Matcher op '!==' val='3=5'>]
tasmota.Rule_Matcher.parse("")
# []
tasmota.Rule_Matcher.parse("A#?#B")
# [<Matcher key='A'>, <Matcher any>, <Matcher key='B'>]
tasmota.Rule_Matcher.parse("A#?>50")
# [<Matcher key='A'>, <Matcher any>, <Matcher op '>' val='50'>]
tasmota.Rule_Matcher.parse("A[1]")
# [<instance: Rule_Matcher_Key()>, <Matcher [0]>]
tasmota.Rule_Matcher.parse("A[1]#B[2]>3")
# [<instance: Rule_Matcher_Key()>, <Matcher [0]>, <instance: Rule_Matcher_Key()>, <Matcher [0]>, <Matcher op '>' val='3'>]
tasmota.Rule_Matcher.parse("A#B[]>3")
# [<instance: Rule_Matcher_Key()>, <instance: Rule_Matcher_Key()>, <Matcher [0]>, <Matcher op '>' val='3'>]
#################################################################################
m = tasmota.Rule_Matcher.parse("AA")
assert(m.match({'aa':1}) == 1)
assert(m.match({'AA':'1'}) == '1')
assert(m.match({'ab':1}) == nil)
m = tasmota.Rule_Matcher.parse("AA#BB")
assert(m.match({'aa':1}) == nil)
assert(m.match({'aa':{'bb':1}}) == 1)
m = tasmota.Rule_Matcher.parse("AA#BB#CC=2")
assert(m.match({'aa':1}) == nil)
assert(m.match({'aa':{'bb':1}}) == nil)
assert(m.match({'aa':{'bb':{'cc':1}}}) == nil)
assert(m.match({'aa':{'bb':{'cc':2}}}) == 2)
m = tasmota.Rule_Matcher.parse("AA#?#CC=2")
assert(m.match({'aa':1}) == nil)
assert(m.match({'aa':{'bb':{'cc':2}}}) == 2)
m = tasmota.Rule_Matcher.parse("AA#Power[0]")
assert(m.match({'aa':{'power':[0.5,1.5,2.5]}}) == 0.5)
m = tasmota.Rule_Matcher.parse("AA#Power[1]")
assert(m.match({'aa':{'power':[0.5,1.5,2.5]}}) == 1.5)
m = tasmota.Rule_Matcher.parse("AA#Power[2]")
assert(m.match({'aa':{'power':[0.5,1.5,2.5]}}) == 2.5)
m = tasmota.Rule_Matcher.parse("AA#Power[3]")
assert(m.match({'aa':{'power':[0.5,1.5,2.5]}}) == nil)
m = tasmota.Rule_Matcher.parse("AA#Power[0]>1")
assert(m.match({'aa':{'power':[0.5,1.5,2.5]}}) == nil)
assert(m.match({'aa':{'power':[1.2,1.5,2.5]}}) == 1.2)
-#
class Rule_Matcher
# We don't actually need a superclass, just implementing `match(val)`
#
# static class Rule_Matcher
# def init()
# end
# # return the next index in tha val string
# # or `nil` if no match
# def match(val)
# return nil
# end
# end
# Each matcher is an instance that implements `match(val) -> any or nil`
#
# The method takes a map or value as input, applies the matcher and
# returns a new map or value, or `nil` if the matcher did not match anything.
#
# Example:
# Payload#?#Temperature>50
# is decomposed as:
# <instance match="Payload">, <instance match_any>, <instance match="Temperature", <instance op='>' val='50'>
#
# Instance types:
# Rule_Matcher_Key(key): checks that the input map contains the key (case insensitive) and
# returns the sub-value or `nil` if the key does not exist
#
# Rule_Matcher_Wildcard: maps any key, which yields to unpredictable behavior if the map
# has multiple keys (gets the first key returned by the iterator)
#
# Rule_Matcher_Operator: checks is a simple value (numerical or string) matches the operator and the value
# returns the value unchanged if match, or `nil` if no match
static class Rule_Matcher_Key
var name # literal name of what to match
def init(name)
self.name = name
end
# find a key in map, case insensitive, return actual key or nil if not found
static def find_key_i(m, keyi)
import string
var keyu = string.toupper(keyi)
if isinstance(m, map)
for k:m.keys()
if string.toupper(k)==keyu
return k
end
end
end
end
def match(val)
if val == nil return nil end # safeguard
if !isinstance(val, map) return nil end # literal name can only match a map key
var k = self.find_key_i(val, self.name)
if k == nil return nil end # no key with value self.name
return val[k]
end
def tostring()
return "<Matcher key='" + str(self.name) + "'>"
end
end
static class Rule_Matcher_Array
var index # index in the array, defaults to zero
def init(index)
self.index = index
end
def match(val)
if val == nil return nil end # safeguard
if !isinstance(val, list) return val end # ignore index if not a list
if self.index >= size(val) return nil end # out of bounds
return val[self.index]
end
def tostring()
return "<Matcher [" + str(self.index) + "]>"
end
end
static class Rule_Matcher_Wildcard
def match(val)
if val == nil return nil end # safeguard
if !isinstance(val, map) return nil end # literal name can only match a map key
if size(val) == 0 return nil end
return val.iter()() # get first value from iterator
end
def tostring()
return "<Matcher any>"
end
end
static class Rule_Matcher_Operator
var op_func # function making the comparison
var op_str # name of the operator like '>'
var op_value # value to compare agains
def init(op_func, op_value, op_str)
self.op_func = op_func
self.op_value = op_value
self.op_str = op_str
end
def match(val)
var t = type(val)
if t != 'int' && t != 'real' && t != 'string' return nil end # must be a simple type
return self.op_func(val, self.op_value) ? val : nil
end
def tostring()
return "<Matcher op '" + self.op_str + "' val='" + str(self.op_value) + "'>"
end
end
###########################################################################################
# instance variables
var rule # original pattern of the rules
var trigger # rule pattern of trigger, excluding operator check (ex: "AA#BB>50" would be "AA#BB")
var matchers # array of Rule_Matcher(s)
def init(rule, trigger, matchers)
self.rule = rule
self.trigger = trigger
self.matchers = matchers
end
# parses a rule pattern and creates a list of Rule_Matcher(s)
static def parse(pattern)
import string
if pattern == nil return nil end
var matchers = []
# changes "Dimmer>50" to ['Dimmer', '>', '50']
# Ex: DS18B20#Temperature<20
var op_list = tasmota.find_op(pattern)
# ex: 'DS18B20#Temperature'
var value_str = op_list[0]
var op_str = op_list[1]
var op_value = op_list[2]
var sz = size(value_str)
var idx_start = 0 # index of current cursor
var idx_end = -1 # end of current item
while idx_start < sz
# split by '#'
var idx_sep = string.find(value_str, '#', idx_start)
var item_str
if idx_sep >= 0
if idx_sep == idx_start raise "pattern_error", "empty pattern not allowed" end
item_str = value_str[idx_start .. idx_sep - 1]
idx_start = idx_sep + 1
else
item_str = value_str[idx_start .. ]
idx_start = sz # will end the loop
end
# check if there is an array accessor
var arr_start = string.find(item_str, '[')
var arr_index = nil
if arr_start >= 0 # we have an array index
var arr_end = string.find(item_str, ']', arr_start)
if arr_end < 0 raise "value_error", "missing ']' in rule pattern" end
var arr_str = item_str[arr_start + 1 .. arr_end - 1]
item_str = item_str[0 .. arr_start - 1] # truncate
arr_index = int(arr_str)
end
if item_str == '?'
matchers.push(_class.Rule_Matcher_Wildcard())
else
matchers.push(_class.Rule_Matcher_Key(item_str))
end
if arr_index != nil
matchers.push(_class.Rule_Matcher_Array(arr_index))
end
end
# if an operator was found, add the operator matcher
if op_str != nil && op_value != nil # we have an operator
var op_func = _class.op_parse(op_str)
if op_func
matchers.push(_class.Rule_Matcher_Operator(op_func, op_value, op_str))
end
end
return _class(pattern, value_str, matchers) # `_class` is a reference to the Rule_Matcher class
end
# apply all matchers, abort if any returns `nil`
def match(val_in)
if self.matchers == nil return nil end
var val = val_in
var idx = 0
while idx < size(self.matchers)
val = self.matchers[idx].match(val)
if val == nil return nil end
idx += 1
end
return val
end
def tostring()
return str(self.matchers)
end
###########################################################################################
# Functions to compare two values
###########################################################################################
static def op_parse(op)
def op_eq_str(a,b) return str(a) == str(b) end
def op_neq_str(a,b) return str(a) != str(b) end
def op_eq(a,b) return real(a) == real(b) end
def op_neq(a,b) return real(a) != real(b) end
def op_gt(a,b) return real(a) > real(b) end
def op_gte(a,b) return real(a) >= real(b) end
def op_lt(a,b) return real(a) < real(b) end
def op_lte(a,b) return real(a) <= real(b) end
if op=='==' return op_eq_str
elif op=='!==' return op_neq_str
elif op=='=' return op_eq
elif op=='!=' return op_neq
elif op=='>' return op_gt
elif op=='>=' return op_gte
elif op=='<' return op_lt
elif op=='<=' return op_lte
end
end
end

View File

@ -1,7 +1,8 @@
#- Native code used for testing and code solidification -#
#- Do not use it -#
class Trigger end # for compilation
class Trigger end # for compilation
class Rule_Matche end # for compilation
tasmota = nil
#@ solidify:Tasmota
@ -65,22 +66,18 @@ class Tasmota
# split the item when there is an operator, returns a list of (left,op,right)
# ex: "Dimmer>50" -> ["Dimmer",tasmota_gt,"50"]
#-
assert(tasmota.find_op("Dimmer>50") == ['Dimmer', '>', '50'])
assert(tasmota.find_op("Dimmer") == ['Dimmer', nil, nil])
assert(tasmota.find_op("Status!==Connected") == ['Status', '!==', 'Connected'])
-#
def find_op(item)
import string
var op_chars = '=<>!'
var pos = self._find_op(item, false) # initial run
if pos >= 0
var op_split = string.split(item,pos)
var op_left = op_split[0]
var op_rest = op_split[1]
pos = self._find_op(op_rest, true)
if pos >= 0
var op_split2 = string.split(op_rest,pos)
var op_middle = op_split2[0]
var op_right = op_split2[1]
return [op_left,op_middle,op_right]
end
var idx_composite = self._find_op(item)
if idx_composite >= 0
var idx_start = idx_composite & 0x7FFF
var idx_end = idx_composite >> 16
return [ item[0 .. idx_start-1], item[idx_start .. idx_end], item[idx_end+1 ..]]
end
return [item, nil, nil]
end
@ -92,7 +89,7 @@ class Tasmota
self._rules=[]
end
if type(f) == 'function'
self._rules.push(Trigger(pat, f, id))
self._rules.push(Trigger(self.Rule_Matcher.parse(pat), f, id))
else
raise 'value_error', 'the second argument is not a function'
end
@ -102,7 +99,7 @@ class Tasmota
if self._rules
var i = 0
while i < size(self._rules)
if self._rules[i].trig == pat && self._rules[i].id == id
if self._rules[i].trig.rule == pat && self._rules[i].id == id
self._rules.remove(i) #- don't increment i since we removed the object -#
else
i += 1
@ -112,43 +109,19 @@ class Tasmota
end
# Rules trigger if match. return true if match, false if not
def try_rule(event, rule, f)
import string
var rl_list = self.find_op(rule)
var sub_event = event
var rl = string.split(rl_list[0],'#')
var i = 0
while i < size(rl)
# for it:rl
var it = rl[i]
var found=self.find_key_i(sub_event,it)
if found == nil return false end
sub_event = sub_event[found]
i += 1
end
var op=rl_list[1]
var op2=rl_list[2]
if op
if op=='=='
if str(sub_event) != str(op2) return false end
elif op=='!=='
if str(sub_event) == str(op2) return false end
elif op=='='
if real(sub_event) != real(op2) return false end
elif op=='!='
if real(sub_event) == real(op2) return false end
elif op=='>'
if real(sub_event) <= real(op2) return false end
elif op=='>='
if real(sub_event) < real(op2) return false end
elif op=='<'
if real(sub_event) >= real(op2) return false end
elif op=='<='
if real(sub_event) > real(op2) return false end
#
# event: native Berry map representing the JSON input
# rule: Rule_Matcher instance
# f: callback to call in case of a match
def try_rule(event, rule_matcher, f)
var sub_event = rule_matcher.match(event)
if sub_event != nil
if f != nil
f(sub_event, rule_matcher.trigger, event)
end
return true
end
f(sub_event, rl_list[0], event)
return true
return false
end
# Run rules, i.e. check each individual rule
@ -162,7 +135,7 @@ class Tasmota
self.cmd_res = nil # disable sunsequent recording of results
var ret = false
var ev = json.load(ev_json) # returns nil if invalid JSON
var ev = json.load(ev_json) # returns nil if invalid JSON
if ev == nil
self.log('BRY: ERROR, bad json: '+ev_json, 3)
ev = ev_json # revert to string
@ -567,7 +540,8 @@ class Tasmota
# iterate and call each closure
var i = 0
while i < size(fl)
var sz = size(fl)
while i < sz
# note: this is not guarded in try/except for performance reasons. The inner function must not raise exceptions
fl[i]()
i += 1

View File

@ -1,21 +1,33 @@
#- Native code used for testing and code solidification -#
#- Do not use it -#
#- Do not use it directly -#
#@ solidify:Trigger
class Trigger
var trig, f, id
var o # optional object
# trig: trigger of the event, either timestamp (int) or a rule matcher instance
# f: function or closure to call
# id: (any) identifier to allow removal of a specific trigger
# o: (instance, optional) instance implementing `timer_reached(trig)`
# this is used to implement cron with a specific method for matching time reached
def init(trig, f, id, o)
self.trig = trig
self.f = f
self.id = id
self.o = o
end
def tostring()
import string
return string.format("<instance: %s(%s, %s, %s)", str(classof(self)),
str(self.trig), str(self.f), str(self.id))
end
###########################################################################################
# For cron triggers only
###########################################################################################
# next() returns the next trigger, or 0 if rtc is invalid, or nil if no more
def next()
if self.o
@ -23,6 +35,7 @@ class Trigger
end
end
# is the time of triggering reached?
def time_reached()
if self.o && self.trig > 0
return self.o.time_reached(self.trig)

File diff suppressed because it is too large Load Diff

View File

@ -190,43 +190,45 @@ be_local_closure(Tasmota_remove_rule, /* name */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[ 6]) { /* constants */
( &(const bvalue[ 7]) { /* constants */
/* K0 */ be_nested_str(_rules),
/* K1 */ be_const_int(0),
/* K2 */ be_nested_str(trig),
/* K3 */ be_nested_str(id),
/* K4 */ be_nested_str(remove),
/* K5 */ be_const_int(1),
/* K3 */ be_nested_str(rule),
/* K4 */ be_nested_str(id),
/* K5 */ be_nested_str(remove),
/* K6 */ be_const_int(1),
}),
&be_const_str_remove_rule,
&be_const_str_solidified,
( &(const binstruction[26]) { /* code */
( &(const binstruction[27]) { /* code */
0x880C0100, // 0000 GETMBR R3 R0 K0
0x780E0016, // 0001 JMPF R3 #0019
0x780E0017, // 0001 JMPF R3 #001A
0x580C0001, // 0002 LDCONST R3 K1
0x6010000C, // 0003 GETGBL R4 G12
0x88140100, // 0004 GETMBR R5 R0 K0
0x7C100200, // 0005 CALL R4 1
0x14100604, // 0006 LT R4 R3 R4
0x78120010, // 0007 JMPF R4 #0019
0x78120011, // 0007 JMPF R4 #001A
0x88100100, // 0008 GETMBR R4 R0 K0
0x94100803, // 0009 GETIDX R4 R4 R3
0x88100902, // 000A GETMBR R4 R4 K2
0x1C100801, // 000B EQ R4 R4 R1
0x78120009, // 000C JMPF R4 #0017
0x88100100, // 000D GETMBR R4 R0 K0
0x94100803, // 000E GETIDX R4 R4 R3
0x88100903, // 000F GETMBR R4 R4 K3
0x1C100802, // 0010 EQ R4 R4 R2
0x78120004, // 0011 JMPF R4 #0017
0x88100100, // 0012 GETMBR R4 R0 K0
0x8C100904, // 0013 GETMET R4 R4 K4
0x5C180600, // 0014 MOVE R6 R3
0x7C100400, // 0015 CALL R4 2
0x70020000, // 0016 JMP #0018
0x000C0705, // 0017 ADD R3 R3 K5
0x7001FFE9, // 0018 JMP #0003
0x80000000, // 0019 RET 0
0x88100903, // 000B GETMBR R4 R4 K3
0x1C100801, // 000C EQ R4 R4 R1
0x78120009, // 000D JMPF R4 #0018
0x88100100, // 000E GETMBR R4 R0 K0
0x94100803, // 000F GETIDX R4 R4 R3
0x88100904, // 0010 GETMBR R4 R4 K4
0x1C100802, // 0011 EQ R4 R4 R2
0x78120004, // 0012 JMPF R4 #0018
0x88100100, // 0013 GETMBR R4 R0 K0
0x8C100905, // 0014 GETMET R4 R4 K5
0x5C180600, // 0015 MOVE R6 R3
0x7C100400, // 0016 CALL R4 2
0x70020000, // 0017 JMP #0019
0x000C0706, // 0018 ADD R3 R3 K6
0x7001FFE8, // 0019 JMP #0003
0x80000000, // 001A RET 0
})
)
);
@ -1932,7 +1934,7 @@ be_local_closure(Tasmota_hs2rgb, /* name */
********************************************************************/
be_local_closure(Tasmota_try_rule, /* name */
be_nested_proto(
15, /* nstack */
9, /* nstack */
4, /* argc */
2, /* varg */
0, /* has upvals */
@ -1940,168 +1942,31 @@ be_local_closure(Tasmota_try_rule, /* name */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[16]) { /* constants */
/* K0 */ be_nested_str(string),
/* K1 */ be_nested_str(find_op),
/* K2 */ be_nested_str(split),
/* K3 */ be_const_int(0),
/* K4 */ be_nested_str(_X23),
/* K5 */ be_nested_str(find_key_i),
/* K6 */ be_const_int(1),
/* K7 */ be_const_int(2),
/* K8 */ be_nested_str(_X3D_X3D),
/* K9 */ be_nested_str(_X21_X3D_X3D),
/* K10 */ be_nested_str(_X3D),
/* K11 */ be_nested_str(_X21_X3D),
/* K12 */ be_nested_str(_X3E),
/* K13 */ be_nested_str(_X3E_X3D),
/* K14 */ be_nested_str(_X3C),
/* K15 */ be_nested_str(_X3C_X3D),
( &(const bvalue[ 2]) { /* constants */
/* K0 */ be_nested_str(match),
/* K1 */ be_nested_str(trigger),
}),
&be_const_str_try_rule,
&be_const_str_solidified,
( &(const binstruction[141]) { /* code */
0xA4120000, // 0000 IMPORT R4 K0
0x8C140101, // 0001 GETMET R5 R0 K1
0x5C1C0400, // 0002 MOVE R7 R2
0x7C140400, // 0003 CALL R5 2
0x5C180200, // 0004 MOVE R6 R1
0x8C1C0902, // 0005 GETMET R7 R4 K2
0x94240B03, // 0006 GETIDX R9 R5 K3
0x58280004, // 0007 LDCONST R10 K4
0x7C1C0600, // 0008 CALL R7 3
0x58200003, // 0009 LDCONST R8 K3
0x6024000C, // 000A GETGBL R9 G12
0x5C280E00, // 000B MOVE R10 R7
0x7C240200, // 000C CALL R9 1
0x14241009, // 000D LT R9 R8 R9
0x7826000C, // 000E JMPF R9 #001C
0x94240E08, // 000F GETIDX R9 R7 R8
0x8C280105, // 0010 GETMET R10 R0 K5
0x5C300C00, // 0011 MOVE R12 R6
0x5C341200, // 0012 MOVE R13 R9
0x7C280600, // 0013 CALL R10 3
0x4C2C0000, // 0014 LDNIL R11
0x1C2C140B, // 0015 EQ R11 R10 R11
0x782E0001, // 0016 JMPF R11 #0019
0x502C0000, // 0017 LDBOOL R11 0 0
0x80041600, // 0018 RET 1 R11
0x94180C0A, // 0019 GETIDX R6 R6 R10
0x00201106, // 001A ADD R8 R8 K6
0x7001FFED, // 001B JMP #000A
0x94240B06, // 001C GETIDX R9 R5 K6
0x94280B07, // 001D GETIDX R10 R5 K7
0x78260066, // 001E JMPF R9 #0086
0x1C2C1308, // 001F EQ R11 R9 K8
0x782E000A, // 0020 JMPF R11 #002C
0x602C0008, // 0021 GETGBL R11 G8
0x5C300C00, // 0022 MOVE R12 R6
0x7C2C0200, // 0023 CALL R11 1
0x60300008, // 0024 GETGBL R12 G8
0x5C341400, // 0025 MOVE R13 R10
0x7C300200, // 0026 CALL R12 1
0x202C160C, // 0027 NE R11 R11 R12
0x782E0001, // 0028 JMPF R11 #002B
0x502C0000, // 0029 LDBOOL R11 0 0
0x80041600, // 002A RET 1 R11
0x70020059, // 002B JMP #0086
0x1C2C1309, // 002C EQ R11 R9 K9
0x782E000A, // 002D JMPF R11 #0039
0x602C0008, // 002E GETGBL R11 G8
0x5C300C00, // 002F MOVE R12 R6
0x7C2C0200, // 0030 CALL R11 1
0x60300008, // 0031 GETGBL R12 G8
0x5C341400, // 0032 MOVE R13 R10
0x7C300200, // 0033 CALL R12 1
0x1C2C160C, // 0034 EQ R11 R11 R12
0x782E0001, // 0035 JMPF R11 #0038
0x502C0000, // 0036 LDBOOL R11 0 0
0x80041600, // 0037 RET 1 R11
0x7002004C, // 0038 JMP #0086
0x1C2C130A, // 0039 EQ R11 R9 K10
0x782E000A, // 003A JMPF R11 #0046
0x602C000A, // 003B GETGBL R11 G10
0x5C300C00, // 003C MOVE R12 R6
0x7C2C0200, // 003D CALL R11 1
0x6030000A, // 003E GETGBL R12 G10
0x5C341400, // 003F MOVE R13 R10
0x7C300200, // 0040 CALL R12 1
0x202C160C, // 0041 NE R11 R11 R12
0x782E0001, // 0042 JMPF R11 #0045
0x502C0000, // 0043 LDBOOL R11 0 0
0x80041600, // 0044 RET 1 R11
0x7002003F, // 0045 JMP #0086
0x1C2C130B, // 0046 EQ R11 R9 K11
0x782E000A, // 0047 JMPF R11 #0053
0x602C000A, // 0048 GETGBL R11 G10
0x5C300C00, // 0049 MOVE R12 R6
0x7C2C0200, // 004A CALL R11 1
0x6030000A, // 004B GETGBL R12 G10
0x5C341400, // 004C MOVE R13 R10
0x7C300200, // 004D CALL R12 1
0x1C2C160C, // 004E EQ R11 R11 R12
0x782E0001, // 004F JMPF R11 #0052
0x502C0000, // 0050 LDBOOL R11 0 0
0x80041600, // 0051 RET 1 R11
0x70020032, // 0052 JMP #0086
0x1C2C130C, // 0053 EQ R11 R9 K12
0x782E000A, // 0054 JMPF R11 #0060
0x602C000A, // 0055 GETGBL R11 G10
0x5C300C00, // 0056 MOVE R12 R6
0x7C2C0200, // 0057 CALL R11 1
0x6030000A, // 0058 GETGBL R12 G10
0x5C341400, // 0059 MOVE R13 R10
0x7C300200, // 005A CALL R12 1
0x182C160C, // 005B LE R11 R11 R12
0x782E0001, // 005C JMPF R11 #005F
0x502C0000, // 005D LDBOOL R11 0 0
0x80041600, // 005E RET 1 R11
0x70020025, // 005F JMP #0086
0x1C2C130D, // 0060 EQ R11 R9 K13
0x782E000A, // 0061 JMPF R11 #006D
0x602C000A, // 0062 GETGBL R11 G10
0x5C300C00, // 0063 MOVE R12 R6
0x7C2C0200, // 0064 CALL R11 1
0x6030000A, // 0065 GETGBL R12 G10
0x5C341400, // 0066 MOVE R13 R10
0x7C300200, // 0067 CALL R12 1
0x142C160C, // 0068 LT R11 R11 R12
0x782E0001, // 0069 JMPF R11 #006C
0x502C0000, // 006A LDBOOL R11 0 0
0x80041600, // 006B RET 1 R11
0x70020018, // 006C JMP #0086
0x1C2C130E, // 006D EQ R11 R9 K14
0x782E000A, // 006E JMPF R11 #007A
0x602C000A, // 006F GETGBL R11 G10
0x5C300C00, // 0070 MOVE R12 R6
0x7C2C0200, // 0071 CALL R11 1
0x6030000A, // 0072 GETGBL R12 G10
0x5C341400, // 0073 MOVE R13 R10
0x7C300200, // 0074 CALL R12 1
0x282C160C, // 0075 GE R11 R11 R12
0x782E0001, // 0076 JMPF R11 #0079
0x502C0000, // 0077 LDBOOL R11 0 0
0x80041600, // 0078 RET 1 R11
0x7002000B, // 0079 JMP #0086
0x1C2C130F, // 007A EQ R11 R9 K15
0x782E0009, // 007B JMPF R11 #0086
0x602C000A, // 007C GETGBL R11 G10
0x5C300C00, // 007D MOVE R12 R6
0x7C2C0200, // 007E CALL R11 1
0x6030000A, // 007F GETGBL R12 G10
0x5C341400, // 0080 MOVE R13 R10
0x7C300200, // 0081 CALL R12 1
0x242C160C, // 0082 GT R11 R11 R12
0x782E0001, // 0083 JMPF R11 #0086
0x502C0000, // 0084 LDBOOL R11 0 0
0x80041600, // 0085 RET 1 R11
0x5C2C0600, // 0086 MOVE R11 R3
0x5C300C00, // 0087 MOVE R12 R6
0x94340B03, // 0088 GETIDX R13 R5 K3
0x5C380200, // 0089 MOVE R14 R1
0x7C2C0600, // 008A CALL R11 3
0x502C0200, // 008B LDBOOL R11 1 0
0x80041600, // 008C RET 1 R11
( &(const binstruction[18]) { /* code */
0x8C100500, // 0000 GETMET R4 R2 K0
0x5C180200, // 0001 MOVE R6 R1
0x7C100400, // 0002 CALL R4 2
0x4C140000, // 0003 LDNIL R5
0x20140805, // 0004 NE R5 R4 R5
0x78160009, // 0005 JMPF R5 #0010
0x4C140000, // 0006 LDNIL R5
0x20140605, // 0007 NE R5 R3 R5
0x78160004, // 0008 JMPF R5 #000E
0x5C140600, // 0009 MOVE R5 R3
0x5C180800, // 000A MOVE R6 R4
0x881C0501, // 000B GETMBR R7 R2 K1
0x5C200200, // 000C MOVE R8 R1
0x7C140600, // 000D CALL R5 3
0x50140200, // 000E LDBOOL R5 1 0
0x80040A00, // 000F RET 1 R5
0x50140000, // 0010 LDBOOL R5 0 0
0x80040A00, // 0011 RET 1 R5
})
)
);
@ -2216,7 +2081,7 @@ be_local_closure(Tasmota_add_cmd, /* name */
********************************************************************/
be_local_closure(Tasmota_find_op, /* name */
be_nested_proto(
13, /* nstack */
7, /* nstack */
2, /* argc */
2, /* varg */
0, /* has upvals */
@ -2224,58 +2089,46 @@ be_local_closure(Tasmota_find_op, /* name */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[ 6]) { /* constants */
/* K0 */ be_nested_str(string),
/* K1 */ be_nested_str(_X3D_X3C_X3E_X21),
/* K2 */ be_nested_str(_find_op),
/* K3 */ be_const_int(0),
/* K4 */ be_nested_str(split),
/* K5 */ be_const_int(1),
( &(const bvalue[ 4]) { /* constants */
/* K0 */ be_nested_str(_find_op),
/* K1 */ be_const_int(0),
/* K2 */ be_const_int(1),
/* K3 */ be_const_int(2147483647),
}),
&be_const_str_find_op,
&be_const_str_solidified,
( &(const binstruction[41]) { /* code */
0xA40A0000, // 0000 IMPORT R2 K0
0x580C0001, // 0001 LDCONST R3 K1
0x8C100102, // 0002 GETMET R4 R0 K2
0x5C180200, // 0003 MOVE R6 R1
0x501C0000, // 0004 LDBOOL R7 0 0
0x7C100600, // 0005 CALL R4 3
0x28140903, // 0006 GE R5 R4 K3
0x78160018, // 0007 JMPF R5 #0021
0x8C140504, // 0008 GETMET R5 R2 K4
0x5C1C0200, // 0009 MOVE R7 R1
0x5C200800, // 000A MOVE R8 R4
0x7C140600, // 000B CALL R5 3
0x94180B03, // 000C GETIDX R6 R5 K3
0x941C0B05, // 000D GETIDX R7 R5 K5
0x8C200102, // 000E GETMET R8 R0 K2
0x5C280E00, // 000F MOVE R10 R7
0x502C0200, // 0010 LDBOOL R11 1 0
0x7C200600, // 0011 CALL R8 3
0x5C101000, // 0012 MOVE R4 R8
0x28200903, // 0013 GE R8 R4 K3
0x7822000B, // 0014 JMPF R8 #0021
0x8C200504, // 0015 GETMET R8 R2 K4
0x5C280E00, // 0016 MOVE R10 R7
0x5C2C0800, // 0017 MOVE R11 R4
0x7C200600, // 0018 CALL R8 3
0x94241103, // 0019 GETIDX R9 R8 K3
0x94281105, // 001A GETIDX R10 R8 K5
0x602C0012, // 001B GETGBL R11 G18
0x7C2C0000, // 001C CALL R11 0
0x40301606, // 001D CONNECT R12 R11 R6
0x40301609, // 001E CONNECT R12 R11 R9
0x4030160A, // 001F CONNECT R12 R11 R10
0x80041600, // 0020 RET 1 R11
0x60140012, // 0021 GETGBL R5 G18
0x7C140000, // 0022 CALL R5 0
0x40180A01, // 0023 CONNECT R6 R5 R1
0x4C180000, // 0024 LDNIL R6
0x40180A06, // 0025 CONNECT R6 R5 R6
0x4C180000, // 0026 LDNIL R6
0x40180A06, // 0027 CONNECT R6 R5 R6
0x80040A00, // 0028 RET 1 R5
( &(const binstruction[31]) { /* code */
0x8C080100, // 0000 GETMET R2 R0 K0
0x5C100200, // 0001 MOVE R4 R1
0x7C080400, // 0002 CALL R2 2
0x280C0501, // 0003 GE R3 R2 K1
0x780E0011, // 0004 JMPF R3 #0017
0x540E7FFE, // 0005 LDINT R3 32767
0x2C0C0403, // 0006 AND R3 R2 R3
0x5412000F, // 0007 LDINT R4 16
0x3C100404, // 0008 SHR R4 R2 R4
0x60140012, // 0009 GETGBL R5 G18
0x7C140000, // 000A CALL R5 0
0x04180702, // 000B SUB R6 R3 K2
0x401A0206, // 000C CONNECT R6 K1 R6
0x94180206, // 000D GETIDX R6 R1 R6
0x40180A06, // 000E CONNECT R6 R5 R6
0x40180604, // 000F CONNECT R6 R3 R4
0x94180206, // 0010 GETIDX R6 R1 R6
0x40180A06, // 0011 CONNECT R6 R5 R6
0x00180902, // 0012 ADD R6 R4 K2
0x40180D03, // 0013 CONNECT R6 R6 K3
0x94180206, // 0014 GETIDX R6 R1 R6
0x40180A06, // 0015 CONNECT R6 R5 R6
0x80040A00, // 0016 RET 1 R5
0x600C0012, // 0017 GETGBL R3 G18
0x7C0C0000, // 0018 CALL R3 0
0x40100601, // 0019 CONNECT R4 R3 R1
0x4C100000, // 001A LDNIL R4
0x40100604, // 001B CONNECT R4 R3 R4
0x4C100000, // 001C LDNIL R4
0x40100604, // 001D CONNECT R4 R3 R4
0x80040600, // 001E RET 1 R3
})
)
);
@ -2349,18 +2202,20 @@ be_local_closure(Tasmota_add_rule, /* name */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[ 7]) { /* constants */
( &(const bvalue[ 9]) { /* constants */
/* K0 */ be_nested_str(check_not_method),
/* K1 */ be_nested_str(_rules),
/* K2 */ be_nested_str(function),
/* K3 */ be_nested_str(push),
/* K4 */ be_nested_str(Trigger),
/* K5 */ be_nested_str(value_error),
/* K6 */ be_nested_str(the_X20second_X20argument_X20is_X20not_X20a_X20function),
/* K5 */ be_nested_str(Rule_Matcher),
/* K6 */ be_nested_str(parse),
/* K7 */ be_nested_str(value_error),
/* K8 */ be_nested_str(the_X20second_X20argument_X20is_X20not_X20a_X20function),
}),
&be_const_str_add_rule,
&be_const_str_solidified,
( &(const binstruction[24]) { /* code */
( &(const binstruction[27]) { /* code */
0x8C100100, // 0000 GETMET R4 R0 K0
0x5C180400, // 0001 MOVE R6 R2
0x7C100400, // 0002 CALL R4 2
@ -2373,18 +2228,21 @@ be_local_closure(Tasmota_add_rule, /* name */
0x5C140400, // 0009 MOVE R5 R2
0x7C100200, // 000A CALL R4 1
0x1C100902, // 000B EQ R4 R4 K2
0x78120008, // 000C JMPF R4 #0016
0x7812000B, // 000C JMPF R4 #0019
0x88100101, // 000D GETMBR R4 R0 K1
0x8C100903, // 000E GETMET R4 R4 K3
0xB81A0800, // 000F GETNGBL R6 K4
0x5C1C0200, // 0010 MOVE R7 R1
0x5C200400, // 0011 MOVE R8 R2
0x5C240600, // 0012 MOVE R9 R3
0x7C180600, // 0013 CALL R6 3
0x7C100400, // 0014 CALL R4 2
0x70020000, // 0015 JMP #0017
0xB0060B06, // 0016 RAISE 1 K5 K6
0x80000000, // 0017 RET 0
0x881C0105, // 0010 GETMBR R7 R0 K5
0x8C1C0F06, // 0011 GETMET R7 R7 K6
0x5C240200, // 0012 MOVE R9 R1
0x7C1C0400, // 0013 CALL R7 2
0x5C200400, // 0014 MOVE R8 R2
0x5C240600, // 0015 MOVE R9 R3
0x7C180600, // 0016 CALL R6 3
0x7C100400, // 0017 CALL R4 2
0x70020000, // 0018 JMP #001A
0xB0060F08, // 0019 RAISE 1 K7 K8
0x80000000, // 001A RET 0
})
)
);
@ -2611,12 +2469,12 @@ be_local_closure(Tasmota_fast_loop, /* name */
0x600C000C, // 0005 GETGBL R3 G12
0x5C100200, // 0006 MOVE R4 R1
0x7C0C0200, // 0007 CALL R3 1
0x140C0403, // 0008 LT R3 R2 R3
0x780E0003, // 0009 JMPF R3 #000E
0x940C0202, // 000A GETIDX R3 R1 R2
0x7C0C0000, // 000B CALL R3 0
0x14100403, // 0008 LT R4 R2 R3
0x78120003, // 0009 JMPF R4 #000E
0x94100202, // 000A GETIDX R4 R1 R2
0x7C100000, // 000B CALL R4 0
0x00080502, // 000C ADD R2 R2 K2
0x7001FFF6, // 000D JMP #0005
0x7001FFF9, // 000D JMP #0008
0x80000000, // 000E RET 0
})
)

View File

@ -453,9 +453,7 @@ extern "C" {
}
// Find for an operator in the string
// takes a string, an offset to start the search from, and works in 2 modes.
// mode1 (false): loog for the first char of an operato
// mode2 (true): finds the last char of the operator
// returns -1 if not found, or returns start in low 16 bits, end in high 16 bits
int32_t tasm_find_op(bvm *vm);
int32_t tasm_find_op(bvm *vm) {
int32_t top = be_top(vm); // Get the number of arguments
@ -463,60 +461,64 @@ extern "C" {
int32_t ret = -1;
if (top >= 2 && be_isstring(vm, 2)) {
const char *c = be_tostring(vm, 2);
if (top >= 3) {
second_phase = be_tobool(vm, 3);
}
// new version, two phases in 1, return start in low 16 bits, end in high 16 bits
if (!second_phase) {
int32_t idx = 0;
// search for `=`, `==`, `!=`, `!==`, `<`, `<=`, `>`, `>=`
while (*c && ret < 0) {
switch (c[0]) {
case '=':
case '<':
case '>':
ret = idx;
break; // anything starting with `=`, `<` or `>` is a valid operator
case '!':
if (c[1] == '=') {
ret = idx; // needs to start with `!=`
}
break;
default:
break;
}
c++;
idx++;
int32_t idx_start = -1;
int32_t idx = 0;
// search for `=`, `==`, `!=`, `!==`, `<`, `<=`, `>`, `>=`
while (*c && idx_start < 0) {
switch (c[0]) {
case '=':
case '<':
case '>':
idx_start = idx;
break; // anything starting with `=`, `<` or `>` is a valid operator
case '!':
if (c[1] == '=') {
idx_start = idx; // needs to start with `!=`
}
break;
default:
break;
}
} else {
c++;
idx++;
}
int idx_end = -1;
if (idx_start >= 0) {
idx_end = idx_start;
// second phase
switch (c[0]) {
case '<':
case '>':
case '=':
if (c[1] != '=') { ret = 1; } // `<` or `>` or `=`
else { ret = 2; } // `<=` or `>=` or `==`
if (c[1] != '=') { idx_end += 1; } // `<` or `>` or `=`
else { idx_end += 2; } // `<=` or `>=` or `==`
break;
case '!':
if (c[1] != '=') { ; } // this is invalid if isolated `!`
if (c[2] != '=') { ret = 2; } // `!=`
else { ret = 3; } // `!==`
if (c[2] != '=') { idx_end += 2; } // `!=`
else { idx_end += 3; } // `!==`
break;
default:
break;
}
}
if (idx_start >= 0 && idx_end >= idx_start) {
ret = ((idx_end & 0x7FFF) << 16) | (idx_start & 0x7FFF);
}
}
be_pushint(vm, ret);
be_return(vm);
}
/*
# test patterns
assert(tasmota._find_op("aaa#bbc==23",false) == 7)
assert(tasmota._find_op("==23",true) == 2)
assert(tasmota._find_op(">23",true) == 1)
assert(tasmota._find_op("aaa#bbc!23",false) == -1)
# test patterns for all-in-one version
assert(tasmota._find_op("aaa#bbc==23") == 0x80007)
assert(tasmota._find_op("az==23") == 0x30002)
assert(tasmota._find_op("a>23") == 0x10001)
assert(tasmota._find_op("aaa#bbc!23") == -1)
*/