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 - Shelly Pro 4PM using standard MCP23xxx driver and needs one time Auto-Configuration
### Changed ### Changed
- Refactored Berry rule engine and support for arrays
### Fixed ### Fixed
- TuyaMcu v1 sequence fix (#17625) - TuyaMcu v1 sequence fix (#17625)

View File

@ -60,25 +60,26 @@ extern int l_i2cenabled(bvm *vm);
extern int tasm_find_op(bvm *vm); extern int tasm_find_op(bvm *vm);
#include "solidify/solidified_tasmota_class.h" #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" #include "be_fixed_be_class_tasmota.h"
/* @const_object_info_begin /* @const_object_info_begin
class be_class_tasmota (scope: global, name: Tasmota) { class be_class_tasmota (scope: global, name: Tasmota) {
_fl, var _fl, var // list of active fast-loop object (faster than drivers)
_rules, var _rules, var // list of active rules
_timers, var _timers, var // list of active timers
_crons, var _crons, var // list of active crons
_ccmd, var _ccmd, var // list of active Tasmota commands implemented in Berry
_drivers, var _drivers, var // list of active drivers
wire1, var wire1, var // Tasmota I2C Wire1
wire2, var wire2, var // Tasmota I2C Wire2
cmd_res, var cmd_res, var // store the command result, nil if disables, true if capture enabled, contains return value
global, var global, var // mapping to TasmotaGlobal
settings, var settings, var // mapping to Tasmota Settings
wd, var wd, var // working dir
_debug_present, var _debug_present, var // is `import debug` present?
_global_def, comptr(&be_tasmota_global_struct) _global_def, comptr(&be_tasmota_global_struct)
_settings_def, comptr(&be_tasmota_settings_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) get_light, closure(Tasmota_get_light_closure)
set_light, closure(Tasmota_set_light_closure) set_light, closure(Tasmota_set_light_closure)
Rule_Matcher, class(be_class_Rule_Matcher)
} }
@const_object_info_end */ @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 -# #- Native code used for testing and code solidification -#
#- Do not use it -# #- Do not use it -#
class Trigger end # for compilation class Trigger end # for compilation
class Rule_Matche end # for compilation
tasmota = nil tasmota = nil
#@ solidify:Tasmota #@ solidify:Tasmota
@ -65,22 +66,18 @@ class Tasmota
# split the item when there is an operator, returns a list of (left,op,right) # 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) def find_op(item)
import string var idx_composite = self._find_op(item)
var op_chars = '=<>!' if idx_composite >= 0
var pos = self._find_op(item, false) # initial run var idx_start = idx_composite & 0x7FFF
if pos >= 0 var idx_end = idx_composite >> 16
var op_split = string.split(item,pos)
var op_left = op_split[0] return [ item[0 .. idx_start-1], item[idx_start .. idx_end], item[idx_end+1 ..]]
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
end end
return [item, nil, nil] return [item, nil, nil]
end end
@ -92,7 +89,7 @@ class Tasmota
self._rules=[] self._rules=[]
end end
if type(f) == 'function' if type(f) == 'function'
self._rules.push(Trigger(pat, f, id)) self._rules.push(Trigger(self.Rule_Matcher.parse(pat), f, id))
else else
raise 'value_error', 'the second argument is not a function' raise 'value_error', 'the second argument is not a function'
end end
@ -102,7 +99,7 @@ class Tasmota
if self._rules if self._rules
var i = 0 var i = 0
while i < size(self._rules) 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 -# self._rules.remove(i) #- don't increment i since we removed the object -#
else else
i += 1 i += 1
@ -112,43 +109,19 @@ class Tasmota
end end
# Rules trigger if match. return true if match, false if not # Rules trigger if match. return true if match, false if not
def try_rule(event, rule, f) #
import string # event: native Berry map representing the JSON input
var rl_list = self.find_op(rule) # rule: Rule_Matcher instance
var sub_event = event # f: callback to call in case of a match
var rl = string.split(rl_list[0],'#') def try_rule(event, rule_matcher, f)
var i = 0 var sub_event = rule_matcher.match(event)
while i < size(rl) if sub_event != nil
# for it:rl if f != nil
var it = rl[i] f(sub_event, rule_matcher.trigger, event)
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
end end
return true
end end
f(sub_event, rl_list[0], event) return false
return true
end end
# Run rules, i.e. check each individual rule # Run rules, i.e. check each individual rule
@ -162,7 +135,7 @@ class Tasmota
self.cmd_res = nil # disable sunsequent recording of results self.cmd_res = nil # disable sunsequent recording of results
var ret = false 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 if ev == nil
self.log('BRY: ERROR, bad json: '+ev_json, 3) self.log('BRY: ERROR, bad json: '+ev_json, 3)
ev = ev_json # revert to string ev = ev_json # revert to string
@ -567,7 +540,8 @@ class Tasmota
# iterate and call each closure # iterate and call each closure
var i = 0 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 # note: this is not guarded in try/except for performance reasons. The inner function must not raise exceptions
fl[i]() fl[i]()
i += 1 i += 1

View File

@ -1,21 +1,33 @@
#- Native code used for testing and code solidification -# #- Native code used for testing and code solidification -#
#- Do not use it -# #- Do not use it directly -#
#@ solidify:Trigger #@ solidify:Trigger
class Trigger class Trigger
var trig, f, id var trig, f, id
var o # optional object 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) def init(trig, f, id, o)
self.trig = trig self.trig = trig
self.f = f self.f = f
self.id = id self.id = id
self.o = o self.o = o
end end
def tostring() def tostring()
import string import string
return string.format("<instance: %s(%s, %s, %s)", str(classof(self)), return string.format("<instance: %s(%s, %s, %s)", str(classof(self)),
str(self.trig), str(self.f), str(self.id)) str(self.trig), str(self.f), str(self.id))
end end
###########################################################################################
# For cron triggers only
###########################################################################################
# next() returns the next trigger, or 0 if rtc is invalid, or nil if no more # next() returns the next trigger, or 0 if rtc is invalid, or nil if no more
def next() def next()
if self.o if self.o
@ -23,6 +35,7 @@ class Trigger
end end
end end
# is the time of triggering reached?
def time_reached() def time_reached()
if self.o && self.trig > 0 if self.o && self.trig > 0
return self.o.time_reached(self.trig) 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 */ 0, /* has sup protos */
NULL, /* no sub protos */ NULL, /* no sub protos */
1, /* has constants */ 1, /* has constants */
( &(const bvalue[ 6]) { /* constants */ ( &(const bvalue[ 7]) { /* constants */
/* K0 */ be_nested_str(_rules), /* K0 */ be_nested_str(_rules),
/* K1 */ be_const_int(0), /* K1 */ be_const_int(0),
/* K2 */ be_nested_str(trig), /* K2 */ be_nested_str(trig),
/* K3 */ be_nested_str(id), /* K3 */ be_nested_str(rule),
/* K4 */ be_nested_str(remove), /* K4 */ be_nested_str(id),
/* K5 */ be_const_int(1), /* K5 */ be_nested_str(remove),
/* K6 */ be_const_int(1),
}), }),
&be_const_str_remove_rule, &be_const_str_remove_rule,
&be_const_str_solidified, &be_const_str_solidified,
( &(const binstruction[26]) { /* code */ ( &(const binstruction[27]) { /* code */
0x880C0100, // 0000 GETMBR R3 R0 K0 0x880C0100, // 0000 GETMBR R3 R0 K0
0x780E0016, // 0001 JMPF R3 #0019 0x780E0017, // 0001 JMPF R3 #001A
0x580C0001, // 0002 LDCONST R3 K1 0x580C0001, // 0002 LDCONST R3 K1
0x6010000C, // 0003 GETGBL R4 G12 0x6010000C, // 0003 GETGBL R4 G12
0x88140100, // 0004 GETMBR R5 R0 K0 0x88140100, // 0004 GETMBR R5 R0 K0
0x7C100200, // 0005 CALL R4 1 0x7C100200, // 0005 CALL R4 1
0x14100604, // 0006 LT R4 R3 R4 0x14100604, // 0006 LT R4 R3 R4
0x78120010, // 0007 JMPF R4 #0019 0x78120011, // 0007 JMPF R4 #001A
0x88100100, // 0008 GETMBR R4 R0 K0 0x88100100, // 0008 GETMBR R4 R0 K0
0x94100803, // 0009 GETIDX R4 R4 R3 0x94100803, // 0009 GETIDX R4 R4 R3
0x88100902, // 000A GETMBR R4 R4 K2 0x88100902, // 000A GETMBR R4 R4 K2
0x1C100801, // 000B EQ R4 R4 R1 0x88100903, // 000B GETMBR R4 R4 K3
0x78120009, // 000C JMPF R4 #0017 0x1C100801, // 000C EQ R4 R4 R1
0x88100100, // 000D GETMBR R4 R0 K0 0x78120009, // 000D JMPF R4 #0018
0x94100803, // 000E GETIDX R4 R4 R3 0x88100100, // 000E GETMBR R4 R0 K0
0x88100903, // 000F GETMBR R4 R4 K3 0x94100803, // 000F GETIDX R4 R4 R3
0x1C100802, // 0010 EQ R4 R4 R2 0x88100904, // 0010 GETMBR R4 R4 K4
0x78120004, // 0011 JMPF R4 #0017 0x1C100802, // 0011 EQ R4 R4 R2
0x88100100, // 0012 GETMBR R4 R0 K0 0x78120004, // 0012 JMPF R4 #0018
0x8C100904, // 0013 GETMET R4 R4 K4 0x88100100, // 0013 GETMBR R4 R0 K0
0x5C180600, // 0014 MOVE R6 R3 0x8C100905, // 0014 GETMET R4 R4 K5
0x7C100400, // 0015 CALL R4 2 0x5C180600, // 0015 MOVE R6 R3
0x70020000, // 0016 JMP #0018 0x7C100400, // 0016 CALL R4 2
0x000C0705, // 0017 ADD R3 R3 K5 0x70020000, // 0017 JMP #0019
0x7001FFE9, // 0018 JMP #0003 0x000C0706, // 0018 ADD R3 R3 K6
0x80000000, // 0019 RET 0 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_local_closure(Tasmota_try_rule, /* name */
be_nested_proto( be_nested_proto(
15, /* nstack */ 9, /* nstack */
4, /* argc */ 4, /* argc */
2, /* varg */ 2, /* varg */
0, /* has upvals */ 0, /* has upvals */
@ -1940,168 +1942,31 @@ be_local_closure(Tasmota_try_rule, /* name */
0, /* has sup protos */ 0, /* has sup protos */
NULL, /* no sub protos */ NULL, /* no sub protos */
1, /* has constants */ 1, /* has constants */
( &(const bvalue[16]) { /* constants */ ( &(const bvalue[ 2]) { /* constants */
/* K0 */ be_nested_str(string), /* K0 */ be_nested_str(match),
/* K1 */ be_nested_str(find_op), /* K1 */ be_nested_str(trigger),
/* 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),
}), }),
&be_const_str_try_rule, &be_const_str_try_rule,
&be_const_str_solidified, &be_const_str_solidified,
( &(const binstruction[141]) { /* code */ ( &(const binstruction[18]) { /* code */
0xA4120000, // 0000 IMPORT R4 K0 0x8C100500, // 0000 GETMET R4 R2 K0
0x8C140101, // 0001 GETMET R5 R0 K1 0x5C180200, // 0001 MOVE R6 R1
0x5C1C0400, // 0002 MOVE R7 R2 0x7C100400, // 0002 CALL R4 2
0x7C140400, // 0003 CALL R5 2 0x4C140000, // 0003 LDNIL R5
0x5C180200, // 0004 MOVE R6 R1 0x20140805, // 0004 NE R5 R4 R5
0x8C1C0902, // 0005 GETMET R7 R4 K2 0x78160009, // 0005 JMPF R5 #0010
0x94240B03, // 0006 GETIDX R9 R5 K3 0x4C140000, // 0006 LDNIL R5
0x58280004, // 0007 LDCONST R10 K4 0x20140605, // 0007 NE R5 R3 R5
0x7C1C0600, // 0008 CALL R7 3 0x78160004, // 0008 JMPF R5 #000E
0x58200003, // 0009 LDCONST R8 K3 0x5C140600, // 0009 MOVE R5 R3
0x6024000C, // 000A GETGBL R9 G12 0x5C180800, // 000A MOVE R6 R4
0x5C280E00, // 000B MOVE R10 R7 0x881C0501, // 000B GETMBR R7 R2 K1
0x7C240200, // 000C CALL R9 1 0x5C200200, // 000C MOVE R8 R1
0x14241009, // 000D LT R9 R8 R9 0x7C140600, // 000D CALL R5 3
0x7826000C, // 000E JMPF R9 #001C 0x50140200, // 000E LDBOOL R5 1 0
0x94240E08, // 000F GETIDX R9 R7 R8 0x80040A00, // 000F RET 1 R5
0x8C280105, // 0010 GETMET R10 R0 K5 0x50140000, // 0010 LDBOOL R5 0 0
0x5C300C00, // 0011 MOVE R12 R6 0x80040A00, // 0011 RET 1 R5
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
}) })
) )
); );
@ -2216,7 +2081,7 @@ be_local_closure(Tasmota_add_cmd, /* name */
********************************************************************/ ********************************************************************/
be_local_closure(Tasmota_find_op, /* name */ be_local_closure(Tasmota_find_op, /* name */
be_nested_proto( be_nested_proto(
13, /* nstack */ 7, /* nstack */
2, /* argc */ 2, /* argc */
2, /* varg */ 2, /* varg */
0, /* has upvals */ 0, /* has upvals */
@ -2224,58 +2089,46 @@ be_local_closure(Tasmota_find_op, /* name */
0, /* has sup protos */ 0, /* has sup protos */
NULL, /* no sub protos */ NULL, /* no sub protos */
1, /* has constants */ 1, /* has constants */
( &(const bvalue[ 6]) { /* constants */ ( &(const bvalue[ 4]) { /* constants */
/* K0 */ be_nested_str(string), /* K0 */ be_nested_str(_find_op),
/* K1 */ be_nested_str(_X3D_X3C_X3E_X21), /* K1 */ be_const_int(0),
/* K2 */ be_nested_str(_find_op), /* K2 */ be_const_int(1),
/* K3 */ be_const_int(0), /* K3 */ be_const_int(2147483647),
/* K4 */ be_nested_str(split),
/* K5 */ be_const_int(1),
}), }),
&be_const_str_find_op, &be_const_str_find_op,
&be_const_str_solidified, &be_const_str_solidified,
( &(const binstruction[41]) { /* code */ ( &(const binstruction[31]) { /* code */
0xA40A0000, // 0000 IMPORT R2 K0 0x8C080100, // 0000 GETMET R2 R0 K0
0x580C0001, // 0001 LDCONST R3 K1 0x5C100200, // 0001 MOVE R4 R1
0x8C100102, // 0002 GETMET R4 R0 K2 0x7C080400, // 0002 CALL R2 2
0x5C180200, // 0003 MOVE R6 R1 0x280C0501, // 0003 GE R3 R2 K1
0x501C0000, // 0004 LDBOOL R7 0 0 0x780E0011, // 0004 JMPF R3 #0017
0x7C100600, // 0005 CALL R4 3 0x540E7FFE, // 0005 LDINT R3 32767
0x28140903, // 0006 GE R5 R4 K3 0x2C0C0403, // 0006 AND R3 R2 R3
0x78160018, // 0007 JMPF R5 #0021 0x5412000F, // 0007 LDINT R4 16
0x8C140504, // 0008 GETMET R5 R2 K4 0x3C100404, // 0008 SHR R4 R2 R4
0x5C1C0200, // 0009 MOVE R7 R1 0x60140012, // 0009 GETGBL R5 G18
0x5C200800, // 000A MOVE R8 R4 0x7C140000, // 000A CALL R5 0
0x7C140600, // 000B CALL R5 3 0x04180702, // 000B SUB R6 R3 K2
0x94180B03, // 000C GETIDX R6 R5 K3 0x401A0206, // 000C CONNECT R6 K1 R6
0x941C0B05, // 000D GETIDX R7 R5 K5 0x94180206, // 000D GETIDX R6 R1 R6
0x8C200102, // 000E GETMET R8 R0 K2 0x40180A06, // 000E CONNECT R6 R5 R6
0x5C280E00, // 000F MOVE R10 R7 0x40180604, // 000F CONNECT R6 R3 R4
0x502C0200, // 0010 LDBOOL R11 1 0 0x94180206, // 0010 GETIDX R6 R1 R6
0x7C200600, // 0011 CALL R8 3 0x40180A06, // 0011 CONNECT R6 R5 R6
0x5C101000, // 0012 MOVE R4 R8 0x00180902, // 0012 ADD R6 R4 K2
0x28200903, // 0013 GE R8 R4 K3 0x40180D03, // 0013 CONNECT R6 R6 K3
0x7822000B, // 0014 JMPF R8 #0021 0x94180206, // 0014 GETIDX R6 R1 R6
0x8C200504, // 0015 GETMET R8 R2 K4 0x40180A06, // 0015 CONNECT R6 R5 R6
0x5C280E00, // 0016 MOVE R10 R7 0x80040A00, // 0016 RET 1 R5
0x5C2C0800, // 0017 MOVE R11 R4 0x600C0012, // 0017 GETGBL R3 G18
0x7C200600, // 0018 CALL R8 3 0x7C0C0000, // 0018 CALL R3 0
0x94241103, // 0019 GETIDX R9 R8 K3 0x40100601, // 0019 CONNECT R4 R3 R1
0x94281105, // 001A GETIDX R10 R8 K5 0x4C100000, // 001A LDNIL R4
0x602C0012, // 001B GETGBL R11 G18 0x40100604, // 001B CONNECT R4 R3 R4
0x7C2C0000, // 001C CALL R11 0 0x4C100000, // 001C LDNIL R4
0x40301606, // 001D CONNECT R12 R11 R6 0x40100604, // 001D CONNECT R4 R3 R4
0x40301609, // 001E CONNECT R12 R11 R9 0x80040600, // 001E RET 1 R3
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
}) })
) )
); );
@ -2349,18 +2202,20 @@ be_local_closure(Tasmota_add_rule, /* name */
0, /* has sup protos */ 0, /* has sup protos */
NULL, /* no sub protos */ NULL, /* no sub protos */
1, /* has constants */ 1, /* has constants */
( &(const bvalue[ 7]) { /* constants */ ( &(const bvalue[ 9]) { /* constants */
/* K0 */ be_nested_str(check_not_method), /* K0 */ be_nested_str(check_not_method),
/* K1 */ be_nested_str(_rules), /* K1 */ be_nested_str(_rules),
/* K2 */ be_nested_str(function), /* K2 */ be_nested_str(function),
/* K3 */ be_nested_str(push), /* K3 */ be_nested_str(push),
/* K4 */ be_nested_str(Trigger), /* K4 */ be_nested_str(Trigger),
/* K5 */ be_nested_str(value_error), /* K5 */ be_nested_str(Rule_Matcher),
/* K6 */ be_nested_str(the_X20second_X20argument_X20is_X20not_X20a_X20function), /* 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_add_rule,
&be_const_str_solidified, &be_const_str_solidified,
( &(const binstruction[24]) { /* code */ ( &(const binstruction[27]) { /* code */
0x8C100100, // 0000 GETMET R4 R0 K0 0x8C100100, // 0000 GETMET R4 R0 K0
0x5C180400, // 0001 MOVE R6 R2 0x5C180400, // 0001 MOVE R6 R2
0x7C100400, // 0002 CALL R4 2 0x7C100400, // 0002 CALL R4 2
@ -2373,18 +2228,21 @@ be_local_closure(Tasmota_add_rule, /* name */
0x5C140400, // 0009 MOVE R5 R2 0x5C140400, // 0009 MOVE R5 R2
0x7C100200, // 000A CALL R4 1 0x7C100200, // 000A CALL R4 1
0x1C100902, // 000B EQ R4 R4 K2 0x1C100902, // 000B EQ R4 R4 K2
0x78120008, // 000C JMPF R4 #0016 0x7812000B, // 000C JMPF R4 #0019
0x88100101, // 000D GETMBR R4 R0 K1 0x88100101, // 000D GETMBR R4 R0 K1
0x8C100903, // 000E GETMET R4 R4 K3 0x8C100903, // 000E GETMET R4 R4 K3
0xB81A0800, // 000F GETNGBL R6 K4 0xB81A0800, // 000F GETNGBL R6 K4
0x5C1C0200, // 0010 MOVE R7 R1 0x881C0105, // 0010 GETMBR R7 R0 K5
0x5C200400, // 0011 MOVE R8 R2 0x8C1C0F06, // 0011 GETMET R7 R7 K6
0x5C240600, // 0012 MOVE R9 R3 0x5C240200, // 0012 MOVE R9 R1
0x7C180600, // 0013 CALL R6 3 0x7C1C0400, // 0013 CALL R7 2
0x7C100400, // 0014 CALL R4 2 0x5C200400, // 0014 MOVE R8 R2
0x70020000, // 0015 JMP #0017 0x5C240600, // 0015 MOVE R9 R3
0xB0060B06, // 0016 RAISE 1 K5 K6 0x7C180600, // 0016 CALL R6 3
0x80000000, // 0017 RET 0 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 0x600C000C, // 0005 GETGBL R3 G12
0x5C100200, // 0006 MOVE R4 R1 0x5C100200, // 0006 MOVE R4 R1
0x7C0C0200, // 0007 CALL R3 1 0x7C0C0200, // 0007 CALL R3 1
0x140C0403, // 0008 LT R3 R2 R3 0x14100403, // 0008 LT R4 R2 R3
0x780E0003, // 0009 JMPF R3 #000E 0x78120003, // 0009 JMPF R4 #000E
0x940C0202, // 000A GETIDX R3 R1 R2 0x94100202, // 000A GETIDX R4 R1 R2
0x7C0C0000, // 000B CALL R3 0 0x7C100000, // 000B CALL R4 0
0x00080502, // 000C ADD R2 R2 K2 0x00080502, // 000C ADD R2 R2 K2
0x7001FFF6, // 000D JMP #0005 0x7001FFF9, // 000D JMP #0008
0x80000000, // 000E RET 0 0x80000000, // 000E RET 0
}) })
) )

View File

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