错误是在语法中还是在代码中?
Is the bug in the grammar or in the code?
我不确定这个语法对于 shell 命令语言是否正确,它也应该能够执行单引号和双引号。似乎非平凡的命令有效,例如ls -al | sort | wc -l
但简单的单引号不起作用: echo 'foo bar'
不起作用。
%{
#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); }
<SINGLE_QUOTED>[^']+ { }
<SINGLE_QUOTED>['] { BEGIN(INITIAL); return ARGUMENT; }
<SINGLE_QUOTED><<EOF>> { return -1; }
["] { BEGIN(DOUBLE_QUOTED); }
<DOUBLE_QUOTED>[^"]+ { }
<DOUBLE_QUOTED>["] { BEGIN(INITIAL); return ARGUMENT; }
<DOUBLE_QUOTED><<EOF>> { return -1; }
[^ \t\r\n|'"]+ { return ARGUMENT; }
%%
我扫描和解析 shell 的代码是
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");
}
CMake 构建文件是
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")
我的项目在 my github 上可用。一个典型的 shell 会话,由于一些错误,只有一些命令可以工作,如下所示。
> 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 }'
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 .
>
我可以观察到这两个命令都有相同的错误:当我们希望它导致 {echo} {foo bar}
时,echo 'foo bar'
会变成 {echo} {'}
乱码,以便 shell 条带引号并执行这样的命令
char *cmd[] = { "/usr/bin/echo", "foo bar", 0 };
yytext
持有指向与最近识别的模式匹配的子字符串的指针。
因此,当您的扫描器 returns ARGUMENT
位于单引号字符串的末尾时,yytext
指向终止单引号。碰巧,这在您的调试跟踪中是可见的。
如果你想"build up"一个令牌,你应该看看flex函数yymore()
。 (不要忘记结束单引号不是引用字符串的一部分。)
为单引号和双引号字符串返回 ARGUMENT
既具有误导性又不精确。
这是不精确的,因为双引号字符串的处理方式与单引号字符串的处理方式截然不同,因为封闭的替换语法得到了扩展,需要对解析器进行递归调用(甚至需要这样做才能识别字符串的末尾:考虑 "$(echo "Hello, world!")"
,作为一个简单的例子)。
这是一种误导,因为引用段的末尾并未标记单词的末尾。事实上,头脑简单的扫描仪不会正确地找到线形结尾。考虑:
x="a b"
printf "[%s]\n" '$x'$x"$x"
最后,我不清楚你为什么选择使用 lemon 而不是 bison/yacc,因为你没有使用一个在这种情况下有用的功能:它实现了 "push" 接口,允许您从词法分析器规则调用解析器。当然,现代野牛版本——甚至不太现代的野牛版本——也实现了此功能。并不是说我对柠檬有任何偏见——我认为它可能是这个问题的完美匹配,正是因为需要进行递归解析。
规则有问题
<SINGLE_QUOTED>[^']+ { }
因为它删除了引号内的所有字符。您得到的 "yytext" 是收盘价(由于规则 <SINGLE_QUOTED>['] ...
)。您必须将文本存储在某处,并在检测到结束引号时使用它。例如。 (非常糟糕的编码风格,错误检查等省略,抱歉)
<SINGLE_QUOTED>[^']+ { mystring = strdup(yytext); }
<SINGLE_QUOTED>['] { BEGIN(INITIAL);
/* mystring contains the whole string now,
yytext contains only "'" */
return ARGUMENT; }
我不确定这个语法对于 shell 命令语言是否正确,它也应该能够执行单引号和双引号。似乎非平凡的命令有效,例如ls -al | sort | wc -l
但简单的单引号不起作用: echo 'foo bar'
不起作用。
%{
#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); }
<SINGLE_QUOTED>[^']+ { }
<SINGLE_QUOTED>['] { BEGIN(INITIAL); return ARGUMENT; }
<SINGLE_QUOTED><<EOF>> { return -1; }
["] { BEGIN(DOUBLE_QUOTED); }
<DOUBLE_QUOTED>[^"]+ { }
<DOUBLE_QUOTED>["] { BEGIN(INITIAL); return ARGUMENT; }
<DOUBLE_QUOTED><<EOF>> { return -1; }
[^ \t\r\n|'"]+ { return ARGUMENT; }
%%
我扫描和解析 shell 的代码是
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");
}
CMake 构建文件是
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")
我的项目在 my github 上可用。一个典型的 shell 会话,由于一些错误,只有一些命令可以工作,如下所示。
> 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 }'
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 .
>
我可以观察到这两个命令都有相同的错误:当我们希望它导致 {echo} {foo bar}
时,echo 'foo bar'
会变成 {echo} {'}
乱码,以便 shell 条带引号并执行这样的命令
char *cmd[] = { "/usr/bin/echo", "foo bar", 0 };
yytext
持有指向与最近识别的模式匹配的子字符串的指针。
因此,当您的扫描器 returns ARGUMENT
位于单引号字符串的末尾时,yytext
指向终止单引号。碰巧,这在您的调试跟踪中是可见的。
如果你想"build up"一个令牌,你应该看看flex函数yymore()
。 (不要忘记结束单引号不是引用字符串的一部分。)
为单引号和双引号字符串返回 ARGUMENT
既具有误导性又不精确。
这是不精确的,因为双引号字符串的处理方式与单引号字符串的处理方式截然不同,因为封闭的替换语法得到了扩展,需要对解析器进行递归调用(甚至需要这样做才能识别字符串的末尾:考虑 "$(echo "Hello, world!")"
,作为一个简单的例子)。
这是一种误导,因为引用段的末尾并未标记单词的末尾。事实上,头脑简单的扫描仪不会正确地找到线形结尾。考虑:
x="a b"
printf "[%s]\n" '$x'$x"$x"
最后,我不清楚你为什么选择使用 lemon 而不是 bison/yacc,因为你没有使用一个在这种情况下有用的功能:它实现了 "push" 接口,允许您从词法分析器规则调用解析器。当然,现代野牛版本——甚至不太现代的野牛版本——也实现了此功能。并不是说我对柠檬有任何偏见——我认为它可能是这个问题的完美匹配,正是因为需要进行递归解析。
规则有问题
<SINGLE_QUOTED>[^']+ { }
因为它删除了引号内的所有字符。您得到的 "yytext" 是收盘价(由于规则 <SINGLE_QUOTED>['] ...
)。您必须将文本存储在某处,并在检测到结束引号时使用它。例如。 (非常糟糕的编码风格,错误检查等省略,抱歉)
<SINGLE_QUOTED>[^']+ { mystring = strdup(yytext); }
<SINGLE_QUOTED>['] { BEGIN(INITIAL);
/* mystring contains the whole string now,
yytext contains only "'" */
return ARGUMENT; }