¿Está el error en la gramática o en el código?

No estoy seguro de si esta gramática es correcta para un lenguaje de comando de shell que también debería ser capaz de ejecutar comillas simples y dobles. Parece que los comandos no triviales funcionan, por ejemplo, ls -al | sort | wc -l ls -al | sort | wc -l ls -al | sort | wc -l pero el simple no funciona con comillas simples: echo 'foo bar' no funciona.

 %{ #include "shellparser.h" %} %option reentrant %option noyywrap %x SINGLE_QUOTED %x DOUBLE_QUOTED %% "|" { return PIPE; } [ \t\r] { } [\n] { return EOL; } [a-zA-Z0-9_\.\-]+ { return FILENAME; } ['] { BEGIN(SINGLE_QUOTED); } [^']+ { } ['] { BEGIN(INITIAL); return ARGUMENT; } <> { return -1; } ["] { BEGIN(DOUBLE_QUOTED); } [^"]+ { } ["] { BEGIN(INITIAL); return ARGUMENT; } <> { return -1; } [^ \t\r\n|'"]+ { return ARGUMENT; } %% 

Mi código que escanea y analiza el shell es

  params[0] = NULL; printf("> "); i=1; do { lexCode = yylex(scanner); text = strdup(yyget_text(scanner));//yyget_text(scanner); /*printf("lexCode %d command %s inc:%d", lexCode, text, i);*/ ca = text; if (lexCode != EOL) { params[i++] = text; } Parse(shellParser, lexCode, text); if (lexCode == EOL) { dump_argv("Before exec_arguments", i, params); exec_arguments(i, params); corpse_collector(); Parse(shellParser, 0, NULL); i=1; } } while (lexCode > 0); if (-1 == lexCode) { fprintf(stderr, "The scanner encountered an error.\n"); } 

El archivo de comstackción de CMake es

 cmake_minimum_required(VERSION 3.0) project(openshell) find_package(FLEX) FLEX_TARGET(ShellScanner shellscanner.l shellscanner.c) set(CMAKE_VERBOSE_MAKEFILE on) include_directories(/usr/include/readline) ADD_EXECUTABLE(lemon lemon.c) add_custom_command(OUTPUT shellparser.c COMMAND lemon -s shellparser.y DEPENDS shellparser.y) add_executable(openshell shellparser.c ${FLEX_ShellScanner_OUTPUTS} main.c openshell.h errors.c errors.h util.c util.h stack.c stack.h shellscanner.l shellscanner.h) file(GLOB SOURCES "./*.c") target_link_libraries(openshell ${READLINE_LIBRARY} ${FLEX_LIBRARIES}) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -O3 -std=c99") 

Mi proyecto está disponible en mi github . Una sesión de shell típica, donde solo algunos comandos funcionan debido a algún error, es la siguiente.

 > ls -al | sort | wc argument ::= FILENAME . argumentList ::= argument . command ::= FILENAME argumentList . command ::= FILENAME . command ::= FILENAME . commandList ::= command . commandList ::= command PIPE commandList . commandList ::= command PIPE commandList . {(null)} {ls} {-al} {|} {sort} {|} {wc} 45 398 2270 3874: child 3881 status 0x0000 in ::= in commandList EOL . > who command ::= FILENAME . commandList ::= command . {(null)} {who} dac :0 2016-04-18 05:17 (:0) dac pts/2 2016-04-18 05:20 (:0) 3874: child 3887 status 0x0000 in ::= in commandList EOL . > ls -al | awk '{print $1}' argument ::= FILENAME . argumentList ::= argument . command ::= FILENAME argumentList . argument ::= ARGUMENT . argumentList ::= argument . command ::= FILENAME argumentList . commandList ::= command . commandList ::= command PIPE commandList . {(null)} {ls} {-al} {|} {awk} {'} awk: cmd. line:1: ' awk: cmd. line:1: ^ invalid char ''' in expression 3874: child 3896 status 0x0100 in ::= in commandList EOL . > 

Puedo observar que ambos comandos tienen el mismo error: echo 'foo bar' se confunde con {echo} {'} cuando queremos que resulte en {echo} {foo bar} para que el shell elimine las comillas y ejecute el comando Me gusta esto

char *cmd[] = { "/usr/bin/echo", "foo bar", 0 };

El problema está en regla.

[^']+ { }

ya que elimina todos los caracteres entre comillas. Todo lo que obtiene como “yytext” es la cita de cierre (debido a la regla ['] ... ). Debe almacenar el texto en algún lugar y utilizarlo cuando se detecte la cita de cierre. Ej. (Muy mal estilo de encoding, comprobación de errores, etc. omitido, lo siento)

 [^']+ { mystring = strdup(yytext); } ['] { BEGIN(INITIAL); /* mystring contains the whole string now, yytext contains only "'" */ return ARGUMENT; } 

yytext tiene un puntero a la subcadena que coincide con el patrón reconocido más recientemente.

Entonces, cuando su escáner devuelve ARGUMENT al final de una sola cadena entre comillas, yytext apunta a la comilla simple que termina. Como sucede, eso es visible en su seguimiento de depuración.

Si desea “construir” un token, debería echar un vistazo a la función flex yymore() . (Y no olvide que la comilla simple de cierre no forma parte de la cadena citada).


Devolver ARGUMENT para cadenas tanto entre comillas simples como dobles es engañoso e impreciso.

Es impreciso porque una cadena entre comillas dobles se maneja de manera muy diferente a una cadena entre comillas simples, ya que las syntax de sustitución incluidas se expanden, lo que requiere una llamada recursiva al analizador (y esto debe hacerse incluso para reconocer el final de la cadena: considere "$(echo "Hello, world!")" , como un ejemplo simple).

Es engañoso porque el final del segmento citado no marca el final de una palabra. De hecho, un escáner de mente simple no encontrará correctamente los finales raros. Considerar:

 x="ab" printf "[%s]\n" '$x'$x"$x" 

Finalmente, no me queda claro por qué eligió usar limón en lugar de bison / yacc, ya que no está usando la única característica que lo haría útil en este caso: el hecho de que implementa una interfaz “push”, que le permite llamar al analizador desde una regla de lexer. Por supuesto, las versiones modernas de bisontes, e incluso las no tan modernas, también implementan esta característica. No es que tenga ningún sesgo en contra del limón: creo que podría ser una excelente combinación para este problema precisamente por la necesidad de realizar un análisis recursivo.