Sunteți pe pagina 1din 10

13 792 857 membres 1 605 Membre 14081179

Search for articles, questions, tips

des articles Réponses rapides discussions fonctionnalités communauté Aidez-moi

Articles » Cycle de vie du développement » Génération de code » Général


Suivre  

Écrire un convertisseur MATLAB en C: analyseur et générateur


de code créés avec Bison / Flex
Shmuel Safonov , 21 novembre 2017

   5.00 (10 votes) Taux:

Utilisation de Bison / Flex pour la création du convertisseur de code d’un sous-ensemble du langage MATLAB vers le code C. Le convertisseur est
utilisé pour créer des applications et des bibliothèques natives à partir de code MATLAB.

Votre adresse email est-elle correcte? Vous êtes inscrit à nos newsletters mais votre adresse e-mail n'est pas confirmée ou n'a
pas été reconfirmée depuis longtemps. Veuillez cliquer ici pour recevoir un e-mail de confirmation afin que nous puissions
confirmer votre adresse e-mail et recommencer à vous envoyer des newsletters. Alternativement, vous pouvez mettre à jour
vos abonnements .

Source du téléchargement - 2.5 MB

introduction
Cet article décrit un convertisseur de code MATLAB-to-C écrit à l'aide du générateur d'analyseur syntaxique GNU Bison et du générateur
d'analyseur lexical GNU Flex. L'article explique les fonctionnalités de base de ces utilitaires et montre comment générer le code de sortie à partir
des résultats de l'analyseur. Ce convertisseur génère un code compilé et lié à la bibliothèque de support d'exécution. Une introduction à
l'utilisation de la bibliothèque est également fournie.

L'analyseur de code source et le générateur de code cible


L'analyseur de code MATLAB est généré (presque :-) automatiquement à partir de la description de la syntaxe du langage à l'aide des utilitaires
GNU Bison et GNU Flex. Ces utilitaires traitent le fichier de définition de la syntaxe (TmcParParser.Y) et le fichier de définition de lexer
(TmcParLexer.L) et génèrent le code C / C ++ de la machine à états de l'analyseur.

L'analyseur d'état résultant est alors appelé en tant que fonction:

Masquer le   code de copie


tmc_parse();

Fichier d'entrée pour Bison: la description de la syntaxe du langage source


L'utilitaire Bison est utilisé comme suit:
Masquer le   code de copie
win_bison -o TmcParParser.tab.cpp -d -v TmcParParser.Y
Bison traite le fichier de définition de syntaxe TmcParParser.Y et crée les fichiers C ++ TmcParParser.tab.cpp et TmcParParser.tab.hpp contenant le
code de l'analyseur. Comprenons la structure du fichier de définition de syntaxe.

Afin de créer un analyseur de langage, la syntaxe du langage source doit être définie par une technique de notation. Une des techniques de
notation utiles pour les langages de programmation est la forme Backus – Naur ( BNF ). La transcription de langue BNF est un ensemble de
règles de dérivation pour les symboles de langue . Le fichier de définition de syntaxe (TmcParParser.Y) contient cette transcription de langage.
Prenons un exemple de fragment de fichier: fichier:

Masquer   Shrink   Copier le code


%{
/// File: TmcParParser.Y
#include "tmcpar_tree.h"
%}
%name-prefix="tmcpar_"

%union
{
char *str;
double num;
LSP_IDENT ident;

struct tree_const_val *lsp_const_val;


class T_ident *lsp_ident_val;
class T_const *lsp_constant_val;
class T_expr_bin *lsp_binary_expression_val;
class T_expr_gen *lsp_expression_val;
}

%token <ident> IDENT


%token <num> NUMBER
%token <num> NUMBER_IM
%token <str> STRING

%token PLUS
%token MUL

%left PLUS
%left MUL

%token END_OF_INPUT

%type <lsp_ident_val> ident


%type <lsp_constant_val> const
%type <lsp_const_val> variant_val
%type <lsp_expression_val> expression
%type <lsp_binary_expression_val> bin_expr

%start module

%%

module
: file_hdr list_function END_OF_INPUT
{
$$=$2;
tmcpar_parsing_module = $$;
YYACCEPT;
}
;
expression : identifier
{
$$=(T_expr_gen*)($1);
}
| const
{
$$=(T_expr_gen*)($1);
}
| bin_expr
{
$$=(T_expr_gen*)($1);
}

;
identifier : IDENT
{
$$ = create_identifier($1,(int)tmcpar_lineno,(int)tmcpar_colno);
}
;
const : variant_val
{
$$ = create_constant((enum CONST_VAL_TYPES)0,$1,(int)tmcpar_lineno,(int)tmcpar_colno);
}
;
variant_val : NUMBER
{
$$ = make_number($1,0,const_val_real);
}
| NUMBER_IM
{
$$ = make_number(0,$1,const_val_complex);
}

| STRING
{
$$ = make_string($1);
}
;

bin_expr : expression PLUS expression


{
$$ = create_binary_expression("+",$1,$3,(int)tmcpar_lineno,(int)tmcpar_colno);
}
| expression MUL expression
{
$$ = create_binary_expression("*",$1,$3,(int)tmcpar_lineno,(int)tmcpar_colno);
}
;

%%
// C++ code here
...

Ce fichier se compose de 4 sections, séparées par %{, %}et des %%séparateurs. La 1ère section (séparée par% {,%}) contient du code C / C ++,
elle peut inclure les fichiers qui définissent les classes et les variables utilisées. La deuxième section contient les définitions de type. La 3ème
section contient les règles de syntaxe et les actions sémantiques à effectuer à chaque correspondance de règle. La machine d'état de l'analyseur
fonctionne de manière itérative. Une nouvelle règle de correspondance est appliquée à chaque état, sinon l'analyseur appelle en interne la
fonction d'analyse lexicaletmcpar_lexqui provient du flux d’entrée suivant le lexema. Chaque règle a des arguments d'entrée ($ 1, $ 2 etc.) et
la sortie ($$). Un argument d'entrée peut être le résultat d'une règle d'enfants ou d'un lexema renvoyé par l'analyseur lexical. Ces arguments sont
représentés de manière interne dans la machine à états par une union avec des champs définis par une %unioncommande pouvant être du type
de données renvoyé par le lexema ou par une autre règle. Le type des arguments qui sont des règles est défini par la %typecommande. Le type
des arguments qui sont des données de lexema est défini par la %tokencommande. L'analyseur lexical associé à la valeur de lexema renvoie
également la valeur du code de jeton.

Par exemple, la définition


Masquer le   code de copie

%union
{
double num;
...
}
%token <num> NUMBER

signifie que lorsque le lexer retourne un nombre, sa valeur de jeton renvoyée est NUMBERet le type de données est double.

La définition

Masquer le   code de copie

%union
{
class T_expr_gen *lsp_expression_val;
...
}
%type <lsp_expression_val> expression

signifie que lorsque la règle de l'analyseur renvoie un expression, son type de données est class T_expr_gen *.
La règle

Masquer le   code de copie

expression : identifier
{
$$=(T_expr_gen*)($1);
}
| const
{
$$=(T_expr_gen*)($1);
}
| bin_expr
{
$$=(T_expr_gen*)($1);
}
;

signifie qui expressionpeut être ident, constou bin_expr.

La règle

Masquer le   code de copie


bin_expr : expression PLUS expression
{
$$ = create_binary_expression("+",$1,$3,(int)tmcpar_lineno,(int)tmcpar_colno);
}
| expression MUL expression
{
$$ = create_binary_expression("*",$1,$3,(int)tmcpar_lineno,(int)tmcpar_colno);
}
;

signifie qu'il bin_expr s'agit d'une séquence de symboles expression , PLUSet expresionou expression, MULet expresion.

Une définition de règle contient également les actions sémantiques , c'est-à-dire le code C ++ à exécuter lors de la correspondance de règles. Par
exemple

Masquer le   code de copie

bin_expr : expression PLUS expression


{
$$ = create_binary_expression("+",$1,$3,(int)tmcpar_lineno,(int)tmcpar_colno);
}
...

signifie que lorsque la règle expression PLUS expressionest vérifiée, le nœud de type class T_expr_bin *sera créé par la
fonction create_binary_expression()et renvoyé par la règle sous forme de symbole bin_expr.

Ces nœuds d'arbre renvoyés à chaque correspondance de règle sont connectés à un arbre d'analyse. La procédure d'analyse est terminée lorsque
le symbole principal moduledéfini par la %start modulecommande est renvoyé. Dans l'exemple présenté, les noeuds sont connectés les
uns aux autres dans un arbre ou sous forme de liste.

Fichier d'entrée pour Flex: logiques d'analyseur lexical


L'analyseur syntaxique décrit dans la section précédente accepte les lexémas en entrée. La description de syntaxe est en fait une description de
langage dépourvue de contexte, mais l’analyseur est basé sur l’analyseur lexical qui devrait résoudre la dépendance au contexte. Dans cet
exemple, l'analyseur lexical est généré à l'aide de l'utilitaire GNU Flex. L'utilitaire Flex est utilisé comme suit:

Masquer le   code de copie

win_flex -olex.tmcpar_.cpp TmcParLexer.L

Cet utilitaire produit l'analyseur lexical à partir du fichier de définition de lexer TmcParLexer.L. La sortie est le fichier C ++ est lex.tmcpar_.cpp. Ce
fichier contient l’implémentation de la fonction lexer tmcpar_lex()appelée en interne par l’analyseur produit par Bison. Le fichier de
définition de lexer utilise les définitions de jeton fournies par le fichier de définition de l'analyseur. Etant donné que le lexer doit renvoyer les
définitions de jeton, Bison doit être exécuté en premier.

Voici un fragment du fichier lexer:

Masquer   Shrink   Copier le code


/// File: TmcParLexer.L

%option prefix = "tmcpar_"


%option yylineno
%option never-interactive

%{
#include "tmcpar_tree.h"
#include "TmcParParser.tab.hpp"
%}

%Start ExpectQuoteAsCTRANSPOSE
IDENTIFIER [a-zA-Z][_a-zA-Z0-9]*
%%

{IDENTIFIER} {
get_identifier();
return ReturnLspToken( IDENT);
}

%%

char* get_identifier()
{
//yyleng is length
//yytext is src
strcpy(tmcpar_lval.ident[0],yytext);
return tmcpar_lval.ident[0];
}

Comme dans le fichier de définition de Bison, le fichier de définition Flex est composé de sections séparées par %{, %}et %%. La première section
contient des options et des définitions pour les expressions régulières . La deuxième section contient les actions, c'est-à-dire le code qui est
exécuté lorsqu'une expression régulière est mise en correspondance. Une action peut être assortie de manière conditionnelle, par exemple, dans
le code suivant:
Masquer le   code de copie
<INITIAL>'[^'\r\f\n]*' {
register int i, size;
char* modified;
const int length = yyleng-2;
for (size = 0, i = 1;i <= length; size++, i++)
if (*(yytext+i) == '\'')
i++;
modified = alloc_string(size+1);
*(modified+size) = '\0';
for (size = 0, i = 1;i <= length; size++, i++)
{
*(modified+size) = *(yytext+i);
if (*(yytext+i) == '\'')
i++;
}
tmcpar_lval.str = modified;
yyinsert_comma_in_input(STRING);
return ReturnLspToken( STRING);
}

reconnaît la chaîne littérale et retourne le lexema STRING, il est exécuté uniquement dans l'état INITIAL. Le code suivant
Masquer le   code de copie

<ExpectQuoteAsCTRANSPOSE>' {
tmcpar_lval.int_value = CTRANSPOSE;
return ReturnLspToken( CTRANSPOSE ); }

reconnaît l'action de transposition (') et renvoie le lexema CTRANSPOSE; il est exécuté uniquement dans l'état ExpectQuoteAsCTRANSPOSE. L'état
initial est défini par l'option
Masquer le   code de copie

%Start ExpectQuoteAsCTRANSPOSE

Le passage à l'état INITIAL est effectué par macro


Masquer le   code de copie

BEGIN(INITIAL)

L'analyseur lexical essaie d'appliquer toutes les règles de la liste en fonction de son ordre. S'il réussit, le code de la règle est exécuté et peut
renvoyer le code de lexema à l'analyseur de syntaxe. La valeur lexema est renvoyée dans l'union tmcpar_lval.

Générateur de code de sortie


Lorsque l'analyse est terminée, un arbre d'analyse est créé. Chaque nœud de l’arbre est une classe C ++ qui représente une construction de
syntaxe. Par exemple, une constante est représentée par la classe T_const:

Masquer le   code de copie

class T_const : public T_expr_gen


{
...
public:
// The actual value
struct tag_tmsMatrix * const val ;
T_const(CONST_VAL_TYPES type,struct tree_const_val* v,int l,int c);
...
virtual void generate_rtl_node(CInstrList *ilist);// constant
};

Le constructeur de classe est appelé à partir de l'analyseur et stocke la valeur constante dans le membre T_const::val. Ensuite, lors de la
génération du code, la méthode virtuelle T_const::generate_rtl_nodecrée les instructions intermédiaires et les ajoute à la liste à
ilistpartir de laquelle le code C cible sera généré. Par exemple, le code C suivant sera généré pour la constante "1":
Masquer le   code de copie

tmcScalar(reg[11],1.000000000000000e+000);

Ceci est un appel à la fonction de bibliothèque d'exécution tmcScalarqui initialise la variable temporaire "registre" avec le résultat d'exécution
du noeud, la constante "1.0". La classe d'instruction intermédiaire CInstrcontient les informations sur le type d'instruction, le numéro de
"registre" en sortie et la valeur constante:
Masquer le   code de copie

class CInstr
{
public:
//! instruction operation types
enum instr_types
{
instr_create_matrix_empty,
instr_complex,
instr_scalar,
instr_create_cell_empty,
instr_create_string_empty,
instr_create_string,
instr_create_magic_colon,
...
};
instr_types m_inst_type;
CIReg outreg; // output register
double m_Real;
double m_Imag;
...
}

Le procédé T_const::generate_rtl_nodeattribue un nouveau numéro de "registre" pour la sortie d'instruction CInstr::outreg,


puis crée une instruction intermédiaire unique avec les valeurs constantes, puis ajoute l'instruction à la liste CInstrList. Le processus de
création du code intermédiaire est récursif et il est important que la sortie d'une expression enfant soit stockée CInstr::outreget utilisée
dans l'expression parent.

Lorsque la liste complète du code intermédiaire CInstrListpour a functionest terminée, le code cible est simplement imprimé par la
CInstrList::print_rtlméthode. Ceci est fait par la CInstr::gen_icodeméthode qui imprime le code C ou par la
CInstr::gen_asmcodeméthode, le code assembleur équivalent, par exemple:
Masquer le   code de copie

...
case instr_scalar: // make a scalar matrix
sprintf(buff,"%s,%.15e",get_reg_name(outreg).c_str(),m_Real);
retval=std::string("tmcScalar(").append(buff).append(");");
break;
...

Un exemple plus complexe est le code de fordéclaration. Il est implémenté par classe T_ctrl_cmd_for:

Masquer le   code de copie

class T_ctrl_cmd_for : public T_cmd_gen


{
private:
//! Expression to modify.
T_expr_gen * const lhs;
//! Expression to evaluate.
T_expr_gen * const expr;
//! List of commands to execute.
L_stmnt_gen * const list;
...
// while command's end and exit labels
CILabel m_end_ilabel;//! end label (for CONTINUE)
CILabel m_exit_ilabel;//! exit label (for BREAK or finish)
...
public:
T_ctrl_cmd_for (T_expr_gen *le, T_expr_gen *re,
L_stmnt_gen *lst,
int l = -1, int c = -1)
: T_cmd_gen (l, c), lhs (le), expr (re), list (lst)
{
...
}
...
public:
virtual void generate_rtl_node(CInstrList *ilist);//for
}

Par exemple, considérons le code

Masquer le   code de copie

for k=0:N-1
..
end

Ici , la variable kest analysée dans T_ctrl_cmd_for::lhs, l'expression 0:N-1dans expret le code du corps list.

La méthode T_ctrl_cmd_for::generate_rtl_nodeenregistre la variable "k" stockée dans T_ctrl_cmd_for::lhsla table des


symboles, appelle le expr->generate_rtlpour générer récursivement l'expression "0: N-1" et appelle list-
>generate_rtl_listpour générer les instructions de code de corps. Cette méthode produit un certain nombre d'instructions
intermédiaires ( instr_for_init, instr_for_next, instr_label, instr_goto) que Constain "goto". Les numéros d'étiquette de
ces instructions sont attribués et stockés dans T_ctrl_cmd_for::m_end_ilabelet T_ctrl_cmd_for::m_exit_ilabel.

Enfin, tout le code du fichier m est créé par la generate_rtl_listfonction:

Masquer le   code de copie

void generate_rtl_list()
// generate list of instructions from the list pointed by tmcpar_parsing_module
{
CInstrList InstList; // intermediate instructions list
class T_func_hdr *tfd;
extern L_stmnt_gen *tmcpar_parsing_module;// global pointer to parser tree, initialized by tmc_parse()
int NumLocalFuncs=0;
if (tmcpar_parsing_module)
{ /// Go through list of functions
for (L_stmnt_gen::iterator p = tmcpar_parsing_module->begin();
p != tmcpar_parsing_module->end();p++)
{
tfd = (T_func_hdr *)(*p);
Compiler.indFunc = NumLocalFuncs;
Compiler.indFunc_RTL = NumLocalFuncs;
InstList.store_function_ind(NumLocalFuncs);
tfd->generate_rtl_node(&InstList);/// Generate intermediate instructions
InstList.print_rtl(NumLocalFuncs);/// Convert instructions to the target code
InstList.reset_new_local_function();
NumLocalFuncs++;
}
}
}

Cordage de hachage
Afin d'optimiser les calculs avec des chaînes littérales et des noms de champs, une table de hachage est utilisée. Les noms de champs de
structure sont accessibles par codes de hachage. L'affectation de champ de structure de code

Masquer le   code de copie

S.x = 100;

est converti en
Masquer le   code de copie

tmcScalar(reg[3],1.000000000000000e+002);
tmcGetRefByFieldHcode(pRefHelper,S,0x00780000);/* x */
tmcAssign(pRefHelper,reg[3]);

La chaîne de nom de champ "x" est accessible par code de hachage 0x00780000. La table de hachage est pré-calculée lors de la conversion et
est chargée lors de l'initialisation de la bibliothèque d'exécution. Chaque chaîne connue au moment de la conversion a son code de hachage
unique et est accessible par ce code. Cette table de hachage contient les chaînes d'origine et leur code de hachage, par exemple:

Masquer le   code de copie

const struct CInitHashData InitHashData[]={


#include "TestO.hash_init.dat"
/** File TestO.hash_init.dat:
"?error",0x21290000,
"x",0x00780000,
*/
};

Table des symboles


La table des symboles est l'élément fondamental de chaque compilateur et générateur de code. La table des symboles contient les noms des
variables et des fonctions avec leurs prototypes. Il est implémenté par class symbol_table. Dans cet exemple, la table des symboles
n'était pas optimisée pour la vitesse d'accès. il est basé sur le listmodèle C ++ .

La base de données de fichiers source est implémentée par class CTmcFileList TmcFileList. Il gère la liste des fichiers source
réellement utilisés trouvés dans le chemin de recherche. La table des symboles est initialisée après la première passe du convertisseur lorsqu'il est
déjà possible de distinguer les variables des fonctions et d'obtenir les prototypes de fonctions.

Bibliothèque d'exécution
Le convertisseur est construit sur les mêmes principes qu’un compilateur, mais contrairement aux compilateurs qui génèrent un code d’assassin
ou de code C bas niveau (par exemple, MATISSE http://specs.fe.up.pt/tools/matisse/), il génère des appels. aux fonctions de la bibliothèque
d'exécution fournie. La raison en est l'absence d'informations de type sur les variables MATLAB. Chacune de ces variables et les variables
intermédiaires ( reg[]) sont implémentées par un seul type tmsMatrix.

La bibliothèque d'exécution implémente des opérations élémentaires sur les matrices et autres fonctions intégrées nécessaires à l'exécution du
code. Les opérations d'algèbre linéaire telles que la division matricielle sont implémentées à l'aide du package LAPACK. La bibliothèque
d'exécution doit être initialisée afin de créer des variables globales et de charger les chaînes hesh-table. Le code d'initialisation nécessaire est
généré par le convertisseur. Enfin, la bibliothèque devrait être non initialisée.

Comment exécuter le convertisseur


Le convertisseur accepte une liste de répertoires de recherche pour les fonctions externes en tant que paramètres, une table de fonctions
intégrées contenant des informations sur le nombre de paramètres d'entrée et de sortie et le nom de la fonction racine convertie. L'analyseur
s'appelle en deux passes.

Pour effectuer le premier passage, le convertisseur TMCCO est appelé par exemple

TMCCO -P -L -w TestO -r ./Stubs/TestO.m -h .. \ .. \ include / -i ./MatSRC/ -o ./OutL/

Il commence à analyser la fonction principale dans le fichier TestO.m. Si un symbole non attribué est trouvé, il est supposé être une fonction
externe et son fichier source est recherché dans les répertoires passés par le commutateur -i. Ces fichiers source sont traités de manière récursive
jusqu'à ce que tous les fichiers dépendants soient analysés. Il en résulte une liste de m-fichiers réellement utilisés (TestO.filelist.txt), une table
contenant tous les prototypes de fonctions (TestO.sym.dat) et le fichier include contenant les fonctions de prototypes (stdtmc.h). Le fichier
TestO.sym.dat inclut également les prototypes de toutes les fonctions intégrées.

Pour effectuer le second passage, le convertisseur TMCCO est appelé comme dans l'exemple suivant:

TMCCO -c -C -g2 -w TestO -r ./Stubs/TestO.m -i ./MatSRC/ -s ./MatSRC/ -s ./Stubs/ -o ./OutC/

Le traitement est effectué de la même manière que lors de la première passe, mais l'analyseur peut déjà distinguer les fonctions des variables et
générer le code de sortie. La machine à états de l'analyseur crée une présentation arborescente du code. Ensuite, la procédure de création de
code parcourt l’arborescence et imprime le code de sortie. Outre les fichiers C cibles, les fichiers d'initialisation d'exécution sont créés
(TestO.globals.c, TestO.globals.h, TestO._init_hash_data.c, TestO.hash_init.dat).

Dans cet exemple, trois types de code de sortie sont présentés: le code C (le plus utile), le code LISP (pour l'analyse du code) et le code ASM (à
des fins de démonstration, peuvent être traités par NASM Assembler et convertis directement en exécutable Win32. !)

La dernière version du convertisseur, des exemples, de la documentation et des outils supplémentaires sont disponibles sur le site de
téléchargement du compilateur TMC (https://sourceforge.net/projects/tmc-m-files-to-c-compiler).

Points d'interêts
Si la tâche consistant à écrire un analyseur de syntaxe est très délicate, la mise en œuvre d'une bibliothèque d'exécution exécutant les opérations
de base l'est encore plus. Le principal défi consistait à éviter toute fuite de mémoire, à optimiser le temps d'exécution et à prendre en charge
différentes plates-formes et compilateurs. L'utilisation de la mémoire a été déboguée à l'aide de mécanismes de débogage MSVC tels que la
_malloc_dbgfonction, les _CrtSetDbgFlagmacros et le comptage précis de la mémoire allouée.
Lorsque le code C était généré, il était difficile de le déboguer car les variables MATLAB étaient stockées dans des objets de structure complexe
alloués dynamiquement. Un simple débogueur a été développé pour afficher toutes les variables créées dans un contexte donné. Pour cela, un
code "de débogage" supplémentaire est généré (en utilisant le commutateur -d dans l'appel de l'analyseur) et la mémoire de processus est lue
par ReadProcessMemoryfonction. Dans l’environnement GNU GCC, la méthode la plus simple était d’utiliser le débogueur GDB et d’appeler
directement la fonction d’exécution tmcdispqui affiche le contenu d’une variable donnée.

Il était intéressant de produire un exécutable directement à partir du m-code. Cela s'est avéré très simple, grâce à l'article "Tiny PE. Création du
plus petit exécutable PE possible". Générer des appels à la bibliothèque d'exécution en C et en assembleur était approximativement de la même
complexité. Ainsi, le code assembleur a été généré par le convertisseur, un en-tête exécutable PE y a été attaché et le fichier de résultat assemblé
par l’assembleur NASM. Cet exercice a été fait uniquement pour le format x86; pour x64, le générateur de code assembleur doit être adapté à la
nouvelle conversion d’appel et les personnes intéressées peuvent l’essayer par lui-même :)

Le livre suivant peut être recommandé pour une compréhension plus approfondie de GNU Bison / Flex:

John R. Levine (2009). flex et bison. O'Reily Media Inc.

Remerciements
Je tiens à remercier particulièrement Vladimir Borisenko, de l’Université d’État de Moscou, pour son excellent cours sur les compilateurs.

Merci à Michael Medved pour son travail acharné sur la révision de cet article.

L'histoire
Première version.

Licence
Cet article, ainsi que tout code source et fichiers associés, est sous licence GNU
General Public License (GPLv3)

Partager
GAZOUILLEMENT FACEBOOK

A propos de l'auteur
Shmuel Safonov
Ingénieur Suivre
Israël ce membre

Ingénieur logiciel et expert en systèmes de contrôle chez Digital Feedback Technologies Ltd.
PhD, Université de Tel-Aviv, 2009
MS, Faculté de mécanique et de mathématiques de Lomonossov Université d’État de Moscou, 1994
Page du blog: http://csafonov.blogspot.co.il/
Personnel Projet open-source: compilateur TMC, convertisseur de code MATLAB en C (https://sourceforge.net/projects/tmc-m-files-to-c-
compiler)
Vous pouvez également être intéressé par ...
Flex / Bison sur MSBuild Principes de base de la
programmation iOS 12 avec Swift:
Intro & Chpt1

Comment: Projet "C" VS2012 / w Flex Construire des analyseurs lexicaux


& Bison rapides avec RE / flex - pourquoi un
autre générateur de scanner?

CQRS "sans serveur" utilisant la grille Écrire votre propre convertisseur RTF
d'événements et les fonctions
durables d'Azure

Commentaires et discussions
 
 
Ajouter un commentaire ou une question  Search Comments

alertes courrier électronique


Espacement Détendu   Disposition Ordinaire   Par page 25     Mettre à jour

- Il n'y a pas de message dans ce forum -

Permalink | Annoncez | Confidentialité | Biscuits | Conditions d'utilisation | Web mobile


Sélectionner une langue ▼
02 | 2.8.181207.3 | Dernière mise à jour 21 nov. 2017
Mise en page: fixe | Article Copyright 2017 by Shmuel Safonov
fluide Tout le reste Copyright © CodeProject , 1999-2018

S-ar putea să vă placă și