Unexpected token: "DELIMITER_START" was, well, unexpected

I was doing some warmup exercises and ran into some unexpected behaviour.

The original code and its inlined version

def bday_pdox(n : Int32) : Float64
    diff = (365 - n + 1..365).product
    all = 365**n
    1 - diff/all
end

def bday_pdox_inlined(n : Int32) : Float64
    1 - (365 - n + 1..365).product/365**n
end


caused an OverflowError once n got too large. Annoying, but not unexpected. So I switched to floating point numbers:

def bday_pdox_inlined(n : Int32) : Float64
    1 - (365 - n + 1..365).product(&.to_f64)/365.0**n
end

Now crystal tool format complained about a syntax error at the /:

syntax error in ‘./birthday_paradox.cr:35:45’: unexpected token: “DELIMITER_START”

I had to add another – unexpected – pair of parentheses to get the code to compile:

def bday_pdox_inlined(n : Int32) : Float64
    1 - ((365 - n + 1..365).product(&.to_f64))/365.0**n
end

The same behaviour happens when I use a block {|n| n.to_f64} instead of the short version but in that case, the parentheses are not unexpected as blocks as final arguments inside complex expressions don’t mesh well.

I assume that the short version gets macro expanded to the full block which then causes the problem. Explainable, but unexpected.

Is that something that can be fixed in crystal or do I need to train my crystal eye to watch out for &.?

Regards,
s.

Looks like a parser bug, similar to "a[0]/2" causes parser error · Issue #4608 · crystal-lang/crystal · GitHub

Unfortunately, the syntax rules for differentiating OP_SLASH and regex delimiter are quite complex.

I found a fix: Fix parse `OP_SLASH` after block by straight-shoota · Pull Request #17159 · crystal-lang/crystal · GitHub

Oh, so it’s not “final blocks inside expressions” in general but specifically the /?

Indeed, the following compiles without the extra parentheses:

def bday_pdox_inlined(n : Int32) : Float64
  1 - (365 - n + 1..365).product(&.to_f64)*1/365.0**n
end

Thanks for your time and the fix.

s.