Existe-t-il une solution de rechange pour les systèmes flex/bison qui soit utilisable sur les systèmes 8 bits intégrés?

j'écris un petit interpréteur pour un langage de base simple comme un exercice sur un microcontrôleur AVR en C en utilisant la chaîne d'outils avr-gcc. Cependant, je me demande s'il y a des outils open source là-bas qui pourraient m'aider à écrire le lexer et l'analyseur.

si j'écrivais ceci pour exécuter sur ma machine Linux, je pourrais utiliser flex/bison. Maintenant que je me suis limité à une plateforme 8-bit, je dois tout faire à la main, ou pas?

77
demandé sur Lundin 2010-02-11 19:38:02

6 réponses

j'ai implémenté un analyseur pour un langage de commande simple ciblé pour le ATmega328p . Cette puce a 32k ROM et seulement 2K RAM. La RAM est certainement la limite la plus importante -- si vous n'êtes pas encore lié à une puce en particulier, choisissez-en une avec autant de RAM que possible. Cela rendra votre vie beaucoup plus facile.

j'ai d'abord envisagé l'utilisation de flex/bison. Je me suis prononcé contre cette option pour deux raisons majeures:

  • par défaut, Flex & Bison dépendent de certaines fonctions de bibliothèque standard (en particulier pour l'E/S) qui ne sont pas disponibles ou ne fonctionnent pas de la même manière dans avr-libc. Je suis presque sûr qu'il y a des solutions de rechange soutenues, mais il s'agit d'un effort supplémentaire que vous aurez besoin de prendre en compte.
  • "151990920 AVR a un , Harvard l'Architecture . C n'est pas conçu pour tenir compte de cela, donc même les variables constantes sont chargées dans la RAM par défaut . Vous doivent utiliser des macros/fonctions spéciales pour stocker et accéder aux données dans flash et EEPROM . Flex & Bison créent quelques relativement grandes tables de recherche, et ceux-ci mangeront votre RAM assez rapidement. A moins que je ne me trompe (ce qui est tout à fait possible) vous devrez éditer la source de sortie afin de profiter des interfaces Flash & EEPROM spéciales.

après avoir rejeté Flex & Bison, I partis à la recherche d'autres outils du générateur. En voici quelques-unes que j'ai considérées:

vous pourriez aussi vouloir jeter un oeil à comparaison de Wikipedia .

finalement, j'ai fini par coder à la main le lexer et le parser.

pour l'analyse j'ai utilisé un analyseur de descente récursif. Je pense que Ira Baxter a déjà fait un travail adéquat de couvrir ce sujet, et il ya beaucoup de tutoriels en ligne.

pour mon lexer, j'ai écrit des expressions régulières pour tous mes terminaux, j'ai fait un diagramme de la machine d'état équivalente, et je l'ai implémentée comme une fonction géante en utilisant goto pour sauter entre les États. Cela a été fastidieux, mais les résultats ont bien fonctionné. Comme un de plus, goto est un excellent outil pour implémenter des machines d'état -- tous vos états peuvent avoir des étiquettes claires juste à côté du code pertinent, il n'y a pas d'appel de fonction ou de variable d'état au-dessus, et c'est aussi rapide que vous pouvez obtenir. C n'a vraiment pas une meilleure construction pour construire des machines statiques.

quelque chose à quoi penser: les lexers ne sont vraiment qu'une spécialisation des parsers. La plus grande différence est que les grammaires régulières sont généralement suffisantes pour analyse lexicale, alors que la plupart des langages de programmation ont (pour la plupart) des grammaires sans contexte. Donc, il n'y a vraiment rien qui vous empêche d'implémenter une lexer comme un parser de descente récursive ou d'utiliser un générateur d'analyseur pour écrire une lexer. C'est juste généralement pas aussi pratique que l'utilisation d'un plus outil spécialisé.

50
répondu Steve S 2018-03-19 14:59:17

si vous voulez un moyen facile de code parsers, ou vous êtes serré sur l'espace, vous devez main-code un parseur de descente récursive; ceux-ci sont essentiellement LL (1) parsers. Cela est particulièrement efficace pour les langues qui sont aussi "simple" que de base. (J'ai fait plusieurs de ces retour dans les années 70!). La bonne nouvelle est que ceux-ci ne contiennent pas de code de bibliothèque; juste ce que vous écrivez.

ils sont assez faciles à coder, si vous avez déjà une grammaire. Tout d'abord, vous avez à se débarrasser des règles récursives de gauche (par exemple, X = X Y ). C'est généralement assez facile à faire, donc je laisse comme un exercice. (Vous n'avez pas à faire cette liste formant des règles; voir la discussion ci-dessous).

alors si vous avez la règle BNF du formulaire:

 X = A B C ;

créer un sous-programme pour chaque élément de la règle (X, A, B, C) qui renvoie un booléen en disant "j'ai vu la construction syntaxique correspondante". Pour X, code:

subroutine X()
     if ~(A()) return false;
     if ~(B()) { error(); return false; }
     if ~(C()) { error(); return false; }
     // insert semantic action here: generate code, do the work, ....
     return true;
end X;

De même pour A, B, C.

si un token est un terminal, écrire un code qui vérifie le flux d'entrée de la chaîne de caractères qui rend le terminal. Par exemple, pour un nombre, vérifier que le flux d'entrée contient des chiffres et avancer le flux d'entrée curseur dernières les chiffres. C'est particulièrement facile si vous sont parsing hors d'un tampon (pour de base, vous avez tendance à obtenir une ligne à la fois) en avançant ou non un pointeur de balayage de tampon. Ce code est essentiellement la partie lexer de analyseur.

si votre règle de la BNF est récursive... ne vous inquiétez pas. Juste le code de l'appel récursif. Cela traite des règles de grammaire comme:

T  =  '('  T  ')' ;

peut être codé comme:

subroutine T()
     if ~(left_paren()) return false;
     if ~(T()) { error(); return false; }
     if ~(right_paren()) { error(); return false; }
     // insert semantic action here: generate code, do the work, ....
     return true;
end T;

si vous avez une règle BNF avec une alternative:

 P = Q | R ;

puis code P avec des choix alternatifs:

subroutine P()
    if ~(Q())
        {if ~(R()) return false;
         return true;
        }
    return true;
end P;

parfois, vous rencontrerez des règles de formation de liste. Ils ont tendance à être laissés récursive, et cette affaire est facile à gérer. Exemple:

L  =  A |  L A ;

vous pouvez coder ceci comme:

subroutine L()
    if ~(A()) then return false;
    while (A()) do // loop
    return true;
end L;

Vous pouvez coder plusieurs centaines de règles de grammaire dans un jour ou deux de cette façon. Il y a plus de détails à remplir, mais les bases ici devraient être plus que suffisantes.

Si vous êtes vraiment serré sur l'espace, vous pouvez créer une machine virtuelle ces idées. C'est ce que j'ai fait dans les années 70, quand 8K 16 mots de bits était ce que vous pouviez obtenir.


si vous ne voulez pas coder cela à la main, vous pouvez l'automatiser avec un metacompiler ( Meta II ) qui produit essentiellement la même chose. Ces sont hallucinants techniques de plaisir et prend vraiment tout le travail de faire cela, même pour de grandes grammaires.

août 2014:

je reçois beaucoup de demandes pour "comment construire un AST avec un analyseur". Pour plus de détails à ce sujet, qui élabore essentiellement cette réponse, Voir mon autre SO réponse https://stackoverflow.com/a/25106688/120163

juillet 2015:

Il ya beaucoup de gens qui veulent écrire un évaluateur d'expression simple. Vous pouvez faire cela en faisant le même genre de choses que le lien "AST builder" ci-dessus suggère; faites juste de l'arithmétique au lieu de construire des noeuds d'arbre. Voici un évaluateur d'expression fait par ici .

179
répondu Ira Baxter 2018-06-28 13:30:02

vous pouvez utiliser flex/bison sur Linux avec son gcc natif pour générer le code que vous allez ensuite compiler avec votre avr gcc pour la cible embarquée.

11
répondu Paul R 2010-02-11 16:59:58

GCC peut compiler à une variété de plates-formes, mais vous exécutez flex et bison sur la plate-forme sur laquelle vous exécutez le compilateur. Ils crachent juste le code C que le compilateur construit ensuite. Testez - le pour voir la taille réelle de l'exécutable résultant. Notez qu'ils ont lancé des bibliothèques de temps ( libfl.a etc.) que vous devrez également effectuer une compilation croisée avec votre cible.

2
répondu ConcernedOfTunbridgeWells 2010-02-11 17:04:11

Essayez De Boost::Spirit. C'est une bibliothèque d'en-tête seulement que vous pouvez déposer et construire un analyseur très rapide et propre complètement en C++. Les opérateurs surchargés en C++ sont utilisés à la place d'un fichier grammatical spécial.

-1
répondu Erik Aronesty 2015-06-24 17:22:32

au lieu de réinventer la roue, regardez LUA: www.lua.org . Il s'agit d'un langage d'interprétation destiné à être intégré à d'autres logiciels et utilisé sur des systèmes à petite échelle, comme les systèmes intégrés. L'arbre d'analyse de syntaxe intégrée, la logique de contrôle, les mathématiques et le support des variables - pas besoin de réinventer quelque chose que des milliers d'autres ont déjà débogué et utilisé. Et il est extensible, ce qui signifie que vous pouvez ajouter à la grammaire en ajoutant votre propre C fonction.

-5
répondu Scott Hall 2012-10-01 11:49:16