Tutoriel NewGen
NewGen
Paris, 28 Novembre 1991
- NewGen (New Generation)
- Outil de génie logiciel
- Aide à l'écriture de logiciels de taille importante
- Développement à l'Ecole des Mines de Paris (Automne 1988)
- Prototype ``Public Domain'' distribué (Mines, Bull, NASA, Boeing, ...)
- Applications: Paralléliseurs (environ 50 klignes)
- Outil de gestion de structures de données
- Méta-langage de définition de domaines: base, somme, produit, liste, ensemble, tableau
- Génération automatique de fonctions de création, manipulation, mise à jour, libération, stockage ...
- Bibliothèque générale (listes, tableaux, itérateurs, ...)
- Intégration dans logiciels existants (externe)
- Modularité (import)
- Efficacité temps/espace
- Problématique
- Atouts
- Description du langage de spécification
- Fonctions de manipulation (en C)
- Bibliothèques
- Un Premier Exemple
- NewGen ``avancé'' : tabulation, importation, external
- Retour sur l'exemple
- NewGen et CommonLISP
- Aspects de l'implémentation
- PIPS: Un exemple ``vraie grandeur''
- Autres systèmes
- Conclusion
- Modèles de Développement Logiciel: waterfall, prototypage
- Prototypage: Spécification vs. Implémentation
- Spécification: Exécutabilité vs. Expressivité
- Implémentation: Efficacité vs. Simplicité
- Solution: Interopérabilité
- Indépendance par rapport au langage cible
- Paradigme uniforme de programmation
- Etat: support pour CommonLISP (orienté spécification) et C (orienté implémentation)
- Compatibilité complète au niveau fichier
- Maintien de la cohérence (persistence)
- Transition souple (conception modulaire)
- Abstraction Fonctionnelle:
- Constructions de haut niveau (fonctions)
- Indépendance de l'implémentation
- Extensions/redéfinitions possibles
- Processus Evolutif:
- Support pour logiciel multi-passes
- Gestion de persistence (partage, cycle)
- Multi-langages
- Intégration progressive (fichiers, pipes, variables globales)
- Environnement de Programmation:
- Lisibilité inter-langages
- Réutilisation des bibliothèques
- Mise-au-point aisée (tests dynamiques possibles)
- - pour les commentaires
- Définitions de domaines:
name = expression ;
- Prédéfinis: unit, bool, char, int, string, float
- Produit de membres:
user = name:string x id:int x passwd x shell:string ;
- Somme de membres:
passwd = crypted:string + clear:string ;
- Enumération (vue comme somme):
passwd_status = {crypted, clear} ;
- Les membres peuvent être complexes
- Définitions récursives autorisées
- Liste ordonnée de domaines:
node = information:int x children:node* ;
- Ensemble (non ordonné) de domaines:
group = elements:user{} ;
- Tableau (indexé) de domaines:
#define BUFFER_SIZE 100 buffer = first:int x lats:int x elements:char[ BUFFER_SIZE ] ;
Création
- Tout domaine domain définit:
- un type domain
- un constructeur make_domain
- une valeur par défaut domain_undefined
- Domaines produit: création à partir des membres:
user pierre = make_user( "jouvelot", 110, passwd_undefined, "/usr/local/bin/ksh" ) ;
- Domaines somme: création à partir d'un tag et d'une valeur
- Chaque membre member d'une somme domain a un tag is_domain_member:
char buffer[ 8 ] ; passwd at_login = make_passwd( is_passwd_encrypted, crypt( gets( buffer ), "aa" )) ;
- Il existe un type tag.
Accès
- Call-by-sharing (vs. call-by-value, call-by-reference)
- Un domaine domain et un membre member définissent un accesseur domain_member:
printf( "User %s logged on\n", user_name( pierre )) ;
- Le tag d'une somme domain s'obtient par domain_tag:
if( passwd_tag( at_login ) == is_passwd_encrypted ) { check( passwd_crypted( at_login )) ; }
- Des prédicats domain_member_p existent pour les sommes:
if( passwd_encrypted_p( at_login )) { check( passwd_crypted( at_login )) ; }
- Implémentation sous forme de macros.
Modification
- Utilisation de =:
passwd_tag( at_login ) = is_passwd_clear ; passwd_clear( at_login ) = "go ahead" ;
- Création de partage et cycle:
node = info:int x next:node ; node n = make_node( 1, node_undefined ) ; next( n ) = n ;
Opérations I/O
- Tout domaine domain définit:
- une fonction d'écriture write_domain
- une fonction de lecture read_domain
user pierre = read_user( open_db()) ; fprintf( stderr, "Read data for user %s\n", user_name( pierre )) ;
- Le partage (sharing) est preservé dans la sous-structure.
- Gestion des cycles.
Libération
- Tout domaine domain définit free_domain.
if( denied_access( pierre )) { fprintf( sdterr, "Permission denied: %s\n", user_name( pierre )) ; free_user( pierre ) ; restart_top_level() ; }
- Réclamation récursive des structures de données
- Gestion des cycles et partage dans la sous-structure (attention au partage transverse)
- Remarque: pas nécessaire en CommonLISP!
- A chaque constructeur de type est associée une bibliothèque
- Listes
- Ensembles
- Remarque: tables de hachage
Listes
- Constructeurs classiques de Lisp: CONS, CAR, CDR, NIL, ENDP, ...
- Nécessité de typage explicite (listes polymorphes)
- Tout domain domain définit la conversion DOMAIN:
list logged_on = NIL ; void add_to_users( u ) user u ; { logged_on = CONS( USER, u, logged_on ) ; }
- Itérateurs:
printf( "Users logged on: " ) ; MAPL( users, { user u = USER( CAR( users )) ; printf( "%s ", user_name( u )) ; }, logged_on ) ;
- Mise-à-jour simple:
CAR( logged_on ) = pierre ;
- Ensemble ``extensible'' de fonctions (remove, nconc, copy, find, length, ...)
Ensembles
- Problème: gestion d'ensemble (et non multi-ensembles), implémentation efficace
- Support pour chaines, entiers et pointeurs
- Convention d'allocation à l'appelant (solution simple pour appels emboités).
- Opérations triadiques:
set_op( result, operand1, operand2 ) ;
- Nécessité d'allocation:
set logged_on = set_undefined ; void add_to_users( u ) user u ; { if( set_undefined_p( logged_on )) { logged_on = set_make( set_pointer) ; } set_add_element( logged_on, logged_on, u ) ; }
- Itérateurs:
printf( "Users logged on: " ) ; SET_MAP( u, { printf( "%s ", user_name( u )) ; }, logged_on ) ;
- Ensemble ``extensible'' de fonctions:
- set_intersection,
- set_union,
- set_equal_p,
- set_free,
- set_size ...
Tables de hachage
- Tables dans Unix SV ``inutilisables''
- Utilisation fréquente: ensembles, NewGen, ...
- Itérateurs:
#define SET_MAP(element,code,set) { \ HASH_MAP(_set_map_key, element, \ code, \ (set)->table); \ }
- SIMPLE est un petit langage d'expression
- Fichier expression.tex:
\title{SIMPLE Language Specifications} \author{Pierre Jouvelot} \begin{document} \domain{expression = constant:int + identifier:string + binary + let ;} { An expression is either an integer constant, an identifier, a binary expression, or a nested let construct. } \domain{binary = operator:string x lhs:expression x rhs:expression ;} { A binary expression consists of an operator and two subexpressions. } \domain{let = bindings:binding* x expression ;} { A let construct includes a binding list and a body expression. } \domain{binding = name:string x value:expression ;} { A binding binds a name to a value. }
- Fichier expression.newgen trié, automatiquement généré
binary = operator:string x lhs:expression x rhs:expression ; binding = name:string x value:expression ; expression = constant:int + identifier:string + binary + let ; let = bindings:binding* x expression ;
- Fichier expression.dvi de documentation automatiquement généré.
- Syntaxe d'entrée à la Lisp:
. 1 . (+ x 1) . (let x 1[+] (+ x 2)) . (let x 1[+] (let y (* 2 x[+] ) (+ x y)))
- Génération des structures de données NewGen:
. make_expression(is_expression_constant,1) . make_expression( is_expression_binary, make_binary( "+", make_expression( is_expression_identifier, "x"), make_expression( is_expression_constant, 1)))
- NewGen est compatible avec tous les outils Unix
- Frontal généré automatiquement par Yacc
%{ #include <stdio.h> /* Unix standard IO */ #include <string.h> /* String managt. */ #include "genC.h" /* Newgen basic C library */ #include "expression.h" /* Newgen-generated header files */ expression Top ; %} %token LP RP %token LET %term INT %term STRING %union { expression expression ; let let ; list list ; identifier identifier ; string string ; } %type <expression> Axiom Expression %type <let> Let %type <identifier> Identifier %type <list> Bindings %type <string> String %% Axiom : Expression { Top = $1 ; } ; Expression : INT { $$ = make_expression( is_expression_constant, atoi( yytext )) ; } | Identifier { $$ = make_expression( is_expression_identifier,$1); } | LP String Expression Expression RP { binary b = make_binary( $2, $3, $4 ) ; $$ = make_expression( is_expression_binary, b ); }
| Let { $$ = make_expression( is_expression_let, $1 ) ; } ; Let : LP LET LP Bindings RP Expression RP { $$ = make_let( $4, $6 ) ; } ; Bindings : { $$ = NIL ; } | Bindings LP String Expression RP { $$ = CONS( BINDING, make_binding( $3, $4 ), $1 ) ; } ; Identifier : String { $$ = make_identifier( $1 ) ; } ; String : STRING { $$ = strdup( yytext ) ; } ; %%
- Commande shell de génération de code
- newgen prend en arguments:
- Language objet (-C, -Lisp),
- Fichiers .newgen
% newgen -C expression.newgen GEN_READ_SPEC order: expression.spec % ls expression.newgen expression.h expression.spec %
- Pour chaque fichier foo.newgen, on obtient deux fichiers:
- Déclarations C: foo.h,
- Spécifications: foo.spec
- Remarque: les spec devraient disparaitre dans une nouvelle version de NewGen
- Fichiers spec lus à l'exécution, avant tout appel de fonctions NewGen.
- Ordre des fichiers spec donné par newgen
#include <stdio.h> #include "genC.h" #include "expression.h" expression Top ; main() { gen_read_spec( "expression.spec", (char*) NULL) ; yyparse() ; fprintf( stdout, "%d\n", constant_fold( Top )) ; free_expression( Top ) ; }
int constant_fold( e ) expression e ; { int value ; tag t ; switch( t = expression_tag( e )) { case is_expression_constant: value = expression_constant( e ) ; break ; case is_expression_binary: binary b = expression_binary( e ) ; int lhs = constant_fold(binary_lhs(b)); int rhs = constant_fold(binary_rhs(b)); value = eval_primitive( binary_operator(b), lhs, rhs ) ; break ; default: fprintf( stderr, "Unimplemented %d\n", t ) ; exit( 1 ) ; } return( value ) ; } int eval_primitive( op, lhs, rhs ) char *op ; int lhs, rhs ; { if( strcmp( op, "+" ) == 0 ) return( lhs+rhs ) ; if( strcmp( op, "-" ) == 0 ) return( lhs-rhs ) ; if( strcmp( op, "*" ) == 0 ) return( lhs*rhs ) ; if( strcmp( op, "/" ) == 0 ) return( lhs/rhs ) ; fprintf( stderr, "Primitive %s unknown\n", op ) ; exit( 1 ) ; }
Tabulation
- Domaines tabulés
- Accès global aux objets d'un même type
- Le premier membre doit être une chaine (unique par objet):
tabulated user = name:string x id:int x passwd x shell:string ;
- Permet une dissociation entre définition et référence
- Notion de object-id en programmation persistente
- Unicité des objets (name est une clé utilisée à la création des objets)
- Utilisation: déallocation, accès fichiers, ...
user pierre, francois, michel ; list roots = CONS( USER, pierre, CONS( USER, francois, NIL )) ; list admins = CONS( USER, michel, CONS( USER, pierre, NIL )) ; group root = make_group( roots ) ; group admin = make_group( admins ) ; free_group( admin ) ; --> CAR( group_elements( root )) ????
- Si user est tabulté, pas de libération automatique
- Chaque domaine tabulé tab définit tab_domain
- Manipulation globale d'objets tabulés en mémoire:
TABULATED_MAP( u, { fprintf( stdout, "User %s\n", user_name( u )) ; }, user_domain ) ;
- Libération explicite (même en CommonLISP) et IO:
FILE *db = fopen("user.database","w"); gen_write_tabulated( db, user_domain ) ; gen_free_tabulated( user_domain ) ;
- Remarque: Attention au problème de partage
- Remarque: Tabulation automatique dans une future version de NewGen
Importation
- Définition modulaire de spécifications NewGen
- Spécification multifichiers
- Complétude requise (mais voir external)
-- network.newgen import workstation from "Include/workstation.newgen" ; import gateway from "Include/gateway.newgen" ; network = nodes:node* ; node = workstation + gateway + repeater:node*;
- newgen donne l'ordre pour gen_read_spec
% newgen -C network.newgen \ workstation.newgen gateway.newgen GEN_READ_SPEC order: workstation.spec gateway.spec network.spec %
Externes
- Compatibilité ascendante ("dusty data")
- Utilisation de NewGen en présence de données non-NewGen
- Contrainte: Compatible avec char * en C et pointeur en CommonLISP
external punch ; import laser from "printers.newgen" ; import daisy from "printers.newgen" ; output_device = laser + daisy + punch ;
- Routines de lecture, écriture, libération et copie à fournir par l'utilisateur
- gen_init_external à appeler avant toute utilisation.
- Définition de DOMAIN pour premier argument de gen_init_external
- Tabulation des identificateurs
- Définition séparée de identifier:
-- File identifier.newgen tabulated identifier = name:string ;
- Forme ASCII compacte external
-- File expression.newgen import identifier from "identifier.newgen" ; external compacted ; binary = operator:string x lhs:expression x rhs:expression ; binding = name:string x value:expression ; expression = constant:int + identifier + compacted + binary + let ; let = bindings:binding* x expression ;
- Appel de newgen:
% newgen -C expression.newgen \ identifier.newgen GEN_READ_SPEC order identifier.spec expression.spec %
- Création des identificateurs:
| Identifier { $$ = make_expression( is_expression_identifier, make_identifier( $1 )) ; }
- Initialisation de compacted dans main:
void compacted_write( FILE *, compacted ) ; compacted compacted_read( FILE *, char (*)()) ; void compacted_free( compacted ) ; compacted compacted_copy( compacted ) ; main() { gen_read_spec( "identifier.spec", "expression.spec", (char*) NULL) ; gen_init_external( COMPACTED, compacted_read, compacted_write, compacted_free, compacted_copy ) ; yyparse() ; fprintf( stdout, "%d\n", constant_fold( Top )) ; #ifdef DEBUG fprintf( stderr,"Bound Identifiers:\n"); TABULATED_MAP( i, { fprintf( stderr, "%s,", identifier_name( i )) ; }, identifier_domain ) ; #endif free_expression( Top ) ; gen_free_tabulated(identifier_domain); }
- Support pour externes
void compacted_write( fd, c ) FILE *fd ; compacted c ; { int val = *(int *)(char *)c ; fprintf( fd, "%d", (int)log2( (double)val )) ; } compacted compacted_read( fd, read ) FILE *fd ; char (*read)() ; { int *c = (int *)malloc( sizeof( int )) ; fscanf( fd, "%d", c ) ; return( (compacted)(char *)c ) ; } void compacted_free( c ) compacted c ; { free( c ) ; } compacted compacted_copy( c ) compacted c ; { int *cc = (int *)malloc( sizeof( int )); *cc = *c ; return( (compacted)(char *)cc ) ; }
- Intérêt: Facilité de prototypage, développement, spécifications
- Permettre le développement ``souple'': LISP fonctionnel, LISP impératif, C
- Intéropérabilité C/LISP limitée en général (foreign function interface)
- NewGen: pont entre deux mondes
- CommonLISP: de facto standard, plus norme ANSI en préparation
- Similitude de programmation (listes), mais GC
- Compatibilité ``fichiers'' ou pipes
- Type NewGen: defstruct
- Adaptation à la syntaxe CommonLISP:
(setf pierre (make-user :name "jouvelot" :id 110 :passwd passwd-undefined :shell "/usr/local/bin/ksh"))
- Modification via setf:
(setf (user-id pierre) 120)
- Le switch de C est définit comme une macro:
(gen-switch (expression-tag e) (is-expression-constant (expression-constant e)) (:default (error "~%Incorrect tag")))
- gen-switch peut aussi créer des liaisons:
(gen-switch (expression-tag e) is-expression-constant c) c) (:default (error "~%Incorrect tag"[+] )
- Pas de libération explicite (sauf pour domaines tabulés)
- Visibilité des fonctions de manipulation via use-package
- Création des fichiers Lisp:
% newgen -lisp expression.newgen \ identifier.newgen REQUIRE order: identifier.cl expression.cl % ls expression.cl expression.spec identifier.cl identifier.spec %
- require pour chargement des fichiers
- Pas d'arguments à gen-read-spec: auto-initialisation des fichiers CommonLISP
(require "genLisplib") ; Newgen basic
; Lisp library
(require "identifier") ; Newgen-generated
; header files
(require "expression")
(use-package '(:newgen
:identifier
:expression))
(defun test (&optional (file *standard-input*))
"FILE contains the parser output."
(gen-read-spec)
(let
*standard-input* (open file[+]
)
(eval-expression (read-expression) '())))
(defun eval-expression (e env) (gen-switch e is-expression-constant c) c) ((is-expression-identifier i) (eval-identifier i env[+] is-expression-binary b) (eval-binary b env[+] is-expression-let l) (eval-let l env[+] )) (defun eval-identifier (i env) (let var-val (assoc (identifier-name i) env :test #'string-equal[+] ) (if (null var-val) (error "~%Unbound identifier ~S" (identifier-name i)) (cdr var-val)))) (defparameter operators ` ,"add" . ,\#'+) (,"sub" . ,\#'-) (,"times" . ,\#'*) (,"cons" . ,\#'cons) (,"eq" . ,\#'eq[+] ) (defun eval-binary (b env) (let op (assoc (binary-operator b) operators :test #'string-equal[+] ) (if (null op) (error "~\%Incorrect op code ~S" (binary-operator b)) (funcall (cdr op) (eval-expression (binary-lhs b) env) (eval-expression (binary-rhs b) env))))) (defun eval-let (l env) (let new-env (mapcar #'(lambda (b) `(,(binding-name b) . ,(eval-expression (binding-value b) env[+] ) (let-bindings l)))) (eval-expression (let-expression l) (append new-env env))))
Exemples
- gen-recurse: Couplage appels récursifs et dispatch:
(defun eval-expression (e env) (gen-recurse e expression tag) tag) (identifier (cdr (assoc (identifier-name i) env :test #'string-equal[+] ) binary lhs rhs) (funcall (cdr (assoc (binary-operator b) operators :test #'string-equal[+] lhs rhs))))
- Opérations implicitement itérées (sur listes)
- Utilisation des domaines tabulés:
(defun gensym () "Generate a brand new identifier." (do i 0 (+ i 1[+] ) gen-find-tabulated (format nil "gensym-~D" i) identifier-domain) (make-identifier :name (format nil "gensym-~D" i[+] )))
- Outil ``léger'':
- 6 klignes de C, Yacc, Lex et Korn shell
- 800 lignes de CommonLISP
- Compilateur:
- token.l
- Lexèmes du langage NewGen
- gram.y
- Syntaxe du langage NewGen
- build.c
- Compilation en fichier spec, création dynamique des descripteurs de domaines
- genC.c, genLisp.c
- Génération de code C et Lisp
- newgen
- Commande shell
- Run time C/Lisp:
- genClib.c,genLisplib.cl
- Bibliothèque run-time C et Lisp
- list.c
- Support de listes en C
- set.c,set.cl
- Support d'ensembles en C et Lisp
- hash.c
- Package de hash-coding dynamique (interne et externe - set) par open coding
- read.l,read.y
- Parser C de structures de données NewGen (utilisation de macros en Lisp)
- Structure mémoire taggée, avec inlining
- Vérification dynamique de types (gen_debug)
- Parcours générique parallèle descripteurs/structures: gestion de partage, écriture, copie, libération
- Un mot supplémentaire pour objets tabulés
- Ecriture compacte sur disque (mais pas binaire)
- PIPS: Paralléliseur Interprocédural de Programmes Scientifiques
- Tranformation DO en DOALL (Fortran77)
- Projet de recherche: structure modulaire en phases (50 klignes)
- Prise en compte complète de Fortran:
-- Entities tabulated entity = name:string x type x value x storage ; -- Expressions expression = reference + range + call ; reference = variable:entity x indices:expression* ; range = lower:expression x upper:expression x increment:expression ; call = function:entity x arguments:expression* ; -- Statements statement = label:entity x number:int x comments:string x instruction ; instruction = block:statement* + test + loop + call + unstructured ; test = condition:expression x true:statement x false:statement ; loop = index:entity x range x body:statement x label:entity ; unstructured = control x exit:control ; control = statement x predecessors:control* x successors:control* ;
- Gestion de la persistence par pipsdbm
- Prototypes du linker incrémental, prettyprinter et détection des réductions en CommonLISP
- IDL
- Outil logiciel (North-Carolina U., développé chez Tartan)
- Génération de structures de données (C, Pascal)
- Description des phases (processes) et des interconnexions
- GC
- Forme limitée de sous-typage
- Assertions (définition d'un langage complet d'assertions)
- Format binaire
- Conclusion: industriel, moins abstrait, plus lourd
- OODB (O2, ORION, VBASE, Exodus, Postgres)
- Extension des modèles orienté-objet aux DB (limitation du modèle relationnel)
- Manipulation et langage de requêtes intégrés dans un langage classique (CO2, CommonLISP, C, C++)
- Orienté accès interactif (SQL)
- Conclusion: plus puissant que NewGen, accès coûteux (persistence implicite)
- OOL (C++, CLOS, Smalltalk, Trellis)
- Dépendent d'un langage, pas upward compatible (sauf C++)
- Plus puissant que NewGen: héritage, redéfinition
- Performances?
- Pas de persistence
- Langages Persistants
- Nécessite des modifications de compilateurs (Pascal/P, PS-algol)
- Pas de standard dans les primitives
- RPC, XDR
- Bas niveau (orienté transferts de données)
- Pas de gestion de sharing ou de cycle
- NewGen: outil de génie logiciel (Ecole des Mines de Paris)
- Abstraction fonctionnelle, Multi-langages (C, CommonLISP), Compatibilité
- Prototype ``Public Domain'' distribué par ftp anonyme (Mines, Bull, NASA, Boeing, ...)
- Applications: PIPS, PMACS (Bull)
- Futur: extensions aux fonctions (tabulation automatique):
typing = expression -> type ;
Document Actions