El método ~
en el analizador combina dos analizadores en uno que aplica los dos analizadores originales sucesivamente y devuelve los dos resultados. Eso podría ser simplemente (en Parser[T]
)
def ~[U](q: =>Parser[U]): Parser[(T,U)].
Si nunca combinado de más de dos analizadores, que estaría muy bien. Sin embargo, si encadena tres de ellos, p1
, p2
, p3
, con tipos de retorno T1
, T2
, T3
, a continuación, p1 ~ p2 ~ p3
, lo que significa p1.~(p2).~(p3)
es de tipo Parser[((T1, T2), T3)]
. Y si combina cinco de ellos como en su ejemplo, eso sería Parser[((((T1, T2), T3), T4), T5)]
. Luego, cuando el patrón coincida con el resultado, también tendrá todas esas parantheses:
case ((((_, id), _), formals), _) => ...
Esto es bastante incómodo.
Luego viene un ingenioso truco sintáctico. Cuando una clase de caso tiene dos parámetros, puede aparecer en infijo en lugar de posición de prefijo en un patrón. Es decir, si tiene case class X(a: A, b: B)
, puede emparejar el patrón con case X(a, b)
, pero también con case a X b
. (Eso es lo que se hace con un patrón x::xs
para que coincida con una Lista no vacía, ::
es una clase de caso). Cuando escribe el estuche a ~ b ~ c
, significa case ~(~(a,b), c)
, pero es mucho más agradable y más agradable que case ((a,b), c)
también, lo que es difícil de hacer bien.
Así el método ~
en Analizador devuelve una Parser[~[T,U]]
en lugar de un Parser[(T,U)]
, por lo que puede coincidir con el patrón fácilmente en el resultado de múltiples ~. Además de eso, ~[T,U]
y (T,U)
son prácticamente lo mismo, lo más isomorfo que se puede obtener.
El mismo nombre se elige para el método de combinación en el analizador y para el tipo de resultado, porque el código resultante es natural de leer. Uno ve inmediatamente cómo cada parte en el procesamiento del resultado se relaciona con los elementos de la regla de la gramática.
parser1 ~ parser2 ~ parser3 ^^ {case part1 ~ part2 ~ part3 => ...}
Tilda se elige porque su prioridad (que se une fuertemente) juega muy bien con los demás operadores del analizador.
Un último punto, hay operadores auxiliares ~>
y <~
que descartar el resultado de uno de los operandos, típicamente las partes constantes en la regla de que no transporta datos útiles. Así que uno preferiría escribir
"class" ~> ID <~ ")" ~ formals <~ ")"
y obtener solo los valores de ID y formales en el resultado.
Muchas gracias por su explicación. No estaba familiarizado con la función de "conversión implícita" de scala. Un buen artículo sobre ese tema se puede encontrar aquí: http://scalada.blogspot.com/2008/03/implicit-conversions-magical-and.html – Jano