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(