518 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			518 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
|  | #!/usr/bin/gawk -f | ||
|  | 
 | ||
|  | # GNU Awk 3.0.4 crashes on the most recent awklisp, so here's the old | ||
|  | # version for you to use instead. | ||
|  | 
 | ||
|  | 
 | ||
|  | # --- Representation of Lisp data | ||
|  | 
 | ||
|  | BEGIN { | ||
|  |     a_number = 0 | ||
|  |     pair_ptr = a_pair = 1 | ||
|  |     symbol_ptr = a_symbol = 2 | ||
|  |      | ||
|  |     type_name[a_number] = "number" | ||
|  |     type_name[a_pair] = "pair" | ||
|  |     type_name[a_symbol] = "symbol" | ||
|  | } | ||
|  | 
 | ||
|  | function is(type, expr)	 | ||
|  | {  | ||
|  |     if (expr % 4 != type)  | ||
|  |         error("Expected a " type_name[type] ", not a " type_name[expr % 4])  | ||
|  |     return expr  | ||
|  | } | ||
|  | 
 | ||
|  | function is_number(expr)	{ return expr % 4 == 0 } | ||
|  | function is_pair(expr)		{ return expr % 4 == 1 } | ||
|  | function is_symbol(expr)	{ return expr % 4 == 2 } | ||
|  | function is_atom(expr)		{ return expr % 4 != 1 } | ||
|  | 
 | ||
|  | function make_number(n)		{ return n * 4 } | ||
|  | 
 | ||
|  | function numeric_value(expr) | ||
|  | {  | ||
|  |     if (expr % 4 != 0) error("Not a number") | ||
|  |     return expr / 4  | ||
|  | } | ||
|  | 
 | ||
|  | # Make a new pair. | ||
|  | function cons(the_car, the_cdr) | ||
|  | { | ||
|  |     if (free_list == NIL) | ||
|  |         gc(the_car, the_cdr) | ||
|  |     result = free_list | ||
|  |     free_list = cdr[free_list] | ||
|  |     car[result] = the_car | ||
|  |     cdr[result] = the_cdr | ||
|  |     return result | ||
|  | } | ||
|  | 
 | ||
|  | # Return the symbol :string names. | ||
|  | function string_to_symbol(string) | ||
|  | { | ||
|  |     if (string in intern) | ||
|  |         return intern[string] | ||
|  |     symbol_ptr += 4 | ||
|  |     intern[string] = symbol_ptr | ||
|  |     printname[symbol_ptr] = string | ||
|  |     return symbol_ptr | ||
|  | } | ||
|  | 
 | ||
|  | # Define a primitive procedure, with :nparams parameters, | ||
|  | # bound to the symbol named :name. | ||
|  | function def_prim(name, nparams,	sym) | ||
|  | { | ||
|  |     sym = string_to_symbol(name) | ||
|  |     value[sym] = string_to_symbol(sprintf("#<Primitive %s>", name)) | ||
|  |     if (nparams != "") | ||
|  |         num_params[value[sym]] = nparams | ||
|  |     return value[sym] | ||
|  | } | ||
|  | 
 | ||
|  | # --- Garbage collection | ||
|  | 
 | ||
|  | function expand_heap(	limit) | ||
|  | { | ||
|  |     if (loud_gc) | ||
|  |         printf("Expanding heap...") >"/dev/stderr" | ||
|  |     limit = pair_ptr + 4 * (heap_increment ? heap_increment : 1500) | ||
|  |     for (; pair_ptr < limit; pair_ptr += 4) { | ||
|  |         cdr[pair_ptr] = free_list | ||
|  |         free_list = pair_ptr | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | function protect(object)	{ protected[++protected_ptr] = object } | ||
|  | function unprotect()		{ --protected_ptr } | ||
|  | 
 | ||
|  | function mark(object) | ||
|  | { | ||
|  |     while (is_pair(object) && !(object in marks)) {		#** speed | ||
|  |         marks[object] = 1 | ||
|  |         mark(car[object]) | ||
|  |         object = cdr[object] | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | function sweep() | ||
|  | { | ||
|  |     free_list = NIL | ||
|  |     for (p = a_pair; p < pair_ptr; p += 4)   # for (p in car) might be faster | ||
|  |         if (!(p in marks)) { | ||
|  |             cdr[p] = free_list | ||
|  |             free_list = p | ||
|  |         } else | ||
|  |             delete marks[p] | ||
|  | } | ||
|  | 
 | ||
|  | function gc(the_car, the_cdr,	p, i) | ||
|  | { | ||
|  |     if (loud_gc)  | ||
|  |         printf("\nGC...") >"/dev/stderr" | ||
|  |     mark(the_car); mark(the_cdr) | ||
|  |     for (p in protected) | ||
|  |         mark(protected[p]) | ||
|  |     for (p in stack) | ||
|  |         mark(stack[p]) | ||
|  |     for (p in value) | ||
|  |         mark(value[p]) | ||
|  |     for (p in property) { | ||
|  |         i = index(SUBSEP, p) | ||
|  |         mark(substr(p, 1, i-1)) | ||
|  |         mark(substr(p, i+1)) | ||
|  |         mark(property[p]) | ||
|  |     } | ||
|  |     sweep() | ||
|  |     if (free_list == NIL) | ||
|  |         expand_heap() | ||
|  | } | ||
|  | 
 | ||
|  | # --- Set up | ||
|  | 
 | ||
|  | BEGIN {	 | ||
|  |     srand() | ||
|  |      | ||
|  |     frame_ptr = stack_ptr = 0 | ||
|  |      | ||
|  |     NIL 	= string_to_symbol("nil") | ||
|  |     T 		= string_to_symbol("t") | ||
|  |      | ||
|  |     value[NIL] = NIL | ||
|  |     value[T] = T | ||
|  |      | ||
|  |     car[NIL] = cdr[NIL] = NIL	# this is convenient in a couple places... | ||
|  | 
 | ||
|  |     free_list = NIL; expand_heap() | ||
|  |     THE_EOF_OBJECT = string_to_symbol("#eof") | ||
|  |     value[string_to_symbol("the-eof-object")] = THE_EOF_OBJECT | ||
|  |      | ||
|  |     eof = "(eof)" | ||
|  | 
 | ||
|  |     QUOTE 	= string_to_symbol("quote");	is_special[QUOTE] = 1 | ||
|  |     LAMBDA 	= string_to_symbol("lambda");	is_special[LAMBDA] = 1 | ||
|  |     IF 		= string_to_symbol("if");	is_special[IF] = 1 | ||
|  |     SETQ 	= string_to_symbol("set!");	is_special[SETQ] = 1 | ||
|  |     DEFINE 	= string_to_symbol("define");	is_special[DEFINE] = 1 | ||
|  |     PROGN 	= string_to_symbol("begin");	is_special[PROGN] = 1 | ||
|  |     WHILE 	= string_to_symbol("while");	is_special[WHILE] = 1 | ||
|  | 
 | ||
|  |     EQ		= def_prim("eq?", 2) | ||
|  |     NULL 	= def_prim("null?", 1) | ||
|  |     CAR 	= def_prim("car", 1) | ||
|  |     CDR 	= def_prim("cdr", 1) | ||
|  |     CADR 	= def_prim("cadr", 1) | ||
|  |     CDDR 	= def_prim("cddr", 1) | ||
|  |     CONS 	= def_prim("cons", 2) | ||
|  |     LIST 	= def_prim("list") | ||
|  |     EVAL 	= def_prim("eval", 1) | ||
|  |     APPLY 	= def_prim("apply", 2) | ||
|  |     READ 	= def_prim("read", 0) | ||
|  |     WRITE 	= def_prim("write", 1) | ||
|  |     NEWLINE 	= def_prim("newline", 0) | ||
|  |     ADD		= def_prim("+", 2) | ||
|  |     SUB 	= def_prim("-", 2) | ||
|  |     MUL 	= def_prim("*", 2) | ||
|  |     DIV 	= def_prim("quotient", 2) | ||
|  |     MOD 	= def_prim("remainder", 2) | ||
|  |     LT 		= def_prim("<", 2) | ||
|  |     GET 	= def_prim("get", 2) | ||
|  |     PUT 	= def_prim("put", 3) | ||
|  |     ATOMP 	= def_prim("atom?", 1) | ||
|  |     PAIRP 	= def_prim("pair?", 1) | ||
|  |     SYMBOLP 	= def_prim("symbol?", 1) | ||
|  |     NUMBERP 	= def_prim("number?", 1) | ||
|  |     SETCAR 	= def_prim("set-car!", 2) | ||
|  |     SETCDR 	= def_prim("set-cdr!", 2) | ||
|  |     NREV 	= def_prim("reverse!", 1) | ||
|  |     GENSYM 	= def_prim("gensym", 0) | ||
|  |     RANDOM	= def_prim("random", 1) | ||
|  |     ERROR	= def_prim("error") | ||
|  | 
 | ||
|  |     DRIVER 	= string_to_symbol("top-level-driver") | ||
|  | } | ||
|  | 
 | ||
|  | # --- The interpreter | ||
|  | 
 | ||
|  | BEGIN {	 | ||
|  |     for (;;) { | ||
|  |         if (DRIVER in value && value[DRIVER] != NIL) | ||
|  |             apply(value[DRIVER]) | ||
|  |         else { | ||
|  |             expr = read() | ||
|  |             if (expr == THE_EOF_OBJECT) | ||
|  |                 break; | ||
|  |             protect(expr) | ||
|  |             print_expr(eval(expr)) | ||
|  |             unprotect() | ||
|  |         } | ||
|  |     } | ||
|  |      | ||
|  |     if (profiling) | ||
|  |         for (proc in call_count) { | ||
|  |             printf("%5d ", call_count[proc]) | ||
|  |             print_expr(proc) | ||
|  |         } | ||
|  | } | ||
|  | 
 | ||
|  | # All the interpretation routines have the precondition that their | ||
|  | # arguments are protected from garbage collection. | ||
|  | 
 | ||
|  | function eval(expr,	old_frame_ptr) | ||
|  | { | ||
|  |     if (is_atom(expr))			#** speed | ||
|  |         if (is_symbol(expr)) { | ||
|  |             if (!(expr in value)) error("Unbound variable: " printname[expr]) | ||
|  |             return value[expr] | ||
|  |         } else | ||
|  |             return expr | ||
|  | 
 | ||
|  |     op = car[expr]		# op is global to save awk stack space | ||
|  | 
 | ||
|  |     if (!(op in is_special)) { | ||
|  |         old_frame_ptr = frame_ptr | ||
|  |         frame_ptr = stack_ptr | ||
|  | 
 | ||
|  |         eval_rands(cdr[expr]) | ||
|  |         protect(proc = eval(car[expr])) | ||
|  |         result = apply(proc) | ||
|  |         unprotect() | ||
|  | 
 | ||
|  |         stack_ptr = frame_ptr | ||
|  |         frame_ptr = old_frame_ptr | ||
|  |         return result | ||
|  |     } | ||
|  | 
 | ||
|  |     if (op == QUOTE)	return car[cdr[expr]] | ||
|  |     if (op == LAMBDA)	return expr | ||
|  |     if (op == IF)	return eval(car[cdr[expr]]) != NIL \ | ||
|  |                                 ? eval(car[cdr[cdr[expr]]])	\ | ||
|  |                                 : eval(car[cdr[cdr[cdr[expr]]]]) | ||
|  |     if (op == PROGN)	return progn(cdr[expr]) | ||
|  |     if (op == SETQ)	{ | ||
|  |         if (!(car[cdr[expr]] in value)) | ||
|  |             error("Unbound variable: " printname[car[cdr[expr]]]) | ||
|  |         return value[car[cdr[expr]]] = eval(car[cdr[cdr[expr]]]) | ||
|  |     } | ||
|  |     if (op == WHILE) { | ||
|  |         while (eval(car[cdr[expr]]) != NIL) | ||
|  |             progn(cdr[cdr[expr]]) | ||
|  |         return NIL | ||
|  |     } | ||
|  |     if (op == DEFINE) { | ||
|  |         value[car[cdr[expr]]] = eval(car[cdr[cdr[expr]]]) | ||
|  |         return car[cdr[expr]] | ||
|  |     } | ||
|  |      | ||
|  |     error("BUG: Unknown special form") | ||
|  | } | ||
|  | 
 | ||
|  | # Evaluate a sequence of expressions, returning the last value. | ||
|  | function progn(exprs) | ||
|  | { | ||
|  |     for (; cdr[exprs] != NIL; exprs = cdr[exprs]) | ||
|  |         eval(car[exprs]) | ||
|  |     return eval(car[exprs]) | ||
|  | } | ||
|  | 
 | ||
|  | # Evaluate the operands of a procedure, pushing the results on the stack. | ||
|  | function eval_rands(rands) | ||
|  | { | ||
|  |     for (; rands != NIL; rands = cdr[rands]) | ||
|  |         stack[stack_ptr++] = eval(car[rands]) | ||
|  | } | ||
|  | 
 | ||
|  | # Call the procedure :proc, with args stack[frame_ptr]..stack[stack_ptr-1] | ||
|  | # (in that order). | ||
|  | function apply(proc) | ||
|  | { | ||
|  |     if (profiling)  | ||
|  |         ++call_count[proc] | ||
|  |     if (car[proc] == LAMBDA) { | ||
|  |         extend_env(car[cdr[proc]]) | ||
|  |         result = progn(cdr[cdr[proc]])	# result is global to save stack space | ||
|  |         unwind_env(car[cdr[proc]]) | ||
|  |         return result | ||
|  |     } | ||
|  |     if (proc in num_params && num_params[proc] != stack_ptr - frame_ptr) | ||
|  |         error("Wrong number of arguments to " printname[cdr[proc]]) | ||
|  | 
 | ||
|  |     if (proc == CAR)	return car[is(a_pair, stack[frame_ptr])] | ||
|  |     if (proc == CDR)	return cdr[is(a_pair, stack[frame_ptr])] | ||
|  |     if (proc == CONS)	return cons(stack[frame_ptr], stack[frame_ptr+1]) | ||
|  |     if (proc == NULL)	return stack[frame_ptr] == NIL ? T : NIL | ||
|  |     if (proc == EQ)	return stack[frame_ptr] == stack[frame_ptr+1] ? T : NIL | ||
|  |     if (proc == ATOMP)	return is_atom(stack[frame_ptr]) ? T : NIL | ||
|  |     if (proc == ADD)	return is(a_number, stack[frame_ptr]) + is(a_number, stack[frame_ptr+1]) | ||
|  |     if (proc == SUB)	return is(a_number, stack[frame_ptr]) - is(a_number, stack[frame_ptr+1]) | ||
|  |     if (proc == MUL)	return make_number(numeric_value(stack[frame_ptr]) * numeric_value(stack[frame_ptr+1])) | ||
|  |     if (proc == DIV)	return make_number(int(numeric_value(stack[frame_ptr]) / numeric_value(stack[frame_ptr+1]))) | ||
|  |     if (proc == MOD)	return make_number(numeric_value(stack[frame_ptr]) % numeric_value(stack[frame_ptr+1])) | ||
|  |     if (proc == LT)	return (stack[frame_ptr] + 0 < stack[frame_ptr+1] + 0) ? T : NIL | ||
|  |     if (proc == GET)	return (stack[frame_ptr], stack[frame_ptr+1]) in property ? property[stack[frame_ptr], stack[frame_ptr+1]] : NIL | ||
|  |     if (proc == PUT) 	return property[stack[frame_ptr], stack[frame_ptr+1]] = stack[frame_ptr+2] | ||
|  |     if (proc == CADR)	return car[is(a_pair, cdr[is(a_pair, stack[frame_ptr])])] | ||
|  |     if (proc == CDDR)	return cdr[is(a_pair, cdr[is(a_pair, stack[frame_ptr])])] | ||
|  |     if (proc == LIST)	return listify_args() | ||
|  |     if (proc == SYMBOLP)return is_symbol(stack[frame_ptr]) ? T : NIL | ||
|  |     if (proc == PAIRP)	return is_pair(stack[frame_ptr]) ? T : NIL | ||
|  |     if (proc == NUMBERP)return is_number(stack[frame_ptr]) ? T : NIL | ||
|  |     if (proc == SETCAR)	return car[is(a_pair, stack[frame_ptr])] = stack[frame_ptr+1] | ||
|  |     if (proc == SETCDR)	return cdr[is(a_pair, stack[frame_ptr])] = stack[frame_ptr+1] | ||
|  |     if (proc == APPLY)	return do_apply(stack[frame_ptr], stack[frame_ptr+1]) | ||
|  |     if (proc == EVAL)	return eval(stack[frame_ptr]) | ||
|  |     if (proc == NREV)	return nreverse(stack[frame_ptr], NIL) | ||
|  |     if (proc == WRITE)	{ write_expr(stack[frame_ptr]); printf(" "); return NIL } | ||
|  |     if (proc == NEWLINE){ printf("\n"); return NIL } | ||
|  |     if (proc == READ)	return read() | ||
|  |     if (proc == RANDOM)	return make_number(int(rand() * numeric_value(stack[frame_ptr]))) | ||
|  |     if (proc == GENSYM)	return string_to_symbol("#G" ++gensym_counter) | ||
|  |     if (proc == ERROR)	{ printf("Error!\n"); print_expr(listify_args()); exit(1) } | ||
|  |     error("Unknown procedure type") | ||
|  | } | ||
|  | 
 | ||
|  | function do_apply(proc, args,		old_frame_ptr) | ||
|  | { | ||
|  |     old_frame_ptr = frame_ptr | ||
|  |     frame_ptr = stack_ptr | ||
|  | 
 | ||
|  |     for (; is_pair(args); args = cdr[args]) | ||
|  |         stack[stack_ptr++] = car[args] | ||
|  |     if (args != NIL) | ||
|  |         error("Bad argument to APPLY: not a proper list") | ||
|  |     result = apply(proc) | ||
|  | 
 | ||
|  |     stack_ptr = frame_ptr | ||
|  |     frame_ptr = old_frame_ptr | ||
|  |     return result | ||
|  | } | ||
|  | 
 | ||
|  | function listify_args(		p, result) | ||
|  | { | ||
|  |     result = NIL | ||
|  |     for (p = stack_ptr - 1; frame_ptr <= p; --p) | ||
|  |         result = cons(stack[p], result) | ||
|  |     return result | ||
|  | } | ||
|  | 
 | ||
|  | # --- The environment | ||
|  | # The current environment is represented by the set of values | ||
|  | # value[sym] where sym is a symbol.  extend_env(vars) adds a new | ||
|  | # set of bindings for vars, saving the old values; unwind_env(vars) | ||
|  | # restores those old values.  The new value for the nth member of | ||
|  | # vars is frame_ptr[n]; coincidentally, that's also where we  | ||
|  | # store away the old value, since that stack frame's not needed  | ||
|  | # for anything else after the extend_env() call. | ||
|  | 
 | ||
|  | function extend_env(vars,	p, temp) | ||
|  | { | ||
|  |     for (p = frame_ptr; vars != NIL; vars = cdr[vars]) { | ||
|  |         if (p == stack_ptr)  | ||
|  | 	    error("Too many arguments to procedure") | ||
|  |         temp = value[car[vars]] | ||
|  |         value[car[vars]] = stack[p] | ||
|  |         stack[p] = temp | ||
|  |         ++p | ||
|  |     } | ||
|  |     if (p != stack_ptr)  | ||
|  | 	error("Not enough arguments to procedure") | ||
|  | } | ||
|  | 
 | ||
|  | function unwind_env(vars,	p) | ||
|  | { | ||
|  |     for (p = frame_ptr; vars != NIL; vars = cdr[vars]) { | ||
|  |         if (stack[p] == "") | ||
|  | 	    delete value[car[vars]] | ||
|  | 	else | ||
|  | 	    value[car[vars]] = stack[p] | ||
|  | 	++p | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | # --- Output | ||
|  | 
 | ||
|  | function print_expr(expr) | ||
|  | { | ||
|  |     write_expr(expr) | ||
|  |     print "" | ||
|  | } | ||
|  | 
 | ||
|  | function write_expr(expr) | ||
|  | { | ||
|  |     if (is_atom(expr)) { | ||
|  |         if (!is_symbol(expr)) | ||
|  |             printf("%d", numeric_value(expr)) | ||
|  |         else { | ||
|  |             if (!(expr in printname)) | ||
|  |                 error("BUG: " expr " has no printname") | ||
|  |             printf("%s", printname[expr]) | ||
|  |         } | ||
|  |     } else { | ||
|  |         printf("(") | ||
|  |         write_expr(car[expr]) | ||
|  |         for (expr = cdr[expr]; is_pair(expr); expr = cdr[expr]) { | ||
|  |             printf(" ") | ||
|  |             write_expr(car[expr]) | ||
|  |         } | ||
|  |         if (expr != NIL) { | ||
|  |             printf(" . ") | ||
|  |             write_expr(expr) | ||
|  |         } | ||
|  |         printf(")") | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | # --- Input | ||
|  | 
 | ||
|  | function read(		committed,	result) | ||
|  | { | ||
|  |     skip_blanks() | ||
|  |     if (token == eof) | ||
|  |         if (committed) | ||
|  |             error("Unexpected EOF") | ||
|  |         else | ||
|  |             return THE_EOF_OBJECT | ||
|  |     if (token == "(") {			# read a list | ||
|  |         advance() | ||
|  |         result = NIL | ||
|  |         for (;;) { | ||
|  |             skip_blanks() | ||
|  |             if (token == ".") { | ||
|  |                 advance() | ||
|  |                 after_dot = read(1) | ||
|  |                 skip_blanks() | ||
|  |                 if (token != ")") | ||
|  |                     error("')' expected") | ||
|  |                 advance() | ||
|  |                 return nreverse(result, after_dot) | ||
|  |             } else if (token == ")") { | ||
|  |                 advance() | ||
|  |                 return nreverse(result, NIL) | ||
|  |             } else { | ||
|  |                 protect(result) | ||
|  |                 result = cons(read(1), result) | ||
|  |                 unprotect() | ||
|  |             } | ||
|  |         } | ||
|  |     } else if (token == "'") {		# a quoted expression | ||
|  |         advance() | ||
|  |         return cons(QUOTE, cons(read(1), NIL)) | ||
|  |     } else if (token ~ /^-?[0-9]+$/) {	# a number | ||
|  |         result = make_number(token) | ||
|  |         advance() | ||
|  |         return result | ||
|  |     } else {				# a symbol | ||
|  |         result = string_to_symbol(token) | ||
|  |         advance() | ||
|  |         return result | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | function skip_blanks() | ||
|  | { | ||
|  |     while (token ~ /^[ \t]*$/) | ||
|  |         advance() | ||
|  | } | ||
|  | 
 | ||
|  | function advance() | ||
|  | { | ||
|  |     if (token == eof) return eof | ||
|  |     if (token == "") { | ||
|  |         if (getline line <= 0) { | ||
|  |             token = eof | ||
|  |             return | ||
|  |         } | ||
|  |     } | ||
|  |     if (match(line, "^[()'.]") || | ||
|  |         match(line, "^[_A-Za-z0-9=!@$%&*<>?+\\-*/:]+") || | ||
|  |         match(line, "^[ \\t]+")) { | ||
|  |         token = substr(line, RSTART, RLENGTH) | ||
|  |         line = substr(line, RLENGTH+1) | ||
|  |     } else if (line == "" || substr(line, 1, 1) == ";") | ||
|  |         token = ""	# this kludge permits interactive use | ||
|  |     else | ||
|  |         error("Lexical error starting at " line) | ||
|  | } | ||
|  | 
 | ||
|  | # --- Miscellany | ||
|  | 
 | ||
|  | # Destructively reverse :list and append :reversed_head. | ||
|  | function nreverse(list, reversed_head,		tail) | ||
|  | { | ||
|  |     while (is_pair(list)) {		#** speed? | ||
|  |         tail = cdr[list] | ||
|  |         cdr[list] = reversed_head | ||
|  |         reversed_head = list | ||
|  |         list = tail | ||
|  |     } | ||
|  |     if (list != NIL) | ||
|  |  	error("Not a proper list - reverse!") | ||
|  |     return reversed_head | ||
|  | } | ||
|  | 
 | ||
|  | function error(reason) | ||
|  | { | ||
|  |     print "ERROR: " reason >"/dev/stderr" | ||
|  |     exit(1) | ||
|  | } |