lucidiot's cybrecluster

La version méconnue du texte bien connu, seconde partie

Lucidiot Informatique 2022-07-17
Une expression régulière qui pourrait gagner un concours d'obfuscation.


Nous voici dans ce que j'espère être le tout dernier article traitant de la validation du Well-Known Text à l'aide d'une expression régulière. Dans cet article, nous allons ajouter des courbes plus complexes, un cube, et beaucoup d'ensembles, tout ça pour faire exploser le nombre de caractères de l'expression régulière et rendre notre script m4 inutilisable.

Placement affine

Le placement affine n'est pas une géométrie à proprement parler, mais c'est un outil dont nous allons avoir besoin pour les prochaines géométries. Il permet de définir un nouveau repère dans l'espace, en utilisant un point et des vecteurs :

AFFINEPLACEMENT [Z] EMPTY
AFFINEPLACEMENT [Z] (
  LOCATION [Z] (1 2 3),
  REFERENCEDIRECTIONS (
    VECTOR [Z] ...
  )
)
AFFINEPLACEMENT [Z] (
  LOCATION [Z] EMPTY,
  REFERENCEDIRECTIONS EMPTY
)

Il n'y a là non plus aucune notion de dimension M, donc on va utiliser la même technique que pour le vecteur pour le définir. Presque tout dans un placement affine peut être vide : le placement affine lui-même, sa position, et chacun de ses vecteurs. Ça n'a pas de sens mais je n'ai pas l'impression que la logique définie dans le standard ne gère véritablement ces cas. Elle gère par contre les dimensions, et impose que le placement affine, sa LOCATION et tous ses vecteurs aient la même dimension, donc on va respecter cette règle.

define(emptyable_group, «(?:\s+»ignorecase(EMPTY)«|\(\s*$1\s*\))»)dnl
define(define_geometry, «define($1_$3, «ignorecase($2)\s*ignorecase($3)\s*emptyable_group($4)»)»)dnl
define(make_geometry, «define_geometry($1, $2, $3, $4)$1_$3»)dnl
define_geometry(affineplacement, AFFINEPLACEMENT, «», «make_geometry(location, LOCATION, «», coords_)\s*«,»\s*ignorecase(REFERENCEDIRECTIONS)\s*emptyable_group(vector_«(?:\s*,\s*»vector_)*)»)dnl
define_geometry(affineplacement, AFFINEPLACEMENT, Z, «make_geometry(location, LOCATION, Z, coords_Z)\s*«,»\s*ignorecase(REFERENCEDIRECTIONS)\s*emptyable_group(vector_Z«(?:\s*,\s*»vector_Z)*)»)dnl
define(affineplacement_M, affineplacement_)dnl
define(affineplacement_ZM, affineplacement_Z)dnl

Je redéfinis make_geometry car j'introduis deux nouvelles macros pour me simplifier la vie : emptyable_group, qui est un raccourci pour permettre soit EMPTY, soit un contenu entre parenthèses, et define_geometry qui définit une macro de géométrie sans pour autant l'utiliser immédiatement. J'utilise ainsi define_geometry pour définir mon placement affine sans pour autant l'inclure immédiatement dans la sortie du script. J'utilise aussi make_geometry pour la LOCATION, car elle a exactement le même format qu'un point classique. On notera aussi la présence de beaucoup de guillemets, car toutes ces macros imbriquées peuvent mal interpréter les virgules ou les parenthèses après quelques itérations.

Maintenant qu'on a défini le placement affine, on va pouvoir l'utiliser dans plusieurs courbes.

Courbe elliptique

Pour définir une courbe elliptique, ou autrement dit un arc d'ellipse, on commence par définir un repère à l'aide d'un placement affine, appelé l'emplacement de référence. On définit ensuite les paramètres de l'ellipse tracée sur ce repère, notamment ses deux rayons, puis on définit l'angle de début et de fin de l'arc d'ellipse. Enfin, quand la dimension M est définie, on indique la valeur de M au début et à la fin de l'arc d'ellipse :

ELLIPTICALCURVE [Z][M] EMPTY
ELLIPTICALCURVE [Z][M] (
  REFERENCELOCATION AFFINEPLACEMENT [Z] ...,
  UAXISLENGTH EMPTY,
  VAXISLENGTH 42,
  STARTANGLE DEGREES (50),
  ENDANGLE RADIANS (3.1415926539)
  [, STARTM 42, ENDM 42]
)

Pour je ne sais quelle raison, les deux paramètres UAXISLENGTH et VAXISLENGTH qui définissent la longueur de l'ellipse peuvent être vides. Je ne sais pas comment on peut avoir une courbe valide avec ça, mais apparemment on peut. De même, le placement affine peut être vide. Par contre, ni les angles ni les mesures ne peuvent être vides.

On note d'abord qu'on utilise le contenu de l'angle qu'on a vu précédemment, mais pas le préfixe ANGLE. Nous allons donc redéfinir l'angle pour avoir accès à ce texte seulement :

define(angle_text, ignorecase((?:DEGREE|G?RADIAN)S)\s*\(\s*number\s*\))dnl
...
^\s*(?:ignorecase(ANGLE)\s+angle_text|...)\s*$

J'ai du coup supprimé la macro angle qui n'est plus utile, puisqu'on ne fait jamais autrement référence à l'angle. On peut maintenant définir la courbe elliptique :

make_geometry(elliptical, ELLIPTICALCURVE, $1, «ignorecase(REFERENCELOCATION)\s+affineplacement_$1\s*««,»»\s*ignorecase(UAXISLENGTH)\s+emptyable(number)\s*««,»»\s*ignorecase(VAXISLENGTH)\s+emptyable(number)\s*««,»»\s*ignorecase(STARTANGLE)\s+angle_text\s*««,»»\s*ignorecase(ENDANGLE)\s+angle_text»ifelse(index($1, M), -1, «», ««\s*««,»»\s*ignorecase(STARTM)\s+number\s*««,»»\s*ignorecase(ENDM)\s+number»»))

Il y a beaucoup à dire cette fois ! D'abord, il y a énormément de guillemets, encore plus que pour le placement affine. Cela est lié aux diverses couches d'abstraction qu'on a ajoutés avec define_geometry et emptyable_group ; les virgules doivent être beaucoup échappées pour éviter qu'elles ne soit interprétées comme indiquant un nouvel argument. Il m'a fallu un grand nombre d'essais pour en arriver à cette combinaison de guillemets.

Ensuite, j'utilise une macro qu'on a encore jamais vu : ifelse. C'est la macro conditionnelle de m4, qui compare deux chaînes de caractères et renvoie une chaîne de caractères si elles sont égales, sinon une autre chaîne de caractères. Par exemple, ifelse(2, 2, oui, non) renverra oui, mais ifelse(4, 2, oui, non) renverra non.

On compare les deux premiers arguments, et les deux suivants servent à indiquer les valeurs de retour. Ici, on essaie de voir si index($1, M) vaut -1. $1 est notre seul argument à make_geometries, qui contient la dimension actuelle sans compter X ni Y ; si on a M à l'intérieur alors on sait qu'il y a la dimension M. index est une macro qui recherche dans une chaîne de caractères une autre chaîne de caractères, et renvoie le numéro du premier caractère ; par exemple index(ABC, AB) renvoie 0, index(ABC, C) renvoie 2, et index(ABC, CD) renvoie -1 parce qu'il n'y a aucun résultat. Notre condition est donc équivalente à « s'il n'y a pas la dimension M, alors... ».

Quand il n'y a pas la dimension M, ifelse renverra une chaîne vide («»). Mais s'il y a la dimension M, alors on ajoute en plus les STARTM et ENDM. Pour le vecteur et le placement affine, il était plus simple de juste se répéter, mais la courbe elliptique est suffisamment complexe pour qu'une condition comme ça soit plus facile à maîtriser.

On notera que ma comparaison est un petit peu étrange ; ce serait plus logique de dire « s'il y a la dimension M, ajoute les attributs ». Cependant, tester s'il y a bel et bien la dimension M, c'est tester que l'index soit non-négatif. Dans m4, tout est une chaîne de caractères, il n'y a pas de notion de nombres, donc je devrais utiliser quelque chose comme ifelse(index(index($1, M), -), -1, ...) pour regarder si la chaîne de caractères renvoyée par index contient un tiret, signe d'un nombre négatif. S'il n'y a pas de tiret, alors il y a la dimension M. C'est encore plus compliqué à comprendre !

Il existe en fait des fonctions arithmétiques dans m4, qui vont interpéter du texte comme un nombre, mais la complexité supplémentaire qu'elles ajoutent ici ne serait pas très utile non plus. On va se contenter de cette négation.

Clothoïde

La clothoïde est un sous-genre de spirale ayant au moins quatre autres noms différents et autant de définitions mathématiques. Je l'ai déjà expliquée en détail, comme toutes les autres courbes, dans la partie 2 de mon étude du WKB ISO. Concrètement, elle demande un facteur d'échelle et une distance de début et de fin. Les distances permettent de définir quelle partie de la spirale de Fresnel, la définition que l'ISO a choisi, on veut utiliser pour cette courbe, et le facteur d'échelle semble permettre de redimensionner la courbe pour qu'elle aie la taille que l'on souhaite.

CLOTHOID [ZM] EMPTY
CLOTHOID [ZM] (
  REFERENCELOCATION AFFINEPLACEMENT [Z] ...,
  SCALEFACTOR EMPTY,
  STARTDISTANCE EMPTY,
  ENDDISTANCE 42
  [, STARTM 42, ENDM 42]
)

Encore une fois, tous les paramètres sauf les mesures peuvent être vides, ce qui n'a toujours pas de sens. On peut reprendre notre définition de la courbe elliptique et renommer tous les paramètres :

make_geometry(clothoid, CLOTHOID, $1, «ignorecase(REFERENCELOCATION)\s+affineplacement_$1\s*««,»»\s*ignorecase(SCALEFACTOR)\s+emptyable(number)\s*««,»»\s*ignorecase(STARTDISTANCE)\s+emptyable(number)\s*««,»»\s*ignorecase(ENDDISTANCE)\s+emptyable(number)»ifelse(index($1, M), -1, «», ««\s*««,»»\s*ignorecase(STARTM)\s+number\s*««,»»\s*ignorecase(ENDM)\s+number»»))

Spirale

La spirale est la version plus générale de la clothoïde, et il est possible de générer une clothoïde avec une spirale, ce qui rend la clothoïde assez inutile dans cette spécification.

SPIRALCURVE [ZM] EMPTY
SPIRALCURVE [ZM] (
  REFERENCELOCATION AFFINEPLACEMENT [Z] ...,
  SPIRALLENGTH EMPTY,
  STARTCURVATURE 420,
  ENDCURVATURE 1337,
  SPIRALTYPE abcdefghijklm nopqrstuvwxyz ABCDEFGHIJKLMN OPQRSTUVWX YZ0123456789(_.'-)
  [, STARTM 42, ENDM 42]
)

Le type de spirale est restreint dans la spécification à 5 types, mais la grammaire EBNF du WKT utilise juste <letters>, ce qui est défini comme étant tous les caractères qu'on a vu autorisés dans le tag d'élément TIN. Cela inclut les espaces, alors qu'il n'y a pas d'apostrophes doubles pour encadrer la chaîne de caractères, pour une confusion maximale. Et encore une fois, tous les autres paramètres peuvent être vides.

make_geometry(spiral, SPIRALCURVE, $1, «ignorecase(REFERENCELOCATION)\s+affineplacement_$1\s*««,»»\s*ignorecase(SPIRALLENGTH)\s+emptyable(number)\s*««,»»\s*ignorecase(STARTCURVATURE)\s+emptyable(number)\s*««,»»\s*ignorecase(ENDCURVATURE)\s+emptyable(number)\s*««,»»\s*ignorecase(SPIRALTYPE)\s+[a-zA-Z0-9()_.' -]+»ifelse(index($1, M), -1, «», ««\s*««,»»\s*ignorecase(STARTM)\s+number\s*««,»»\s*ignorecase(ENDM)\s+number»»))

Courbe NURBS

Encore une courbe compliquée à représenter, mais c'est la dernière. Elle a des propriétés imbriquées à plusieurs niveaux et des supports différents pour les dimensions, un régal. Voilà un résumé de toute sa grammaire :

NURBSCURVE [ZM] EMPTY
NURBSCURVE [ZM] (
  DEGREE [+-]7,
  CONTROLPOINTS [Z] EMPTY,
  KNOTS EMPTY
)
NURBSCURVE [ZM] (
  DEGREE [+-]2,
  CONTROLPOINTS [Z] (
    NURBSPOINT EMPTY,
    NURBSPOINT (
      WEIGHTEDPOINT [Z] EMPTY,
      WEIGHT 0
    ),
    NURBSPOINT (
      WEIGHTEDPOINT [Z] (10, 20),
      WEIGHT 132E5
    )
  ),
  KNOTS (
    KNOT EMPTY,
    KNOT (VALUE 42.123, MULTIPLICITY [+-]4)
  )
  [, STARTM 42, ENDM 999]
)

Encore une fois, beaucoup de choses peuvent être vides, toujours de sorte à ne pas avoir de sens. Toutes ces valeurs vides ne sont pourtant pas autorisées dans le Well-Known Binary, ce qui doit donner des résultats très intéressants en termes d'interopérabilité. Puisque le CONTROLPOINTS et les WEIGHTEDPOINT n'acceptent pas la dimension M, on va devoir les définir séparément comme pour le vecteur et le placement affine :

define(define_geometry_list, «define_geometry($1, $2, $3, $4(?:\s*,\s*$4)*)»)dnl
define_geometry_list(controlpoints, CONTROLPOINTS, «», ignorecase(NURBSPOINT)\s*emptyable_group(make_geometry(weightedpoint, WEIGHTEDPOINT, «», coords_)\s*««,»»\s*ignorecase(WEIGHT)\s+number))dnl
define_geometry_list(controlpoints, CONTROLPOINTS, Z, ignorecase(NURBSPOINT)\s*emptyable_group(make_geometry(weightedpoint, WEIGHTEDPOINT, Z, coords_Z)\s*««,»»\s*ignorecase(WEIGHT)\s+number))dnl
define(controlpoints_M, controlpoints_)dnl
define(controlpoints_ZM, controlpoints_Z)dnl

De la même manière que j'ai ajouté define_geometry précédemment, on ajoute define_geometry_list pour pouvoir définir des choses sans les inclure dans la sortie du script. CONTROLPOINTS est défini comme une liste, et je définis directement le NURBSPOINT à l'intérieur. On peut ensuite définir la courbe NURBS :

make_geometry(nurbs, NURBSCURVE, $1, «ignorecase(DEGREE)\s+[+-]?\d+\s*««,»»\s*controlpoints_$1\s*««,»»\s*ignorecase(KNOTS)\s*(?:\s+ignorecase(EMPTY)|make_list(ignorecase(KNOT)\s*emptyable_group(ignorecase(VALUE)\s+number\s*««,»»\s*ignorecase(MULTIPLICITY)\s+[+-]?\d+)))»ifelse(index($1, M), -1, «», ««\s*««,»»\s*ignorecase(STARTM)\s+number\s*««,»»\s*ignorecase(ENDM)\s+number»»))

On notera que le DEGREE et la MULTIPLICITY sont des nombres entiers signés, et donc je n'utilise pas notre bon vieux number.

Courbe composée

La courbe composée va être assez facile à définir : c'est une courbe composée de toutes les autres courbes qu'on a défini jusque là, comme une collection de géométries spécialisée. La courbe composée peut donc contenir une ligne brisée (puisqu'une ligne est juste un cas particulier de courbe qui s'avère être droite), une chaîne d'arcs de cercle, un cercle, une géodésique, une courbe elliptique, une courbe NURBS, une clothoïde, une spirale ou une autre courbe composée. On ne peut pas faire de la récursion avec les expressions régulières, en tous cas pas avec le moteur limité d'expressions que j'utilise pour cette série d'articles. Pour rappel, c'est celui défini dans la spécification XSD, puisque nous étions parti au départ de la définition d'un schéma XML pour une spécification obsolète de moteurs de recherche. On ignorera donc la courbe composée contenue dans la courbe composée ; si vous voulez faire une courbe composée de courbes composées, fusionnez bêtement leurs contenus en une seule.

make_geometry_list(compoundcurve, COMPOUNDCURVE, $1, (?:coords_list_$1|circularstring_$1|circle_$1|geodesic_$1|elliptical_$1|clothoid_$1|spiral_$1|nurbs_$1))

Notez que pour la ligne brisée, on utilise coords_list et pas linestring : la grammaire EBNF indique qu'on doit juste utiliser une liste de points, et mettre LINESTRING devant est interdit.

Polygone courbe

Le polygone courbe a exactement la même syntaxe que la courbe composée, mais a une signification différente. Chaque courbe doit être fermée, c'est-à-dire qu'elle doit se terminer là où elle commence, pour former les anneaux du polygone. Cette fois, puisqu'on n'aura pas de récursion, on peut permettre au polygone courbe de contenir une courbe composée.

make_geometry_list(curvepolygon, CURVEPOLYGON, $1, (?:coords_list_$1|circularstring_$1|circle_$1|geodesic_$1|elliptical_$1|clothoid_$1|spiral_$1|nurbs_$1|compoundcurve_$1))

Ensemble de courbes

L'ensemble de courbes a lui aussi la même syntaxe que la courbe composée et que le polygone courbe. Contrairement à la courbe composée, où chaque courbe se termine là où la suivante commence, et contrairement au polygone courbe, où chaque courbe doit se finir là où elle commence, l'ensemble de courbes n'a aucune restriction.

make_geometry_list(multicurve, MULTICURVE, $1, (?:coords_list_$1|circularstring_$1|circle_$1|geodesic_$1|elliptical_$1|clothoid_$1|spiral_$1|nurbs_$1|compoundcurve_$1))

Surface composée

Une surface est le terme abstrait pour tout ce qui a des anneaux, et au-dessus : cela inclut donc le polygone, le polygone courbe, le triangle, la surface polyédrale, et le TIN. Comme pour la courbe composée, la surface composée est définie comme pouvant contenir une surface composée, mais nous n'allons pas permettre cela. Chaque surface est aussi censée avoir au moins un bord en commun avec l'autre, pour assurer la continuité de la surface composée qui peut être vue comme une surface unique, très complexe.

make_geometry_list(compoundsurface, COMPOUNDSURFACE, $1, (?:polygon_$1|triangle_$1|curvepolygon_$1|polyhedralsurface_$1|tin_$1))

Notons que puisque la surface composée peut contenir un TIN, on doit la définir dans make_geometries_2, la seconde passe de géométries.

Ensemble de surfaces

L'ensemble de surfaces a lui aussi la même syntaxe que la surface composée, et on va le définir à part pour pouvoir y ajouter le support de la surface composée.

make_geometry_list(multisurface, MULTISURFACE, $1, (?:polygon_$1|triangle_$1|curvepolygon_$1|polyhedralsurface_$1|tin_$1|compoundsurface_$1))

Solide B-Rep

Le solide à représentation surfacique est une tentative de l'ISO d'ajouter des notions au-delà des surfaces au standard : des solides. Ce solide est très particulier, puisqu'il n'existe que dans la dimension Z, et pas dans M. On va devoir le construire séparément, après make_geometries_2, car il est défini comme une liste de surfaces polyédrales ou de surfaces composées, et car il n'est disponible que dans une seule dimension.

^\s*(?:ignorecase(ANGLE)\s+angle_text|dnl
ignorecase(DIRECTION)\s*\(\s*ignorecase(N)\s+number\s*\)|dnl
vector_|vector_Z|dnl
call_dimensions(«make_geometries_1»)|dnl
call_dimensions(«make_geometries_2»)|dnl
make_geometry_list(brepsolid, BREPSOLID, Z, (?:polyhedralsurface_Z|compoundsurface_Z)))\s*$

Collection de géométries

Nous avions déjà défini la collection de géométries, puisqu'elle existe dans le Well-Known Text standard. Il est cependant grand temps de la mettre à jour avec toutes les nouvelles géométries que nous avons ajouté. La collection de géométries contient maintenant des points, toutes les courbes que nous avons vues, toutes les surfaces que nous avons vues, tous les ensembles que nous avons vus, le solide à représentation surfacique, et elle-même. Nous ne pouvons toujours pas faire de récursion, donc on ignorera ce dernier cas.

Puisque le solide à représentation surfacique est un cas à part assez étrange, commençons juste par définir toutes les autres géométries dans la collection :

make_geometry_list(geometrycollection, GEOMETRYCOLLECTION, $1, (?:point_$1|linestring_$1|circularstring_$1|circle_$1|geodesic_$1|elliptical_$1|clothoid_$1|spiral_$1|nurbs_$1|compoundcurve_$1|polygon_$1|triangle_$1|curvepolygon_$1|polyhedralsurface_$1|compoundsurface_$1|tin_$1|multipoint_$1|multilinestring_$1|multicurve_$1|multipolygon_$1|multisurface_$1))

Pour rajouter le cas spécial du solide à représentation surfacique, le plus simple serait d'utiliser un ifelse : ifelse($1, Z, |brepsolid_$1). Cela ajouterait le solide uniquement lorsqu'on est en mode XYZ et rien d'autre. Cependant, nous n'avons défini le brepsolid_Z qu'à la toute fin de notre expression ; il va falloir le déplacer. On va placer son make_geometry_list vers make_geometries_2 avec un autre ifelse:

ifelse($1, Z, make_geometry_list(brepsolid, BREPSOLID, Z, (?:polyhedralsurface_Z|compoundsurface_Z)))dnl
make_geometry_list(geometrycollection, GEOMETRYCOLLECTION, $1, (?:point_$1|linestring_$1|circularstring_$1|circle_$1|geodesic_$1|elliptical_$1|clothoid_$1|spiral_$1|nurbs_$1|compoundcurve_$1|polygon_$1|triangle_$1|curvepolygon_$1|polyhedralsurface_$1|compoundsurface_$1|tin_$1|multipoint_$1|multilinestring_$1|multicurve_$1|multipolygon_$1|multisurface_$1«»ifelse($1, Z, |brepsolid_$1)))

Notez qu'on utilise un «» pour séparer correctement entre le multisurface_$1 et le ifelse ; multisurface_Zifelse n'est pas une macro.

Conclusion

Nous en avons enfin terminé avec le Well-Known Text, et pour de bon cette fois. Notre expression régulière a absolument explosé avec tous ces ensembles, géométries composées et la collection de géométries : on atteint 9 313 034 caractères ! J'ai largement explosé mon record de taille d'expression régulière encore une fois.

Script m4 générant une expression régulière pour valider du Well-Known Text ISO

changequote(«, »)dnl
define(ignorecase, «patsubst($1, \w, «[«\&»translit(\&, A-Z, a-z)]»)»)dnl
define(number, [+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:ignorecase(E)[+-]?\d+)?)dnl
define(coords_, number\s+number)dnl
define(coords_Z, number\s+number\s+number)dnl
define(coords_M, coords_Z)dnl
define(coords_ZM, number\s+number\s+number\s+number)dnl
define(angle_text, ignorecase((?:DEGREE|G?RADIAN)S)\s*\(\s*number\s*\))dnl
define(vector_, ignorecase(VECTOR)\s+emptyable(coords_))dnl
define(vector_Z, ignorecase(VECTOR)\s+ignorecase(Z)\s+emptyable(coords_Z))dnl
define(vector_M, vector_)dnl
define(vector_ZM, vector_Z)dnl
define(tinelement_label, (?:\s+ignorecase(ID)\s+\d+)?(?:\s+ignorecase(TAG)\s*"[a-zA-Z0-9()_.' -]+")?\s+)dnl
define(emptyable, «(?:»ignorecase(EMPTY)«|$1)»)dnl
define(emptyable_group, «(?:\s+»ignorecase(EMPTY)«|\(\s*$1\s*\))»)dnl
define(make_list, \(\s*$1(?:\s*,\s*$1)*\s*\))dnl
define(define_geometry, «define($1_$3, «ignorecase($2)\s*ignorecase($3)\s*emptyable_group($4)»)»)dnl
define(make_geometry, «define_geometry($1, $2, $3, $4)$1_$3»)dnl
define(define_geometry_list, «define_geometry($1, $2, $3, $4(?:\s*,\s*$4)*)»)dnl
define(make_geometry_list, «make_geometry($1, $2, $3, $4(?:\s*,\s*$4)*)»)dnl
define_geometry(affineplacement, AFFINEPLACEMENT, «», «make_geometry(location, LOCATION, «», coords_)\s*«,»\s*ignorecase(REFERENCEDIRECTIONS)\s*emptyable_group(vector_«(?:\s*,\s*»vector_)*)»)dnl
define_geometry(affineplacement, AFFINEPLACEMENT, Z, «make_geometry(location, LOCATION, Z, coords_Z)\s*«,»\s*ignorecase(REFERENCEDIRECTIONS)\s*emptyable_group(vector_Z«(?:\s*,\s*»vector_Z)*)»)dnl
define(affineplacement_M, affineplacement_)dnl
define(affineplacement_ZM, affineplacement_Z)dnl
define_geometry_list(controlpoints, CONTROLPOINTS, «», ignorecase(NURBSPOINT)\s*emptyable_group(make_geometry(weightedpoint, WEIGHTEDPOINT, «», coords_)\s*««,»»\s*ignorecase(WEIGHT)\s+number))dnl
define_geometry_list(controlpoints, CONTROLPOINTS, Z, ignorecase(NURBSPOINT)\s*emptyable_group(make_geometry(weightedpoint, WEIGHTEDPOINT, Z, coords_Z)\s*««,»»\s*ignorecase(WEIGHT)\s+number))dnl
define(controlpoints_M, controlpoints_)dnl
define(controlpoints_ZM, controlpoints_Z)dnl
define(make_geometries_1,
«define(coords_list_$1, emptyable(make_list(coords_$1)))dnl
define(coords_list_list_$1, emptyable(make_list(coords_list_$1)))dnl
make_geometry(point, POINT, $1, coords_$1)|dnl
make_geometry_list(linestring, LINESTRING, $1, coords_$1)|dnl
make_geometry_list(circularstring, CIRCULARSTRING, $1, coords_$1)|dnl
make_geometry_list(circle, CIRCLE, $1, coords_$1)|dnl
make_geometry_list(geodesic, GEODESICSTRING, $1, coords_$1)|dnl
make_geometry_list(polygon, POLYGON, $1, coords_list_$1)|dnl
make_geometry_list(triangle, TRIANGLE, $1, coords_list_$1)|dnl
make_geometry_list(multipoint, MULTIPOINT, $1, emptyable(\(\s*coords_$1\s*\)))|dnl
make_geometry_list(multilinestring, MULTILINESTRING, $1, coords_list_$1)|dnl
make_geometry_list(multipolygon, MULTIPOLYGON, $1, coords_list_list_$1)|dnl
make_geometry(polyhedralsurface, POLYHEDRALSURFACE, $1, ignorecase(PATCHES)\s*make_list(polygon_$1))|dnl
make_geometry(elliptical, ELLIPTICALCURVE, $1, «ignorecase(REFERENCELOCATION)\s+affineplacement_$1\s*««,»»\s*ignorecase(UAXISLENGTH)\s+emptyable(number)\s*««,»»\s*ignorecase(VAXISLENGTH)\s+emptyable(number)\s*««,»»\s*ignorecase(STARTANGLE)\s+angle_text\s*««,»»\s*ignorecase(ENDANGLE)\s+angle_text»ifelse(index($1, M), -1, «», ««\s*««,»»\s*ignorecase(STARTM)\s+number\s*««,»»\s*ignorecase(ENDM)\s+number»»))|dnl
make_geometry(clothoid, CLOTHOID, $1, «ignorecase(REFERENCELOCATION)\s+affineplacement_$1\s*««,»»\s*ignorecase(SCALEFACTOR)\s+emptyable(number)\s*««,»»\s*ignorecase(STARTDISTANCE)\s+emptyable(number)\s*««,»»\s*ignorecase(ENDDISTANCE)\s+emptyable(number)»ifelse(index($1, M), -1, «», ««\s*««,»»\s*ignorecase(STARTM)\s+number\s*««,»»\s*ignorecase(ENDM)\s+number»»))|dnl
make_geometry(spiral, SPIRALCURVE, $1, «ignorecase(REFERENCELOCATION)\s+affineplacement_$1\s*««,»»\s*ignorecase(SPIRALLENGTH)\s+emptyable(number)\s*««,»»\s*ignorecase(STARTCURVATURE)\s+emptyable(number)\s*««,»»\s*ignorecase(ENDCURVATURE)\s+emptyable(number)\s*««,»»\s*ignorecase(SPIRALTYPE)\s+[a-zA-Z0-9()_.' -]+»ifelse(index($1, M), -1, «», ««\s*««,»»\s*ignorecase(STARTM)\s+number\s*««,»»\s*ignorecase(ENDM)\s+number»»))|dnl
make_geometry(nurbs, NURBSCURVE, $1, «ignorecase(DEGREE)\s+[+-]?\d+\s*««,»»\s*controlpoints_$1\s*««,»»\s*ignorecase(KNOTS)\s*(?:\s+ignorecase(EMPTY)|make_list(ignorecase(KNOT)\s*emptyable_group(ignorecase(VALUE)\s+number\s*««,»»\s*ignorecase(MULTIPLICITY)\s+[+-]?\d+)))»ifelse(index($1, M), -1, «», ««\s*««,»»\s*ignorecase(STARTM)\s+number\s*««,»»\s*ignorecase(ENDM)\s+number»»))|dnl
make_geometry_list(compoundcurve, COMPOUNDCURVE, $1, (?:coords_list_$1|circularstring_$1|circle_$1|geodesic_$1|elliptical_$1|clothoid_$1|spiral_$1|nurbs_$1))|dnl
make_geometry_list(curvepolygon, CURVEPOLYGON, $1, (?:coords_list_$1|circularstring_$1|circle_$1|geodesic_$1|elliptical_$1|clothoid_$1|spiral_$1|nurbs_$1|compoundcurve_$1))|dnl
make_geometry_list(multicurve, MULTICURVE, $1, (?:coords_list_$1|circularstring_$1|circle_$1|geodesic_$1|elliptical_$1|clothoid_$1|spiral_$1|nurbs_$1|compoundcurve_$1))»)dnl
define(make_geometries_2,
«make_geometry(tin, TIN, $1, ignorecase(PATCHES)\s*coords_list_list_$1()(?:\s+ignorecase(ELEMENTS)\s*make_list((?:ignorecase(STOPLINE)tinelement_label()(?:linestring_|linestring_Z|linestring_M|linestring_ZM)|ignorecase((?:BREAKLINE|SOFTBREAK|CONTROLCONTOUR))tinelement_label()(?:linestring_Z|linestring_ZM)|ignorecase((?:DRAPEVOID|HOLE))tinelement_label()(?:polygon_|polygon_Z|polygon_M|polygon_ZM)|ignorecase((?:BOUNDARY|(?:BREAK)?VOID))tinelement_label()(?:polygon_Z|polygon_ZM)|ignorecase((?:POINTS|GROUPSPOT))tinelement_label()(?:multipoint_Z|multipoint_ZM))))?(?:\s+ignorecase(MAXSIDELENGTH)\s+\d+)?)|dnl
make_geometry_list(compoundsurface, COMPOUNDSURFACE, $1, (?:polygon_$1|triangle_$1|curvepolygon_$1|polyhedralsurface_$1|tin_$1))|dnl
make_geometry_list(multisurface, MULTISURFACE, $1, (?:polygon_$1|triangle_$1|curvepolygon_$1|polyhedralsurface_$1|tin_$1|compoundsurface_$1))|dnl
ifelse($1, Z, make_geometry_list(brepsolid, BREPSOLID, Z, (?:polyhedralsurface_Z|compoundsurface_Z)))dnl
make_geometry_list(geometrycollection, GEOMETRYCOLLECTION, $1, (?:point_$1|linestring_$1|circularstring_$1|circle_$1|geodesic_$1|elliptical_$1|clothoid_$1|spiral_$1|nurbs_$1|compoundcurve_$1|polygon_$1|triangle_$1|curvepolygon_$1|polyhedralsurface_$1|compoundsurface_$1|tin_$1|multipoint_$1|multilinestring_$1|multicurve_$1|multipolygon_$1|multisurface_$1«»ifelse($1, Z, |brepsolid_$1)))»)dnl
define(call_dimensions, «$1(«»)|$1(Z)|$1(M)|$1(ZM)»)dnl
^\s*(?:ignorecase(ANGLE)\s+angle_text|dnl
ignorecase(DIRECTION)\s*\(\s*ignorecase(N)\s+number\s*\)|dnl
vector_|vector_Z|dnl
call_dimensions(«make_geometries_1»)|dnl
call_dimensions(«make_geometries_2»))\s*$

Je ne vois pas mieux que de conclure avec une citation du manuel de m4, qui me semble tout à fait approprié à ce que je viens de faire :

Some people find m4 to be fairly addictive. They first use m4 for simple problems, then take bigger and bigger challenges, learning how to write complex sets of m4 macros along the way. Once really addicted, users pursue writing of sophisticated m4 applications even to solve simple problems, devoting more time debugging their m4 scripts than doing real work. Beware that m4 can be dangerous for the health of compulsive programmers. —GNU M4 manual


Commentaires

Il n'y a pour l'instant aucun commentaire. Soyez le premier !