Bring in basecode from development repo.

This commit is contained in:
Gene Stark 2022-03-27 16:45:17 -04:00
commit 8ca7e77e25
30 changed files with 6526 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*~
#*
*.d
*.o
*.out
bin/*
build/*

23
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,23 @@
image: hwrunner:latest
variables:
GIT_SSL_NO_VERIFY: "true"
EXEC: mush
HW_DIR: hw4
before_script:
- make clean all -C ${HW_DIR}
stages:
- build
- run
- test
build:
stage: build
script:
- echo "Build done"
run:
stage: run
script:
- cd ${HW_DIR} && bin/${EXEC} < rsrc/run_test.mush
test:
stage: test
script:
- cd ${HW_DIR} && bin/${EXEC}_tests -S --verbose=0 --timeout 5

61
hw4/Makefile Normal file
View File

@ -0,0 +1,61 @@
CC := gcc
YACC := bison
LEX := flex
SRCD := src
TSTD := tests
BLDD := build
BIND := bin
INCD := include
MAIN := $(BLDD)/main.o
ALL_SRCF := $(shell find $(SRCD) -type f -name *.c)
ALL_OBJF := $(patsubst $(SRCD)/%,$(BLDD)/%,$(ALL_SRCF:.c=.o))
ALL_FUNCF := $(filter-out $(MAIN) $(AUX), $(ALL_OBJF))
TEST_SRC := $(shell find $(TSTD) -type f -name *.c)
INC := -I $(INCD)
CFLAGS := -Wall -Werror -Wno-unused-function -MMD
COLORF := -DCOLOR
DFLAGS := -g -DDEBUG -DCOLOR
PRINT_STAMENTS := -DERROR -DSUCCESS -DWARN -DINFO
TEST_LIB := -lcriterion
LIBS :=
EXEC := mush
TEST_EXEC := $(EXEC)_tests
.PHONY: clean all setup debug
all: setup $(BIND)/$(EXEC) $(BIND)/$(TEST_EXEC)
debug: CFLAGS += $(DFLAGS) $(PRINT_STAMENTS) $(COLORF)
debug: all
setup: $(BIND) $(BLDD)
$(BIND):
mkdir -p $(BIND)
$(BLDD):
mkdir -p $(BLDD)
$(BIND)/$(EXEC): $(BLDD)/mush.tab.o $(BLDD)/mush.lex.o $(ALL_OBJF)
$(CC) $^ -o $@ $(LIBS)
$(BIND)/$(TEST_EXEC): $(ALL_FUNCF) $(BLDD)/mush.tab.o $(BLDD)/mush.lex.o $(TEST_SRC)
$(CC) $(CFLAGS) $(INC) $(ALL_FUNCF) $(TEST_SRC) $(TEST_LIB) $(LIBS) -o $@
$(BLDD)/%.o: $(SRCD)/%.c $(INCD)/$(EXEC).tab.h
$(CC) $(CFLAGS) $(INC) -c -o $@ $<
clean:
rm -rf $(BLDD) $(BIND)
# Cancel the implicit rule that is doing the wrong thing.
%.c: %.y
%.c: %.l
.PRECIOUS: $(BLDD)/*.d
-include $(BLDD)/*.d

BIN
hw4/demo/mush Executable file

Binary file not shown.

88
hw4/include/debug.h Normal file
View File

@ -0,0 +1,88 @@
#ifndef DEBUG_H
#define DEBUG_H
#include <stdio.h>
#define NL "\n"
#ifdef COLOR
#define KNRM "\033[0m"
#define KRED "\033[1;31m"
#define KGRN "\033[1;32m"
#define KYEL "\033[1;33m"
#define KBLU "\033[1;34m"
#define KMAG "\033[1;35m"
#define KCYN "\033[1;36m"
#define KWHT "\033[1;37m"
#define KBWN "\033[0;33m"
#else
#define KNRM ""
#define KRED ""
#define KGRN ""
#define KYEL ""
#define KBLU ""
#define KMAG ""
#define KCYN ""
#define KWHT ""
#define KBWN ""
#endif
#ifdef VERBOSE
#define DEBUG
#define INFO
#define WARN
#define ERROR
#define SUCCESS
#endif
#ifdef DEBUG
#define debug(S, ...) \
do { \
fprintf(stderr, KMAG "DEBUG: %s:%s:%d " KNRM S NL, __FILE__, \
__extension__ __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} while (0)
#else
#define debug(S, ...)
#endif
#ifdef INFO
#define info(S, ...) \
do { \
fprintf(stderr, KBLU "INFO: %s:%s:%d " KNRM S NL, __FILE__, \
__extension__ __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} while (0)
#else
#define info(S, ...)
#endif
#ifdef WARN
#define warn(S, ...) \
do { \
fprintf(stderr, KYEL "WARN: %s:%s:%d " KNRM S NL, __FILE__, \
__extension__ __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} while (0)
#else
#define warn(S, ...)
#endif
#ifdef SUCCESS
#define success(S, ...) \
do { \
fprintf(stderr, KGRN "SUCCESS: %s:%s:%d " KNRM S NL, __FILE__, \
__extension__ __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} while (0)
#else
#define success(S, ...)
#endif
#ifdef ERROR
#define error(S, ...) \
do { \
fprintf(stderr, KRED "ERROR: %s:%s:%d " KNRM S NL, __FILE__, \
__extension__ __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} while (0)
#else
#define error(S, ...)
#endif
#endif /* DEBUG_H */

53
hw4/include/mush.h Normal file
View File

@ -0,0 +1,53 @@
/*
* DO NOT MODIFY THE CONTENTS OF THIS FILE.
* IT WILL BE REPLACED DURING GRADING
*/
#include "syntax.h"
/* Names of special store variables to hold results from job execution. */
#define JOB_VAR "JOB"
#define STATUS_VAR "STATUS"
#define OUTPUT_VAR "OUTPUT"
/*
* If you find it convenient, you may assume that the maximum number of jobs
* that can exist at one time is given by the following preprocessor symbol.
* Your code should continue to work even if the particular value of this
* symbol is changed before compilation.
*/
#define MAX_JOBS 10
/* Functions in program store module. */
int prog_list(FILE *out);
int prog_insert(STMT *stmt);
int prog_delete(int min, int max);
void prog_reset();
STMT *prog_fetch();
STMT *prog_next();
STMT *prog_goto(int lineno);
/* Functions in data store module. */
char *store_get_string(char *var);
int store_get_int(char *var, long *valp);
int store_set_string(char *var, char *val);
int store_set_int(char *var, long val);
void store_show(FILE *f);
/* Functions in execution module. */
int exec_interactive();
int exec_stmt(STMT *stmt);
char *eval_to_string(EXPR *expr);
long eval_to_numeric(EXPR *expr);
/* Functions in jobs module. */
int jobs_init(void);
int jobs_fini(void);
int jobs_run(PIPELINE *pp);
int jobs_expunge(int jobid);
int jobs_wait(int jobid);
int jobs_poll(int jobid);
int jobs_cancel(int jobid);
int jobs_pause(void);
char *jobs_get_output(int jobid);
int jobs_show(FILE *file);

125
hw4/include/mush.tab.h Normal file
View File

@ -0,0 +1,125 @@
/* A Bison parser, made by GNU Bison 3.5.1. */
/* Bison interface for Yacc-like parsers in C
Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2020 Free Software Foundation,
Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* As a special exception, you may create a larger work that contains
part or all of the Bison parser skeleton and distribute that work
under terms of your choice, so long as that work isn't itself a
parser generator using the skeleton or a modified version thereof
as a parser skeleton. Alternatively, if you modify or redistribute
the parser skeleton itself, you may (at your option) remove this
special exception, which will cause the skeleton and the resulting
Bison output files to be licensed under the GNU General Public
License without this special exception.
This special exception was added by the Free Software Foundation in
version 2.2 of Bison. */
/* Undocumented macros, especially those whose name start with YY_,
are private implementation details. Do not rely on them. */
#ifndef YY_YY_INCLUDE_MUSH_TAB_H_INCLUDED
# define YY_YY_INCLUDE_MUSH_TAB_H_INCLUDED
/* Debug traces. */
#ifndef YYDEBUG
# define YYDEBUG 1
#endif
#if YYDEBUG
extern int yydebug;
#endif
/* Token type. */
#ifndef YYTOKENTYPE
# define YYTOKENTYPE
enum yytokentype
{
NUMBER = 258,
NAME = 259,
WORD = 260,
STRING = 261,
LIST = 262,
DELETE = 263,
RUN = 264,
CONT = 265,
STOP = 266,
BG = 267,
CAPTURE = 268,
WAIT = 269,
POLL = 270,
CANCEL = 271,
PAUSE = 272,
SET = 273,
UNSET = 274,
IF = 275,
GOTO = 276,
SOURCE = 277,
EQ = 278,
PIPE = 279,
LESS = 280,
GREATER = 281,
EQUAL = 282,
LESSEQ = 283,
GREATEQ = 284,
AND = 285,
OR = 286,
NOT = 287,
LPAREN = 288,
RPAREN = 289,
PLUS = 290,
MINUS = 291,
TIMES = 292,
DIVIDE = 293,
MOD = 294,
COMMA = 295,
SHARP = 296,
DOLLAR = 297,
EOL = 298,
EoF = 299,
UNKNOWN = 300
};
#endif
/* Value type. */
#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
union YYSTYPE
{
#line 30 "src/mush.y"
int number;
char *string;
STMT *stmt;
EXPR *expr;
ARG *args;
COMMAND *cmds;
PIPELINE *pline;
#line 113 "include/mush.tab.h"
};
typedef union YYSTYPE YYSTYPE;
# define YYSTYPE_IS_TRIVIAL 1
# define YYSTYPE_IS_DECLARED 1
#endif
extern YYSTYPE yylval;
int yyparse (void);
#endif /* !YY_YY_INCLUDE_MUSH_TAB_H_INCLUDED */

212
hw4/include/syntax.h Normal file
View File

@ -0,0 +1,212 @@
/*
* DO NOT MODIFY THE CONTENTS OF THIS FILE.
* IT WILL BE REPLACED DURING GRADING
*/
/*
* The elements of this enumerated type are used in the "class" field
* of the "STMT" structure to specify the particular kind of statement
* that the structure represents. The parenthesized names in the comments
* indicate which element (if any) of the "members" union in the STMT
* structure is used by each kind of statement.
*/
typedef enum {
NO_STMT_CLASS,
LIST_STMT_CLASS, // "list" statement
DELETE_STMT_CLASS, // "delete" statement (delete_stmt)
RUN_STMT_CLASS, // "run" statement
CONT_STMT_CLASS, // "cont" statement
STOP_STMT_CLASS, // "stop" statement
WAIT_STMT_CLASS, // "wait" statement (jobctl_stmt)
POLL_STMT_CLASS, // "poll" statement (jobctl_stmt)
CANCEL_STMT_CLASS, // "cancel" statement (jobctl_stmt)
PAUSE_STMT_CLASS, // "pause" statement
SET_STMT_CLASS, // "set" statement (set_stmt)
UNSET_STMT_CLASS, // "unset" statement (unset_stmt)
IF_STMT_CLASS, // "if" statement (if_stmt)
GOTO_STMT_CLASS, // "goto" statement (goto_stmt)
SOURCE_STMT_CLASS, // "source" statement (source_stmt)
FG_STMT_CLASS, // pipeline run in foreground (sys_stmt)
BG_STMT_CLASS // pipeline run in background (sys_stmt)
} STMT_CLASS;
/*
* This structure is used to represent a statement.
* The value in the "class" field tells what kind of statement it is.
* Depending on this value, one of the fields of the "members" union
* may be valid.
*/
typedef struct stmt {
STMT_CLASS class;
int lineno;
union {
struct {
int from;
int to;
} delete_stmt;
struct {
struct pipeline *pipeline;
} sys_stmt;
struct {
struct expr *expr;
} jobctl_stmt;
struct {
char *name;
struct expr *expr;
} set_stmt;
struct {
char *name;
} unset_stmt;
struct {
struct expr *expr;
int lineno;
} if_stmt;
struct {
int lineno;
} goto_stmt;
struct {
char *file;
} source_stmt;
} members;
} STMT;
/*
* This structure is used to represent a command. The "args" field points
* to the head of as a nonempty linked list of "args" that make up the command.
* The first "arg" is interpreted as the name of the command; the remainder
* are arguments to it. The "next" field is used to link commands into a
* pipeline.
*/
typedef struct command {
struct arg *args;
struct command *next;
} COMMAND;
/*
* This structure is used to represent a "pipeline". The "commands" field
* points to the head of a nonempty list of commands. The "input_file" field,
* if non-NULL, is the name of a file from which input to the first command
* in the pipeline is to be redirected. Similarly, the "output_file" field,
* if non-NULL, is the name of a file to which output from the last command
* in the pipeline is to be redirected. If the "capture_output" field is
* nonzero, then instead of being redirected to a file, the output from the
* last command in the pipeline is to be redirected to a pipe. This pipe is
* to be read by the main process, which will "captures" the output and make
* it available as the value of a variable in the data store.
*/
typedef struct pipeline {
COMMAND *commands;
char *input_file;
char *output_file;
int capture_output;
} PIPELINE;
/*
* Values of this enumerated type are used to identify the various kinds
* of expressions.
*/
typedef enum {
NO_EXPR_CLASS,
LIT_EXPR_CLASS, // literal string (value)
NUM_EXPR_CLASS, // numeric variable (variable)
STRING_EXPR_CLASS, // string variable (variable)
UNARY_EXPR_CLASS, // unary expression (unary_expr)
BINARY_EXPR_CLASS // binary expression (binary_expr)
} EXPR_CLASS;
/*
* Values of this enumerated type are used to identify the various kinds
* of operators in expressions.
*/
typedef enum {
NO_OPRTR,
NOT_OPRTR, // "not" operator (unary_expr)
AND_OPRTR, // "and" operator (binary_expr)
OR_OPRTR, // "or" operator (binary_expr)
EQUAL_OPRTR, // "equal to" operator (binary_expr)
LESS_OPRTR, // "less than" operator (binary_expr)
GREATER_OPRTR, // "greater than" operator (binary_expr)
LESSEQ_OPRTR, // "less than or equal" operator (binary_expr)
GREATEQ_OPRTR, // "greater than or equal" operator (binary_expr)
PLUS_OPRTR, // "plus" (binary_expr)
MINUS_OPRTR, // "minus" (binary_expr)
TIMES_OPRTR, // "times" (binary_expr)
DIVIDE_OPRTR, // "divide" (binary_expr)
MOD_OPRTR // "mod" (binary_expr)
} OPRTR;
/*
* Values of this enumerated type are used to identify the various kinds
* values that an expression can have.
*/
typedef enum {
NO_VALUE_TYPE,
NUM_VALUE_TYPE, // numeric value
STRING_VALUE_TYPE // string value
} VALUE_TYPE;
/*
* This structure is used to represent an "expression".
*/
typedef struct expr {
EXPR_CLASS class;
VALUE_TYPE type;
union {
char *variable;
char *value;
struct {
OPRTR oprtr;
struct expr *arg;
} unary_expr;
struct {
OPRTR oprtr;
struct expr *arg1;
struct expr *arg2;
} binary_expr;
} members;
} EXPR;
/*
* This structure is used to represent an "argument", which is
* a single element of a command. Arguments contain arbitrary expressions,
* which allows commands to be constructed that depend on the values
* of variables.
*/
typedef struct arg {
EXPR *expr;
struct arg *next;
} ARG;
/*
* The following functions are used to print representations
* of the various syntactic objects to a specified output stream.
* A nonzero value for the "parens" argument of show_expr() causes
* a compound expression to be printed within enclosing parentheses.
*/
void show_stmt(FILE *file, STMT *stmt);
void show_pipeline(FILE *file, PIPELINE *pline);
void show_command(FILE *file, COMMAND *cmd);
void show_expr(FILE *file, EXPR *expr, int parens);
void show_oprtr(FILE *file, OPRTR oprtr);
void show_lineno(FILE *file, int lineno);
/*
* The following functions are use to free the various syntactic
* objects. Freeing an object with one of these functions implies
* freeing all the objects that it references.
*/
void free_stmt(STMT *stmt);
void free_pipeline(PIPELINE *pline);
void free_commands(COMMAND *cmd);
void free_args(ARG *args);
void free_expr(EXPR *expr);
void free_oprtr(OPRTR oprtr);
/*
* The following functions are use to make deep copies of the
* various syntactic objects.
*/
PIPELINE *copy_pipeline(PIPELINE *pline);
COMMAND *copy_commands(COMMAND *cmd);
ARG *copy_args(ARG *args);
EXPR *copy_expr(EXPR *expr);

7
hw4/rsrc/bg_test.mush Normal file
View File

@ -0,0 +1,7 @@
10 sleep 10 &
15 echo line 15
20 sleep 5 &
25 echo line 25
30 sleep 3 &
35 echo line 35
run

View File

@ -0,0 +1,6 @@
10 sleep 10 &
15 set j10 = #JOB
20 sleep 2
30 cancel #j10
40 wait #j10
run

View File

@ -0,0 +1,7 @@
10 echo line 10
20 echo line 20
30 echo line 30
40 echo line 40
50 echo line 50
delete 10,25
list

7
hw4/rsrc/fg_test.mush Normal file
View File

@ -0,0 +1,7 @@
10 sleep 10
15 echo line 15
20 sleep 5
25 echo line 25
30 sleep 3
35 echo line 35
run

4
hw4/rsrc/goto_test.mush Normal file
View File

@ -0,0 +1,4 @@
10 goto 30
20 stop
30 echo yes
run

3
hw4/rsrc/list_test.mush Normal file
View File

@ -0,0 +1,3 @@
10 echo line 10
20 echo line 20
list

4
hw4/rsrc/loop1.mush Normal file
View File

@ -0,0 +1,4 @@
10 echo hello
20 sleep 1
30 goto 10
run

8
hw4/rsrc/loop2.mush Normal file
View File

@ -0,0 +1,8 @@
10 set x = 1
20 echo #x
25 sleep 1
30 set x = !#x
35 echo #x
40 sleep 1
50 goto 10
run

3
hw4/rsrc/pause_test.mush Normal file
View File

@ -0,0 +1,3 @@
10 sleep 10 &
20 pause
run

View File

@ -0,0 +1,3 @@
10 cat "/usr/share/dict/words" | grep program | grep sub > "pipeline_test.out"
20 cat "pipeline_test.out"
run

4
hw4/rsrc/run_test.mush Normal file
View File

@ -0,0 +1,4 @@
10 echo line 10
20 echo line 20
30 echo line 30
run

4
hw4/rsrc/stop_test.mush Normal file
View File

@ -0,0 +1,4 @@
10 echo line 10
20 stop
30 echo line 30
run

19
hw4/rsrc/wait_test.mush Normal file
View File

@ -0,0 +1,19 @@
10 sleep 10 &
11 set j10 = #JOB
12 echo j10 #j10
15 echo line 15
20 sleep 5 &
21 set j20 = #JOB
22 echo j20 #j20
25 echo line 25
30 sleep 3 &
31 set j30 = #JOB
32 echo j30 #j30
35 echo line 35
40 wait #j30
45 echo j30 #STATUS
50 wait #j20
55 echo j20 #STATUS
60 wait #j10
65 echo j10 #STATUS
run

392
hw4/src/execution.c Normal file
View File

@ -0,0 +1,392 @@
/*
* DO NOT MODIFY THE CONTENTS OF THIS FILE.
* IT WILL BE REPLACED DURING GRADING
*/
#include <stdlib.h>
#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include "mush.h"
#include "mush.tab.h"
#include "debug.h"
/*
* Mush: Execution engine.
*/
extern int yylex_destroy();
extern void push_input(FILE *in);
extern int pop_input(void);
extern int input_depth(void);
extern STMT *mush_parsed_stmt;
static int exec_run();
static int exec_cont();
#define PROMPT "mush: "
/* Uncomment this to enable tracing of the parser. */
//int yydebug = 1;
/*
* A quit handler is installed to allow user-initiated escape
* from constructs that wait for signals.
*/
static volatile sig_atomic_t got_quit = 0;
static void handler(int sig) {
got_quit = 1;
}
/*
* Target for longjmp() to jump to after a low-level error has
* occurred, e.g. in expression evaluation.
*/
static jmp_buf onerror;
/*
* Top-level interpreter loop.
* Reads single statements from stdin and either inserts them into the program,
* if they have a line number, otherwise executes them immediately.
*/
int exec_interactive() {
signal(SIGQUIT, SIG_IGN);
while(1) {
if(!input_depth() && isatty(fileno(stdin)))
fprintf(stdout, "%s", PROMPT);
fflush(stdout);
if(!yyparse()) {
STMT *stmt = mush_parsed_stmt;
if(stmt != NULL) {
if(stmt->lineno) {
prog_insert(stmt);
} else {
if(stmt->class == RUN_STMT_CLASS) {
free_stmt(stmt);
stmt = NULL;
exec_run();
} else if(stmt->class == CONT_STMT_CLASS) {
free_stmt(stmt);
stmt = NULL;
exec_cont();
} else {
exec_stmt(stmt);
free_stmt(stmt);
stmt = NULL;
}
}
}
} else {
if(pop_input())
break;
}
if(!input_depth() && isatty(fileno(stdin))) {
store_show(stderr);
fprintf(stderr, "\n");
jobs_show(stderr);
}
}
yylex_destroy();
return 0;
}
/*
* Enter an execution loop starting at the beginning of the program.
*/
static int exec_run() {
prog_reset();
return exec_cont();
}
/*
* Enter an execution loop starting at the current line number.
*/
static int exec_cont() {
int err = 0;
STMT *stmt;
stmt = prog_fetch();
if(stmt == NULL) {
fprintf(stderr, "No statement to execute\n");
return -1;
}
if(setjmp(onerror))
return -1;
signal(SIGQUIT, handler);
while(!got_quit) {
stmt = prog_fetch();
if(!stmt) {
fprintf(stderr, "STOP (end of program)\n");
break;
}
prog_next();
err = exec_stmt(stmt);
if(err)
break;
}
signal(SIGQUIT, SIG_IGN);
if(got_quit)
fprintf(stderr, "Quit!\n");
got_quit = 0;
return err;
}
/*
* Execute a statement.
* This function is called from exec_run().
* It can also be called separately to execute an individual statement
* read interactively.
*
* Successful execution (except for STOP) returns 0.
* Successful execution of STOP returns 1.
* Unsuccessful execution returns -1.
*/
int exec_stmt(STMT *stmt) {
int val; char *str;
FILE *in;
if(setjmp(onerror))
return -1;
if(stmt->lineno)
debug("execute statement %d", stmt->lineno);
switch(stmt->class) {
case LIST_STMT_CLASS:
prog_list(stdout);
break;
case DELETE_STMT_CLASS:
prog_delete(stmt->members.delete_stmt.from, stmt->members.delete_stmt.to);
break;
case STOP_STMT_CLASS:
if(stmt->lineno)
fprintf(stderr, "STOP at line %d\n", stmt->lineno);
return 1;
case GOTO_STMT_CLASS:
if(!prog_goto(stmt->members.goto_stmt.lineno))
return -1;
break;
case SET_STMT_CLASS:
switch(stmt->members.set_stmt.expr->type) {
case NUM_VALUE_TYPE:
val = eval_to_numeric(stmt->members.set_stmt.expr);
store_set_int(stmt->members.set_stmt.name, val);
break;
case STRING_VALUE_TYPE:
str = eval_to_string(stmt->members.set_stmt.expr);
store_set_string(stmt->members.set_stmt.name, str);
break;
default:
break;
}
break;
case UNSET_STMT_CLASS:
store_set_string(stmt->members.unset_stmt.name, NULL);
break;
case IF_STMT_CLASS:
val = eval_to_numeric(stmt->members.if_stmt.expr);
if(val) {
if(!prog_goto(stmt->members.if_stmt.lineno))
return -1;
else
return 0;
}
break;
case SOURCE_STMT_CLASS:
in = fopen(stmt->members.source_stmt.file, "r");
if(!in) {
fprintf(stderr, "Couldn't open source file: '%s'\n",
stmt->members.source_stmt.file);
return -1;
}
push_input(in);
break;
case FG_STMT_CLASS:
{
PIPELINE *pp = stmt->members.sys_stmt.pipeline;
int job = jobs_run(pp);
store_set_int(JOB_VAR, job);
int status = jobs_wait(job);
store_set_int(STATUS_VAR, status);
if(pp->capture_output) {
char *output = jobs_get_output(job);
debug("Captured output: '%s'", output);
store_set_string(OUTPUT_VAR, output);
}
jobs_expunge(job);
}
break;
case BG_STMT_CLASS:
{
int job = jobs_run(stmt->members.sys_stmt.pipeline);
store_set_int(JOB_VAR, job);
}
break;
case WAIT_STMT_CLASS:
{
int job = eval_to_numeric(stmt->members.jobctl_stmt.expr);
int status = jobs_wait(job);
store_set_int(STATUS_VAR, status);
jobs_expunge(job);
}
break;
case POLL_STMT_CLASS:
{
int job = eval_to_numeric(stmt->members.jobctl_stmt.expr);
int status = jobs_poll(job);
store_set_int(STATUS_VAR, status);
if(status >= 0)
jobs_expunge(job);
}
break;
case CANCEL_STMT_CLASS:
{
int job = eval_to_numeric(stmt->members.jobctl_stmt.expr);
jobs_cancel(job);
}
break;
case PAUSE_STMT_CLASS:
{
jobs_pause();
}
default:
fprintf(stderr, "Unknown statement class: %d\n", stmt->class);
abort();
}
return 0;
}
/*
* Evaluate an expression, returning an integer result.
* It is assumed that the jmp_buf onerror has been initialized by the caller
* with a control point to escape to in case there is an error during evaluation.
*/
long eval_to_numeric(EXPR *expr) {
char *endp, *str1, *str2;
long opr1, opr2;
int err;
switch(expr->class) {
case LIT_EXPR_CLASS:
opr1 = strtol(expr->members.value, &endp, 0);
if(*endp == '\0') {
return opr1;
} else {
fprintf(stderr, "Literal '%s' is not an integer\n", expr->members.value);
longjmp(onerror, 0);
}
case STRING_EXPR_CLASS:
case NUM_EXPR_CLASS:
err = store_get_int(expr->members.variable, &opr1);
if(err) {
fprintf(stderr, "Variable %s does not have an integer value\n",
expr->members.variable);
longjmp(onerror, 0);
}
return opr1;
fprintf(stderr, "String variable %s in expression not implemented\n",
expr->members.variable);
abort();
case UNARY_EXPR_CLASS:
opr1 = eval_to_numeric(expr->members.unary_expr.arg);
switch(expr->members.unary_expr.oprtr) {
case NOT_OPRTR:
return opr1 ? 0 : 1;
default:
fprintf(stderr, "Unknown unary numeric operator: %d\n",
expr->members.unary_expr.oprtr);
abort();
}
case BINARY_EXPR_CLASS:
if(expr->members.binary_expr.oprtr == EQUAL_OPRTR) {
if(expr->members.binary_expr.arg1->type == NUM_VALUE_TYPE &&
expr->members.binary_expr.arg2->type == NUM_VALUE_TYPE) {
opr1 = eval_to_numeric(expr->members.binary_expr.arg1);
opr2 = eval_to_numeric(expr->members.binary_expr.arg2);
return opr1 == opr2;
} else {
str1 = eval_to_string(expr->members.binary_expr.arg1);
str2 = eval_to_string(expr->members.binary_expr.arg2);
return !strcmp(str1, str2);
}
}
opr1 = eval_to_numeric(expr->members.binary_expr.arg1);
opr2 = eval_to_numeric(expr->members.binary_expr.arg2);
switch(expr->members.binary_expr.oprtr) {
case AND_OPRTR:
return opr1 && opr2;
case OR_OPRTR:
return opr1 || opr2;
case PLUS_OPRTR:
return opr1 + opr2;
case MINUS_OPRTR:
return opr1 - opr2;
case TIMES_OPRTR:
return opr1 * opr2;
case DIVIDE_OPRTR:
return opr1 / opr2;
case MOD_OPRTR:
return opr1 % opr2;
case LESS_OPRTR:
return opr1 < opr2;
case GREATER_OPRTR:
return opr1 > opr2;
case LESSEQ_OPRTR:
return opr1 <= opr2;
case GREATEQ_OPRTR:
return opr1 >= opr2;
default:
fprintf(stderr, "Unknown binary numeric operator: %d\n",
expr->members.binary_expr.oprtr);
abort();
}
default:
fprintf(stderr, "Unknown expression class: %d\n", expr->class);
abort();
}
return 0;
}
/*
* Evaluate an expression, returning a string result.
* It is assumed that the jmp_buf onerror has been initialized by the caller
* with a control point to escape to in case there is an error during evaluation.
*/
char *eval_to_string(EXPR *expr) {
char *str1, *str2;
switch(expr->class) {
case LIT_EXPR_CLASS:
return expr->members.value;
case NUM_EXPR_CLASS:
case STRING_EXPR_CLASS:
str1 = store_get_string(expr->members.variable);
if(!str1) {
fprintf(stderr, "Variable %s does not have a value\n",
expr->members.variable);
longjmp(onerror, 0);
}
return str1;
case UNARY_EXPR_CLASS:
str1 = eval_to_string(expr->members.unary_expr.arg);
switch(expr->members.unary_expr.oprtr) {
default:
(void)str1;
fprintf(stderr, "Unknown unary string operator: %d\n",
expr->members.unary_expr.oprtr);
abort();
}
case BINARY_EXPR_CLASS:
str1 = eval_to_string(expr->members.binary_expr.arg1);
str2 = eval_to_string(expr->members.binary_expr.arg2);
switch(expr->members.binary_expr.oprtr) {
default:
(void)str1; (void)str2;
fprintf(stderr, "Unknown binary string operator: %d\n",
expr->members.binary_expr.oprtr);
abort();
}
default:
fprintf(stderr, "Unknown expression class: %d\n", expr->class);
abort();
}
return 0;
}

223
hw4/src/jobs.c Normal file
View File

@ -0,0 +1,223 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <wait.h>
#include <time.h>
#include <errno.h>
#include <sys/time.h>
#include "mush.h"
#include "debug.h"
/*
* This is the "jobs" module for Mush.
* It maintains a table of jobs in various stages of execution, and it
* provides functions for manipulating jobs.
* Each job contains a pipeline, which is used to initialize the processes,
* pipelines, and redirections that make up the job.
* Each job has a job ID, which is an integer value that is used to identify
* that job when calling the various job manipulation functions.
*
* At any given time, a job will have one of the following status values:
* "new", "running", "completed", "aborted", "canceled".
* A newly created job starts out in with status "new".
* It changes to status "running" when the processes that make up the pipeline
* for that job have been created.
* A running job becomes "completed" at such time as all the processes in its
* pipeline have terminated successfully.
* A running job becomes "aborted" if the last process in its pipeline terminates
* with a signal that is not the result of the pipeline having been canceled.
* A running job becomes "canceled" if the jobs_cancel() function was called
* to cancel it and in addition the last process in the pipeline subsequently
* terminated with signal SIGKILL.
*
* In general, there will be other state information stored for each job,
* as required by the implementation of the various functions in this module.
*/
/**
* @brief Initialize the jobs module.
* @details This function is used to initialize the jobs module.
* It must be called exactly once, before any other functions of this
* module are called.
*
* @return 0 if initialization is successful, otherwise -1.
*/
int jobs_init(void) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Finalize the jobs module.
* @details This function is used to finalize the jobs module.
* It must be called exactly once when job processing is to be terminated,
* before the program exits. It should cancel all jobs that have not
* yet terminated, wait for jobs that have been cancelled to terminate,
* and then expunge all jobs before returning.
*
* @return 0 if finalization is completely successful, otherwise -1.
*/
int jobs_fini(void) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Print the current jobs table.
* @details This function is used to print the current contents of the jobs
* table to a specified output stream. The output should consist of one line
* per existing job. Each line should have the following format:
*
* <jobid>\t<pgid>\t<status>\t<pipeline>
*
* where <jobid> is the numeric job ID of the job, <status> is one of the
* following strings: "new", "running", "completed", "aborted", or "canceled",
* and <pipeline> is the job's pipeline, as printed by function show_pipeline()
* in the syntax module. The \t stand for TAB characters.
*
* @param file The output stream to which the job table is to be printed.
* @return 0 If the jobs table was successfully printed, -1 otherwise.
*/
int jobs_show(FILE *file) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Create a new job to run a pipeline.
* @details This function creates a new job and starts it running a specified
* pipeline. The pipeline will consist of a "leader" process, which is the direct
* child of the process that calls this function, plus one child of the leader
* process to run each command in the pipeline. All processes in the pipeline
* should have a process group ID that is equal to the process ID of the leader.
* The leader process should wait for all of its children to terminate before
* terminating itself. The leader should return the exit status of the process
* running the last command in the pipeline as its own exit status, if that
* process terminated normally. If the last process terminated with a signal,
* then the leader should terminate via SIGABRT.
*
* If the "capture_output" flag is set for the pipeline, then the standard output
* of the last process in the pipeline should be redirected to be the same as
* the standard output of the pipeline leader, and this output should go via a
* pipe to the main Mush process, where it should be read and saved in the data
* store as the value of a variable, as described in the assignment handout.
* If "capture_output" is not set for the pipeline, but "output_file" is non-NULL,
* then the standard output of the last process in the pipeline should be redirected
* to the specified output file. If "input_file" is set for the pipeline, then
* the standard input of the process running the first command in the pipeline should
* be redirected from the specified input file.
*
* @param pline The pipeline to be run. The jobs module expects this object
* to be valid for as long as it requires, and it expects to be able to free this
* object when it is finished with it. This means that the caller should not pass
* a pipeline object that is shared with any other data structure, but rather should
* make a copy to be passed to this function.
*
* @return -1 if the pipeline could not be initialized properly, otherwise the
* value returned is the job ID assigned to the pipeline.
*/
int jobs_run(PIPELINE *pline) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Wait for a job to terminate.
* @details This function is used to wait for the job with a specified job ID
* to terminate. A job has terminated when it has entered the COMPLETED, ABORTED,
* or CANCELED state.
*
* @param jobid The job ID of the job to wait for.
* @return the exit status of the job leader, as returned by waitpid(),
* or -1 if any error occurs that makes it impossible to wait for the specified job.
*/
int jobs_wait(int jobid) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Poll to find out if a job has terminated.
* @details This function is used to poll whether the job with the specified ID
* has terminated. This is similar to jobs_wait(), except that this function returns
* immediately without waiting if the job has not yet terminated.
*
* @param jobid The job ID of the job to wait for.
* @return the exit status of the job leader, as returned by waitpid(), if the job
* has terminated, or -1 if the job has not yet terminated or if any other error occurs.
*/
int jobs_poll(int jobid) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Expunge a terminated job from the jobs table.
* @details This function is used to expunge (remove) a job that has terminated from
* the jobs table, so that space in the table can be used to start some new job.
* In order to be expunged, a job must have terminated; if an attempt is made to expunge
* a job that has not yet terminated, it is an error. Any resources (exit status,
* open pipes, captured output, etc.) that were being used by the job are finalized
* and/or freed and will no longer be available.
*
* @param jobid The job ID of the job to expunge.
* @return 0 if the job was successfully expunged, -1 if the job could not be expunged.
*/
int jobs_expunge(int jobid) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Attempt to cancel a job.
* @details This function is used to attempt to cancel a running job.
* In order to be canceled, the job must not yet have terminated and there
* must not have been any previous attempt to cancel the job.
* Cancellation is attempted by sending SIGKILL to the process group associated
* with the job. Cancellation becomes successful, and the job actually enters the canceled
* state, at such subsequent time as the job leader terminates as a result of SIGKILL.
* If after attempting cancellation, the job leader terminates other than as a result
* of SIGKILL, then cancellation is not successful and the state of the job is either
* COMPLETED or ABORTED, depending on how the job leader terminated.
*
* @param jobid The job ID of the job to cancel.
* @return 0 if cancellation was successfully initiated, -1 if the job was already
* terminated, a previous attempt had been made to cancel the job, or any other
* error occurred.
*/
int jobs_cancel(int jobid) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Get the captured output of a job.
* @details This function is used to retrieve output that was captured from a job
* that has terminated, but that has not yet been expunged. Output is captured for a job
* when the "capture_output" flag is set for its pipeline.
*
* @param jobid The job ID of the job for which captured output is to be retrieved.
* @return The captured output, if the job has terminated and there is captured
* output available, otherwise NULL.
*/
char *jobs_get_output(int jobid) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Pause waiting for a signal indicating a potential job status change.
* @details When this function is called it blocks until some signal has been
* received, at which point the function returns. It is used to wait for a
* potential job status change without consuming excessive amounts of CPU time.
*
* @return -1 if any error occurred, 0 otherwise.
*/
int jobs_pause(void) {
// TO BE IMPLEMENTED
abort();
}

12
hw4/src/main.c Normal file
View File

@ -0,0 +1,12 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "mush.h"
int main(int argc, char *argv[]) {
jobs_init();
exec_interactive();
jobs_fini();
}

2219
hw4/src/mush.lex.c Normal file

File diff suppressed because it is too large Load Diff

2418
hw4/src/mush.tab.c Normal file

File diff suppressed because it is too large Load Diff

136
hw4/src/program.c Normal file
View File

@ -0,0 +1,136 @@
#include <stdlib.h>
#include <stdio.h>
#include "mush.h"
#include "debug.h"
/*
* This is the "program store" module for Mush.
* It maintains a set of numbered statements, along with a "program counter"
* that indicates the current point of execution, which is either before all
* statements, after all statements, or in between two statements.
* There should be no fixed limit on the number of statements that the program
* store can hold.
*/
/**
* @brief Output a listing of the current contents of the program store.
* @details This function outputs a listing of the current contents of the
* program store. Statements are listed in increasing order of their line
* number. The current position of the program counter is indicated by
* a line containing only the string "-->" at the current program counter
* position.
*
* @param out The stream to which to output the listing.
* @return 0 if successful, -1 if any error occurred.
*/
int prog_list(FILE *out) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Insert a new statement into the program store.
* @details This function inserts a new statement into the program store.
* The statement must have a line number. If the line number is the same as
* that of an existing statement, that statement is replaced.
* The program store assumes the responsibility for ultimately freeing any
* statement that is inserted using this function.
* Insertion of new statements preserves the value of the program counter:
* if the position of the program counter was just before a particular statement
* before insertion of a new statement, it will still be before that statement
* after insertion, and if the position of the program counter was after all
* statements before insertion of a new statement, then it will still be after
* all statements after insertion.
*
* @param stmt The statement to be inserted.
* @return 0 if successful, -1 if any error occurred.
*/
int prog_insert(STMT *stmt) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Delete statements from the program store.
* @details This function deletes from the program store statements whose
* line numbers fall in a specified range. Any deleted statements are freed.
* Deletion of statements preserves the value of the program counter:
* if before deletion the program counter pointed to a position just before
* a statement that was not among those to be deleted, then after deletion the
* program counter will still point the position just before that same statement.
* If before deletion the program counter pointed to a position just before
* a statement that was among those to be deleted, then after deletion the
* program counter will point to the first statement beyond those deleted,
* if such a statement exists, otherwise the program counter will point to
* the end of the program.
*
* @param min Lower end of the range of line numbers to be deleted.
* @param max Upper end of the range of line numbers to be deleted.
*/
int prog_delete(int min, int max) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Reset the program counter to the beginning of the program.
* @details This function resets the program counter to point just
* before the first statement in the program.
*/
void prog_reset(void) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Fetch the next program statement.
* @details This function fetches and returns the first program
* statement after the current program counter position. The program
* counter position is not modified. The returned pointer should not
* be used after any subsequent call to prog_delete that deletes the
* statement from the program store.
*
* @return The first program statement after the current program
* counter position, if any, otherwise NULL.
*/
STMT *prog_fetch(void) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Advance the program counter to the next existing statement.
* @details This function advances the program counter by one statement
* from its original position and returns the statement just after the
* new position. The returned pointer should not be used after any
* subsequent call to prog_delete that deletes the statement from the
* program store.
*
* @return The first program statement after the new program counter
* position, if any, otherwise NULL.
*/
STMT *prog_next() {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Perform a "go to" operation on the program store.
* @details This function performs a "go to" operation on the program
* store, by resetting the program counter to point to the position just
* before the statement with the specified line number.
* The statement pointed at by the new program counter is returned.
* If there is no statement with the specified line number, then no
* change is made to the program counter and NULL is returned.
* Any returned statement should only be regarded as valid as long
* as no calls to prog_delete are made that delete that statement from
* the program store.
*
* @return The statement having the specified line number, if such a
* statement exists, otherwise NULL.
*/
STMT *prog_goto(int lineno) {
// TO BE IMPLEMENTED
abort();
}

102
hw4/src/store.c Normal file
View File

@ -0,0 +1,102 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/*
* This is the "data store" module for Mush.
* It maintains a mapping from variable names to values.
* The values of variables are stored as strings.
* However, the module provides functions for setting and retrieving
* the value of a variable as an integer. Setting a variable to
* an integer value causes the value of the variable to be set to
* a string representation of that integer. Retrieving the value of
* a variable as an integer is possible if the current value of the
* variable is the string representation of an integer.
*/
/**
* @brief Get the current value of a variable as a string.
* @details This function retrieves the current value of a variable
* as a string. If the variable has no value, then NULL is returned.
* Any string returned remains "owned" by the data store module;
* the caller should not attempt to free the string or to use it
* after any subsequent call that would modify the value of the variable
* whose value was retrieved. If the caller needs to use the string for
* an indefinite period, a copy should be made immediately.
*
* @param var The variable whose value is to be retrieved.
* @return A string that is the current value of the variable, if any,
* otherwise NULL.
*/
char *store_get_string(char *var) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Get the current value of a variable as an integer.
* @details This retrieves the current value of a variable and
* attempts to interpret it as an integer. If this is possible,
* then the integer value is stored at the pointer provided by
* the caller.
*
* @param var The variable whose value is to be retrieved.
* @param valp Pointer at which the returned value is to be stored.
* @return If the specified variable has no value or the value
* cannot be interpreted as an integer, then -1 is returned,
* otherwise 0 is returned.
*/
int store_get_int(char *var, long *valp) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Set the value of a variable as a string.
* @details This function sets the current value of a specified
* variable to be a specified string. If the variable already
* has a value, then that value is replaced. If the specified
* value is NULL, then any existing value of the variable is removed
* and the variable becomes un-set. Ownership of the variable and
* the value strings is not transferred to the data store module as
* a result of this call; the data store module makes such copies of
* these strings as it may require.
*
* @param var The variable whose value is to be set.
* @param val The value to set, or NULL if the variable is to become
* un-set.
*/
int store_set_string(char *var, char *val) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Set the value of a variable as an integer.
* @details This function sets the current value of a specified
* variable to be a specified integer. If the variable already
* has a value, then that value is replaced. Ownership of the variable
* string is not transferred to the data store module as a result of
* this call; the data store module makes such copies of this string
* as it may require.
*
* @param var The variable whose value is to be set.
* @param val The value to set.
*/
int store_set_int(char *var, long val) {
// TO BE IMPLEMENTED
abort();
}
/**
* @brief Print the current contents of the data store.
* @details This function prints the current contents of the data store
* to the specified output stream. The format is not specified; this
* function is intended to be used for debugging purposes.
*
* @param f The stream to which the store contents are to be printed.
*/
void store_show(FILE *f) {
// TO BE IMPLEMENTED
abort();
}

357
hw4/src/syntax.c Normal file
View File

@ -0,0 +1,357 @@
/*
* DO NOT MODIFY THE CONTENTS OF THIS FILE.
* IT WILL BE REPLACED DURING GRADING
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "syntax.h"
/*
* Mush: Functions for manipulating syntax trees.
*/
void show_stmt(FILE *file, STMT *stmt) {
show_lineno(file, stmt->lineno);
switch(stmt->class) {
case LIST_STMT_CLASS:
fprintf(file, "list");
break;
case DELETE_STMT_CLASS:
fprintf(file, "delete %d, %d",
stmt->members.delete_stmt.from,
stmt->members.delete_stmt.to);
break;
case RUN_STMT_CLASS:
fprintf(file, "run");
break;
case CONT_STMT_CLASS:
fprintf(file, "cont");
break;
case STOP_STMT_CLASS:
fprintf(file, "stop");
break;
case FG_STMT_CLASS:
show_pipeline(file, stmt->members.sys_stmt.pipeline);
break;
case BG_STMT_CLASS:
show_pipeline(file, stmt->members.sys_stmt.pipeline);
fprintf(file, "& ");
break;
case WAIT_STMT_CLASS:
fprintf(file, "wait ");
show_expr(file, stmt->members.jobctl_stmt.expr, 0);
break;
case POLL_STMT_CLASS:
fprintf(file, "poll ");
show_expr(file, stmt->members.jobctl_stmt.expr, 0);
break;
case CANCEL_STMT_CLASS:
fprintf(file, "cancel ");
show_expr(file, stmt->members.jobctl_stmt.expr, 0);
break;
case PAUSE_STMT_CLASS:
fprintf(file, "pause");
break;
case SET_STMT_CLASS:
fprintf(file, "set ");
fprintf(file, "%s = ", stmt->members.set_stmt.name);
show_expr(file, stmt->members.set_stmt.expr, 0);
break;
case UNSET_STMT_CLASS:
fprintf(file, "unset ");
fprintf(file, "%s", stmt->members.unset_stmt.name);
break;
case IF_STMT_CLASS:
fprintf(file, "if ");
show_expr(file, stmt->members.if_stmt.expr, 0);
fprintf(file, " goto %d", stmt->members.if_stmt.lineno);
break;
case GOTO_STMT_CLASS:
fprintf(file, "goto %d", stmt->members.goto_stmt.lineno);
break;
case SOURCE_STMT_CLASS:
fprintf(file, "source %s", stmt->members.source_stmt.file);
break;
default:
fprintf(stderr, "Unknown statement class: %d\n", stmt->class);
abort();
}
fprintf(file, "\n");
}
void show_pipeline(FILE *file, PIPELINE *pline) {
COMMAND *cmds = pline->commands;
while(cmds) {
show_command(file, cmds);
if(cmds->next)
fprintf(file, " | ");
cmds = cmds->next;
}
if(pline->input_file)
fprintf(file, " < %s", pline->input_file);
if(pline->capture_output)
fprintf(file, " >@");
if(pline->output_file)
fprintf(file, " > %s", pline->output_file);
}
void show_command(FILE *file, COMMAND *cmd) {
ARG *arg = cmd->args;
while(arg) {
show_expr(file, arg->expr, 0);
if(arg->next)
fprintf(file, " ");
arg = arg->next;
}
}
void show_expr(FILE *file, EXPR *expr, int parens) {
switch(expr->class) {
case LIT_EXPR_CLASS:
fprintf(file, "%s", expr->members.value);
break;
case NUM_EXPR_CLASS:
fprintf(file, "#%s", expr->members.variable);
break;
case STRING_EXPR_CLASS:
fprintf(file, "$%s", expr->members.variable);
break;
case UNARY_EXPR_CLASS:
if(parens)
fprintf(file, "(");
show_oprtr(file, expr->members.unary_expr.oprtr);
fprintf(file, " ");
show_expr(file, expr->members.unary_expr.arg, 1);
if(parens)
fprintf(file, ")");
break;
case BINARY_EXPR_CLASS:
if(parens)
fprintf(file, "(");
show_expr(file, expr->members.binary_expr.arg1, 1);
fprintf(file, " ");
show_oprtr(file, expr->members.binary_expr.oprtr);
fprintf(file, " ");
show_expr(file, expr->members.binary_expr.arg2, 1);
if(parens)
fprintf(file, ")");
break;
default:
fprintf(stderr, "Unknown expression class: %d\n", expr->class);
abort();
}
}
void show_oprtr(FILE *file, OPRTR oprtr) {
switch(oprtr) {
case PLUS_OPRTR:
fprintf(file, "+");
break;
case MINUS_OPRTR:
fprintf(file, "-");
break;
case TIMES_OPRTR:
fprintf(file, "*");
break;
case DIVIDE_OPRTR:
fprintf(file, "/");
break;
case MOD_OPRTR:
fprintf(file, "%s", "%");
break;
case AND_OPRTR:
fprintf(file, "&&");
break;
case OR_OPRTR:
fprintf(file, "||");
break;
case NOT_OPRTR:
fprintf(file, "!");
break;
case EQUAL_OPRTR:
fprintf(file, "==");
break;
case LESS_OPRTR:
fprintf(file, "<");
break;
case GREATER_OPRTR:
fprintf(file, ">");
break;
case LESSEQ_OPRTR:
fprintf(file, "<=");
break;
case GREATEQ_OPRTR:
fprintf(file, ">=");
break;
default:
fprintf(stderr, "Unknown operator: %d\n", oprtr);
abort();
}
}
void show_lineno(FILE *file, int lineno) {
if(lineno)
fprintf(file, "%7d\t", lineno);
else
fprintf(file, "\t");
}
void free_stmt(STMT *stmt) {
if(stmt == NULL) // Produced by error recovery production
return;
switch(stmt->class) {
case LIST_STMT_CLASS:
case DELETE_STMT_CLASS:
case RUN_STMT_CLASS:
case CONT_STMT_CLASS:
case STOP_STMT_CLASS:
break;
case FG_STMT_CLASS:
free_pipeline(stmt->members.sys_stmt.pipeline);
break;
case BG_STMT_CLASS:
free_pipeline(stmt->members.sys_stmt.pipeline);
break;
case GOTO_STMT_CLASS:
break;
case WAIT_STMT_CLASS:
case POLL_STMT_CLASS:
case CANCEL_STMT_CLASS:
free_expr(stmt->members.jobctl_stmt.expr);
break;
case SET_STMT_CLASS:
free(stmt->members.set_stmt.name);
free_expr(stmt->members.set_stmt.expr);
break;
case UNSET_STMT_CLASS:
free(stmt->members.unset_stmt.name);
break;
case IF_STMT_CLASS:
free_expr(stmt->members.if_stmt.expr);
break;
case SOURCE_STMT_CLASS:
free(stmt->members.source_stmt.file);
break;
case PAUSE_STMT_CLASS:
break;
default:
fprintf(stderr, "Unknown statement class: %d\n", stmt->class);
abort();
}
free(stmt);
}
void free_pipeline(PIPELINE *pline) {
free_commands(pline->commands);
if(pline->input_file)
free(pline->input_file);
if(pline->output_file)
free(pline->output_file);
free(pline);
}
void free_commands(COMMAND *cmd) {
if(cmd->next)
free_commands(cmd->next);
if(cmd->args)
free_args(cmd->args);
free(cmd);
}
void free_args(ARG *args) {
if(args->next)
free_args(args->next);
free_expr(args->expr);
free(args);
}
void free_expr(EXPR *expr) {
switch(expr->class) {
case LIT_EXPR_CLASS:
free(expr->members.value);
break;
case NUM_EXPR_CLASS:
free(expr->members.variable);
break;
case STRING_EXPR_CLASS:
free(expr->members.variable);
break;
case UNARY_EXPR_CLASS:
free_expr(expr->members.unary_expr.arg);
break;
case BINARY_EXPR_CLASS:
free_expr(expr->members.binary_expr.arg1);
free_expr(expr->members.binary_expr.arg2);
break;
default:
fprintf(stderr, "Unknown expression class: %d\n", expr->class);
abort();
}
free(expr);
}
PIPELINE *copy_pipeline(PIPELINE *pline) {
if(!pline)
return NULL;
PIPELINE *copy = calloc(sizeof(PIPELINE), 1);
copy->commands = copy_commands(pline->commands);
copy->capture_output = pline->capture_output;
if(pline->input_file)
copy->input_file = strdup(pline->input_file);
if(pline->output_file)
copy->output_file = strdup(pline->output_file);
return(copy);
}
COMMAND *copy_commands(COMMAND *cmd) {
if(!cmd)
return NULL;
COMMAND *copy = calloc(sizeof(COMMAND), 1);
copy->next = copy_commands(cmd->next);
copy->args = copy_args(cmd->args);
return copy;
}
ARG *copy_args(ARG *args) {
if(!args)
return NULL;
ARG *copy = calloc(sizeof(ARG), 1);
copy->next = copy_args(args->next);
copy->expr = copy_expr(args->expr);
return copy;
}
EXPR *copy_expr(EXPR *expr) {
if(!expr)
return NULL;
EXPR *copy = calloc(sizeof(EXPR), 1);
copy->class = expr->class;
copy->type = expr->type;
switch(expr->class) {
case LIT_EXPR_CLASS:
copy->members.value = strdup(expr->members.value);
break;
case NUM_EXPR_CLASS:
copy->members.variable = strdup(expr->members.variable);
break;
case STRING_EXPR_CLASS:
copy->members.variable = strdup(expr->members.variable);
break;
case UNARY_EXPR_CLASS:
copy->members.unary_expr.oprtr = expr->members.unary_expr.oprtr;
copy->members.unary_expr.arg = copy_expr(expr->members.unary_expr.arg);
break;
case BINARY_EXPR_CLASS:
copy->members.binary_expr.oprtr = expr->members.binary_expr.oprtr;
copy->members.binary_expr.arg1 = copy_expr(expr->members.binary_expr.arg1);
copy->members.binary_expr.arg2 = copy_expr(expr->members.binary_expr.arg2);
break;
default:
fprintf(stderr, "Unknown expression class: %d\n", expr->class);
abort();
}
return copy;
}

19
hw4/tests/base_tests.c Normal file
View File

@ -0,0 +1,19 @@
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>
#include <criterion/criterion.h>
/*
* This just checks if mush exits normally on an empty input.
* It is not very interesting, unfortunately.
* It is just a place holder where something more interesting might go.
*/
Test(basecode_suite, mush_eof_test, .timeout=20)
{
char *cmd = "ulimit -t 10; bin/mush < /dev/null";
int code = WEXITSTATUS(system(cmd));
cr_assert_eq(code, EXIT_SUCCESS,
"Program exited with %d instead of EXIT_SUCCESS",
code);
}