Major update to the ahead-of-time syntax checker

darcs-hash:20060521192524-ac50b-48713f826558e66ef21046d1bb779623cc2fd97a.gz
This commit is contained in:
axel 2006-05-22 05:25:24 +10:00
parent bbf2a3836f
commit 1c2cbb00bc
4 changed files with 362 additions and 63 deletions

View file

@ -76,27 +76,6 @@ parameter expansion.
*/ */
#define COMPLETE_LAST_DESC _( L"Last background job") #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 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, error( SYNTAX_ERROR,
-1, -1,
COMPLETE_VAR_NULL_DESC, COMPLETE_VAR_NULL_DESC );
in[stop_pos] );
break; break;
} }

View file

@ -112,6 +112,27 @@ enum
/** String containing the character for separating two array elements */ /** String containing the character for separating two array elements */
#define ARRAY_SEP_STR L"\x1e" #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'.")
/** /**

322
parser.c
View file

@ -76,7 +76,7 @@ The fish parser. Contains functions for parsing code.
/** /**
Error message for short circuit command error. 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 Error message on reaching maximum recusrion depth
@ -2542,10 +2542,237 @@ int eval( const wchar_t *cmd, io_data_t *io, int block_type )
return code; 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,
&paran_begin,
&paran_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 ) int babble )
{ {
tokenizer tok; 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;
/*
Set to one if a command name has been given for the currently
parsed process specification
*/
int had_cmd=0; int had_cmd=0;
int count = 0; int count = 0;
int err=0; int err=0;
@ -2554,9 +2781,15 @@ int parser_test( wchar_t * buff,
static int block_pos[BLOCK_MAX_COUNT]; static int block_pos[BLOCK_MAX_COUNT];
static int block_type[BLOCK_MAX_COUNT]; static int block_type[BLOCK_MAX_COUNT];
int is_pipeline = 0; int is_pipeline = 0;
/*
Set to one if the currently specified process can not be used inside a pipeline
*/
int forbid_pipeline = 0; int forbid_pipeline = 0;
/*
Set to one if an additional process specification is needed
*/
int needs_cmd=0; int needs_cmd=0;
int require_additional_commands=0; void *context = halloc( 0, 0 );
current_tokenizer = &tok; current_tokenizer = &tok;
@ -2576,7 +2809,23 @@ int parser_test( wchar_t * buff,
int mark = tok_get_pos( &tok ); int mark = tok_get_pos( &tok );
had_cmd = 1; 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), if( contains_str( tok_last(&tok),
L"end", L"end",
@ -2593,7 +2842,7 @@ int parser_test( wchar_t * buff,
} }
} }
require_additional_commands--; needs_cmd=0;
} }
/* /*
@ -2638,7 +2887,6 @@ int parser_test( wchar_t * buff,
// debug( 2, L"add block of type %d after cmd %ls\n", block_type[count], tok_last(&tok) ); // debug( 2, L"add block of type %d after cmd %ls\n", block_type[count], tok_last(&tok) );
block_pos[count] = current_tokenizer_pos; block_pos[count] = current_tokenizer_pos;
tok_next( &tok ); tok_next( &tok );
count++; count++;
@ -2658,9 +2906,6 @@ int parser_test( wchar_t * buff,
had_cmd = 0; had_cmd = 0;
} }
/*
The short circuit commands requires _two_ additional commands.
*/
if( contains_str( tok_last( &tok ), if( contains_str( tok_last( &tok ),
L"or", L"or",
L"and", L"and",
@ -2679,12 +2924,11 @@ int parser_test( wchar_t * buff,
} }
} }
require_additional_commands=1;
} }
/* /*
There are a lot of situations where pipelines There are a lot of situations where pipelines
are forbidden, inclusing when using the exec are forbidden, including when using the exec
builtin. builtin.
*/ */
if( parser_is_pipe_forbidden( tok_last( &tok ) ) ) 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; break;
} }
@ -2840,7 +3089,31 @@ int parser_test( wchar_t * buff,
case TOK_PIPE: 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; err=1;
if( babble ) if( babble )
@ -2852,22 +3125,37 @@ int parser_test( wchar_t * buff,
print_errors(); print_errors();
} }
} }
needs_cmd=0; else
{
needs_cmd=1;
is_pipeline=1; is_pipeline=1;
had_cmd=0;
}
break;
} }
case TOK_BACKGROUND: case TOK_BACKGROUND:
{ {
if( needs_cmd && !had_cmd ) if( !had_cmd )
{ {
err = 1; err = 1;
if( babble ) if( babble )
{
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, error( SYNTAX_ERROR,
tok_get_pos( &tok ), tok_get_pos( &tok ),
CMD_ERR_MSG, CMD_ERR_MSG,
tok_get_desc( tok_last_type(&tok))); tok_get_desc( tok_last_type(&tok)));
}
print_errors(); print_errors();
} }
@ -2898,7 +3186,7 @@ int parser_test( wchar_t * buff,
} }
} }
if( require_additional_commands ) if( needs_cmd )
{ {
err=1; err=1;
if( babble ) if( babble )
@ -2928,6 +3216,8 @@ int parser_test( wchar_t * buff,
error_code=0; error_code=0;
halloc_free( context );
return err | ((count!=0)<<1); return err | ((count!=0)<<1);
} }

View file

@ -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. Test if the specified string can be parsed, or if more bytes need
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. 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 Returns the full path of the specified directory. If the \c in is a