I'm facing a problem while using Drools.
I try to update an attribute from a nested member. The update seems to work, but the when clause do not consider it.
I have 2 Obj object, sharing the same Cpt object.
Cpt cpt = new Cpt();
Obj obj1 = new Obj("obj1");
obj1.setComposant("R2");
obj1.counter = cpt;
Obj obj2 = new Obj("obj2");
obj2.setComposant("R2");
obj2.counter = cpt;
kSession.insert(obj2);
kSession.insert(obj1);
My rule is define as:
rule "R2"
when
m : Obj(composant == "R2" && counter.value == 0)
then
System.out.println(m.getName() + " " + m.getCounter().getValue());
m.getCounter().increment();
end
I was expecting Obj1 to match the when clause, then update the value of the counter (from 0 to 1). So the Obj2 should not match the where clause.
But in fact, it does, even if the display is as I expected :
obj1 0
obj2 1
Can someone explain me what am I doing wrong ?
All reactions of the Drools Rule Engine with respect to changes in the set of facts require to be notified by using one of the extensions for the Right Hand Side language. You need to call update(f) for the modified fact object f, or you may use the modify(f){...} statement.
However... Changing a contained object X via the reference from fact A and telling the Engine that fact A has been modified will not make it see that fact B, also referencing X, has been changed as well.
This is where you should reconsider your design. Is it really necessary to have an X shared via references from A and B? Or: what about making X a fact and updating it? The latter may mean that you have to rewrite your rules, making the relation between Obj and Cpt visible on the left hand side. But, in my experience, it is usually better to have this than some complex mechanism propagating update notifications from some joint contained object to its parents.
Edit What I mean by "making the relation visible" is shown by the rule below:
rule "R2"
when
Obj(composant == "R2", $counter: counter )
$c: Cpt( this == $counter, value == 0)
then
modify( $c ){ increment() }
end
Related
I have a rule like this
top_clause returns [TopClause tc] :
tat = top_or_any_top
SP
e = expr
EP
{
var ln = getLineNum($tat);
$tc = new TopClauseExtant(ln, [...other params...]);
};
Note I'm trying to get the line number and pass it to the AST object I'm creating. getLineNum() is
public static LineNum getLineNum(ParserRuleContext pr) {
return new LineNum(pr.Start.Line);
}
getLineNum() may later get more from the token and return more, so I'm while I am getting an integer now (the pr.Start.Line) that may change and the return type LineNum (which currently just wraps an the int that is the line number) may hold more detail. Hence the getLineNum() func + LineNum class abstraction.
Unfortunately antlr complains: missing attribute access on rule reference tat in $tat. It basically wants $tat.<something> but I specifically want to pass the whole rule $tat so I can pick it apart more later.
Does that make sense? Any ideas on how to get what I want?
Answered by kaby76; use .ctx, as in $e.ctx
I recently came accross `Attribute Values.VB_UserMemId = 0'. I like lists so I thought I'd build a bespoke collection type object.
The minimal code for the class that can reproduce the error is:
Class Lst
Option Explicit
Public c As New Collection
'this is the default property
Public Property Get item(Optional index)
'Attribute Values.VB_UserMemId = 0
If IsMissing(index) Then
Set item = Me
'DoEvents
Else
item = c(index)
End If
End Property
Public Property Let item(Optional index, itm)
If IsMissing(index) Then 'assume itm is list
If IsObject(itm) Then Set c = itm.c Else c.add itm
Else
c.add itm, , index
c.Remove index + 1
End If
End Property
Essentially, lst(i) returns the ith element of the private collection, Lst(i)=6 sets the ith element. (errorhandling and index checking code stripped for clarity).
I noticed that objects that return themselves from the default property can be returned from a function in a variant (e.g LstFunc=L below), without the need for a set removing complexity from my students eyes...(you cant do that with a collection object)
Unfortunately, I encountered two challenges...the minimum code for these is:
The Problem
Function LstFunc() As Variant
Dim L As New Lst
L = 4 'replaces L.item=3
LstFunc = L 'this is not normally allowed, but desirable (for me!)
End Function
Sub try()
Dim L As New Lst
L = LstFunc 'replaces L.item=LstFunc-->L.c: [4]
L = 3 'L.c: [4,3]
If L = 6 Then DoEvents
End Sub
Here is what happens
1) when the expression L = 6 is evaluated excel hangs. Some times ESC gets you it back in, but my experience is that excel stops responding and needs a restart.
To evaluate the expression the L.item function is called initially, returning a Lst, for which item is called, etc.etc. resulting in unwanted, and undetected infinite repetition (not quite recursion). Uncommenting the DoEvents statement in the get item property allows you to stop without a crash
2) after uncommenting the DoEvents, I run in debugger mode step by step. If i now hover (by accident..) over the variable L, the debugger crashes, and I get the green triangle of death, which I fear will be very confusing for the students:
Note this behaviour is recoverable if the DoEvents statement in the class is commented out again. A veritable catch 22...
Bit of an intricate one this, but any sugesstions as to how I can trap the unwanted repetition in (1) at low computational cost and without losing the ability to pass the object like a variant would be greatfully received.
PS this is a code snipped that provides an unsafe workaround discussed in a comment below:
Public Property Get item(Optional index)
'Attribute Values.VB_UserMemId = 0
static i
If IsMissing(index) Then
Set item = Me
i=i+1:if i>1000 then item="":exit property
'DoEvents
Else
item = c(index)
i=0
End If
End Property
The recursion can't be avoided.
From section 5.6.2.2 of the VBA language specification:
If the expression’s value type is a specific class:
If the source object has a public default Property Get or a public default function, and this default member’s parameter list is
compatible with an argument list containing 0 parameters, the simple
data value’s value is the result of evaluating this default member as
a simple data value.
Note that with your sample class, this line of code meets all of those conditions:
If L = 6 Then DoEvents
The type of the expression L = 6 is Boolean, with an Lst on the left hand side and an Integer on the right hand side. That means the type of the comparison is Integer, so the run-time checks to see if there is a default Property Get, which you provide here:
Public Property Get item(Optional index)
'Attribute Values.VB_UserMemId = 0
The parameter list is compatible with an argument list containing 0 parameters, because the index is optional. So, it evaluates to L.item() = 6. The only test you do inside the property is If IsMissing(index), which is guaranteed to be true if it's called as the default member - remember, it can't require a parameter to be passed. As you found out, this leads you to...
5.6.2.3 Default Member Recursion Limits
Evaluation of an object whose default Property Get or default function
returns another object can lead to a recursive evaluation process if
the returned object has a further default member. Recursion through
this chain of default members may be implicit if evaluating to a
simple data value and each default member has an empty parameter list,
or explicit if index expressions are specified that specifically
parameterize each default member.
How this is handled is implementation specific. Office VBA implementations, however, do not cap the recursion depth and will simply crash the host when it runs out of stack space.
That said, the rest of your question is simply an x-y problem, although my suggestion is to scrap this. Using default members hides the intent of your code and robust, maintainable code should be readable.
In the following I will describe a simplification of my Core Data schema that I am pretty sure to be equivalent to my real situation.
I have two entity First and Second linked by a many-to-many relationship r.
Second has got two Boolean attribute, let us call it take and you_should_not_take.
I want to make a query that select a row of First iff it exits an associated row of Second for with take = true.
I have tried this predicate:
NSPredicate(format: "ANY (%K == YES && %K == NO)", "r.take","r.you_should_not_take")
but Core Data gives me the following error: "[General] Unable to parse the format string".
Maybe I have to use this:
NSPredicate(format: "((ANY %K == YES) && (ANY %K == NO))", "r.take","r.you_should_not_take")
but I am afraid that this last query will select a row of First iff it exists a row x of Second for which x.take == YES && it exists a row y if Second for which y.you_should_not_take == NO, with no guarantee that x == y.
An SQL query would be very simple to be made, but I am not so experienced with Core Data, so while I will try some more queries (and I will test if the second query does what I think it does) I have also asked here hoping the answer would be as simple as in SQL.
To select all First objects which are related to (at least one) Second object for which take==true and you_should_not_take==false you have
to use a SUBQUERY. Something like (untested):
SUBQUERY(r, $x, $x.take == YES AND $x.you_should_not_take == NO).#count > 0
This is for a simple script I'm writing for an oldschool MUD game for those who know what that is.
Basically I am searching through one table (which I'm reading from gmcp) and trying to search the value's on that table to see if any of them match any of the value's on another table which I'm storing the value's I'm looking for.
I've successfully managed to do it with singular value's by simply using a "for" loop to grab the value from gmcp and store it as a variable and then using another "for" loop to search the other table to see if any of the values there match the variable.
Trouble is, it only works for a singular value and misses all the others if there is more than one value I need to check in that table.
The code I have is as follows,
for _, v in pairs(gmcptable) do
checkvalue = v
end
for _, v in pairs(mytable) do
if v == checkvalue then
echo("yay")
else
echo("no!")
end
end
again this works fine for gmcp tables with one value, but fails if more. I tried doing this to,
for _, v in pairs(gmcptable) do
checkvalue = v
for _, v in pairs(mytable) do
if v == checkvalue then
echo("yay")
else
echo("no!")
end
end
end
my hope was that it might set the variable, run the second for loop to check the variable and then repeat for the next value on the gmcp table since it's a for loop and the second loop was within the loop, but that didn't work either. I also tried making my own function to add to the mix and simplify it,
function searchtable(table, element)
for _, v in pairs(table) do
if v == element then
return true
else
return false
end
end
end
for _, v in pairs(gmcptable) do
if searchtable(mytable, v) == true then
echo("yay")
else
echo("no!")
end
end
that was a bust also... I'm sure I'm just overlooking something or showing what an amateur I am, but I've googled loads and tried everything I can think of, but I'm just self taught and only recently started understanding how tables and for loops even work. Hopefully someone out there can get back to me with something that works soonish!
UPDATE!
#Piglet Okay so, gmcptable was actually me trying to simplify the question for those who could answer the coding question. gmcptable actually is a long list of tables received by my client via the connection from the server the game this is for. so in all actuality, I have 3 tables I'm parsing data from. "gmcp.Char.Items.List.items", "gmcp.Char.Items.Add" and "gmcp.Char.Items.Remove". Now gmcp.Char.Items.List.items is the list of everything in the room I'm in within the game. gmcp.Char.Items.Add is the list of anything that enter the room and is sent each time anything enters the room aside from other players and gmcp.Char.Items.Remove is the same, but for when anything leaves the room. I'm trying to use this information to create a targeting table that will automatically add desired targets to my targeting que and remove them if they are not in the room. the room list (gmcp.Char.Items.List) is updated only when I enter or exit the room and possibly when I look, but for now I'm assuming it doesn't update when I look because that will be a whole other problem to solve later.
I currently have a simple script in what my client ID's as a trigger, this is set to fire once when I log into the game in question and the script define the tables that hold the value's I'm cross referencing the gmcp tables with to figure out if it's information I want added to my target table, this script also defines the target table as empty, which is meant to ensure that for the duration of the session, both tables exist and are defined.
I then added three separate scripts that parse the three gmcp tables and figure out whether that are on my desired targets table and if so adds it or in the of the case of the remove table checks if its currently on the targets table and if so removes it. below I'll show the current scripts I'm using (which have changed several times over since yesterday and might change again before I get a look at any future replies to this. I will also include a what the gmcp tables in question look like and if I'm currently seeing any error or debug details from my client I'll include that as well.
log on trigger
match on > ^Password correct\. in perl regex
bashtargets = {}
bashlist = {
"a baby rat",
"a young rat",
"a rat",
"an old rat",
"a black rat"
}
(the above trigger appears to be working properly and I can print the tables accurately)
script in the room
event handlers > gmcp.Char.Items.List
for _, v in pairs(gmcp.Char.Items.List.items) do
bashname = v.name
bashid = v.id
for _, v in pairs(bashlist) do
if v == bashname then
table.insert(bashtargets, bashid)
end
end
end
script addcheck
event handlers "gmcp.Char.Items.Add"
for _, v in pairs(gmcp.Char.Items.Add) do
addname = v.name
addid = v.id
for _, v in pairs(bashlist) do
if v == addname then
table.insert(bashtargets, addid)
end
end
end
script removecheck
event handlers "gmcp.Char.Items.Remove"
for _, v in pairs(gmcp.Char.Items.Remove) do
delid = v.id
for _, v in pairs(bashtargets) do
if v == delid then
table.remove(bashtargets, delid)
end
end
end
gmcp table "gmcp.Char.Items"
{
Remove = {
location = "room",
item = {
id = "150558",
name = "a filthy gutter mutt",
attrib = "m"
}
},
Add = {
location = "room",
item = {
id = "150558",
name = "a filthy gutter mutt",
attrib = "m"
}
},
List = {
location = "room",
items = {
{
id = "59689",
name = "a statue of a decaying gargoyle",
icon = "profile"
},
{
id = "84988",
name = "a gas lamp"
},
{
id = "101594",
attrib = "t",
name = "a monolith sigil",
icon = "rune"
},
{
id = "196286",
name = "a wooden sign"
},
{
id = "166410",
name = "Lain, the Lamplighter",
attrib = "m"
}
}
}
}
I have parsed the information successfully several times, so I've got the right tables and syntax and what have you where gmcp is concerned.
using this I have also managed to get it to half work. currently the set up seems to capture single targets at a time even if there are dozens and add that one, sometimes it oddly enough adds the same target 3 - 5 times for some reason, not sure why, haven't been able to figure it out yet.
these two error messages have been output by my client repeatedly, no idea what to do about them though or how to fix them... "left the room" and "entered the room" are the names currently assigned to the scripts for adding and removing data from the tables in my client.
[ERROR:] object:<event handler function> function:<left the room>
<Lua error:[string "return left the room"]:1: '<eof>' expected near 'the'>
[ERROR:] object:<event handler function> function:<entered the room>
<Lua error:[string "return entered the room"]:1: '<eof>' expected near 'the'>
I have no idea what '' means though, or why it's expected near 'the' it's all got my head pounding though...
I can see through the debug feature on my client that all the handlers are being sent by the server so it's not the gmcp... I'm not actually seeing any bugs on the debug feature (which btw is separate from the error feature that keeps putting out those other two errors I mentions.
anyways that's my update... Hopefully that give some people a better handle on what I'm doing wrong so I can get this figured out and learn something new.
Thanks again in advance and extra thanks to you #Piglet for you answer I definitely learned something new from it and thought it was very helpful.
In your first attempt you have 2 separate loops. You overwrite checkvalue for every element in gmcptable. Once you enter your second loop checkvalue will have the value last asigned in your first loop. So you only have 1 checkvalue and you only run across your table once as you only run your second loop once.
for _, v in pairs(gmcptable) do
checkvalue = v
end
for _, v in pairs(mytable) do
if v == checkvalue then
echo("yay")
else
echo("no!")
end
end
Your second attempt should work if I understood your problem.
You iterate over every element of gmcptable and compare it to every element in mytable. So whenever gmcptable contains a value that is also contained in mytable you should get a "yay".
for _, v in pairs(gmcptable) do
checkvalue = v
for _, v in pairs(mytable) do
if v == checkvalue then
echo("yay")
else
echo("no!")
end
end
end
One remark on your third attempt with a function. You should not call arguments table as you will then have no access to the global table functions inside your function. A call to table.sort for example would result in an error as you will index your local parameter table instead.
Okay so I tinkered with it a bit more tonight and kept getting the same error, eventually I googled the error and came to understand that the error was an end of file error, the client I use uses the name of a script as the name for a function if you don't declare it in the actual script, or at least that is my understanding of it, and so I didn't bother defining the function, and if I added an additional end to the script the client threw up another error because it wasn't defined, so I guess the client adds it's own definition to the function but doesn't add it's own end to the function which created the mix up. So long story short, the solution was to define each of the scripts as a function and add another end to close the function and that fixed the problem.
Thanks again to #Piglet for his answer, it was very helpful and dead accurate too! Thanks mate!
I'm working on a project in Lua where I will be creating tables and storing them in a master table, to be erased at a later time. I will pass around references to these tables to other sibling tables.
master = {}
table.insert(master, {name = 'hello'})
table.insert(master, {name = 'world', pre = master[1]})
The problem that occurs is that when I wish to erase the reference from the master table, the reference still remains in master[2] here. Obviously my first solution was to make the tables have weak values. (through .__mode on a metatable, not shown here)
This worked, and would work, so long as I would never store a singly-referenced table within these tables.
table.insert(master, {name = 'goodbye', pre = master[2], some_table = {123}})
The third element, some_table would eventually be collected, because the tables have weak values, and this table (some_table) is not referenced anywhere else. This is undesired behavior. My latest solution involves creating "weak reference objects" to the tables within the master table. A naive implementation follows:
function WeakRef(t)
r = {__mode = 'v', __index = t, __newindex = t}
setmetatable(r, r)
return r
end
These weak reference objects act similarly to boost::weak_ptrs and accomplish my goal, but I am uncertain if they are the best solution to my problem.
Is there a better way; a more elegant solution?
Is my design, which requires this master table, perhaps flawed?
Given that:
You want master to be the "one place" where you define whether an object exists or not
Your objects can have links between them
Then probably the simplest architecture is reserving one of the members of each object as a "middle man" in charge of managing the references to others. Here're the steps:
Make master a regular table (not weak)
On each physical object, create a weak table called links (or whatever name suits your logic better)
Make all links tables weak. Use them to store references to other objects.
And this is a possible implementation. I've tried it in Lua 5.1:
local function newWeakTable()
return setmetatable({}, {__mode = "v"})
end
local master = {}
-- create two physical objects
local obj1 = { name = "obj1", links = newWeakTable() }
local obj2 = { name = "obj2", links = newWeakTable() }
-- link them
obj2.links.pre = obj1
-- insert them into master
table.insert(master, obj1)
table.insert(master, obj2)
-- master has 2 objects, and they are linked
assert(#master == 2)
assert(obj2.links.pre == obj1)
-- remove obj1 from master, and remove the variable reference
table.remove(master, 1)
obj1 = nil
-- run gc manually
collectgarbage("collect")
-- master has only 1 object now, and the link has dissapeared
assert(#master == 1)
assert(obj2.links.pre == nil)
print("Everything went as expected")