aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar ridiculousfish <corydoras@ridiculousfish.com>2012-09-01 01:46:14 -0700
committerGravatar ridiculousfish <corydoras@ridiculousfish.com>2012-09-01 01:46:14 -0700
commitcc1395797e78a26583461333199181b4f8ec0b13 (patch)
tree9f191547887932abe9e6a34d27349085453ed622
parent122791646ec7dc421a679cb39cce7a8cbc0a7594 (diff)
First stab at elseif implementation
-rw-r--r--builtin.cpp52
-rw-r--r--fish_tests.cpp5
-rw-r--r--parser.cpp160
-rw-r--r--parser.h15
-rw-r--r--parser_keywords.cpp2
-rw-r--r--parser_keywords.h2
-rw-r--r--proc.cpp2
-rw-r--r--proc.h87
-rw-r--r--tests/test7.in58
-rw-r--r--tests/test7.out12
10 files changed, 287 insertions, 108 deletions
diff --git a/builtin.cpp b/builtin.cpp
index 38dcb8db..19c7e412 100644
--- a/builtin.cpp
+++ b/builtin.cpp
@@ -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" ) },
diff --git a/fish_tests.cpp b/fish_tests.cpp
index a5bddfa2..0d0c5f9b 100644
--- a/fish_tests.cpp
+++ b/fish_tests.cpp
@@ -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" );
diff --git a/parser.cpp b/parser.cpp
index df0677de..b8818a9a 100644
--- a/parser.cpp
+++ b/parser.cpp
@@ -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)
{
}
diff --git a/parser.h b/parser.h
index 4c1cd7f5..31f08b7b 100644
--- a/parser.h
+++ b/parser.h
@@ -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
*/
diff --git a/parser_keywords.cpp b/parser_keywords.cpp
index efc2ff6e..8a5f1b74 100644
--- a/parser_keywords.cpp
+++ b/parser_keywords.cpp
@@ -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" );
diff --git a/parser_keywords.h b/parser_keywords.h
index 9af1e230..803df49d 100644
--- a/parser_keywords.h
+++ b/parser_keywords.h
@@ -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
diff --git a/proc.cpp b/proc.cpp
index fe4b9dda..26b80bbe 100644
--- a/proc.cpp
+++ b/proc.cpp
@@ -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 )
diff --git a/proc.h b/proc.h
index 30c1defb..aea95ba6 100644
--- a/proc.h
+++ b/proc.h
@@ -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
-
- 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.
-*/
-#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
+ */
+ 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 job is under job control
-*/
-#define JOB_CONTROL 128
-/**
- 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,
- 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;
};
/**
diff --git a/tests/test7.in b/tests/test7.in
index c2dece30..60cc1385 100644
--- a/tests/test7.in
+++ b/tests/test7.in
@@ -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
diff --git a/tests/test7.out b/tests/test7.out
index 3d0164d0..454240b4 100644
--- a/tests/test7.out
+++ b/tests/test7.out
@@ -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