Avatar

Maëlan

Aucun abonné

↪ j’étais sur Progdupeupl et encore avant sur le Site du Zéro.


Messages utiles (pour réutilisation future) :

plus quelques trucs moins intéressants là-bas (et voir aussi la bio de realmagma).


À propos de chameaux.

# let default x0 x = let f ?(x=x0) = x in f ?x ;;
val default : 'a -> 'a option -> 'a = <fun>
OCaml, ce langage de script

L’interprète d’OCaml accepte les shebangs, ce qui permet d’écrire de s’en servir comme d’un langage de script :

:::ocaml
#!/usr/bin/ocaml
(* … le code OCaml ici … *)

Par contre, le compilateur (ocamlc et ocamlopt) signale une erreur de syntaxe. On peut toutefois leur donner l’option -pp 'sed "1s/^#\!.*//"'. Elle leur dit d’utiliser la commande fournie comme préprocesseur, et cette commande supprime la ligne du shebang.

Comme pour n’importe quel langage, on peut même utiliser le shebang pour passer des options à l’interprète, par exemple pour charger des modules :

:::ocaml
#!/usr/bin/ocaml str.cma

Problème (non spécifique à OCaml) : beaucoup de systèmes, dont Linux, ne séparent pas les arguments ainsi passés. La ligne suivante passe en fait un seul argument "str.cma unix.cma" :

:::ocaml
#!/usr/bin/ocaml str.cma unix.cma

Il n’y a pas de solution directe et universelle à ce problème. Une astuce consiste à appeler non pas directement l’interprète, mais le shell (sh) et à rendre le fichier polyglotte : lu par le shell, il exécutera des instructions du shell (qui consisteront à appeler l’interprète du langage avec les options souhaitées), et lu par l’interprète du langage, il exécutera notre vrai programme. Bien sûr, il faut que les deux programmes soient syntaxiquement corrects et ne fassent que ce qu’on veut… Ci-dessous, deux patrons possibles. Ils utilisent tous deux la plus grande souplesse d’OCaml en ce qui concerne les apostrophes simples, afin d’envelopper le script shell dans un commentaire OCaml sans que les délimiteurs de ces commentaires ne provoquent une erreur de syntaxe pour le shell.

  • Celle-ci utilise la commande true qui ignore tous ses arguments. Elle induit léger surcoût à l’exécution du code OCaml.

    :::ocaml #!/bin/sh "true" = let x' = "" in (' commandes sh ici ) x'

  • Celle-ci utilise la commande type en ignorant sa sortie. Elle ne provoque aucun surcoût à l’exécution du code OCaml, mais crée un nouveau nom de type (qu’on peut prendre arbitrairement tarabiscoté si c’est gênant).

    :::ocaml
    #!/bin/sh
    type int' (*' >&- 2>&-
        commandes sh ici
    *)
    

Dans ces deux patrons, le script shell doit se terminer (par exemple avec un appel à exec ou exit sans atteindre la fin du commentaire, sans quoi on obtient une erreur de syntaxe (on peut compliquer ces modèles pour gérer ça, mais ce n’est pas utile pour l’usage qu’on en fait…).

Notez qu’on peut écrire un script shell arbitrairement complexe dans ces patrons (il faut juste éviter la séquence *)). Personnellement, j’utilise ceci, qui me permet soit d’exécuter le script directement, soit de le compiler en passant l’argument --compile.

:::ocaml
#!/bin/sh
type int' (*' >&- 2>&-
	if [ "$1" = "--compile" ]; then
        name="${0%.ml}"
		ocamlopt -pp 'sed "1s/^#\!.*//"' \
          str.cmxa unix.cmxa "$name.ml" -o "$name" \
          || exit
        rm "$name".{cm*,o}
        exit
	else
		exec ocaml str.cma unix.cma "$0" "$@"
	fi
*)

Parlons C.

Liens

Les normes : C89, C99, C11.

Deux liens sur l’histoire du C : l’article de Dennis Ritchie, une page instructive.

Aide-mémoire des opérateurs

lignes triées par priorité décroissante :

type associativité opérateurs
suffixe ltr → ++ -- . -> [] () (appel de fonction)
préfixe ← trl ++ -- ! ~ + - & * (Type) sizeof _Alignof
multiplication rtl → * / %
addition rtl → + -
décalage binaire rtl → << >>
comparaison rtl → < <= >= >
== !=
bit-à-bit rtl → &
^
¦
logique rtl → && (court-circuit)
¦¦ (court-circuit)
ternaire ← rtl ?: (point de séquence ; expression centrale automatiquement parenthésée, mais pas celle de droite, attention aux affectations)
affectation ← rtl = += -= *= /= %= <<= >>= &= ^= ¦=
séquençage ltr → , (point de séquence)

(si ça ne vous suffit pas)

Des codes ± utiles rigolos…

Afficher les arguments passés en CLI.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int n, char const* const* a) {
    char wd[1024];
    if(getcwd(wd, sizeof wd / sizeof *wd) == NULL)
        puts("[error when trying to get working directory"
             " (function `getcwd`).]");
    printf("[current working directory: '%s']\n"
           "[%u argument%c passed%c]\n", wd, n, n>1?'s':0, n?':':'.');
    for(; *a; ++a)
        puts(*a);
    return EXIT_SUCCESS;
}

Convertir les tabulations d’un code source en espaces (à chaîner avec xclip pour le coller sur un site web par exemple).

#include <uchar.h>

#define _const_const_char(_) _const_const_char ((unsigned char const**const  \
         )                          (_))                            //      __
char32_t(_const_const_char           )(          unsigned char const**const __
                                    //\\
                                   //||\\
                                  // || \\
                                 //  ||  \\
                                //   ||   \\
                               //    ||    \\                              \_o<
                              /*     ||     *\
                              \\     ||     //
                               \\    ||    //
                                \\   ||   //
                                 \\  ||  //
                                  \\ || //
                                   \\||//
                                    \**/
                                     ){
                                                                  ;char32_t O
                                     ,                          o ;  size_t
                                     l;
                                    for
                                   (l=0,
                                  o=0200,
                                O=*(*__)++;
                               o&/*_*/O;++l,
                               O&=~ o,o>>=1);
                             (l && (!--l||l&4))
                           && (O^=~O); while //&=
                          (O+ 1&&l --) 0200^0300 &
                          (o=*(*__)++) &&(O^=~O)||
                          (O=O <<6 | o&077);return
                                    O;}

Implémentation de printf (ou une proposition plus sérieuse).

#include <stdarg.h>
#include <stdio.h>
#undef    printf
 int      printf(char const * ____ ,... ) {
                  va_list   (    ___    ) ;
                  va_start  ( ___, ____ ) ;
 int              va_lid                  =
                  vprintf   ( ____ ,___ ) ;
                  va_end    (    ___    ) ;
          return  va_lid                  ;
                                          }
Quelques quines

un quine lisible :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char* escape (const char* s)
{
	char* u = calloc(2*strlen(s), 1);
	
	for (char* t = u; *s; s++) switch (*s) {
	  case '\n':
		strcpy(t, "\\n\"\n\""); t += 5; break;
	  case '\"':
		*t++ = '\\'; *t++ = '"'; break;
	  case '\\':
		*t++ = '\\'; *t++ = '\\'; break;
	  default:
		*t++ = *s;
	}
	
	return u;
}

int main (void)
{
	const char* s =
"#include <stdio.h>\n"
"#include <stdlib.h>\n"
"#include <string.h>\n"
"\n"
"char* escape (const char* s)\n"
"{\n"
"	char* u = calloc(2*strlen(s), 1);\n"
"	\n"
"	for (char* t = u; *s; s++) switch (*s) {\n"
"	  case '\\n':\n"
"		strcpy(t, \"\\\\n\\\"\\n\\\"\"); t += 5; break;\n"
"	  case '\\\"':\n"
"		*t++ = '\\\\'; *t++ = '\"'; break;\n"
"	  case '\\\\':\n"
"		*t++ = '\\\\'; *t++ = '\\\\'; break;\n"
"	  default:\n"
"		*t++ = *s;\n"
"	}\n"
"	\n"
"	return u;\n"
"}\n"
"\n"
"int main (void)\n"
"{\n"
"	const char* s =\n"
"\"%s\";\n"
"	char* t = escape(s);\n"
"	printf(s, t);\n"
"	free(t);\n"
"}\n"
"";
	char* t = escape(s);
	printf(s, t);
	free(t);
}

un quine court (utilise ça) :

#define _(a,b) a ("_("#a" , "#b")") b
_(int puts(char*); int main (void) { puts("#define _(a,b) a (\"_(\"#a\" , \"#b\")\") b"); puts , ; return 0; })

le même encore plus court, aux dépens de la lisibilité et de la stricte conformité :

#define _(a,b)a("_("#a","#b")")b
_(main(){puts("#define _(a,b)a(\"_(\"#a\",\"#b\")\")b");puts,;})
Des exceptions… en C

Ce code implémente des macros try et catch en C. cf. ici. Utilise les sauts non locaux (en-tête <setjmp.h>). Évidemment à ne pas utiliser dans la vie réelle, pour des raisons de performance (cf. ici pour la culture). On peut vraiment s’amuser avec la syntaxe du C (C99) pour coder des macros. Je devrais enlever tous ces commentaires.

trycatch.h
#ifndef INCLUDED_TRYCATCH_H
#define INCLUDED_TRYCATCH_H
 
/* Ce header nécessite C99. */
#if !defined (__STDC_VERSION__)  ||  __STDC_VERSION__ < 199901L
#error : USE OF “TRYCATCH.H” REQUIRES C99.
#endif
 
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
 
 
 
/* type : pile d’environnements `jmp_buf` (utilisée dans la macro `try`) */
struct try {
    struct try  *prev;
    jmp_buf     env;
};
 
/* type : informations de gestion de la pile de jmp_buf et des exceptions */
struct catch {
    struct try  *envs;    /* haut de la pile (NULL si pas dans un bloc `try`) */
    int         code;     /* code numérique de l’exception (0 si sans objet) */
};
 
/* globale de gestion du système d’exceptions */
extern  struct catch  catch;
 
 
 
/* gestion des erreurs (message d’erreur détaillé) */
#define  Throw(msg)                                                         \
    fprintf(stderr, "error [file `%s`, line %u, function `%s`]: %s.\n",        \
      __FILE__, __LINE__, __func__, (msg))
 
 
 
/** TRY **/
 
/* Crée un élément de la pile de classe automatique (non nommé et local au bloc
   `try`), le rajoute au sommet de la pile globale, puis appelle `setjmp` pour
   définir le point de retour de `throw`.
   Dépile à la fin. On doit dépiler dans le `for` car l’élément de pile lui est
   local. Dans tous les cas, la boucle `for` ne doit pas se répéter.
   Deux cas de figure :
   − sortie normale du bloc `try` : cf la 3è partie du `for` pour dépiler, et la
     1ère condition pour ne pas recommencer le bloc (utilise le comportement en
     cours-circuit de &&) ;
   − sortie avec un `throw` : cf le ternaire de la 2ème condition, pour dépiler
     et arrêter le `for`. */
 
#define  try                                                                   \
    for( catch.code = 1,  catch.envs = & (struct try) { .prev = catch.envs }   \
       ; catch.code  &&  ( setjmp(catch.envs->env)                             \
                           ? (catch.envs = catch.envs->prev, 0)                \
                           : 1                                  )              \
       ; catch.code = 0,  catch.envs = catch.envs->prev                        \
    )
 
 
 
/** CATCH (+ catchSwitch) **/
 
/* Exécute le contenu du bloc s’il y a une exception (code non nul), en stockant
   son code dans la variable de type `int` passée (qui peut être une déclaration,
   auquel cas cette variable sera locale au bloc `catch`).
   Un bloc `catch` n’aura d’effet que s’il est le premier après un bloc `try`
   (remet le code d’exception à 0). */
 
#define  catch(VAR)                                                            \
    for(VAR = catch.code  ;  catch.code  ;  catch.code = 0)
 
/* Raccourci pour effectuer un `switch` sur le code de l’exception, sans nommer
   une variable pour le stocker (s’utilise comme un `switch` normal). */
 
#define  catchSwitch()                                                         \
    for(;  catch.code  ;  catch.code = 0)                                      \
        switch(catch.code)
 
 
 
/** THROW (+ rethrow) **/
 
/* « Lance » le code d’exception passé, qui doit être non nul. L’exécution du
   programme revient grâce à `longjmp` au dernier bloc `try` englobant. Si aucun
   bloc `try` n’englobe `throw`, quitte le programme avec le code lancé. */
 
#define  throw(CODE)                                                           \
    do {                                                                       \
        catch.code = (CODE);                                                   \
        if(!catch.code) {                                                      \
            Throw("throwing a null exception code");                           \
            exit(EXIT_FAILURE);                                                \
        }                                                                      \
        else if(!catch.envs) {                                                 \
            Throw("`throw` outside of a `try` block");                         \
            exit(catch.code);                                                  \
        }                                                                      \
        else                                                                   \
            longjmp(catch.envs->env, catch.code);                              \
    } while(0)
 
/* « Relance » l’exception qui a été capturée (possible seulement dans un bloc
   `catch`). */
 
#define  rethrow()                                                             \
    throw(catch.code)
 
 
 
#endif
trycatch.c
#include "trycatch.h"
 
struct catch  catch =  {NULL, 0};
test.c
#include "trycatch.h"
#include <stdio.h>
 
 
 
typedef  enum {EXC_Z, EXC_NOTRY, EXC_42, EXC_DIVBYZ, EXC_ORDER}  Exception;
 
int division(int a, int b) {
    if(!a)
        throw(EXC_Z);        /* doit provoquer une erreur (code nul) */
    else if(a==-1)
        throw(EXC_NOTRY);    /* doit provoquer une erreur (throw sans try) */
    else if(b==42)
        throw(EXC_42);       /* exception relancée avec rethrow() */
    else if(!b)
        throw(EXC_DIVBYZ);     /* exception gérée dans le catchSwitch() */
    else if(a<b)
        throw(EXC_ORDER);      /* idem */
    return a / b;
}
 
int main(void) {
    int a,  b;
 
    while(1) {
        fputs("a, b? ", stdout);
        scanf("%i%i", &a, &b);
 
        try {
         
            try {
                printf("%i / %i  =  %i\n", a, b, division(a,b));
            }
            catchSwitch() {
              case EXC_DIVBYZ:
                puts("exc: /0");     break;
              case EXC_ORDER:
                puts("exc: a<b");    break;
              default:    /* EXC_42 ou EXC_NOTRY */
                rethrow();          break;
            }
             
        }
         
        catch(Exception e) {
            if(e == EXC_42)
                puts("exc: 42");
            else    /* EXC_NOTRY */
                rethrow();    /* erreur : pas dans un bloc `try` */
        }
    }
}

Moi moi moi

Chez moi (<del>mplayer</del> mpv / smplayer ; à tester : gmplayer, gnome-mplayer, dmlenu).

écrire français sous Windows : fr-oss (azerty++) ou bépo (étudié pour le français) | <insérer un truc spirituel ici>

Dernières publications Voir tout

Derniers sujets créés Voir tout

Signaler ce profil