From 1c2cbb00bcb6fb31d509019b035a0828c940bf6e Mon Sep 17 00:00:00 2001 From: axel Date: Mon, 22 May 2006 05:25:24 +1000 Subject: [PATCH] Major update to the ahead-of-time syntax checker darcs-hash:20060521192524-ac50b-48713f826558e66ef21046d1bb779623cc2fd97a.gz --- expand.c | 24 +--- expand.h | 21 ++++ parser.c | 364 +++++++++++++++++++++++++++++++++++++++++++++++++------ parser.h | 16 ++- 4 files changed, 362 insertions(+), 63 deletions(-) diff --git a/expand.c b/expand.c index 0ae783a51..d2b393ba6 100644 --- a/expand.c +++ b/expand.c @@ -76,27 +76,6 @@ parameter expansion. */ #define COMPLETE_LAST_DESC _( L"Last background job") -/** - Error issued on invalid variable name -*/ -#define COMPLETE_VAR_DESC _( L"The '$' character begins a variable name. The character '%lc', which directly followed a '$', is not allowed as a part of a variable name, and variable names may not be zero characters long. To learn more about variable expansion in fish, type 'help expand-variable'.") - -/** - Error issued on invalid variable name -*/ -#define COMPLETE_VAR_NULL_DESC _( L"The '$' begins a variable name. It was given at the end of an argument. Variable names may not be zero characters long. To learn more about variable expansion in fish, type 'help expand-variable'.") - -/** - Error issued on invalid variable name -*/ -#define COMPLETE_VAR_BRACKET_DESC _( L"Did you mean {$VARIABLE}? The '$' character begins a variable name. A bracket, which directly followed a '$', is not allowed as a part of a variable name, and variable names may not be zero characters long. To learn more about variable expansion in fish, type 'help expand-variable'." ) - -/** - Error issued on invalid variable name -*/ -#define COMPLETE_VAR_PARAN_DESC _( L"Did you mean (COMMAND)? In fish, the '$' character is only used for accessing variables. To learn more about command substitution in fish, type 'help expand-command-substitution'.") - - /** String in process expansion denoting ourself */ @@ -804,8 +783,7 @@ static int expand_variables( wchar_t *in, array_list_t *out, int last_idx ) { error( SYNTAX_ERROR, -1, - COMPLETE_VAR_NULL_DESC, - in[stop_pos] ); + COMPLETE_VAR_NULL_DESC ); break; } diff --git a/expand.h b/expand.h index 24337dcd8..b1df1c233 100644 --- a/expand.h +++ b/expand.h @@ -112,6 +112,27 @@ enum /** String containing the character for separating two array elements */ #define ARRAY_SEP_STR L"\x1e" +/** + Error issued on invalid variable name +*/ +#define COMPLETE_VAR_DESC _( L"The '$' character begins a variable name. The character '%lc', which directly followed a '$', is not allowed as a part of a variable name, and variable names may not be zero characters long. To learn more about variable expansion in fish, type 'help expand-variable'.") + +/** + Error issued on invalid variable name +*/ +#define COMPLETE_VAR_NULL_DESC _( L"The '$' begins a variable name. It was given at the end of an argument. Variable names may not be zero characters long. To learn more about variable expansion in fish, type 'help expand-variable'.") + +/** + Error issued on invalid variable name +*/ +#define COMPLETE_VAR_BRACKET_DESC _( L"Did you mean {$VARIABLE}? The '$' character begins a variable name. A bracket, which directly followed a '$', is not allowed as a part of a variable name, and variable names may not be zero characters long. To learn more about variable expansion in fish, type 'help expand-variable'." ) + +/** + Error issued on invalid variable name +*/ +#define COMPLETE_VAR_PARAN_DESC _( L"Did you mean (COMMAND)? In fish, the '$' character is only used for accessing variables. To learn more about command substitution in fish, type 'help expand-command-substitution'.") + + /** diff --git a/parser.c b/parser.c index 68a3c19d6..fd3f710bf 100644 --- a/parser.c +++ b/parser.c @@ -76,7 +76,7 @@ The fish parser. Contains functions for parsing code. /** Error message for short circuit command error. */ -#define COND_ERR_MSG _( L"Short circuit command requires additional command") +#define COND_ERR_MSG _( L"Pipe or short circuit command requires additional command") /** Error message on reaching maximum recusrion depth @@ -1994,7 +1994,7 @@ static int parse_job( process_t *p, /* We couln't find the specified command. - + What we want to happen now is that the specified job won't get executed, and an error message is printed on-screen, but @@ -2542,11 +2542,238 @@ int eval( const wchar_t *cmd, io_data_t *io, int block_type ) return code; } -int parser_test( wchar_t * buff, +static int parser_test_argument( const wchar_t *arg, int babble ) +{ + wchar_t *unesc; + wchar_t *pos; + int err=0; + + const wchar_t *paran_begin, *paran_end; + wchar_t *arg_cpy = wcsdup( arg ); + int do_loop = 1; + + while( do_loop ) + { + switch( parse_util_locate_cmdsubst(arg_cpy, + ¶n_begin, + ¶n_end, + 0 ) ) + { + case -1: + err=1; + if( babble ) + { + error( SYNTAX_ERROR, + -1, + L"Mismatched parans" ); + print_errors(); + } + free( arg_cpy ); + return 1; + + case 0: + do_loop = 0; + break; + + case 1: + { + + wchar_t *subst = wcsndup( paran_begin+1, paran_end-paran_begin-1 ); + string_buffer_t tmp; + sb_init( &tmp ); + + sb_append_substring( &tmp, arg_cpy, paran_begin - arg_cpy); + sb_append_char( &tmp, INTERNAL_SEPARATOR); + sb_append( &tmp, paran_end+1); + +// debug( 1, L"%ls -> %ls %ls", arg_cpy, subst, tmp.buff ); + + err |= parser_test( subst, babble ); + + free( subst ); + free( arg_cpy ); + arg_cpy = (wchar_t *)tmp.buff; + + /* + Do _not_ call sb_destroy on this stringbuffer - it's + buffer is used as the new 'arg_cpy'. + */ + break; + } + + } + } + + unesc = unescape( arg_cpy, 1 ); + free( arg_cpy ); + + /* + Check for invalid variable expansions + */ + for( pos = unesc; *pos; pos++ ) + { + switch( *pos ) + { + case VARIABLE_EXPAND: + case VARIABLE_EXPAND_SINGLE: + { + switch( *(pos+1)) + { + case BRACKET_BEGIN: + { + err=1; + if( babble ) + { + error( SYNTAX_ERROR, + -1, + COMPLETE_VAR_BRACKET_DESC ); + print_errors(); + } + break; + } + + case INTERNAL_SEPARATOR: + { + err=1; + if( babble ) + { + error( SYNTAX_ERROR, + -1, + COMPLETE_VAR_PARAN_DESC ); + print_errors(); + } + break; + } + + case 0: + { + err=1; + if( babble ) + { + error( SYNTAX_ERROR, + -1, + COMPLETE_VAR_NULL_DESC ); + print_errors(); + } + break; + } + + default: + { + if( !iswalnum(*(pos+1)) && + *(pos+1)!=L'_' && + *(pos+1)!=VARIABLE_EXPAND && + *(pos+1)!=VARIABLE_EXPAND_SINGLE ) + { + err=1; + if( babble ) + { + error( SYNTAX_ERROR, + -1, + COMPLETE_VAR_DESC, + *(pos+1) ); + print_errors(); + } + } + + break; + } + + } + + break; + } + } + + + } + + free( unesc ); + return err; + +} + +int parser_test_args(const wchar_t * buff, + int babble ) +{ + tokenizer tok; + tokenizer *previous_tokenizer = current_tokenizer; + int previous_pos = current_tokenizer_pos; + int do_loop = 1; + int err = 0; + + current_tokenizer = &tok; + + for( tok_init( &tok, buff, 0 ); + do_loop && tok_has_next( &tok ); + tok_next( &tok ) ) + { + current_tokenizer_pos = tok_get_pos( &tok ); + switch( tok_last_type( &tok ) ) + { + + case TOK_STRING: + { + err |= parser_test_argument( tok_last( &tok ), babble ); + break; + } + + case TOK_END: + { + break; + } + + case TOK_ERROR: + { + if( babble ) + { + error( SYNTAX_ERROR, + tok_get_pos( &tok ), + TOK_ERR_MSG, + tok_last(&tok) ); + print_errors(); + } + err=1; + do_loop=0; + break; + } + + default: + { + if( babble ) + { + error( SYNTAX_ERROR, + tok_get_pos( &tok ), + UNEXPECTED_TOKEN_ERR_MSG, + tok_get_desc( tok_last_type(&tok)) ); + print_errors(); + } + err=1; + do_loop=0; + break; + } + } + } + + tok_destroy( &tok ); + + current_tokenizer=previous_tokenizer; + current_tokenizer_pos = previous_pos; + + error_code=0; + + return err; +} + +int parser_test( const wchar_t * buff, int babble ) { tokenizer tok; - int had_cmd=0; + /* + Set to one if a command name has been given for the currently + parsed process specification + */ + int had_cmd=0; int count = 0; int err=0; tokenizer *previous_tokenizer=current_tokenizer; @@ -2554,10 +2781,16 @@ int parser_test( wchar_t * buff, static int block_pos[BLOCK_MAX_COUNT]; static int block_type[BLOCK_MAX_COUNT]; int is_pipeline = 0; + /* + Set to one if the currently specified process can not be used inside a pipeline + */ int forbid_pipeline = 0; - int needs_cmd=0; - int require_additional_commands=0; - + /* + Set to one if an additional process specification is needed + */ + int needs_cmd=0; + void *context = halloc( 0, 0 ); + current_tokenizer = &tok; for( tok_init( &tok, buff, 0 ); @@ -2575,8 +2808,24 @@ int parser_test( wchar_t * buff, { int mark = tok_get_pos( &tok ); had_cmd = 1; - - if( require_additional_commands ) + + if( !expand_one( context, + wcsdup( tok_last( &tok ) ), + EXPAND_SKIP_SUBSHELL | EXPAND_SKIP_VARIABLES ) ) + { + err=1; + if( babble ) + { + error( SYNTAX_ERROR, + tok_get_pos( &tok ), + ILLEGAL_CMD_ERR_MSG, + tok_last( &tok ) ); + + print_errors(); + } + } + + if( needs_cmd ) { if( contains_str( tok_last(&tok), L"end", @@ -2588,14 +2837,14 @@ int parser_test( wchar_t * buff, error( SYNTAX_ERROR, tok_get_pos( &tok ), COND_ERR_MSG ); - + print_errors(); } } - - require_additional_commands--; + + needs_cmd=0; } - + /* Decrement block count on end command */ @@ -2635,10 +2884,9 @@ int parser_test( wchar_t * buff, block_type[count] = BEGIN; else block_type[count] = -1; - + // debug( 2, L"add block of type %d after cmd %ls\n", block_type[count], tok_last(&tok) ); - - + block_pos[count] = current_tokenizer_pos; tok_next( &tok ); count++; @@ -2657,10 +2905,7 @@ int parser_test( wchar_t * buff, needs_cmd = 1; had_cmd = 0; } - - /* - The short circuit commands requires _two_ additional commands. - */ + if( contains_str( tok_last( &tok ), L"or", L"and", @@ -2679,12 +2924,11 @@ int parser_test( wchar_t * buff, } } - require_additional_commands=1; } - + /* There are a lot of situations where pipelines - are forbidden, inclusing when using the exec + are forbidden, including when using the exec builtin. */ if( parser_is_pipe_forbidden( tok_last( &tok ) ) ) @@ -2795,6 +3039,11 @@ int parser_test( wchar_t * buff, } } } + else + { + err = parser_test_argument( tok_last( &tok ), babble ); + } + break; } @@ -2840,7 +3089,31 @@ int parser_test( wchar_t * buff, case TOK_PIPE: { - if( forbid_pipeline ) + if( !had_cmd ) + { + err=1; + if( babble ) + { + if( tok_get_pos(&tok)>0 && buff[tok_get_pos(&tok)-1] == L'|' ) + { + error( SYNTAX_ERROR, + tok_get_pos( &tok ), + CMD_OR_ERR_MSG, + tok_get_desc( tok_last_type(&tok) ) ); + + } + else + { + error( SYNTAX_ERROR, + tok_get_pos( &tok ), + CMD_ERR_MSG, + tok_get_desc( tok_last_type(&tok))); + } + + print_errors(); + } + } + else if( forbid_pipeline ) { err=1; if( babble ) @@ -2848,31 +3121,46 @@ int parser_test( wchar_t * buff, error( SYNTAX_ERROR, tok_get_pos( &tok ), EXEC_ERR_MSG ); - + print_errors(); } } - needs_cmd=0; - is_pipeline=1; + else + { + needs_cmd=1; + is_pipeline=1; + had_cmd=0; + } + break; } - - + case TOK_BACKGROUND: { - if( needs_cmd && !had_cmd ) + if( !had_cmd ) { err = 1; if( babble ) { - error( SYNTAX_ERROR, - tok_get_pos( &tok ), - CMD_ERR_MSG, - tok_get_desc( tok_last_type(&tok))); - + if( tok_get_pos(&tok)>0 && buff[tok_get_pos(&tok)-1] == L'&' ) + { + error( SYNTAX_ERROR, + tok_get_pos( &tok ), + CMD_AND_ERR_MSG, + tok_get_desc( tok_last_type(&tok) ) ); + + } + else + { + error( SYNTAX_ERROR, + tok_get_pos( &tok ), + CMD_ERR_MSG, + tok_get_desc( tok_last_type(&tok))); + } + print_errors(); } } - + if( had_cmd ) { had_cmd = 0; @@ -2898,7 +3186,7 @@ int parser_test( wchar_t * buff, } } - if( require_additional_commands ) + if( needs_cmd ) { err=1; if( babble ) @@ -2928,6 +3216,8 @@ int parser_test( wchar_t * buff, error_code=0; + halloc_free( context ); + return err | ((count!=0)<<1); } diff --git a/parser.h b/parser.h index 028788d04..944e1d3f4 100644 --- a/parser.h +++ b/parser.h @@ -315,10 +315,20 @@ const wchar_t *parser_get_block_desc( int block ); /** - Test if the specified string can be parsed, or if more bytes need to be read first. - The result has the first bit set if the string contains errors, and the second bit is set if the string contains an unclosed block. + Test if the specified string can be parsed, or if more bytes need + to be read first. The result has the first bit set if the string + contains errors, and the second bit is set if the string contains + an unclosed block. */ -int parser_test( wchar_t * buff, int babble ); +int parser_test( const wchar_t * buff, int babble ); + +/** + Test if the specified string can be parsed as an argument list, + e.g. sent to eval_args. The result has the first bit set if the + string contains errors, and the second bit is set if the string + contains an unclosed block. +*/ +int parser_test_args( const wchar_t * buff, int babble ); /** Returns the full path of the specified directory. If the \c in is a