¿Cómo escribir un intérprete / analizador de comandos?


22

Problema: ejecutar comandos en forma de cadena.

  • ejemplo de comando:

    /user/files/ list all; equivalente a: /user/files/ ls -la;

  • otro:

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

equivalente a: post -tf "HOW DO YOU STOP THE TICKLE MONSTER?;"

Solución actual:

tokenize string(string, array);

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

Los problemas que veo en esta solución son:

  • No hay errores de comprobación que no sean anidados ifs-else dentro de cada caso. El guión se vuelve muy grande y difícil de mantener.
  • Los comandos y las respuestas están codificados.
  • No hay forma de saber si los indicadores son parámetros correctos o faltantes.
  • Falta de inteligencia para sugerir "es posible que desee ejecutar $ command".

Y lo último que no puedo abordar es sinónimos en diferentes codificaciones, por ejemplo:

case command:
case command_in_hebrew:
    do stuff;
break;

El último podría ser trivial, pero bueno, lo que quiero ver son los fundamentos sólidos de este tipo de programa.

Actualmente estoy programando esto en PHP, pero podría hacerlo en PERL.


No veo en absoluto cómo se relaciona esto específicamente con PHP. Ya hay muchos hilos sobre este tema de intérprete / compilador en SO y SE.
Raffael

3
Nadie mencionó getopt?
Anton Barkovsky

@ AntonBarkovsky: lo hice. Mira mis enlaces. Creo que respuestas como las de Ubermensch son demasiado complicadas para lo que el OP está tratando de hacer.
quentin-starin

1
También he citado un enfoque simple usando RegExp. La respuesta también se actualiza
Ubermensch

No mencionó ningún programa específico. lang. puede agregar una etiqueta "c", una etiqueta "ruby", una etiqueta "php", tal vez haya una biblioteca de código abierto, una biblioteca estándar o "de uso común, aún no una biblioteca estándar". para tu progr. lang.
umlcat

Respuestas:


14

Permítanme admitir francamente, que el analizador de construcción es un trabajo tedioso y se acerca a la tecnología de compilación, pero construir uno resultaría ser una buena aventura. Y un analizador viene con intérprete. Entonces tienes que construir ambos.

Una introducción rápida a analizadores e intérpretes

Esto no es demasiado técnico. Entonces los expertos no se preocupan por mí.

Cuando introduce alguna entrada en un terminal, el terminal divide la entrada en varias unidades. La entrada se llama expresión y las múltiples unidades se llaman tokens. Estos tokens pueden ser operadores o símbolos. Entonces, si ingresa 4 + 5 en una calculadora, esta expresión se divide en tres tokens 4, +, 5. El plus se considera un operador mientras que 4 y 5 símbolos. Esto se pasa a un programa (considérelo como un intérprete) que contiene la definición para los operadores. Según la definición (en nuestro caso, add), agrega los dos símbolos y devuelve el resultado al terminal. Todos los compiladores se basan en esta tecnología. El programa que divide una expresión en múltiples tokens se llama lexer y el programa que convierte estos tokens en etiquetas para su posterior procesamiento y ejecución se llama analizador.

Lex y Yacc son las formas canónicas para construir lexers y analizadores basados ​​en la gramática BNF bajo C y es la opción recomendada. La mayoría de los analizadores son un clon de Lex y Yacc.

Pasos para construir un analizador / intérprete

  1. Clasifique sus tokens en símbolos, operadores y palabras clave (las palabras clave son operadores)
  2. Construya su gramática usando el formulario BNF
  3. Escribe funciones de analizador para tus operaciones
  4. Compilarlo y ejecutarlo como un programa

Entonces, en el caso anterior, sus tokens de suma serían cualquier dígito y un signo más con la definición de qué hacer con el signo más en el lexer

Notas y consejos

  • Elija una técnica de analizador que evalúe de izquierda a derecha LALR
  • Lea este libro de dragones en Compiladores para tener una idea del mismo. Yo personalmente no he terminado el libro.
  • Este enlace daría una visión súper rápida de Lex y Yacc en Python

Un enfoque simple

Si solo necesita un mecanismo de análisis simple con funciones limitadas, convierta su requisito en una Expresión regular y simplemente cree un montón de funciones. Para ilustrar, suponga un analizador simple para las cuatro funciones aritméticas. Por lo tanto, primero llamaría al operador y luego a la lista de funciones (similar a lisp) en el estilo (+ 4 5)o (add [4,5])luego podría usar un RegExp simple para obtener la lista de operadores y los símbolos que se utilizarán.

Los casos más comunes podrían resolverse fácilmente con este enfoque. La desventaja es que no puede tener muchas expresiones anidadas con una sintaxis clara y no puede tener funciones fáciles de orden superior.


2
Esta es una de las formas más difíciles posibles. Separación de pases de análisis y lexing, etc., probablemente sea útil para implementar un analizador de alto rendimiento para un lenguaje muy complejo pero arcaico. En el mundo moderno, el análisis sin lexer es una opción predeterminada más simple. Los combinadores de análisis o eDSL son más fáciles de usar que los preprocesadores dedicados como Yacc.
SK-logic

Estoy de acuerdo con SK-logic pero como se requiere una respuesta general detallada, sugerí Lex y Yacc y algunos conceptos básicos del analizador. getopts sugerido por Anton también es una opción más simple.
Ubermensch

eso es lo que he dicho: lex y yacc se encuentran entre las formas más difíciles de analizar, y ni siquiera son lo suficientemente genéricas. El análisis sin Lexer (p. Ej., Packrat, o Parsec simple) es mucho más simple para un caso general. Y el libro del Dragón ya no es una introducción muy útil para analizar: está demasiado desactualizado.
SK-logic

@ SK-logic ¿Puede recomendarme un libro mejor actualizado? Parece cubrir todos los conceptos básicos para una persona que intenta comprender el análisis (al menos en mi opinión). Con respecto a lex y yacc, aunque es difícil, es ampliamente utilizado y muchos lenguajes de programación proporcionan su implementación.
Ubermensch

1
@ alfa64: asegúrese de informarnos cuando codifique una solución basada en esta respuesta
quentin-starin

7

Primero, cuando se trata de gramática, o cómo especificar argumentos, no inventes el tuyo. El estándar de estilo GNU ya es muy popular y conocido.

En segundo lugar, dado que está utilizando un estándar aceptado, no reinvente la rueda. Use una biblioteca existente para hacerlo por usted. Si usa argumentos de estilo GNU, es casi seguro que ya haya una biblioteca madura en su idioma de elección. Por ejemplo: c # , php , c .

Una buena biblioteca de análisis de opciones incluso imprimirá ayuda formateada sobre las opciones disponibles para usted.

EDITAR 27/12

Parece que estás haciendo que esto sea más complicado de lo que es.

Cuando miras una línea de comando, es realmente bastante simple. Son solo opciones y argumentos para esas opciones. Hay muy pocos problemas complicados. La opción puede tener alias. Los argumentos pueden ser listas de argumentos.

Un problema con su pregunta es que realmente no ha especificado ninguna regla para qué tipo de línea de comando le gustaría tratar. He sugerido el estándar GNU, y sus ejemplos se acercan a eso (¿aunque realmente no entiendo su primer ejemplo con la ruta como primer elemento?).

Si estamos hablando de GNU, cualquier opción puede tener solo una forma larga y una forma corta (carácter único) como alias. Cualquier argumento que contenga un espacio debe estar entre comillas. Se pueden encadenar múltiples opciones de forma corta. Las opciones de forma corta se deben proceder con un solo guión, la forma larga con dos guiones. Solo las últimas opciones de forma corta encadenadas pueden tener un argumento.

Todo muy sencillo. Todo muy comun. También se ha implementado en todos los idiomas que puede encontrar, probablemente cinco veces.

No lo escribas Usa lo que ya está escrito.

A menos que tenga algo en mente que no sean argumentos de línea de comandos estándar, simplemente use una de las MUCHAS bibliotecas ya probadas que lo hacen.

¿Cuál es la complicación?


3
Siempre, siempre aprovecha la comunidad de código abierto.
Spencer Rathbun

¿Has probado getoptionkit?
alfa64

No, no he trabajado en PHP en muchos años. Es muy posible que también haya otras bibliotecas php. He utilizado la biblioteca del analizador de línea de comandos c # a la que me he vinculado.
quentin-starin

4

¿Ya has probado algo como http://qntm.org/loco ? Este enfoque es mucho más limpio que cualquier ad hoc escrito a mano, pero no requerirá una herramienta de generación de código independiente como Lemon.

EDITAR: Y un truco general para manejar líneas de comando con sintaxis compleja es combinar los argumentos nuevamente en una sola cadena separada por espacios en blanco y luego analizarlos correctamente como si fuera una expresión de algún lenguaje específico de dominio.


+1 buen enlace, me pregunto si está disponible en github o algo más. ¿Y qué hay de los términos de uso?
Hakre

1

No ha dado muchos detalles sobre su gramática, solo algunos ejemplos. Lo que puedo ver es que hay algunas cadenas, espacios en blanco y una (probablemente, su ejemplo es indiferente en su pregunta) una cadena doble entre comillas y luego un ";" al final.

Parece que esto podría ser similar a la sintaxis de PHP. Si es así, PHP viene con un analizador, puede reutilizarlo y luego validarlo más concretamente. Finalmente, debe lidiar con los tokens, pero parece que esto es simplemente de izquierda a derecha, por lo que en realidad solo es una iteración sobre todos los tokens.

En token_get_alllas respuestas a las siguientes preguntas se dan algunos ejemplos para reutilizar el analizador de tokens PHP ( ):

Ambos ejemplos también contienen un analizador simple, probablemente algo así se ajuste a su escenario.


Sí, apresuré la gramática, la agregaré ahora.
alfa64

1

Si sus necesidades son simples, y ambos tienen el tiempo y están interesados ​​en ello, iré a contracorriente aquí y les diré que no tengan miedo de escribir su propio analizador. Es una buena experiencia de aprendizaje, por lo menos. Si tiene requisitos más complejos (llamadas a funciones anidadas, matrices, etc.), tenga en cuenta que hacerlo podría llevar bastante tiempo. Uno de los grandes aspectos positivos de rodar la suya es que no habrá problemas de integración con su sistema. La desventaja es, por supuesto, que todos los errores son tu culpa.

Sin embargo, trabaje contra tokens, no use comandos codificados. Entonces ese problema con comandos de sonido similares desaparece.

Todo el mundo siempre recomienda el libro del dragón, pero siempre he encontrado que "Escribir compiladores e intérpretes" de Ronald Mak es una mejor introducción.


0

He escrito programas que funcionan así. Uno era un bot IRC que tiene una sintaxis de comando similar. Hay un archivo enorme que es una gran declaración de cambio. Funciona, funciona rápido, pero es algo difícil de mantener.

Otra opción, que tiene un giro más OOP, es utilizar controladores de eventos. Crea una matriz de valores-clave con comandos y sus funciones dedicadas. Cuando se da un comando, verifica si la matriz tiene la clave dada. Si es así, llame a la función. Esta sería mi recomendación para el nuevo código.


He leído su código y es exactamente el mismo esquema que mi código, pero como dije, si desea que otras personas lo usen, debe agregar la comprobación de errores y esas cosas
alfa64

1
@ alfa64 Agregue aclaraciones a la pregunta, en lugar de comentarios. No está muy claro qué es exactamente lo que está pidiendo, aunque está algo claro que está buscando algo realmente específico. Si es así, dinos exactamente qué es eso. No creo que es muy fácil pasar de I think my implementation is very crude and faultya but as i stated, if you want other people to use, you need to add error checking and stuff... Dinos exactamente lo que está crudo sobre él y lo que es defectuoso, que ayudaría a obtener mejores respuestas.
yannis

claro, volveré a trabajar la pregunta
alfa64

0

Sugiero usar una herramienta, en lugar de implementar un compilador o un intérprete. Irony usa C # para expresar la gramática del idioma de destino (la gramática de su línea de comando). La descripción en CodePlex dice: "Irony es un kit de desarrollo para implementar lenguajes en la plataforma .NET".

Vea la página oficial de Irony en CodePlex: Irony - .NET Language Implementation Kit .


¿Cómo lo usarías con PHP?
SK-logic

No veo ninguna etiqueta PHP o referencia a PHP en la pregunta.
Olivier Jacot-Descombes

Ya veo, solía ser sobre PHP originalmente, pero ahora reescrito.
SK-logic

0

Mi consejo sería google para una biblioteca que resuelva su problema.

He estado usando NodeJS mucho últimamente, y Optimist es lo que uso para el procesamiento de línea de comandos. Te animo a buscar uno que puedas usar para tu propio idioma de elección. Si no ... escriba uno y abra el código fuente: D Incluso puede leer el código fuente de Optimist y transferirlo al idioma de su elección.


0

¿Por qué no simplificas un poco tus requisitos?

No use un analizador completo, es demasiado complejo e incluso innecesario para su caso.

Haga un bucle, escriba un mensaje que represente su "solicitud", puede ser la ruta actual que es.

Espere una cadena, "analice" la cadena y haga algo dependiendo del contenido de la cadena.

La cadena podría "analizar" como si esperara una línea, en la que los espacios son los separadores ("tokenizer"), y el resto de los caracteres están agrupados.

Ejemplo.

El programa genera (y permanece en la misma línea): / user / files / El usuario escribe (en la misma línea) enumera todos;

Su programa generará una lista, colección o matriz como

list

all;

o si ";" se considera un separador como espacios

/user/files/

list

all

Su programa podría comenzar esperando una sola instrucción, sin "canalizaciones" de estilo unix, ni redirección de estilo de ventana.

Su programa podría hacer un diccionario de instrucciones, cada instrucción puede tener una lista de parámetros.

El patrón de diseño del comando se aplica a su caso:

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

Este es un pseudocódigo "simple c", no se ha probado ni terminado, solo una idea de cómo se podría hacer.

También podría hacerlo más orientado a objetos y, en el lenguaje de programación, le guste.

Ejemplo:


// "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()

No mencionaste tu lenguaje de programación. También puede mencionar cualquier lenguaje de programación, pero preferiblemente "XYZ".


0

Tienes varias tareas por delante.

mirando sus requisitos ...

  • Necesita analizar el comando. Esa es una tarea bastante fácil
  • Necesita tener un lenguaje de comando extensible.
  • Necesita tener verificación de errores y sugerencias.

El lenguaje de comando extensible indica que se requiere un DSL. Sugeriría no rodar el tuyo sino usar JSON si tus extensiones son simples. Si son complejos, una sintaxis de expresión s es buena.

La comprobación de errores implica que su sistema también conoce los posibles comandos. Eso sería parte del sistema posterior al comando.

Si yo estaba ejecutando un sistema de este tipo a partir de cero, me gustaría utilizar Common Lisp con un lector reducidos al mínimo. Cada token de comando se correlacionaría con un símbolo, que se especificaría en un archivo RC de expresión s. Después de la tokenización, se evaluaría / expandiría en un contexto limitado, atrapando los errores, y cualquier patrón de error reconocible devolvería sugerencias. Después de eso, el comando real se enviaría al sistema operativo.


0

Hay una buena característica en la programación funcional que puede interesarle considerar.

Se llama coincidencia de patrones .

Aquí hay dos enlaces para algún ejemplo de coincidencia de patrones en Scala y en F # .

Estoy de acuerdo con usted en que el uso de switchestructuras es un poco tedioso, y disfruté especialmente el uso de la correspondencia patern durante la implementación de un compilador en Scala.

En particular, le recomendaría que consulte el ejemplo de cálculo lambda del sitio web de Scala.

Esa es, en mi opinión, la forma más inteligente de proceder, pero si tiene que seguir estrictamente con PHP, entonces está atrapado en la "vieja escuela" switch.


0

Echa un vistazo a la CLI de Apache , parece que todo su propósito es hacer exactamente lo que quieres hacer, por lo que incluso si no puedes usarlo, puedes revisar su arquitectura y copiarlo.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.