Comment écrire un interpréteur / analyseur de commandes?

22

Problème: exécutez les commandes sous la forme d'une chaîne.

  • exemple de commande:

    /user/files/ list all; équivalent à: /user/files/ ls -la;

  • un autre:

    post tw fb "HOW DO YOU STOP THE TICKLE MONSTER?;"

équivalent à: post -tf "HOW DO YOU STOP THE TICKLE MONSTER?;"

Solution actuelle:

tokenize string(string, array);

switch(first item in array) {
    case "command":
        if ( argument1 > stuff) {
           // do the actual work;
        }
}

Les problèmes que je vois dans cette solution sont:

  • Aucune vérification d'erreur autre que des ifs imbriqués sinon dans chaque cas. Le script devient très gros et difficile à maintenir.
  • Les commandes et les réponses sont codées en dur.
  • Aucun moyen de savoir si les drapeaux sont des paramètres corrects ou manquants.
  • Manque d'intelligence pour suggérer "vous pourriez vouloir exécuter $ command".

Et la dernière chose que je ne peux pas aborder est les synonymes dans différents encodages, par exemple:

case command:
case command_in_hebrew:
    do stuff;
break;

Le dernier peut être trivial, mais bon, ce que je veux voir, ce sont les solides fondations de ce type de programme.

Je programme actuellement cela en PHP mais je pourrais le faire en PERL.

alfa64
la source
Je ne vois pas du tout comment cela se rapporte spécifiquement à PHP. Il y a déjà beaucoup de fils sur cet interprète / compilateur sur SO et SE.
Raffael
3
Personne n'a mentionné getopt?
Anton Barkovsky
@AntonBarkovsky: Oui. Voir mes liens. Je pense que des réponses comme celles d'Ubermensch sont tout simplement trop compliquées pour ce que l'OP essaie de faire.
quentin-starin
1
J'ai également cité une approche simple utilisant RegExp. La réponse est également mise à jour
Ubermensch
N'a pas mentionné de programme spécifique. lang. vous pouvez ajouter une balise "c", une balise "ruby", une balise "php", peut-être qu'il existe une bibliothèque opensource, une bibliothèque standard ou "couramment utilisée, pas encore une bibliothèque standard". pour votre progr. lang.
umlcat

Réponses:

14

Permettez-moi d'admettre franchement, la construction de l'analyseur est un travail fastidieux et se rapproche de la technologie du compilateur, mais la construction de celui-ci se révélerait être une bonne aventure. Et un analyseur vient avec un interprète. Vous devez donc construire les deux.

Une introduction rapide à l'analyseur et aux interprètes

Ce n'est pas trop technique. Les experts ne s'inquiètent donc pas de moi.

Lorsque vous introduisez une entrée dans un terminal, le terminal divise l'entrée en plusieurs unités. L'entrée est appelée expression et les unités multiples sont appelées jetons. Ces jetons peuvent être des opérateurs ou des symboles. Donc, si vous entrez 4 + 5 dans une calculatrice, cette expression est divisée en trois jetons 4, +, 5. Le plus est considéré comme un opérateur avec 4 et 5 symboles. Ceci est transmis à un programme (considérez-le comme un interpréteur) qui contient la définition des opérateurs. Sur la base de la définition (dans notre cas, ajoutez), il ajoute les deux symboles et renvoie le résultat au terminal. Tous les compilateurs sont basés sur cette technologie. Le programme qui divise une expression en plusieurs jetons est appelé un lexer et le programme qui convertit ces jetons en balises pour un traitement et une exécution ultérieurs est appelé analyseur.

Lex et Yacc sont les formes canoniques pour la construction de lexers et d'analyseurs basés sur la grammaire BNF sous C et c'est l'option recommandée. La plupart des analyseurs sont un clone de Lex et Yacc.

Étapes de la construction d'un analyseur / intrépète

  1. Classez vos jetons en symboles, opérateurs et mots clés (les mots clés sont des opérateurs)
  2. Construisez votre grammaire en utilisant le formulaire BNF
  3. Écrire des fonctions d'analyseur pour vos opérations
  4. Compilez-le en un programme

Donc, dans le cas ci-dessus, vos jetons d'addition seraient des chiffres et un signe plus avec une définition de ce qu'il faut faire avec le signe plus dans le lexer

Notes et astuces

  • Choisissez une technique d'analyse qui évalue de gauche à droite LALR
  • Lisez ce livre de dragon sur les compilateurs pour en avoir une idée. Personnellement, je n'ai pas fini le livre
  • Ce lien donnerait un aperçu ultra-rapide de Lex et Yacc sous Python

Une approche simple

Si vous avez juste besoin d'un mécanisme d'analyse simple avec des fonctions limitées, transformez votre exigence en une expression régulière et créez simplement un tas de fonctions. Pour illustrer, supposons un analyseur simple pour les quatre fonctions arithmétiques. Vous devez donc appeler l'opérateur en premier puis la liste des fonctions (similaire à lisp) dans le style (+ 4 5)ou (add [4,5])alors vous pouvez utiliser un simple RegExp pour obtenir la liste des opérateurs et les symboles à utiliser.

La plupart des cas courants pourraient être facilement résolus par cette approche. L'inconvénient est que vous ne pouvez pas avoir beaucoup d'expressions imbriquées avec une syntaxe claire et que vous ne pouvez pas avoir de fonctions faciles d'ordre supérieur.

Ubermensch
la source
2
C'est l'un des moyens les plus difficiles. Séparer les passes de lexing et d'analyse, etc. - il est probablement utile pour implémenter un analyseur haute performance pour un langage très complexe mais archaïque. Dans le monde moderne, l'analyse syntaxique sans lexer est une option par défaut la plus simple. Les combinateurs d'analyse ou eDSL sont plus faciles à utiliser que les préprocesseurs dédiés comme Yacc.
SK-logic
D'accord avec SK-logic mais comme une réponse détaillée générale est requise, j'ai suggéré Lex et Yacc et quelques bases de l'analyseur. getopts suggéré par Anton est également une option plus simple.
Ubermensch
c'est ce que j'ai dit - lex et yacc sont parmi les moyens d'analyse les plus difficiles, et même pas assez génériques. L'analyse Lexerless (par exemple, packrat, ou simple type Parsec) est beaucoup plus simple pour un cas général. Et le livre Dragon n'est plus une introduction très utile à l'analyse - il est trop obsolète.
SK-logic
@ SK-logic Pouvez-vous recommander un meilleur livre mis à jour. Il semble couvrir toutes les bases pour une personne essayant de comprendre l'analyse syntaxique (au moins dans ma perception). Concernant lex et yacc, bien que difficile, il est largement utilisé et de nombreux langages de programmation assurent sa mise en œuvre.
Ubermensch
1
@ alfa64: assurez-vous de nous informer lorsque vous coderez réellement une solution basée sur cette réponse
quentin-starin
7

Tout d'abord, quand il s'agit de grammaire, ou comment spécifier des arguments, n'inventez pas les vôtres. La norme de style GNU est déjà très populaire et bien connue.

Deuxièmement, puisque vous utilisez une norme acceptée, ne réinventez pas la roue. Utilisez une bibliothèque existante pour le faire pour vous. Si vous utilisez des arguments de style GNU, il existe presque certainement déjà une bibliothèque mature dans la langue de votre choix. Par exemple: c # , php , c .

Une bonne bibliothèque d'analyse des options imprimera même une aide formatée sur les options disponibles pour vous.

EDIT 12/27

Il semble que ce soit plus compliqué que ça.

Lorsque vous regardez une ligne de commande, c'est vraiment très simple. Ce ne sont que des options et des arguments pour ces options. Il y a très peu de problèmes compliqués. L'option peut avoir des alias. Les arguments peuvent être des listes d'arguments.

Un problème avec votre question est que vous n'avez pas vraiment spécifié de règles pour le type de ligne de commande que vous souhaitez traiter. J'ai suggéré la norme GNU, et vos exemples s'en rapprochent (même si je ne comprends pas vraiment votre premier exemple avec le chemin comme premier élément?).

Si nous parlons de GNU, une seule option ne peut avoir que des formes longues et courtes (caractère unique) comme alias. Tout argument contenant un espace doit être entouré de guillemets. Plusieurs options de formulaire court peuvent être chaînées. Les options de forme courte doivent être précédées d'un seul tiret, la forme longue de deux tirets. Seule la dernière des options de forme courte chaînées peut avoir un argument.

Tout est très simple. Tout cela est très courant. Également implémenté dans toutes les langues que vous pouvez trouver, probablement cinq fois.

Ne l'écris pas. Utilisez ce qui est déjà écrit.

À moins que vous n'ayez à l'esprit autre chose que les arguments de ligne de commande standard, utilisez simplement l'une des NOMBREUSES bibliothèques déjà testées qui le font.

Quelle est la complication?

quentin-starin
la source
3
Tirez toujours parti de la communauté open source.
Spencer Rathbun
avez-vous essayé getoptionkit?
alfa64
Non, je n'ai pas travaillé en php depuis quelques années. Il peut également y avoir d'autres bibliothèques php. J'ai utilisé la bibliothèque d'analyseur de ligne de commande c # à laquelle j'ai lié.
quentin-starin
4

Avez-vous déjà essayé quelque chose comme http://qntm.org/loco ? Cette approche est beaucoup plus propre que tout autre ad hoc manuscrit, mais ne nécessitera pas d'outil de génération de code autonome comme Lemon.

EDIT: Et une astuce générale pour gérer les lignes de commande avec une syntaxe complexe consiste à combiner les arguments en une seule chaîne séparée par des espaces, puis à les analyser correctement comme s'il s'agissait d'une expression d'un langage spécifique au domaine.

SK-logic
la source
+1 joli lien, je me demande s'il est disponible sur github ou autre chose. Et qu'en est-il des conditions d'utilisation?
hakre
1

Vous n'avez pas donné beaucoup de détails sur votre grammaire, juste quelques exemples. Ce que je peux voir, c'est qu'il y a des chaînes, des espaces et une (probablement, votre exemple est indifférent dans votre question) une chaîne entre guillemets puis un ";" à la fin.

Il semble que cela puisse être similaire à la syntaxe PHP. Si c'est le cas, PHP est livré avec un analyseur, vous pouvez réutiliser puis valider plus concrètement. Enfin, vous devez gérer les jetons, mais il semble que ce soit simplement de gauche à droite, donc en fait juste une itération sur tous les jetons.

Quelques exemples pour réutiliser l'analyseur de jeton PHP ( token_get_all) sont donnés dans les réponses aux questions suivantes:

Les deux exemples contiennent également un analyseur simple, probablement quelque chose comme ceux qui convient à votre scénario.

hakre
la source
oui, j'ai précipité les trucs de grammaire, je vais les ajouter maintenant.
alfa64
1

Si vos besoins sont simples et que vous avez tous les deux le temps et que cela vous intéresse, je vais aller à contre-courant et dire: n'hésitez pas à écrire votre propre analyseur. C'est une bonne expérience d'apprentissage, si rien d'autre. Si vous avez des exigences plus complexes - appels de fonctions imbriquées, tableaux, etc. - sachez que cela pourrait prendre beaucoup de temps. L'un des grands avantages de rouler le vôtre est qu'il n'y aura pas de problème d'intégration avec votre système. L'inconvénient est, bien sûr, que tous les échecs sont de votre faute.

Travaillez contre les jetons, cependant, n'utilisez pas de commandes codées en dur. Ensuite, ce problème avec des commandes de sondage similaires disparaît.

Tout le monde recommande toujours le livre du dragon, mais j'ai toujours trouvé que "Writing Compilers and Interpreters" de Ronald Mak était une meilleure introduction.

GrandmasterB
la source
0

J'ai écrit des programmes qui fonctionnent comme ça. L'un était un bot IRC qui a une syntaxe de commande similaire. Il y a un énorme fichier qui est une grosse déclaration de commutateur. Ça marche - ça marche vite - mais c'est un peu difficile à entretenir.

Une autre option, qui a une rotation plus POO, consiste à utiliser des gestionnaires d'événements. Vous créez un tableau de valeurs-clés avec des commandes et leurs fonctions dédiées. Lorsqu'une commande est donnée, vous vérifiez si le tableau a la clé donnée. Si c'est le cas, appelez la fonction. Ce serait ma recommandation pour un nouveau code.

Brigand
la source
j'ai lu votre code et c'est exactement le même schéma que mon code, mais comme je l'ai dit, si vous voulez que d'autres personnes l'utilisent, vous devez ajouter une vérification des erreurs et des trucs
alfa64
1
@ alfa64 Veuillez ajouter des clarifications à la question, au lieu de commentaires. Ce que vous demandez exactement n'est pas très clair, bien qu'il soit assez clair que vous recherchez quelque chose de vraiment spécifique. Si oui, dites-nous exactement ce que c'est. Je ne pense pas qu'il est très facile de passer de I think my implementation is very crude and faultyla but as i stated, if you want other people to use, you need to add error checking and stuff... Dites - nous exactement ce qui est brut à ce sujet et ce qui est défectueux, il vous aider à obtenir de meilleures réponses.
yannis
bien sûr, je retravaillerai la question
alfa64
0

Je suggère d'utiliser un outil, au lieu d'implémenter vous-même un compilateur ou un interprète. Irony utilise C # pour exprimer la grammaire de la langue cible (la grammaire de votre ligne de commande). La description sur CodePlex dit: "Irony est un kit de développement pour implémenter des langages sur la plate-forme .NET."

Voir la page d'accueil officielle d'Irony sur CodePlex: Irony - Kit de mise en œuvre du langage .NET .

Olivier Jacot-Descombes
la source
Comment l'utiliseriez-vous avec PHP?
SK-logic
Je ne vois aucune balise PHP ou référence à PHP dans la question.
Olivier Jacot-Descombes
Je vois, il s'agissait à l'origine de PHP, mais maintenant réécrit.
SK-logic
0

Mon conseil serait google pour une bibliothèque qui résout votre problème.

J'utilise beaucoup NodeJS ces derniers temps, et Optimist est ce que j'utilise pour le traitement en ligne de commande. Je vous encourage à rechercher celui que vous pouvez utiliser pour votre propre langue de choix. Sinon ... écrivez-en un et ouvrez-le: D Vous pouvez même lire le code source d'Optimist et le porter dans la langue de votre choix.

ming_codes
la source
0

Pourquoi ne simplifiez-vous pas un peu vos exigences?

N'utilisez pas un analyseur complet, c'est trop complexe et même inutile pour votre cas.

Faites une boucle, écrivez un message qui représente votre "invite", peut être le chemin actuel que vous êtes.

Attendez une chaîne, "analysez" la chaîne et faites quelque chose en fonction du contenu de la chaîne.

La chaîne pourrait "analyser" comme attendre une ligne, dans laquelle les espaces sont les séparateurs ("tokenizer"), et le reste des caractères sont regroupés.

Exemple.

Le programme génère (et reste sur la même ligne): / user / files / L'utilisateur écrit (sur la même ligne) liste tout;

Votre programme va générer une liste, une collection ou un tableau comme

list

all;

ou si ";" est considéré comme un séparateur comme des espaces

/user/files/

list

all

Votre programme pourrait commencer par attendre une seule instruction, sans "tuyaux" de style Unix, ni redirection de style windowze.

Votre programme pourrait créer un dictionnaire d'instructions, chaque instruction peut avoir une liste de paramètres.

Le modèle de conception de commande s'applique à votre cas:

http://en.wikipedia.org/wiki/Command_pattern

Ce pseudocode "plain c" n'est ni testé ni terminé, juste une idée de la façon de le faire.

Vous pouvez également le rendre plus orienté objet, et dans le langage de programmation, vous aimez.

Exemple:


// "global function" pointer type declaration
typedef
  void (*ActionProc) ();

struct Command
{
  char[512] Identifier;
  ActionProc Action; 
};

// global var declarations

list<char*> CommandList = new list<char*>();
list<char*> Tokens = new list<char*>();

void Action_ListDirectory()
{
  // code to list directory
} // Action_ListDirectory()

void Action_ChangeDirectory()
{
  // code to change directory
} // Action_ChangeDirectory()

void Action_CreateDirectory()
{
  // code to create new directory
} // Action_CreateDirectory()

void PrepareCommandList()
{
  CommandList->Add("ls", &Action_ListDirectory);
  CommandList->Add("cd", &Action_ChangeDirectory);
  CommandList->Add("mkdir", &Action_CreateDirectory);

  // register more commands
} // void PrepareCommandList()

void interpret(char* args, int *ArgIndex)
{
  char* Separator = " ";
  Tokens = YourSeparateInTokensFunction(args, Separator);

  // "LocateCommand" may be case sensitive
  int AIndex = LocateCommand(CommandList, args[ArgIndex]);
  if (AIndex >= 0)
  {
    // the command

    move to the next parameter
    *ArgIndex = (*ArgIndex + 1);

    // obtain already registered command
    Command = CommandList[AIndex];

    // execute action
    Command.Action();
  }
  else
  {
    puts("some kind of command not found error, or, error syntax");
  }  
} // void interpret()

void main(...)
{
  bool CanContinue = false;
  char* Prompt = "c\:>";

  char Buffer[512];

  // which command line parameter string is been processed
  int ArgsIndex = 0;

  PrepareCommandList();

  do
  {
    // display "prompt"
    puts(Prompt);
    // wait for user input
      fgets(Buffer, sizeof(Buffer), stdin);

    interpret(buffer, &ArgsIndex);

  } while (CanContinue);

} // void main()

Vous n'avez pas mentionné votre langage de programmation. Vous pouvez également mentionner n'importe quel langage de programmation, mais de préférence "XYZ".

umlcat
la source
0

vous avez plusieurs tâches devant vous.

en regardant vos besoins ...

  • Vous devez analyser la commande. C'est une tâche assez facile
  • Vous devez disposer d'un langage de commande extensible.
  • Vous devez avoir une vérification des erreurs et des suggestions.

Le langage de commande extensible indique qu'un DSL est requis. Je suggérerais de ne pas rouler le vôtre mais d'utiliser JSON si vos extensions sont simples. S'ils sont complexes, une syntaxe d'expression S est agréable.

La vérification des erreurs implique que votre système connaît également les commandes possibles. Cela ferait partie du système post-commandement.

Si j'exécutait un tel système à partir de zéro, j'utiliser Common Lisp avec un lecteur-dépouillé. Chaque jeton de commande correspondrait à un symbole, qui serait spécifié dans un fichier RC d'expression s. Après la tokenisation, il serait évalué / développé dans un contexte limité, interceptant les erreurs, et tout modèle d'erreur reconnaissable renverrait des suggestions. Après cela, la commande réelle serait envoyée au système d'exploitation.

Paul Nathan
la source
0

Il y a une fonctionnalité intéressante dans la programmation fonctionnelle qui pourrait vous intéresser.

Cela s'appelle la correspondance de motifs .

Voici deux liens pour un exemple de correspondance de motifs en Scala et en F # .

Je suis d'accord avec vous que l'utilisation des switchstructures est un peu fastidieuse, et j'ai particulièrement apprécié l'utilisation de la correspondance de patern pendant la mise en œuvre d'un compilateur dans Scala.

En particulier, je vous recommande de regarder l' exemple du calcul lambda du site Web de Scala.

C'est, à mon avis, la façon la plus intelligente de procéder, mais si vous devez vous en tenir strictement à PHP, alors vous êtes coincé avec la "vieille école" switch.

SRKX
la source
0

Consultez Apache CLI , son objectif semble faire exactement ce que vous voulez faire, donc même si vous ne pouvez pas l'utiliser, vous pouvez vérifier son architecture et le copier.

Stephen Rudolph
la source