First stab at elseif implementation

This commit is contained in:
ridiculousfish 2012-09-01 01:46:14 -07:00
parent 122791646e
commit cc1395797e
10 changed files with 287 additions and 108 deletions

View file

@ -3379,7 +3379,8 @@ static int builtin_else( parser_t &parser, wchar_t **argv )
if (parser.current_block != NULL && parser.current_block->type() == IF)
{
if_block = static_cast<if_block_t *>(parser.current_block);
if (if_block->if_expr_evaluated && ! if_block->else_evaluated)
/* Ensure that we're past IF but not up to an ELSE */
if (if_block->if_expr_evaluated && ! if_block->has_reached_else())
{
block_ok = true;
}
@ -3395,9 +3396,10 @@ static int builtin_else( parser_t &parser, wchar_t **argv )
}
else
{
/* If the 'if' expression evaluated to false, then we ought to take the else branch, which means skip ought to be false. So the sense of the skip variable matches the sense of the 'if' expression result. */
if_block->skip = if_block->if_expr_result;
if_block->else_evaluated = true;
/* Run the else block if the IF expression was false and so were all the ELSEIF expressions (if any) */
bool run_else = ! if_block->any_branch_taken;
if_block->skip = ! run_else;
if_block->if_state = if_block_t::if_state_else;
env_pop();
env_push(0);
}
@ -3408,6 +3410,44 @@ static int builtin_else( parser_t &parser, wchar_t **argv )
return proc_get_last_status();
}
static int builtin_elseif( parser_t &parser, wchar_t **argv )
{
puts("BULITIN ELSEIF");
bool block_ok = false;
if_block_t *if_block = NULL;
if (parser.current_block != NULL && parser.current_block->type() == IF)
{
if_block = static_cast<if_block_t *>(parser.current_block);
/* Make sure that we're past IF, but not up to an ELSE */
if (if_block->if_expr_evaluated && ! if_block->has_reached_else())
{
block_ok = true;
}
}
if( ! block_ok )
{
append_format(stderr_buffer,
_( L"%ls: Not inside of 'if' block\n" ),
argv[0] );
builtin_print_help( parser, argv[0], stderr_buffer );
return STATUS_BUILTIN_ERROR;
}
else
{
/* Run this elseif if the IF expression was false, and so were all ELSEIF expressions thus far. */
bool run_elseif = ! if_block->any_branch_taken;
if_block->skip = ! run_elseif;
env_pop();
env_push(0);
}
/*
If everything goes ok, return status of last command to execute.
*/
return proc_get_last_status();
}
/**
This function handles both the 'continue' and the 'break' builtins
that are used for loop control.
@ -3767,6 +3807,7 @@ static int builtin_history( parser_t &parser, wchar_t **argv )
/**
Data about all the builtin commands in fish.
Functions that are bound to builtin_generic are handled directly by the parser.
NOTE: These must be kept in sorted order!
*/
static const builtin_data_t builtin_datas[]=
{
@ -3787,8 +3828,9 @@ static const builtin_data_t builtin_datas[]=
{ L"contains", &builtin_contains, N_( L"Search for a specified string in a list" ) },
{ L"continue", &builtin_break_continue, N_( L"Skip the rest of the current lap of the innermost loop" ) },
{ L"count", &builtin_count, N_( L"Count the number of arguments" ) },
{ L"echo", &builtin_echo, N_( L"Print arguments" ) },
{ L"echo", &builtin_echo, N_( L"Print arguments" ) },
{ L"else", &builtin_else, N_( L"Evaluate block if condition is false" ) },
{ L"elseif", &builtin_generic, N_( L"Evaluate block if this condition is true but all previous were false" ) },
{ L"emit", &builtin_emit, N_( L"Emit an event" ) },
{ L"end", &builtin_end, N_( L"End a block of commands" ) },
{ L"exec", &builtin_generic, N_( L"Run command in current process" ) },

View file

@ -434,6 +434,11 @@ static void test_parser()
{
err( L"'else' command outside of conditional block context undetected" );
}
if( !parser.test( L"elseif", 0, 0, 0 ) )
{
err( L"'elseif' command outside of conditional block context undetected" );
}
if( !parser.test( L"break", 0, 0, 0 ) )
{
err( L"'break' command outside of loop block context undetected" );

View file

@ -280,7 +280,7 @@ struct block_lookup_entry
/**
The block type id. The legal values are defined in parser.h.
*/
int type;
block_type_t type;
/**
The name of the builtin that creates this type of block, if any.
@ -356,7 +356,7 @@ static const struct block_lookup_entry block_lookup[]=
}
,
{
0, 0, 0
(block_type_t)0, 0, 0
}
};
@ -1402,14 +1402,17 @@ void parser_t::parse_job_argument_list( process_t *p,
*/
skip=1;
/*
But if this is in fact a case statement, then it should be evaluated
*/
if( (current_block->type() == SWITCH) && args.at(0).completion == L"case" && p->type == INTERNAL_BUILTIN )
/* But if this is in fact a case statement or an elseif statement, then it should be evaluated */
block_type_t type = current_block->type();
if( type == SWITCH && args.at(0).completion == L"case" && p->type == INTERNAL_BUILTIN )
{
skip=0;
}
else if (type == IF && args.at(0).completion == L"elseif")
{
//skip = 0;
printf("SKIP elseif???\n");
}
}
if( !skip )
@ -1701,17 +1704,18 @@ int parser_t::parse_job( process_t *p,
int use_builtin = 1; // May builtins be considered when checking what action this command represents
int use_command = 1; // May commands be considered when checking what action this command represents
int is_new_block=0; // Does this command create a new block?
bool unskip = false; // Maybe we are an elseif inside an if block; if so we may want to evaluate this even if the if block is currently set to skip
block_t *prev_block = current_block;
int prev_tokenizer_pos = current_tokenizer_pos;
int prev_tokenizer_pos = current_tokenizer_pos;
current_tokenizer_pos = tok_get_pos( tok );
while( args.size() == 0 )
while( args.empty() )
{
wcstring nxt;
bool has_nxt = false;
int consumed = 0; // Set to one if the command requires a second command, like e.g. while does
bool consumed = false; // Set to one if the command requires a second command, like e.g. while does
int mark; // Use to save the position of the beginning of the token
switch( tok_last_type( tok ))
@ -1780,7 +1784,7 @@ int parser_t::parse_job( process_t *p,
}
mark = tok_get_pos( tok );
if( contains( nxt,
L"command",
L"builtin",
@ -1815,7 +1819,7 @@ int parser_t::parse_job( process_t *p,
tok_next( tok );
}
consumed=1;
consumed = true;
if( nxt == L"command" || nxt == L"builtin" )
{
@ -1879,7 +1883,7 @@ int parser_t::parse_job( process_t *p,
this->push_block( wb );
}
consumed=1;
consumed = true;
is_new_block=1;
}
@ -1892,8 +1896,23 @@ int parser_t::parse_job( process_t *p,
ib->tok_pos = mark;
is_new_block=1;
consumed=1;
consumed = true;
}
else if( nxt == L"elseif" )
{
/* Piggyback on the if block, but we need the elseif command */
job_set_flag( j, JOB_ELSEIF, 1 );
tok_next( tok );
consumed = true;
/* Determine if we need to unskip */
if (current_block->type() == IF)
{
const if_block_t *ib = static_cast<const if_block_t *>(current_block);
/* We want to execute this ELSEIF if the IF expression was evaluated, it failed, and so has every other ELSEIF (if any) */
unskip = (ib->if_expr_evaluated && ! ib->any_branch_taken);
}
}
/*
Test if we need another command
@ -1978,7 +1997,7 @@ int parser_t::parse_job( process_t *p,
If we are not executing the current block, allow
non-existent commands.
*/
if( current_block->skip )
if( current_block->skip && ! unskip)
{
p->actual_cmd.clear();
}
@ -2243,6 +2262,7 @@ void parser_t::skipped_exec( job_t * j )
{
process_t *p;
/* Handle other skipped guys */
for( p = j->first_process; p; p=p->next )
{
if( p->type == INTERNAL_BUILTIN )
@ -2265,13 +2285,17 @@ void parser_t::skipped_exec( job_t * j )
}
else if( wcscmp( p->argv0(), L"else" )==0)
{
if( (current_block->type() == IF ) &&
(static_cast<const if_block_t*>(current_block)->if_expr_evaluated))
{
exec( *this, j );
return;
}
}
if (current_block->type() == IF)
{
/* Evaluate this ELSE if the IF expression failed, and so has every ELSEIF (if any) expression thus far */
const if_block_t *ib = static_cast<const if_block_t*>(current_block);
if (ib->if_expr_evaluated && ! ib->any_branch_taken)
{
exec( *this, j );
return;
}
}
}
else if( wcscmp( p->argv0(), L"case" )==0)
{
if(current_block->type() == SWITCH)
@ -2285,6 +2309,27 @@ void parser_t::skipped_exec( job_t * j )
job_free( j );
}
/* Return whether we should skip the current block, if it is an elseif. */
static bool job_should_skip_elseif(const job_t *job, const block_t *current_block)
{
if (current_block->type() != IF)
{
/* Not an IF block, so just honor the skip property */
return current_block->skip;
}
else
{
/* We are an IF block */
const if_block_t *ib = static_cast<const if_block_t *>(current_block);
/* Execute this ELSEIF if the IF expression has been evaluated, it evaluated to false, and all ELSEIFs so far have evaluated to false. */
bool execute_elseif = (ib->if_expr_evaluated && ! ib->any_branch_taken);
/* Invert the sense */
return ! execute_elseif;
}
}
/**
Evaluates a job from the specified tokenizer. First calls
parse_job to parse the job and then calls exec to execute it.
@ -2367,7 +2412,24 @@ void parser_t::eval_job( tokenizer *tok )
profile_item->skipped=current_block->skip;
}
skip = skip || current_block->skip;
/* If we're an ELSEIF, then we may want to unskip, if we're skipping because of an IF */
if (job_get_flag(j, JOB_ELSEIF))
{
bool skip_elseif = job_should_skip_elseif(j, current_block);
/* Record that we're entering an elseif */
if (! skip_elseif)
{
/* We must be an IF block here */
assert(current_block->type() == IF);
static_cast<if_block_t *>(current_block)->is_elseif_entry = true;
}
/* Record that in the block too. This is similar to what builtin_else does. */
current_block->skip = skip_elseif;
}
skip = skip || current_block->skip;
skip = skip || job_get_flag( j, JOB_WILDCARD_ERROR );
skip = skip || job_get_flag( j, JOB_SKIP );
@ -2405,7 +2467,7 @@ void parser_t::eval_job( tokenizer *tok )
{
case WHILE_TEST_FIRST:
{
// PCA I added the 'current_block->skip ||' part because we couldn't reliably
// PCA I added the 'wb->skip ||' part because we couldn't reliably
// control-C out of loops like this: while test 1 -eq 1; end
wb->skip = wb->skip || proc_get_last_status()!= 0;
wb->status = WHILE_TESTED;
@ -2416,15 +2478,28 @@ void parser_t::eval_job( tokenizer *tok )
if( current_block->type() == IF )
{
if_block_t *ib = static_cast<if_block_t *>(current_block);
if( (! ib->if_expr_evaluated) &&
(!current_block->skip) )
{
if_block_t *ib = static_cast<if_block_t *>(current_block);
if (ib->skip)
{
/* Nothing */
}
else if (! ib->if_expr_evaluated)
{
/* Execute the IF */
bool if_result = (proc_get_last_status() == 0);
ib->if_expr_result = if_result; // store expression result
current_block->skip = ! if_result; //don't execute if the expresion result was not zero
ib->any_branch_taken = if_result;
current_block->skip = ! if_result; //don't execute if the expression failed
ib->if_expr_evaluated = true;
}
}
else if (ib->is_elseif_entry && ! ib->any_branch_taken)
{
/* Maybe mark an ELSEIF branch as taken */
bool elseif_taken = (proc_get_last_status() == 0);
ib->any_branch_taken = elseif_taken;
current_block->skip = ! elseif_taken;
ib->is_elseif_entry = false;
}
}
}
@ -2625,7 +2700,7 @@ int parser_t::eval( const wcstring &cmdStr, const io_chain_t &io, enum block_typ
/**
\return the block type created by the specified builtin, or -1 on error.
*/
int parser_get_block_type( const wcstring &cmd )
block_type_t parser_get_block_type( const wcstring &cmd )
{
int i;
@ -2636,7 +2711,7 @@ int parser_get_block_type( const wcstring &cmd )
return block_lookup[i].type;
}
}
return -1;
return (block_type_t)-1;
}
/**
@ -2862,17 +2937,16 @@ int parser_t::test( const wchar_t * buff,
Set to one if a command name has been given for the currently
parsed process specification
*/
int had_cmd=0;
int count = 0;
int had_cmd=0;
int err=0;
int unfinished = 0;
tokenizer *previous_tokenizer=current_tokenizer;
int previous_pos=current_tokenizer_pos;
// PCA These statics are terrifying - I have no idea whether and why these variables need to be static
static int block_pos[BLOCK_MAX_COUNT];
static int block_type[BLOCK_MAX_COUNT];
int block_pos[BLOCK_MAX_COUNT] = {};
block_type_t block_type[BLOCK_MAX_COUNT] = {};
int count = 0;
int res = 0;
/*
@ -2933,7 +3007,6 @@ int parser_t::test( const wchar_t * buff,
{
if( !had_cmd )
{
int is_else;
int mark = tok_get_pos( &tok );
had_cmd = 1;
arg_count=0;
@ -2989,17 +3062,16 @@ int parser_t::test( const wchar_t * buff,
count--;
tok_set_pos( &tok, mark );
}
is_else = (command == L"else");
/*
Store the block level. This needs to be done
_after_ checking for end commands, but _before_
shecking for block opening commands.
*/
bool is_else_or_elseif = (command == L"else" || command == L"elseif");
if( block_level )
{
block_level[tok_get_pos( &tok )] = count + (is_else?-1:0);
block_level[tok_get_pos( &tok )] = count + (is_else_or_elseif?-1:0);
}
/*
@ -3644,8 +3716,10 @@ block_t::~block_t()
if_block_t::if_block_t() :
if_expr_evaluated(false),
if_expr_result(false),
any_branch_taken(false),
is_elseif_entry(false),
else_evaluated(false),
if_state(if_state_if),
block_t(IF)
{
}

View file

@ -99,7 +99,7 @@ struct block_t
The job that is currently evaluated in the specified block.
*/
job_t *job;
#if 0
union
{
@ -141,8 +141,18 @@ struct block_t
struct if_block_t : public block_t
{
bool if_expr_evaluated; // whether the clause of the if statement has been tested
bool if_expr_result; // if so, whether it evaluated to true
bool any_branch_taken; // whether the clause of the if statement or any elseif has been found to be true
bool is_elseif_entry; // whether we're the first command in an elseif.
bool else_evaluated; // whether we've encountered a terminal else block
enum {
if_state_if,
if_state_elseif,
if_state_else
} if_state;
bool has_reached_else() const { return if_state == if_state_else; }
if_block_t();
};
@ -229,7 +239,6 @@ enum while_status
;
/**
Errors that can be generated by the parser
*/

View file

@ -43,6 +43,7 @@ bool parser_keywords_is_subcommand( const wcstring &cmd )
L"while",
L"exec",
L"if",
L"elseif",
L"and",
L"or",
L"not" );
@ -68,6 +69,7 @@ bool parser_keywords_is_reserved( const wcstring &word)
L"end",
L"case",
L"else",
L"elseif",
L"return",
L"continue",
L"break" );

View file

@ -26,7 +26,7 @@ bool parser_keywords_is_switch( const wcstring &cmd );
/**
Tests if the specified commands parameters should be interpreted as another command, which will be true if the command is either 'command', 'exec', 'if', 'while' or 'builtin'.
Tests if the specified commands parameters should be interpreted as another command, which will be true if the command is either 'command', 'exec', 'if', 'while', 'elseif', or 'builtin'.
\param cmd The command name to test
\return 1 of the command parameter is a command, 0 otherwise

View file

@ -306,7 +306,7 @@ void job_set_flag( job_t *j, int flag, int set )
if( set )
j->flags |= flag;
else
j->flags = j->flags & (0xffffffff ^ flag);
j->flags = j->flags & ((unsigned int)(-1) ^ flag);
}
int job_get_flag( const job_t *j, int flag )

87
proc.h
View file

@ -214,64 +214,42 @@ class process_t
#endif
};
/**
Constant for the flag variable in the job struct
true if user was told about stopped job
*/
#define JOB_NOTIFIED 1
/**
Constant for the flag variable in the job struct
Whether this job is in the foreground
*/
#define JOB_FOREGROUND 2
/**
Constant for the flag variable in the job struct
/* Constants for the flag variable in the job struct */
enum {
/** true if user was told about stopped job */
JOB_NOTIFIED = 1 << 0,
/** Whether this job is in the foreground */
JOB_FOREGROUND = 1 << 1,
/**
Whether the specified job is completely constructed,
i.e. completely parsed, and every process in the job has been
forked, etc.
*/
#define JOB_CONSTRUCTED 4
/**
Constant for the flag variable in the job struct
*/
JOB_CONSTRUCTED = 1 << 2,
/** Whether the specified job is a part of a subshell, event handler or some other form of special job that should not be reported */
JOB_SKIP_NOTIFICATION = 1 << 3,
Whether the specified job is a part of a subshell, event handler or some other form of special job that should not be reported
*/
#define JOB_SKIP_NOTIFICATION 8
/**
Constant for the flag variable in the job struct
/** Should the exit status be negated? This flag can only be set by the not builtin. */
JOB_NEGATE = 1 << 4,
/** Should the exit status be used to reevaluate the condition in an if block? This is only used by elseif and is a big hack. */
JOB_ELSEIF = 1 << 5,
/** This flag is set to one on wildcard expansion errors. It means that the current command should not be executed */
JOB_WILDCARD_ERROR = 1 << 6,
/** Skip executing this job. This flag is set by the short-circut builtins, i.e. and and or */
JOB_SKIP = 1 << 7,
/** Whether the job is under job control */
JOB_CONTROL = 1 << 8,
Should the exit status be negated? This flag can only be set by the not builtin.
*/
#define JOB_NEGATE 16
/**
Constant for the flag variable in the job struct
This flag is set to one on wildcard expansion errors. It means that the current command should not be executed
*/
#define JOB_WILDCARD_ERROR 32
/**
Constant for the flag variable in the job struct
Skip executing this job. This flag is set by the short-circut builtins, i.e. and and or
*/
#define JOB_SKIP 64
/**
Constant for the flag variable in the job struct
Whether the job is under job control
*/
#define JOB_CONTROL 128
/**
Constant for the flag variable in the job struct
Whether the job wants to own the terminal when in the foreground
*/
#define JOB_TERMINAL 256
/** Whether the job wants to own the terminal when in the foreground */
JOB_TERMINAL = 1 << 9
};
/**
A struct represeting a job. A job is basically a pipeline of one
@ -352,8 +330,7 @@ class job_t
/**
Bitset containing information about the job. A combination of the JOB_* constants.
*/
int flags;
unsigned int flags;
};
/**

View file

@ -36,3 +36,61 @@ contains -i string a b c d; or echo nothing
contains -i -- string a b c string d
contains -i -- -- a b c; or echo nothing
contains -i -- -- a b c -- v
# Test if, else, and elseif
# Test if, else, and elseif
if true
echo alpha1.1
echo alpha1.2
elseif false
echo beta1.1
echo beta1.2
elseif false
echo gamma1.1
echo gamma1.2
else
echo delta1.1
echo delta1.2
end
if false
echo alpha2.1
echo alpha2.2
elseif begin ; true ; end
echo beta2.1
echo beta2.2
elseif begin ; echo nope2.1; false ; end
echo gamma2.1
echo gamma2.2
else
echo delta2.1
echo delta2.2
end
if false
echo alpha3.1
echo alpha3.2
elseif begin ; echo yep3.1; false ; end
echo beta3.1
echo beta3.2
elseif begin ; echo yep3.2; true ; end
echo gamma3.1
echo gamma3.2
else
echo delta3.1
echo delta3.2
end
if false
echo alpha4.1
echo alpha4.2
elseif begin ; echo yep4.1; false ; end
echo beta4.1
echo beta4.2
elseif begin ; echo yep4.2; false ; end
echo gamma4.1
echo gamma4.2
else
echo delta4.1
echo delta4.2
end

View file

@ -11,3 +11,15 @@ nothing
4
nothing
4
alpha1.1
alpha1.2
beta2.1
beta2.2
yep3.1
yep3.2
gamma3.1
gamma3.2
yep4.1
yep4.2
delta4.1
delta4.2