fcard / ExpressionPatterns.jl

Match, Destructure and Dispatch on expressions.
Other
8 stars 5 forks source link

Have variables consistent by default outside slurps #5

Open fcard opened 9 years ago

fcard commented 9 years ago

The main reason I made consistency optional is because of slurps, because when one writes (*{x},), they probably mean a tuple of any sequence of elements, not a sequence of the same element. However, when one writes (x,x), they likely mean a tuple of two of the same element, so maybe it's best to have consistency be the default outside slurps, and nonconsistency on the inside.

fcard commented 9 years ago

Maybe a better solution would be to have variables inside slurps match the entire sequence of values rather than each individually, e.g. Given the expression (1,1,1), x in (*{x},) matches [1,1,1], rather than 1 three times. That would make it consistent with destructuring. Then I could change the meaning of :C{x} to "sequence of the same element", or maybe just remove it, I don't know whether that's useful enough to have special syntax.

fcard commented 9 years ago

Mostly done, but :(*{x},*{x}), which would match :(1,2,1,2), :(a,b,c,a,b,c), etc, doesn't work yet.

fcard commented 7 years ago

Time to try fixing this. For reference, the current algorithm first scans the whole sequence to determine ranges for the slurps.

sequence: :[(1,2),(3,4),(5,6),7,8,9,10]
pattern: [*{(x,y)}, a, *{z}, 10]

range for pattern 1: 1:3
range for pattern 2: 4:5

Only then does it start putting values into the variables, in a manner that simplified would look like this:

vars[:x] = []
vars[:y] = []
for i in 1:3
  push!(vars[:x], sequence.args[i].args[1])
  push!(vars[:y], sequence.args[i].args[2])
end

vars[:z] = []
for i in 5:6
  push!(vars[:z], sequence.args[i])
end

Since extracting the values is almost always the costliest part, it's very handy to only have to do once it as all the backtracking because of failed matches occurred in the "finding the ranges" part. However, once we have the same variable in different slurps, the actual values of the variables would start interfering with the ranges. It's possible to solve this more or less:

pattern: [*{x},*{x}]
sequence: :[1,2,1,2]

attempted range for 1: 1:4
attempted range for 2: []
#--ranges are not the same size, backtrack...
attempted range for 1: 1:3
attempted range for 2: 4:4
#--ranges are not the same size, backtrack...
attempted range for 1: 1:2
attempted range for 2: 3:4
#--ranges are the same size, proceed

Then we extract the values of the first one, and on the subsequent ones we just compare to see if they are equal and fail the match otherwise.

function extract_simple(vars, range, name, seq)
  if haskey(vars, name)
    action = (v,i) -> (vars[name][i-start(range)+1] == v)
  else
    vars[name] = []
    action = (v,i) -> (push!(vars[name], v); true)
  end

  for i in range
    action(seq[i], i) || return false
  end
  return true
end

extract_simple(vars, 1:2, :x, :[1,2,1,2].args) || return false
extract_simple(vars, 3:4, :x, :[1,2,1,2].args) || return false
return true

I am thinking of cases where this might fail... (Also I just found a bug in the *normal* algorithm so I need to fix that first)

The whole thing is due to a rewrite anyway, looking back 2 years after it's much more messy and harder to follow than any of the other code, which I still can read pretty easily even after all this time (go past me! Other then this slurp thing!)