ztellman / automat

better automata through combinators
588 stars 50 forks source link

`greedy-find` not setting `:checkpoint` #55

Closed osfameron closed 7 years ago

osfameron commented 7 years ago

I'm not sure if this is a bug, or just a confusion on my part about greedy-find vs find: The documentation is a little unclear, but it seems to suggest that the :checkpoint value would be set by greedy-find if a greedy match is found which skipped a non-greedy match.

For example with this automaton:

(def foo (a/compile 
          (a/or [:d :d (a/$ :first)]
                [:d :d :g (a/$ :second)])
          {:reducers {:first (fn [s t] :first)
                      :second (fn [s t] :second)}}))

I run e.g.:

boot.user=> (require '[automat.core :as a])
nil
boot.user=> (def foo (a/compile
       #_=>           (a/or [:d :d (a/$ :first)]
       #_=>                 [:d :d :g (a/$ :second)])
       #_=>           {:reducers {:first (fn [s t] :first)
       #_=>                       :second (fn [s t] :second)}}))
#'boot.user/foo
boot.user=> (a/find foo nil [:d :d])
#automat.compiler.core.CompiledAutomatonState{
    :accepted? true, :checkpoint nil, 
    :state-index 2, :start-index 0, 
    :stream-index 2, :value :first}
boot.user=> (a/greedy-find foo nil [:d :d])
#automat.compiler.core.CompiledAutomatonState{
    :accepted? true, :checkpoint nil, 
    :state-index 2, :start-index 0, 
    :stream-index 2, :value :first}
boot.user=> (a/find foo nil [:d :d :g])
#automat.compiler.core.CompiledAutomatonState{
    :accepted? true, :checkpoint nil, 
    :state-index 2, :start-index 0,
    :stream-index 2, :value :first}
boot.user=> (a/greedy-find foo nil [:d :d :g])
#automat.compiler.core.CompiledAutomatonState{
    :accepted? true, :checkpoint -->nil<---, 
    :state-index 3, :start-index 0,
    :stream-index 3, :value :second}

The first 3 calls are as expected. I'm not finding the full sequence [:d :d :g] because either I didn't pass in enough tokens, or because I did a non-greedy find.

In the 4th example, I find the full pattern, and get the expected :value. But should the :checkpoint not have been set to the previous value? (I'm not sure what to expect here - perhaps the :first symbol, or the state-index 2, but certainly something to indicate that the most recent non-greedy find.)

ztellman commented 7 years ago

The checkpoint is mostly for internal use, but maybe this example will be a bit more illuminating:

user> (def foo (a/compile 
          (a/or [:d :d]
                [:d :d :g :h])))
#'user/foo
user>  (a/greedy-find foo nil [:d :d :g])
#automat.compiler.core.CompiledAutomatonState{:accepted? false, :checkpoint #automat.compiler.core.CompiledAutomatonState{:accepted? true, :checkpoint nil, :state-index 2, :start-index 0, :stream-index 2, :value nil}, :state-index 3, :start-index 0, :stream-index 3, :value nil}
user>  (a/greedy-find foo *1 [:z])
#automat.compiler.core.CompiledAutomatonState{:accepted? true, :checkpoint nil, :state-index 2, :start-index 0, :stream-index 2, :value nil}

All of your examples were accepted states, so they don't need to have a reference back to the previous accepted state. In this example, we're halfway between one accepted state and maybe a greedier one. Once we get something other than :h in the last input, it simply returns the checkpointed state.

The documentation could certainly be clearly here, but does that make sense?

osfameron commented 7 years ago

Aha, yes, thanks for the reply! I think that's clear, so basically my misunderstandings were:

In any case, as it turns out those semantics aren't useful to my use-case*, I suspect I need to roll something up with direct use of (a/advance) instead, so I'll play with that :-)

(* for example, I was musing on a key-binding parser. If the user types "dd", should we process the :first action, or wait a little longer to see if they go on to type a "g"?)