!This is a very experimental extension in “I am not even sure how should I design it” state!
Posh expansion
Motivation
Some time ago, Jakub has asked me to expand posh plans with variables and constants. The reason is rather simple, posh can't pass any info into primitives (actions and sense). That is a limitations we can live without, no parametrization of senses = needless duplication of code, no variables=duplication of constants in the code.
Another limitations, IMO more severe, is whole “evaluate posh plan and call method” concept. Imagine simple strolling bot that should stop when it sees another bot. Under normal circumstances, we would choose destination and used path executor to walk there, while checking every turn, if we see another bot and if we did, we would stop path executor. Rather simple.
Now imagine it in posh. The posh plans are not hindered by what was in the past, it simply chooses one method without regard to the last one executed. In our simple case, we could either implement walking method that would move bot only quite short distance or we could add some code to other methods to stop moving before doing any other job. In first case we face jerkiness, in other bloated spaghetti code. Neither of solutions provides desirable outcomes.
So I thought that we could use objects and provide them with some kind of life cycle and adjust posh engine so it would notify them about changes in their life-cycle. If the object would be called multiple times in the row, it could simply do its job without any interruptions and when planner would decide, that some other job should be done, the object would be notified, that its services are no longer required and it should clean after itself.
Specification
Variables
Goals:
- Backward compatibility
- Primitives can be called with parameters
- AP/C can be parametrized
Variable are annotated with dollar sign '$' at the beginning, followed by alpha character which is followed by sequence of alphanumerical characters with possible underscore '_'.
Change in grammar
Since we are adding some new information into posh, we need to adjust grammar accordingly. I don't intend to write whole new grammar here(although I should), because there may be still some changes.
First, we need to specify how will declaration of parameters look in AP and C. Joanna doesn't like typed languages and things like that. I do, very much. Unless you have declared your variable in C/AP, you will get syntax error if you try to pass it as parameter to another AP/C/primitive. Declaration of two parameters with same name in the same AP/C is also syntax error. Also note that every parameter has compulsory default value that is used, if AP/C is called, but some parameters are missin. In order to change the grammar, we specify two nonterminal symbols, parameters and varDelcaration:
parameters :: "vars" "(" varDeclaration ( "," varDeclaration )* ")" varDeclaration :: variable "=" value variable :: "$" ["a"-"z","A"-"Z"] (["_","a"-"z","A"-"Z","0"-"9"])*
AP has changed from
"AP" name ( "nil" | "(" time | ) "(" action-pattern-elements opt-comment ")"
to
"AP" [ parameters ] name ( "nil" | "(" time | ) "(" action-pattern-elements opt-comment ")"
Competence has changed from
"C" name ( "nil" | "(" time | ) ( "nil" | "(" goal | ) "(" "elements" competence-priorities ")" opt-comment ")"
to
"C" name [ parameters ] ( "nil" | "(" time | ) ( "nil" | "(" goal | ) "(" "elements" competence-priorities ")" opt-comment ")"
Now that we know how to declare what parameters can be used in the AP/C, you most likely want to use the parameters as variables. You can either use them in senses in trigger and goal or evaluating the AP/C/primitives in drive and competence elements or action patterns.
Just to remind you, grammar of senses list if following:
goal :: "goal" senses ")" trigger :: "trigger" senses ")" senses :: ( "nil" | "(" sensesList ")" ) sensesList :: ( sensesListElement )+ sensesListElement :: name | fullSense fullSense :: "(" name [ value [ predicate ] ] ")"
Introduction of parameters has changed only predicate fullSense:
fullSense :: "(" senseCall [ value [ predicate ] ] ")" senseCall :: name [ "(" callParametersList ] callParametersList:: callParameter() ( "," callParameter() )* ")" callParameter :: variable | value
You may have noticed, that the name nonterminal in sensesListElement hasn't changed. The reason is unambiguity of grammar. Consider following list of senses:
(string1(string2))
it can either mean
(sense1 (constantParameterString) )
or
(sense1 (sense2) )
The “(sense2)” is a valid full-sense. There is no way for parser to distinguish between them so I forbid it. Parameters are allowed only in full-senses.
Good, now we know how to apply declared parameters to senses, but how to call AP/C/Primitive? Reminder: parser doesn't distinguish between AP or C or primitive, as far as it is concerned. There is no difference between calling either of them.
The change is, that where was only name of an action before, there is now nonterminal senseCall
competenceElement :: "(" name [ "(" trigger ] senseCall [ numint ] opt-comment ")" driveElement :: "(" name [ "nil" | "(" trigger ] senseCall [ "nil" | "(" freq ] opt-comment ")"
Well, that was all quite academic, but a little difficult to digest, after all, not many people know exact grammar of Java, yet many program in it. They do it by learning from examples, such as this one(just syntax, it wouldn't work):
( (C testC vars($var1=12.5, $var2='seznam, $var3=12) (elements ((test (trigger((sense($var1, 5)) sense2)) doSomething))) ) (AP testAP vars($who=everyone, $why='who_cares, $when=anytime, $method=brutal) (action1($who, 'killAllHumans, 12.6, $when) action2) ) (RDC PoshBot (goal ((fail))) (drives ((stay testC(99.9,hate, not_necessarily_number, useless_argument))) ((stay testAP(me, skip_rest))) ) ) )
XXX: There is still open question about returning values from AP/C/primitives.
Engine
This idea looks appealing and simple, in reality, it is not so rosy. How exactly should it work and how well would it work in real life? I don't know…yet, but I have some ideas.
Proposition
So how does single evaluation could look like?
DC.fire():
if (goal fulfilled) return Done, plan is done, bot has done its job for every DE according to priority: // is trigger of DE ok and is interval between this DE calls less than DE freq. if (DE.isReady()): DE.fire() return ElementFired; return Fail, no elements fired
DE.fire():
// call element furthest in the callstack element2fire = DE.callStack.last result = DE.callStack.fire(element2fire)
callstack.fire(element):
result = element.fire() switch (result): case OK: // element was fired, but there is no change to callstack. // typical case is successful execution of non-last primitive in AP. do nothing case Dive: // the element has fired AP or C and it hasn't returned immediately // = it wasn't primitive. The element represented in new stack level // will be next one in execution call. // careful, don't use same object to represent the element, there are multiple // callstacks in various state of execution. Joanna calls it something like // local copy of control state. callstack.add(result.getElement()) case Surface: // Yay, element hasn't failed and has done its work and its goal is finished // Cases : AP has been finished without fail, C goal is fulfilled. callstack.remove() case Fail: // element failed miserably. // This causes stack to call the method specified in the DE during initialization reset the callstack.
primitive.fire():
return callJavaPrimitive()
Current state
This small list should track state of progress, you can also try commits in SposhCore.
- Parser adjusted for variables…[DONE]
- Elements are filled with info…[DONE]
- Engine designed…[IN QUEUE]
- Engine implemented…[IN QUEUE]