web-dev-qa-db-fra.com

Syntaxe des références arrières dans les chaînes de remplacement (pourquoi le signe dollar?)

En Java, et il semble dans quelques autres langages, les références arrières dans le modèle sont précédées d'une barre oblique inverse (par exemple \1, \2, \3, etc.), mais dans une chaîne de remplacement, ils sont précédés d'un signe dollar (par exemple $1, $2, $3, et aussi $0).

Voici un extrait pour illustrer:

System.out.println(
    "left-right".replaceAll("(.*)-(.*)", "\\2-\\1") // WRONG!!!
); // prints "2-1"

System.out.println(
    "left-right".replaceAll("(.*)-(.*)", "$2-$1")   // CORRECT!
); // prints "right-left"

System.out.println(
    "You want million dollar?!?".replaceAll("(\\w*) dollar", "US\\$ $1")
); // prints "You want US$ million?!?"

System.out.println(
    "You want million dollar?!?".replaceAll("(\\w*) dollar", "US$ \\1")
); // throws IllegalArgumentException: Illegal group reference

Des questions:

  • L'utilisation de $ pour les références arrières dans les chaînes de remplacement propres à Java? Sinon, quelle langue a commencé? Quelles saveurs l'utilisent et qu'est-ce qui ne fonctionne pas?
  • Pourquoi est-ce une bonne idée? Pourquoi ne pas s'en tenir à la même syntaxe de modèle? Cela ne conduirait-il pas à une langue plus cohérente et plus facile à apprendre?
    • La syntaxe ne serait-elle pas plus rationalisée si les instructions 1 et 4 ci-dessus étaient les "correctes" au lieu de 2 et 3?
47

L'utilisation de $ for backreferences dans les chaînes de remplacement est-elle propre à Java?

Non. Perl l'utilise et Perl est certainement antérieur à la classe Pattern de Java. La prise en charge des expressions régulières Java est explicitement décrite en termes d'expressions régulières Perl.

Par exemple: http://perldoc.Perl.org/perlrequick.html#Search-and-replace

Pourquoi est-ce une bonne idée?

Et bien évidemment vous ne pensez pas que ce soit une bonne idée! Mais l'une des raisons pour lesquelles c'est une bonne idée est de rendre le support Java recherche/remplacement (plus) compatible avec Perl).

Il existe une autre raison pour laquelle $ aurait pu être considéré comme un meilleur choix que \. C'est que \ doit être écrit comme \\ dans un Java chaîne littérale.

Mais tout cela n'est que pure spéculation. Aucun de nous n'était dans la pièce lorsque les décisions de conception ont été prises. Et finalement, peu importe pourquoi ils ont conçu la syntaxe de remplacement de cette façon. Les décisions ont été prises et concrétisées, et toute discussion ultérieure est purement académique ... à moins que vous ne soyez en train de concevoir un nouveau langage ou une nouvelle bibliothèque regex pour Java.

33
Stephen C

Après avoir fait quelques recherches, j'ai compris les problèmes maintenant: Perl avait pour utiliser un symbole différent pour les références arrières de modèle et les références arrières de remplacement, et tandis que Java.util.regex.* n'a pas à suivre, il choisit de le faire, non pour une raison technique mais plutôt traditionnelle.


Côté Perl

(Veuillez garder à l'esprit que tout ce que je sais à propos de Perl à ce stade vient de la lecture d'articles sur Wikipédia, alors n'hésitez pas à corriger les erreurs que j'ai pu commettre)

La raison pour laquelle devait être fait de cette façon en Perl est la suivante:

  • Perl utilise $ comme un sceau (c'est-à-dire un symbole attaché au nom de la variable).
  • Les littéraux de chaîne Perl sont à interpolation variable.
  • Perl regex capture en fait les groupes sous forme de variables $1, $2, etc.

Ainsi, en raison de la façon dont Perl est interprété et du fonctionnement de son moteur d'expression rationnelle, une barre oblique précédente pour les références arrières (par exemple \1) dans le modèle doit être utilisé, car si le sceau $ est utilisé à la place (par exemple $1), cela provoquerait une interpolation de variable involontaire dans le motif.

La chaîne de remplacement, en raison de son fonctionnement en Perl, est évaluée dans le contexte de chaque correspondance. Il est tout à fait naturel pour Perl d'utiliser ici l'interpolation de variables, donc le moteur d'expression régulière capture des groupes dans des variables $1, $2, etc., pour que cela fonctionne de manière transparente avec le reste de la langue.

Références


Du côté Java côté

Java est un langage très différent de Perl, mais le plus important ici est qu'il n'y a pas d'interpolation variable. De plus, replaceAll est un appel de méthode, et comme pour tous les appels de méthode en Java, les arguments sont évalués une fois, avant la méthode invoquée.

Ainsi, la fonction d'interpolation variable en elle-même n'est pas suffisante, car en substance, la chaîne de remplacement doit être réévaluée à chaque correspondance, et ce n'est tout simplement pas la sémantique des appels de méthode en Java. Une chaîne de remplacement à interpolation variable évaluée avant le replaceAll est même invoqué est pratiquement inutile; l'interpolation doit se produire pendant la méthode, à chaque match.

Puisque ce n'est pas la sémantique de Java, replaceAll doit faire cette interpolation "juste à temps" manuellement. En tant que tel, il n'y a absolument aucune raison technique pour laquelle $ est le symbole d'échappement pour les références arrières dans les chaînes de remplacement. Cela aurait très bien pu être le \. Inversement, des références inverses dans le modèle auraient également pu être évitées avec $ au lieu de \, et ça aurait toujours fonctionné aussi bien techniquement.

La raison Java ne regex la façon dont il le fait est purement traditionnelle: elle suit simplement le précédent établi par Perl.

18