@@ -31,12 +31,48 @@ local faces = {}
3131-- STATIC FUNCTIONS --
3232---- ------------------
3333
34+ -- inspired by Penlight string_lambda:
35+ -- http://stevedonovan.github.io/Penlight/api/libraries/pl.utils.html#string_lambda
36+ local lambda
37+ do
38+ local memory = {}
39+ lambda = setmetatable ({ clear = function () memory = {} end , },
40+ {
41+ __call = function (self ,fstr )
42+ local fun = memory [fstr ]
43+ if not fun then
44+ local args ,code = fstr :match (" ^%s*|(.+)|(.+)$" )
45+ assert (args and code , " Needs args and code sections, e.g., |args|code" )
46+ local fun_src = (" return function(%s) return %s end" ):format (args ,code )
47+ fun = assert (loadstring (fun_src ))()
48+ memory [fstr ] = fun
49+ end
50+ return fun
51+ end ,
52+ })
53+ end
54+
55+ -- transform a user function string into a lambda function
56+ local function lambda_transform (func_str )
57+ return function (vars )
58+ local values ,keys = {},{}
59+ for k ,v in pairs (vars ) do
60+ values [# values + 1 ] = v
61+ keys [# keys + 1 ] = k
62+ end
63+ local code = (' |%s|%s' ):format (table.concat (keys ," ," ),func_str )
64+ local f = lambda (code )
65+ return f (table.unpack (values ))
66+ end
67+ end
68+
3469-- converts to in-mutable the given table argument
3570local function inmutable (tbl )
3671 return setmetatable ({}, {
3772 __index = function (_ ,k ) return tbl [k ] end ,
3873 __newindex = function () error (" Unable to modify an in-mutable table" ) end ,
3974 __len = function () return # tbl end ,
75+ __pairs = function () return pairs (tbl ) end ,
4076 })
4177end
4278
131167 end
132168
133169 assign_variables = function (self , vars , patterns , sequence , var_matches ,
134- user_clauses )
170+ user_clauses , fact_vars )
135171 for i ,pat in ipairs (patterns ) do
136172 local fid = sequence [i ]
137173 local fact = self .fact_list [fid ]
138174 if not assign_fact_vars (vars , pat , fact , var_matches ) then return false end
139175 end
176+ for vname ,i in pairs (fact_vars ) do vars [vname ] = sequence [i ] end
140177 local inmutable_vars = inmutable (vars )
141178 for _ ,func in ipairs (user_clauses ) do
142- if not func (sequence , inmutable_vars ) then return false end
179+ if not func (inmutable_vars ) then return false end
143180 end
144181 return true
145182 end
@@ -186,7 +223,8 @@ local function regenerate_agenda(self)
186223 if not rule_entailements [sequence ] then
187224 local seq_vars = {}
188225 if assign_variables (self , seq_vars , rule .patterns , sequence ,
189- rule .var_matches , rule .user_clauses ) then
226+ rule .var_matches , rule .user_clauses ,
227+ rule .fact_vars ) then
190228 table.insert (combinations , sequence )
191229 table.insert (variables , seq_vars )
192230 end
@@ -231,7 +269,7 @@ local function fire_rule(self, rule_name, args, vars)
231269 for i ,v in ipairs (args ) do self .fact_entailment [v ] = args end
232270 -- execute rule actions
233271 for _ ,action in ipairs (rule .actions ) do
234- action (args , inmutable (vars ))
272+ action (inmutable (vars ))
235273 end
236274end
237275
@@ -464,14 +502,27 @@ end
464502-- declares a new rule in the knowledge base
465503function faces_methods :defrule (rule_name )
466504 local rule = { patterns = {}, user_clauses = {},
467- actions = {}, salience = 0 , var_matches = {} }
505+ actions = {}, salience = 0 , var_matches = {}, fact_vars = {} }
468506 self .kb_table [rule_name ] = rule
469- local rule_builder = {
507+ local rule_builder
508+ rule_builder = {
470509 pattern = function (rule_builder , pattern )
471510 table.insert (rule .patterns , tuple (pattern ))
472511 return rule_builder
473512 end ,
513+ var = function (rule_builder , varname )
514+ varname = assert (varname :match (" %?([^%s]+)" ),
515+ string.format (" Incorrect variable name: %s" , varname ))
516+ rule .fact_vars [varname ] = # rule .patterns + 1
517+ return {
518+ pattern = function (_ ,...)
519+ return rule_builder .pattern (rule_builder ,... )
520+ end
521+ }
522+ end ,
474523 u = function (rule_builder , func )
524+ -- user_func receives one argument: vars
525+ if type (func ) == " string" then func = lambda_transform (func ) end
475526 table.insert (rule .user_clauses , func )
476527 return rule_builder
477528 end ,
@@ -502,7 +553,8 @@ function faces_methods:defrule(rule_name)
502553 __index = function (rule_builder , key )
503554 if key == " u" then
504555 return function (rule_builder , user_func )
505- -- user_func receives two arguments (fact_ids, vars)
556+ -- user_func receives one argument: vars
557+ if type (func ) == " string" then func = lambda_transform (func ) end
506558 table.insert (rule .actions , user_func )
507559 return rule_builder
508560 end
@@ -514,7 +566,7 @@ function faces_methods:defrule(rule_name)
514566 local args = table.pack (... )
515567 for i = 1 ,args .n do args [i ] = tuple (args [i ]) end
516568 table.insert (rule .actions ,
517- function (fact_ids , vars )
569+ function (vars )
518570 local new_args = replace_variables (args , vars )
519571 return self [key ](self , table.unpack (new_args ))
520572 end )
0 commit comments