eryali neskol'ko razdelitel'nyh zapyatyh!
Vtoraya oshibka kasaetsya togo, chto mozhno zabyt' postavit' slovo break v operatore
switch, i dolgo posle etogo gadat' o nepredskazuemom povedenii lyubogo postupayushchego na
vhod znacheniya. Delo prosto: probegayutsya vse sluchai, upravlenie provalivaetsya iz case
v sleduyushchij case, i tak mnogo raz podryad! |to i est' prichina togo, chto v predydushchem
A. Bogatyrev, 1992-95 - 328 - Si v UNIX
primere vse case oformleny netrivial'nym makrosom Bcase.
7.54. Sostav'te programmu kodirovki i raskodirovki fajlov po zadannomu klyuchu (stroke
simvolov).
7.55. Sostav'te programmu, kotoraya zaprashivaet anketnye dannye tipa familii, imeni,
otchestva, daty rozhdeniya i formiruet fajl. Programma dolzhna otlavlivat' oshibki vvoda
nesimvol'noj i necifrovoj informacii, vyhoda sostavlyayushchih daty rozhdeniya za dopustimye
granicy s vydachej soobshchenij ob oshibkah. Programma dolzhna davat' vozmozhnost' korrekti-
rovat' vvodimye dannye. Vse dannye ob odnom cheloveke zapisyvayutsya v odnu stroku fajla
cherez probel. Vot vozmozhnyj primer chasti dialoga (otvety pol'zovatelya vydeleny
zhirno):
Vvedite mesyac rozhdeniya [1-12]: 14 <ENTER>
*** Nepravil'nyj nomer mesyaca (14).
Vvedite mesyac rozhdeniya [1-12]: mart <ENTER>
*** Nomer mesyaca soderzhit bukvu 'm'.
Vvedite mesyac rozhdeniya [1-12]: <ENTER>
Vy hotite zakonchit' vvod ? n
Vvedite mesyac rozhdeniya [1-12]: 11 <ENTER>
Noyabr'
Vvedite datu rozhdeniya [1-30]: _
V takih programmah obychno otvet pol'zovatelya vvoditsya kak stroka:
printf("Vvedite mesyac rozhdeniya [1-12]: ");
fflush(stdout); gets(input_string);
zatem (esli nado) otbrasyvayutsya lishnie probely v nachale i v konce stroki, zatem vve-
dennyj tekst input_string analiziruetsya na dopustimost' simvolov (net li v nem ne
cifr?), zatem stroka preobrazuetsya k nuzhnomu tipu (naprimer, pri pomoshchi funkcii atoi
perevoditsya v celoe) i proveryaetsya dopustimost' poluchennogo znacheniya, i.t.d.
Vvodimuyu informaciyu snachala zanosite v strukturu; zatem zapisyvajte soderzhimoe
polej struktury v fajl v tekstovom vide (ispol'zujte funkciyu fprintf, a ne fwrite).
7.56. Sostav'te programmu, osushchestvlyayushchuyu vyborku informacii iz fajla, sformirovan-
nogo v predydushchej zadache, i ee raspechatku v tablichnom vide. Vyborka dolzhna osushchestv-
lyat'sya po znacheniyu lyubogo zadannogo polya (t.e. vy vybiraete pole, zadaete ego znache-
nie i poluchaete te stroki, v kotoryh znachenie ukazannogo polya sovpadaet s zakazannym
vami znacheniem). Uslozhnenie: ispol'zujte funkciyu sravneniya stroki s regulyarnym vyra-
zheniem dlya vyborki po shablonu polya (t.e. otbirayutsya tol'ko te stroki, v kotoryh zna-
chenie zadannogo polya udovletvoryaet shablonu). Dlya chteniya fajla ispol'zujte fscanf,
libo fgets i zatem sscanf. Vtoroj sposob luchshe tem, chto pozvolyaet proverit' po shab-
lonu znachenie lyubogo polya - ne tol'ko tekstovogo, no i chislovogo: tak 1234 (stroka -
izobrazhenie chisla) udovletvoryaet shablonu "12*".
7.57. Sostav'te variant programmy podscheta sluzhebnyh slov yazyka Si, ne uchityvayushchij
poyavlenie etih slov, zaklyuchennyh v kavychki.
7.58. Sostav'te programmu udaleniya iz programmy na yazyke Si vseh kommentariev. Obra-
tite vnimanie na osobye sluchai so strokami v kavychkah i simvol'nymi konstantami; tak
stroka
char s[] = "/*";
ne yavlyaetsya nachalom kommentariya! Kommentarii zapisyvajte v otdel'nyj fajl.
7.59. Sostav'te programmu vydachi perekrestnyh ssylok, t.e. programmu, kotoraya vyvo-
dit spisok vseh identifikatorov peremennyh, ispol'zuemyh v programme, i dlya kazhdogo
iz identifikatorov vyvodit spisok nomerov strok, v kotorye on vhodit.
A. Bogatyrev, 1992-95 - 329 - Si v UNIX
7.60. Razrabotajte prostuyu versiyu preprocessora dlya obrabotki operatorov #include.
V kachestve prototipa takoj programmy mozhno rassmatrivat' takuyu (ona ponimaet direk-
tivy vida #include imyafajla - bez <> ili "").
#include <stdio.h>
#include <string.h>
#include <errno.h>
char KEYWORD[] = "#include "; /* with a trailing space char */
void process(char *name, char *from){
FILE *fp;
char buf[4096];
if((fp = fopen(name, "r")) == NULL){
fprintf(stderr, "%s: cannot read \"%s\", %s\n",
from, name, strerror(errno));
return;
}
while(fgets(buf, sizeof buf, fp) != NULL){
if(!strncmp(buf, KEYWORD, sizeof KEYWORD - 1)){
char *s;
if((s = strchr(buf, '\n')) != NULL) *s = '\0';
fprintf(stderr, "%s: including %s\n",
name, s = buf + sizeof KEYWORD - 1);
process(s, name);
} else fputs(buf, stdout);
}
fclose(fp);
}
int main(int ac, char *av[]){
int i;
for(i=1; i < ac; i++)
process(av[i], "MAIN");
return 0;
}
7.61. Razrabotajte prostuyu versiyu preprocessora dlya obrabotki operatorov #define.
Snachala realizujte makrosy bez argumentov. Napishite obrabotchik makrosov vida
#macro imya(argu,menty)
telo makrosa - mozhno neskol'ko strok
#endm
7.62. Napishite programmu, obrabatyvayushchuyu opredeleniya #ifdef, #else, #endif. Uchtite,
chto eti direktivy mogut byt' vlozhennymi:
#ifdef A
# ifdef B
... /* defined(A) && defined(B) */
# endif /*B*/
... /* defined(A) */
#else /*not A*/
... /* !defined(A) */
# ifdef C
... /* !defined(A) && defined(C) */
# endif /*C*/
A. Bogatyrev, 1992-95 - 330 - Si v UNIX
#endif /*A*/
7.63. Sostav'te programmu modelirovaniya prostejshego kal'kulyatora, kotoryj schityvaet
v kazhdoj strochke po odnomu chislu (vozmozhno so znakom) ili po odnoj operacii slozheniya
ili umnozheniya, osushchestvlyaet operaciyu i vydaet rezul'tat.
7.64. Sostav'te programmu-kal'kulyator, kotoraya proizvodit operacii slozheniya, vychita-
niya, umnozheniya, deleniya; operandy i znak arifmeticheskoj operacii yavlyayutsya strokovymi
argumentami funkcii main.
7.65. Sostav'te programmu, vychislyayushchuyu znachenie komandnoj stroki, predstavlyayushchej
soboj obratnuyu pol'skuyu zapis' arifmeticheskogo vyrazheniya. Naprimer, 20 10 5 + *
vychislyaetsya kak 20 * (10 + 5) .
7.66. Sostav'te funkcii raboty so stekom:
- dobavlenie v stek
- udalenie vershiny steka (s vozvratom udalennogo znacheniya)
Ispol'zujte dva varianta: stek-massiv i stek-spisok.
7.67. Sostav'te programmu, kotoraya ispol'zuet funkcii raboty so stekom dlya perevoda
arifmeticheskih vyrazhenij yazyka Si v obratnuyu pol'skuyu zapis'.
/*#!/bin/cc $* -lm
* Kal'kulyator. Illyustraciya algoritma prevrashcheniya vyrazhenij
* v pol'skuyu zapis' po metodu prioritetov.
*/
#include <stdio.h>
#include <stdlib.h> /* extern double atof(); */
#include <math.h> /* extern double sin(), ... */
#include <ctype.h> /* isdigit(), isalpha(), ... */
#include <setjmp.h> /* jmp_buf */
jmp_buf AGAIN; /* kontrol'naya tochka */
err(n){ longjmp(AGAIN,n);} /* prygnut' v kontrol'nuyu tochku */
A. Bogatyrev, 1992-95 - 331 - Si v UNIX
/* VYCHISLITELX --------------------------------------- */
/* Esli vmesto pomeshcheniya operandov v stek stk[] prosto
* pechatat' operandy, a vmesto vypolneniya operacij nad
* stekom prosto pechatat' operacii, my poluchim "pol'skuyu"
* zapis' vyrazheniya:
* a+b -> a b +
* (a+b)*c -> a b + c *
* a + b*c -> a b c * +
*/
/* stek vychislenij */
#define MAXDEPTH 20 /* glubina stekov */
int sp; /* ukazatel' steka (stack pointer) */
double stk[MAXDEPTH];
double dpush(d) double d; /* zanesti chislo v stek */
{
if( sp == MAXDEPTH ){ printf("Stek operandov polon\n");err(1);}
else return( stk[sp++] = d );
}
double dpop(){ /* vzyat' vershinu steka */
if( !sp ){ printf("Stek operandov pust\n"); err(2); }
else return stk[--sp];
}
static double r,p; /* vspomogatel'nye registry */
void add() { dpush( dpop() + dpop()); }
void mult() { dpush( dpop() * dpop()); }
void sub() { r = dpop(); dpush( dpop() - r); }
void divide() { r = dpop();
if(r == 0.0){ printf("Delenie na 0\n"); err(3); }
dpush( dpop() / r );
}
void pwr() { r = dpop(); dpush( pow( dpop(), r )); }
void dup() { dpush( dpush( dpop())); }
void xchg(){ r = dpop(); p = dpop(); dpush(r); dpush(p); }
void neg() { dpush( - dpop()); }
void dsin(){ dpush( sin( dpop())); }
void dcos(){ dpush( cos( dpop())); }
void dexp(){ dpush( exp( dpop())); }
void dlog(){ dpush( log( dpop())); }
void dsqrt(){ dpush( sqrt( dpop())); }
void dsqr(){ dup(); mult(); }
/* M_PI i M_E opredeleny v <math.h> */
void pi() { dpush( M_PI /* chislo pi */ ); }
void e() { dpush( M_E /* chislo e */ ); }
void prn() { printf("%g\n", dpush( dpop())); }
void printstk(){
if( !sp ){ printf("Stek operandov pust\n"); err(4);}
while(sp) printf("%g ", dpop());
putchar('\n');
}
A. Bogatyrev, 1992-95 - 332 - Si v UNIX
/* KOMPILYATOR ---------------------------------------- */
/* nomera leksem */
#define END (-3) /* = */
#define NUMBER (-2) /* chislo */
#define BOTTOM 0 /* psevdoleksema "dno steka" */
#define OPENBRACKET 1 /* ( */
#define FUNC 2 /* f( */
#define CLOSEBRACKET 3 /* ) */
#define COMMA 4 /* , */
#define PLUS 5 /* + */
#define MINUS 6 /* - */
#define MULT 7 /* * */
#define DIV 8 /* / */
#define POWER 9 /* ** */
/* Prioritety */
#define NOTDEF 333 /* ne opredelen */
#define INFINITY 3000 /* beskonechnost' */
/* Stek translyatora */
typedef struct _opstack {
int cop; /* kod operacii */
void (*f)(); /* "otlozhennoe" dejstvie */
} opstack;
int osp; /* operations stack pointer */
opstack ost[MAXDEPTH];
void push(n, func) void (*func)();
{
if(osp == MAXDEPTH){ printf("Stek operacij polon\n");err(5);}
ost[osp].cop = n; ost[osp++].f = func;
}
int pop(){
if( !osp ){ printf("Stek operacij pust\n"); err(6); }
return ost[--osp].cop;
}
int top(){
if( !osp ){ printf("Stek operacij pust\n"); err(7); }
return ost[osp-1].cop;
}
void (*topf())(){
return ost[osp-1].f;
}
#define drop() (void)pop()
void nop(){ printf( "???\n" ); } /* no operation */
void obr_err(){ printf( "Ne hvataet )\n" ); err(8); }
A. Bogatyrev, 1992-95 - 333 - Si v UNIX
/* Tablica prioritetov */
struct synt{
int inp_prt; /* vhodnoj prioritet */
int stk_prt; /* stekovyj prioritet */
void (*op)(); /* dejstvie nad stekom vychislenij */
} ops[] = {
/* BOTTOM */ {NOTDEF, -1, nop },
/* OPENBRACKET */ {INFINITY, 0, obr_err},
/* FUNC */ {INFINITY, 0, obr_err},
/* CLOSEBRACKET */ {1, NOTDEF, nop }, /* NOPUSH */
/* COMMA */ {1, NOTDEF, nop }, /* NOPUSH */
/* PLUS */ {1, 1, add },
/* MINUS */ {1, 1, sub },
/* MULT */ {2, 2, mult },
/* DIV */ {2, 2, divide },
/* POWER */ {3, 3, pwr }
};
#define stkprt(i) ops[i].stk_prt
#define inpprt(i) ops[i].inp_prt
#define perform(i) (*ops[i].op)()
/* znacheniya, zapolnyaemye leksicheskim analizatorom */
double value; void (*fvalue)();
int tprev; /* predydushchaya leksema */
A. Bogatyrev, 1992-95 - 334 - Si v UNIX
/* Translyator v pol'skuyu zapis' + interpretator */
void reset(){ sp = osp = 0; push(BOTTOM, NULL); tprev = END;}
void calc(){
int t;
do{
if( setjmp(AGAIN))
printf( "Steki posle oshibki sbrosheny\n" );
reset();
while((t = token()) != EOF && t != END){
if(t == NUMBER){
if(tprev == NUMBER){
printf("%g:Dva chisla podryad\n",value);
err(9);
}
/* lyuboe chislo prosto zanositsya v stek */
tprev = t; dpush(value); continue;
}
/* inache - operator */
tprev = t;
/* Vytalkivanie i vypolnenie operacij so steka */
while(inpprt(t) <= stkprt( top()) )
perform( pop());
/* Sokrashchenie ili podmena skobok */
if(t == CLOSEBRACKET){
if( top() == OPENBRACKET || top() == FUNC ){
void (*ff)() = topf();
drop(); /* shlopnut' skobki */
/* obrabotka funkcii */
if(ff) (*ff)();
}else{ printf( "Ne hvataet (\n"); err(10); }
}
/* Zanesenie operacij v stek (krome NOPUSH-operacij) */
if(t != CLOSEBRACKET && t != COMMA)
push(t, t == FUNC ? fvalue : NULL );
}
if( t != EOF ){
/* Dovypolnit' ostavshiesya operacii */
while( top() != BOTTOM )
perform( pop());
printstk(); /* pechat' steka vychislenij (otvet) */
}
} while (t != EOF);
}
/* Leksicheskij analizator ---------------------------- */
extern void getn(), getid(), getbrack();
int token(){ /* prochest' leksemu */
int c;
while((c = getchar())!= EOF && (isspace(c) || c == '\n'));
if(c == EOF) return EOF;
ungetc(c, stdin);
if(isdigit(c)){ getn(); return NUMBER; }
if(isalpha(c)){ getid(); getbrack(); return FUNC; }
return getop();
}
A. Bogatyrev, 1992-95 - 335 - Si v UNIX
/* Prochest' chislo (s tochkoj) */
void getn(){
int c, i; char s[80];
s[0] = getchar();
for(i=1; isdigit(c = getchar()); i++ ) s[i] = c;
if(c == '.'){ /* drobnaya chast' */
s[i] = c;
for(i++; isdigit(c = getchar()); i++) s[i] = c;
}
s[i] = '\0'; ungetc(c, stdin); value = atof(s);
}
/* Prochest' operaciyu */
int getop(){
int c;
switch( c = getchar()){
case EOF: return EOF;
case '=': return END;
case '+': return PLUS;
case '-': return MINUS;
case '/': return DIV;
case '*': c = getchar();
if(c == '*') return POWER;
else{ ungetc(c, stdin); return MULT; }
case '(': return OPENBRACKET;
case ')': return CLOSEBRACKET;
case ',': return COMMA;
default: printf( "Oshibochnaya operaciya %c\n", c);
return token();
}
}
struct funcs{ /* Tablica imen funkcij */
char *fname; void (*fcall)();
} tbl[] = {
{ "sin", dsin }, { "cos", dcos },
{ "exp", dexp }, { "sqrt", dsqrt },
{ "sqr", dsqr }, { "pi", pi },
{ "sum", add }, { "ln", dlog },
{ "e", e }, { NULL, NULL }
};
char *lastf; /* imya najdennoj funkcii */
/* Prochest' imya funkcii */
void getid(){
struct funcs *ptr = tbl;
char name[80]; int c, i;
*name = getchar();
for(i=1; isalpha(c = getchar()); i++) name[i] = c;
name[i] = '\0'; ungetc(c, stdin);
/* poisk v tablice */
for( ; ptr->fname; ptr++ )
if( !strcmp(ptr->fname, name)){
fvalue = ptr->fcall;
lastf = ptr->fname; return;
}
printf( "Funkciya \"%s\" neizvestna\n", name ); err(11);
}
A. Bogatyrev, 1992-95 - 336 - Si v UNIX
/* prochest' otkryvayushchuyu skobku posle imeni funkcii */
void getbrack(){
int c;
while((c = getchar()) != EOF && c != '(' )
if( !isspace(c) && c != '\n' ){
printf("Mezhdu imenem funkcii %s i ( simvol %c\n", lastf, c);
ungetc(c, stdin); err(12);
}
}
void main(){ calc();}
/* Primery:
( sin( pi() / 4 + 0.1 ) + sum(2, 4 + 1)) * (5 - 4/2) =
otvet: 23.3225
(14 + 2 ** 3 * 7 + 2 * cos(0)) / ( 7 - 4 ) =
otvet: 24
*/
7.68. Privedem eshche odin arifmeticheskij vychislitel', ispol'zuyushchij klassicheskij rekur-
sivnyj podhod:
/* Kal'kulyator na osnove rekursivnogo grammaticheskogo razbora.
* Po motivam arifmeticheskoj chasti programmy csh (SiSHell).
* csh napisan Billom Dzhoem (Bill Joy).
: var1 = (x = 1+3) * (y=x + x++) 36
: s = s + 1 oshibka
: y 9
: s = (1 + 1 << 2) == 1 + (1<<2) 0
: var1 + 3 + -77 -38
: a1 = 3; a2 = (a4=a3 = 2; a1++)+a4+2 8
: sum(a=2;b=3, a++, a*3-b) 12
*/
#include <stdio.h>
#include <ctype.h>
#include <setjmp.h>
typedef enum { NUM, ID, OP, OPEN, CLOSE, UNKNOWN, COMMA, SMC } TokenType;
char *toknames[] = { "number", "identifier", "operation",
"open_paren", "close_paren", "unknown", "comma", "semicolon" };
typedef struct _Token {
char *token; /* leksema (slovo) */
struct _Token *next; /* ssylka na sleduyushchuyu */
TokenType type; /* tip leksemy */
} Token;
extern void *malloc(unsigned); extern char *strchr(char *, char);
char *strdup(const char *s){
char *p = (char *)malloc(strlen(s)+1);
if(p) strcpy(p,s); return p;
}
A. Bogatyrev, 1992-95 - 337 - Si v UNIX
/* Leksicheskij razbor ------------------------------------------*/
/* Ochistit' cepochku tokenov */
void freelex(Token **p){
Token *thisTok = *p;
while( thisTok ){ Token *nextTok = thisTok->next;
free((char *) thisTok->token); free((char *) thisTok);
thisTok = nextTok;
}
*p = NULL;
}
/* Dobavit' token v hvost spiska */
void addtoken(Token **hd, Token **tl, char s[], TokenType t){
Token *newTok = (Token *) malloc(sizeof(Token));
newTok->next = (Token *) NULL;
newTok->token = strdup(s); newTok->type = t;
if(*hd == NULL) *hd = *tl = newTok;
else{ (*tl)->next = newTok; *tl = newTok; }
}
/* Razobrat' stroku v spisok leksem (tokenov) */
#define opsym(c) ((c) && strchr("+-=!~^|&*/%<>", (c)))
#define is_alpha(c) (isalpha(c) || (c) == '_')
#define is_alnum(c) (isalnum(c) || (c) == '_')
void lex(Token **hd, Token **tl, register char *s){
char *p, csave; TokenType type;
while(*s){
while( isspace(*s)) ++s; p = s;
if( !*s ) break;
if(isdigit (*s)){ type = NUM; while(isdigit (*s))s++; }
else if(is_alpha(*s)){ type = ID; while(is_alnum(*s))s++; }
else if(*s == '('){ type = OPEN; s++; }
else if(*s == ')'){ type = CLOSE; s++; }
else if(*s == ','){ type = COMMA; s++; }
else if(*s == ';'){ type = SMC; s++; }
else if(opsym(*s)){ type = OP; while(opsym(*s)) s++; }
else { type = UNKNOWN; s++; }
csave = *s; *s = '\0'; addtoken(hd, tl, p, type); *s = csave;
}
}
/* Raspechatka spiska leksem */
void printlex(char *msg, Token *t){
if(msg && *msg) printf("%s: ", msg);
for(; t != NULL; t = t->next)
printf("%s`%s' ", toknames[(int)t->type], t->token);
putchar('\n');
}
A. Bogatyrev, 1992-95 - 338 - Si v UNIX
/* Sistema peremennyh ----------------------------------------- */
#define NEXT(v) *v = (*v)->next
#define TOKEN(v) (*v)->token
#define TYPE(v) (*v)->type
#define eq(str1, str2) (!strcmp(str1, str2))
jmp_buf breakpoint;
#define ERR(msg,val) { printf("%s\n", msg);longjmp(breakpoint, val+1);}
typedef struct {
char *name; /* Imya peremennoj */
int value; /* Znachenie peremennoj */
int isset; /* Poluchila li znachenie ? */
} Var;
#define MAXV 40
Var vars[MAXV];
/* Poluchit' znachenie peremennoj */
int getVar(char *name){ Var *ptr;
for(ptr=vars; ptr->name; ptr++)
if(eq(name, ptr->name)){
if(ptr->isset) return ptr->value;
printf("%s: ", name); ERR("variable is unbound yet", 0);
}
printf("%s: ", name); ERR("undefined variable", 0);
}
/* Sozdat' novuyu peremennuyu */
Var *internVar(char *name){ Var *ptr;
for(ptr=vars; ptr->name; ptr++)
if(eq(name, ptr->name)) return ptr;
ptr->name = strdup(name);
ptr->isset = 0; ptr->value = 0; return ptr;
}
/* Ustanovit' znachenie peremennoj */
void setVar(Var *ptr, int val){ ptr->isset = 1; ptr->value = val; }
/* Raspechatat' znacheniya peremennyh */
void printVars(){ Var *ptr;
for(ptr=vars; ptr->name; ++ptr)
printf("\t%s %s %d\n", ptr->isset ? "BOUND ":"UNBOUND",
ptr->name, ptr->value);
}
A. Bogatyrev, 1992-95 - 339 - Si v UNIX
/* Sintaksicheskij razbor i odnovremennoe vychislenie ----------- */
/* Vychislenie vstroennyh funkcij */
int apply(char *name, int args[], int nargs){
if(eq(name, "power2")){
if(nargs != 1) ERR("power2: wrong argument count", 0);
return (1 << args[0]);
} else if(eq(name, "min")){
if(nargs != 2) ERR("min: wrong argument count", 0);
return (args[0] < args[1] ? args[0] : args[1]);
} else if(eq(name, "max")){
if(nargs != 2) ERR("max: wrong argument count", 0);
return (args[0] < args[1] ? args[1] : args[0]);
} else if(eq(name, "sum")){ register i, sum;
for(i=0, sum=0; i < nargs; sum += args[i++]);
return sum;
} else if(eq(name, "rand")){
switch(nargs){
case 0: return rand();
case 1: return rand() % args[0];
case 2: return args[0] + rand() % (args[1] - args[0] + 1);
default: ERR("rand: wrong argument count", 0);
}
}
ERR("Unknown function", args[0]);
}
/* Vychislit' vyrazhenie iz spiska leksem. */
/* Sintaksis zadan pravorekursivnoj grammatikoj */
int expr(Token *t){ int val = 0;
if(val = setjmp(breakpoint)) return val - 1;
val = expression(&t);
if(t){ printlex(NULL, t); ERR("Extra tokens", val); }
return val;
}
/* <EXPRESSION> = <EXPASS> |
<EXPASS> ";" <EXPRESSION> */
int expression(Token **v){ int arg = expass(v);
if(*v && TYPE(v) == SMC ){
NEXT(v); return expression(v);
} else return arg;
}
/* <EXPASS> = <PEREMENNAYA> "=" <EXPASS> |
<EXP0> */
int expass(Token **v){ int arg;
if(*v && (*v)->next && (*v)->next->type == OP &&
eq((*v)->next->token, "=")){ Var *ptr;
/* prisvaivanie (assignment) */
if( TYPE(v) != ID ) /* sleva nuzhna peremennaya */
ERR("Lvalue needed", 0);
ptr = internVar(TOKEN(v));
NEXT(v); NEXT(v); setVar(ptr, arg = expass(v)); return arg;
}
return exp0(v);
}
A. Bogatyrev, 1992-95 - 340 - Si v UNIX
/* <EXP0> = <EXP1> | <EXP1> "||" <EXP0> */
int exp0(Token **v){ int arg = exp1(v);
if(*v && TYPE(v) == OP && eq(TOKEN(v), "||")){
NEXT(v); return(exp0(v) || arg );
/* pomeshchaem arg VTORYM, chtoby vtoroj operand vychislyalsya
* VSEGDA (inache ne budet ischerpan spisok tokenov i
* vozniknet oshibka v expr(); |to ne sovsem po pravilam Si.
*/
} else return arg;
}
/* <EXP1> = <EXP2> | <EXP2> "&&" <EXP1> */
int exp1(Token **v){ int arg = exp2(v);
if(*v && TYPE(v) == OP && eq(TOKEN(v), "&&")){
NEXT(v); return(exp1(v) && arg);
} else return arg;
}
/* <EXP2> = <EXP2A> | <EXP2A> "|" <EXP2> */
int exp2(Token **v){ int arg = exp2a(v);
if(*v && TYPE(v) == OP && eq(TOKEN(v), "|")){
NEXT(v); return( arg | exp2(v));
} else return arg;
}
/* <EXP2A> = <EXP2B> | <EXP2B> "^" <EXP2A> */
int exp2a(Token **v){ int arg = exp2b(v);
if(*v && TYPE(v) == OP && eq(TOKEN(v), "^")){
NEXT(v); return( arg ^ exp2a(v));
} else return arg;
}
/* <EXP2B> = <EXP2C> | <EXP2C> "&" <EXP2B> */
int exp2b(Token **v){ int arg = exp2c(v);
if(*v && TYPE(v) == OP && eq(TOKEN(v), "&")){
NEXT(v); return( arg & exp2b(v));
} else return arg;
}
/* <EXP2C> = <EXP3> | <EXP3> "==" <EXP3>
| <EXP3> "!=" <EXP3> */
int exp2c(Token **v){ int arg = exp3(v);
if(*v && TYPE(v) == OP && eq(TOKEN(v), "==")){
NEXT(v); return( arg == exp3(v));
} else if(*v && TYPE(v) == OP && eq(TOKEN(v), "!=")){
NEXT(v); return( arg != exp3(v));
} else return arg;
}
A. Bogatyrev, 1992-95 - 341 - Si v UNIX
/* <EXP3> = <EXP3A> | <EXP3A> ">" <EXP3>
| <EXP3A> "<" <EXP3>
| <EXP3A> ">=" <EXP3>
| <EXP3A> "<=" <EXP3> */
int exp3(Token **v){ int arg = exp3a(v);
if(*v && TYPE(v) == OP && eq(TOKEN(v), ">")){
NEXT(v); return( arg && exp3(v));
}else if(*v && TYPE(v) == OP && eq(TOKEN(v), "<")){
NEXT(v); return( arg && exp3(v));
}else if(*v && TYPE(v) == OP && eq(TOKEN(v), ">=")){
NEXT(v); return( arg && exp3(v));
}else if(*v && TYPE(v) == OP && eq(TOKEN(v), "<=")){
NEXT(v); return( arg && exp3(v));
} else return arg;
}
/* <EXP3A> = <EXP4> | <EXP4> "<<" <EXP3A>
| <EXP4> ">>" <EXP3A> */
int exp3a(Token **v){ int arg = exp4(v);
if(*v && TYPE(v) == OP && eq(TOKEN(v), "<<")){
NEXT(v); return( arg << exp3a(v));
}else if(*v && TYPE(v) == OP && eq(TOKEN(v), ">>")){
NEXT(v); return( arg && exp3a(v));
} else return arg;
}
/* <EXP4> = <EXP5> | <EXP5> "+" <EXP4>
| <EXP5> "-" <EXP4> */
int exp4(Token **v){ int arg = exp5(v);
if(*v && TYPE(v) == OP && eq(TOKEN(v), "+")){
NEXT(v); return( arg + exp4(v));
}else if(*v && TYPE(v) == OP && eq(TOKEN(v), "-")){
NEXT(v); return( arg - exp4(v));
} else return arg;
}
/* <EXP5> = <EXP6> | <EXP6> "*" <EXP5>
| <EXP6> "/" <EXP5>
| <EXP6> "%" <EXP5> */
int exp5(Token **v){ int arg = exp6(v), arg1;
if(*v && TYPE(v) == OP && eq(TOKEN(v), "*")){
NEXT(v); return( arg * exp5(v));
}else if(*v && TYPE(v) == OP && eq(TOKEN(v), "/")){
NEXT(v); if((arg1 = exp5(v)) == 0) ERR("Zero divide", arg);
return( arg / arg1);
}else if(*v && TYPE(v) == OP && eq(TOKEN(v), "%")){
NEXT(v); if((arg1 = exp5(v)) == 0) ERR("Zero module", arg);
return( arg % arg1);
} else return arg;
}
A. Bogatyrev, 1992-95 - 342 - Si v UNIX
/* <EXP6> = "!"<EXP6> | "~"<EXP6> | "-"<EXP6>
| "(" <EXPRESSION> ")"
| <IMYAFUNKCII> "(" [ <EXPRESSION> [ "," <EXPRESSION> ]... ] ")"
| <CHISLO>
| <CH_PEREMENNAYA> */
int exp6(Token **v){ int arg;
if( !*v) ERR("Lost token", 0);
if(TYPE(v) == OP && eq(TOKEN(v), "!")){
NEXT(v); return !exp6(v);
}
if(TYPE(v) == OP && eq(TOKEN(v), "~")){
NEXT(v); return ~exp6(v);
}
if(TYPE(v) == OP && eq(TOKEN(v), "-")){
NEXT(v); return -exp6(v); /* unarnyj minus */
}
if(TYPE(v) == OPEN){
NEXT(v); arg = expression(v);
if( !*v || TYPE(v) != CLOSE) ERR("Lost ')'", arg);
NEXT(v); return arg;
}
if(TYPE(v) == NUM){ /* izobrazhenie chisla */
arg = atoi(TOKEN(v)); NEXT(v); return arg;
}
if(TYPE(v) == ID){
char *name = (*v)->token; int args[20], nargs = 0;
NEXT(v);
if(! (*v && TYPE(v) == OPEN)){ /* Peremennaya */
return expvar(v, name);
}
/* Funkciya */
args[0] = 0;
do{ NEXT(v);
if( *v && TYPE(v) == CLOSE ) break; /* f() */
args[nargs++] = expression(v);
} while( *v && TYPE(v) == COMMA);
if(! (*v && TYPE(v) == CLOSE)) ERR("Error in '()'", args[0]);
NEXT(v);
return apply(name, args, nargs);
}
printlex(TOKEN(v), *v); ERR("Unknown token type", 0);
}
/* <CH_PEREMENNAYA> = <PEREMENNAYA> |
<PEREMENNAYA> "++" |
<PEREMENNAYA> "--"
Nashi operacii ++ i -- sootvetstvuyut ++x i --x iz Si */
int expvar(Token **v, char *name){
int arg = getVar(name); Var *ptr = internVar(name);
if(*v && TYPE(v) == OP){
if(eq(TOKEN(v), "++")){ NEXT(v); setVar(ptr, ++arg); return arg; }
if(eq(TOKEN(v), "--")){ NEXT(v); setVar(ptr, --arg); return arg; }
}
return arg;
}
A. Bogatyrev, 1992-95 - 343 - Si v UNIX
/* Golovnaya funkciya ------------------------------------------- */
char input[256];
Token *head, *tail;
void main(){
do{ printf(": "); fflush(stdout);
if( !gets(input)) break;
if(!*input){ printVars(); continue; }
if(eq(input, "!!")) ; /* nichego ne delat', t.e. povtorit' */
else{ if(head) freelex(&head); lex(&head, &tail, input); }
printf("Result: %d\n", expr(head));
} while(1); putchar('\n');
}
7.69. Napishite programmu, vydelyayushchuyu n-oe pole iz kazhdoj stroki fajla. Polya razdelya-
yutsya dvoetochiyami. Predusmotrite zadanie simvola-razdelitelya iz argumentov programmy.
Ispol'zujte etu programmu dlya vydeleniya polya "domashnij katalog" iz fajla /etc/passwd.
Dlya vydeleniya ocherednogo polya mozhno ispol'zovat' sleduyushchuyu proceduru:
main(){
char c, *next, *strchr(); int nfield;
char *s = "11111:222222222:333333:444444";
for(nfield=0;;nfield++){
if(next = strchr(s, ':')){
c= *next; *next= '\0';
}
printf( "Pole #%d: '%s'\n", nfield, s);
/* mozhno sdelat' s polem s chto-to eshche */
if(next){ *next= c; s= next+1; continue; }
else { break; /* poslednee pole */ }
}
}
7.70. Razrabotajte arhitekturu i sistemu komand uchebnoj mashiny i napishite interpre-
tator uchebnogo assemblera, otrabatyvayushchego po krajnej mere takie komandy:
mov peresylka (:=) add slozhenie
sub vychitanie cmp sravnenie i vyrabotka priznaka
jmp perehod jeq perehod, esli ==
jlt perehod, esli < jle perehod, esli <=
neg izmenenie znaka not invertirovanie priznaka
7.71. Napishite programmu, preobrazuyushchuyu opredeleniya funkcij Si v "starom" stile v
"novyj" stil' standarta ANSI ("prototipy" funkcij).
f(x, y, s, v)
int x;
char *s;
struct elem *v;
{ ... }
preobrazuetsya v
int f(int x, int y, char *s, struct elem *v)
{ ... }
(obratite vnimanie, chto peremennaya y i sama funkciya f opisany po umolchaniyu kak int).
A. Bogatyrev, 1992-95 - 344 - Si v UNIX
Eshche primer:
char *ff() { ... }
zamenyaetsya na
char *ff(void){ ... }
V dannoj zadache vam vozmozhno pridetsya ispol'zovat' programmu lex.
V spiske argumentov prototipa dolzhny byt' yavno ukazany tipy vseh argumentov -
opisatel' int nel'zya opuskat'. Tak
q(x, s) char *s; { ... } // ne prototip, dopustimo.
// x - int po umolchaniyu.
q(x, char *s); // nedopustimo.
q(int x, char *s); // verno.
Sobstvenno pod "prototipom" ponimayut predvaritel'noe opisanie funkcii v novom stile -
gde vmesto tela {...} srazu posle zagolovka stoit tochka s zapyatoj.
long f(long x, long y); /* prototip */
...
long f(long x, long y){ return x+y; } /* realizaciya */
V prototipe imena argumentov mozhno opuskat':
long f(long, long); /* prototip */
char *strchr(char *, char);
|to predvaritel'noe opisanie pomeshchayut gde-nibud' v nachale programmy, do pervogo
vyzova funkcii. V sovremennom Si prototipy zamenyayut opisaniya vida
extern long f();
o kotoryh my govorili ran'she. Prototipy predostavlyayut programmistu mehanizm dlya
avtomaticheskogo kontrolya formata vyzova funkcii. Tak, esli funkciya imeet prototip
double f( double );
i vyzyvaetsya kak
double x = f( 12 );
to kompilyator avtomaticheski prevratit eto v
double x = f( (double) 12 );
(poskol'ku sushchestvuet privedenie tipa ot int k double); esli zhe napisano
f( "privet" );
to kompilyator soobshchit ob oshibke (tak kak net preobrazovaniya tipa (char *) v double).
Prototip prinuzhdaet kompilyator proveryat':
a) sootvetstvie TIPOV fakticheskih parametrov (pri vyzove) tipam formal'nyh paramet-
rov (v prototipe);
b) sootvetstvie KOLICHESTVA fakticheskih i formal'nyh parametrov;
c) tip vozvrashchaemogo funkciej znacheniya.
Prototipy obychno pomeshchayut v include-fajly. Tak v ANSI standarte Si predusmotren fajl,
podklyuchaemyj
#include <stdlib.h>
A. Bogatyrev, 1992-95 - 345 - Si v UNIX
v kotorom opredeleny prototipy funkcij iz standartnoj biblioteki yazyka Si. CHerezvy-
chajno polezno pisat' etu direktivu include, chtoby kompilyator proveryal, verno li vy
vyzyvaete standartnye funkcii.
Zametim, chto esli vy opredelili prototipy kakih-to funkcij, no v svoej programme
ispol'zuete ne vse iz etih funkcij, to funkcii, sootvetstvuyushchie "lishnim" prototipam,
NE budut dobavlyat'sya k vashej programme iz biblioteki. T.e. prototipy - eto ukazanie
kompilyatoru; ni v kakie mashinnye komandy oni ne transliruyutsya. To zhe samoe kasaetsya
opisanij vneshnih peremennyh i funkcij v vide
extern int x;
extern char *func();
Esli vy ne ispol'zuete peremennuyu ili funkciyu s takim imenem, to eti stroki ne imeyut
nikakogo effekta (kak by voobshche otsutstvuyut).
7.72. Obratnaya zadacha: napishite preobrazovatel' iz novogo stilya v staryj.
int f( int x, char *y ){ ... }
perevodit' v
int f( x, y ) int x; char *y; { ... }
7.73. Dovol'no legko ispol'zovat' prototipy takim obrazom, chto oni poteryayut vsyakij
smysl. Dlya etogo nado napisat' programmu, sostoyashchuyu iz neskol'kih fajlov, i v kazhdom
fajle ispol'zovat' svoi prototipy dlya odnoj i toj zhe funkcii. Tak byvaet, kogda vy
pomenyali funkciyu i prototip v odnom fajle, byt' mozhet vo vtorom, no zabyli sdelat'
eto v ostal'nyh.
--------
fajl a.c
--------
void g(void);
void h(void);
int x = 0, y = 13;
void f(int arg){
printf("f(%d)\n", arg);
x = arg;
x++;
}
int main(int ac, char *av[]){
h();
f(1);
g();
printf("x=%d y=%d\n", x, y);
return 0;
}
A. Bogatyrev, 1992-95 - 346 - Si v UNIX
--------
fajl b.c
--------
extern int x, y;
int f(int);
void g(){
y = f(5);
}
--------
fajl c.c
--------
void f();
void h(){
f();
}
Vydacha programmy:
abs@wizard$ cc a.c b.c c.c -o aaa
a.c:
b.c:
c.c:
abs@wizard$ aaa
f(-277792360)
f(1)
f(5)
x=6 y=5
abs@wizard$
Obratite vnimanie, chto vo vseh treh fajlah f() imeet raznye prototipy! Poetomu prog-
ramma pechataet nechto, chto dovol'no-taki bessmyslenno!
Reshenie takovo: starat'sya vynesti prototipy v include-fajl, chtoby vse fajly
programmy vklyuchali odni i te zhe prototipy. Starat'sya, chtoby etot include-fajl vklyu-
chalsya takzhe v fajl s samim opredeleniem funkcii. V takom sluchae izmenenie tol'ko
zagolovka funkcii ili tol'ko prototipa vyzovet rugan' kompilyatora o nesootvetstvii.
Vot kak dolzhen vyglyadet' nash proekt:
-------------
fajl header.h
-------------
extern int x, y;
void f(int arg);
int main(int ac, char *av[]);
void g(void);
void h(void);
A. Bogatyrev, 1992-95 - 347 - Si v UNIX
--------
fajl a.c
--------
#include "header.h"
int x = 0, y = 13;
void f(int arg){
printf(