/* I18N Guidelines and Techniques */ /* FILE NOT I18N */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PLUS_WARNING_TEXT "Cannot use '+' for string concatenation." #define UNWRAPPED_WARNING_TEXT "Cannot have an unwrapped string/character constant." #define FIXUP_TEXT "----!!FIXUP: I18N BY HAND!!----" #define FIXUP_TEXT_BEGIN ">>>>!!FIXUP: I18N BY HAND!!>>>>(" #define FIXUP_TEXT_END ")<<< 0) { fprintf(fp_err, "\"%s\": line %d: error: ", file, lineno); } else { fprintf(fp_err, "\"%s\": error: ", file); } fprintf(fp_err, fmt, a1, a2, a3); fprintf(fp_err, "\n"); exit (1); } void formatted_warning(int lineno, char *fmt, void *a1, void *a2, void *a3, void *a4) { if (lineno > 0) { fprintf(fp_err, "\"%s\": line %d: warning: ", file, lineno); } else { fprintf(fp_err, "\"%s\": warning: ", file); } fprintf(fp_err, fmt, a1, a2, a3, a4); fprintf(fp_err, "\n"); } static char * token_type_string(token_type type) { switch (type) { case UNKNOWN: return "UNKNOWN"; case SEPARATOR: return "SEPARATOR"; case OPENPAREN: return "OPENPAREN"; case CLOSEPAREN: return "CLOSEPAREN"; case OPENCURLY: return "OPENCURLY"; case CLOSECURLY: return "CLOSECURLY"; case DQUOTE: return "DQUOTE"; case SQUOTE: return "SQUOTE"; case DOT: return "DOT"; case ID: return "ID"; case PLUS: return "PLUS"; default: formatted_error(0, "Found type %d in token_type_string", (void *)type, 0, 0); } /*NOTREACHED*/ return NULL; } static void token_set_cur_type(token_type type) { token_stack[token_pointer - 1].type = type; } static void token_set_cur_position(int position) { token_stack[token_pointer - 1].position = position; } static void token_set_cur_name(Name *name) { token_stack[token_pointer - 1].u.name = name; } static void token_set_cur_is_comma() { token_stack[token_pointer - 1].u.is_comma = 1; } static void token_set_cur_len(int len) { token_stack[token_pointer - 1].len = len; } static void token_set_cur_lineno(int lineno) { token_stack[token_pointer - 1].lineno = lineno; } static void token_push(token_type type, int position) { token_stack[token_pointer].type = type; token_stack[token_pointer].position = position; token_stack[token_pointer].u.name = NULL; token_stack[token_pointer].len = 1; token_stack[token_pointer].lineno = 0; token_pointer++; } static token * token_peek(int i) { return &token_stack[i]; } static int token_size() { return token_pointer; } static void token_empty() { token_pointer = 0; } static void flush(void) { fflush(fp_out); fflush(fp_err); if (fp_err != stderr) { fflush(stderr); } } static void message(char *message, char *arg) { flush(); fprintf(fp_err,"%s%s\n",message,arg); if (fp_err != stderr && fp_err != stdout) { fprintf(stderr,"%s%s\n",message,arg); } flush(); } static void warning(char *message, char *arg) { if( file==NULL) file="unknown file"; exit_status = 1; if (!quiet) { flush(); fprintf(fp_err,"\"%s\": line %d: warning: %s %s\n",file,lineno,message,arg); fprintf(fp_err,"%6d\t%s\n",lineno,lastline); flush(); } } static void fatal(char *message, char *arg) { if( file==NULL) file="unknown file"; exit_status = 1; flush(); fprintf(fp_err,"\"%s\": line %d: fatal: %s %s\n",file,lineno,message,arg); fprintf(fp_err,"%6d\t%s\n",lineno,lastline); flush(); fprintf(fp_err,"\"%s\": line %d: fatal: %s %s\n",file,lineno, "rest of this file and any following files NOT processed",""); flush(); exit(exit_status); } static int my_getc(FILE *fp) { int ch; ch = getc(fp); if ( lastline_pos < MAX_LINE ) { lastline[lastline_pos++] = ch; strcpy(lastline+lastline_pos,"..."); } if ( ch=='\n' ) { lineno++; lastline_pos = 0; lastline[lastline_pos] = 0; } return ch; } static int my_ungetc(int ch, FILE *fp) { if ( ch=='\n' ) { lineno--; } if ( lastline_pos > 0 ) lastline_pos--; ungetc(ch,fp); } static int ignore_string(char *token) { int len = strlen(token); int i; if ( len <= 2 ) return 1; if ( len > 5 && strncmp(token,"\"@(#)",5)==0 ) return 1; if ( len == 3 && strcmp(token,"\" \"")==0 ) return 1; for(i=1; i< len ; i++ ) { if ( token[i]=='%' ) { i++; continue;} if ( token[i]=='\\' ) { i++; if ( token[i]=='x' ) i+=2; continue; } if ( isalnum(token[i]) ) return 0; } return 1; } static char * get_token(FILE *fp, char *token) { int quote; int ch; int ch2; int count = 0; int bogus_character_in_string = 0; #define STUFF(ch) token[count++]=(ch);\ if(count>MAX_TOKEN)fatal("token too long",token); token_push(UNKNOWN, 0); startover: token_set_cur_position(ftell(fp)); /* needed in case of 'goto startover' */ count = 0; token[0] = 0; ch = my_getc(fp); if ( ch==EOF ) return NULL; switch ( ch ) { case ' ': case '\t': case '\f': case '\n': goto startover; /* Pass up comments and jump back to startover */ case '/': ch = my_getc(fp); if ( ch=='/' ) { while(ch!='\n' && ch!=EOF){ ch=my_getc(fp); if ( ch==EOF ) fatal("premature EOF","in comment");; } goto startover; } else if ( ch=='*' ) { char comment_text[NOI18N_COMMENT_SIZE + 1]; char *comment_pointer = comment_text; ch = my_getc(fp); if ( ch==EOF ) fatal("premature EOF","in comment");; ch2 = my_getc(fp); if ( ch2==EOF ) fatal("premature EOF","in comment");; while ( ch!='*' || ch2!='/' ) { if (comment_pointer < &comment_text[NOI18N_COMMENT_SIZE + 1]) { *comment_pointer++ = ch; } ch = ch2; ch2 = my_getc(fp); if ( ch2==EOF ) fatal("premature EOF","in comment");; } *comment_pointer = '\0'; if (strcmp(comment_text, NOI18N_COMMENT) == 0) { token_set_cur_type(NOI18NCOMMENT); /* add 2 each for comment opener and closer */ token_set_cur_len(NOI18N_COMMENT_SIZE + 4); count=0; } else { goto startover; } } else { my_ungetc(ch,fp); STUFF('/'); } break; /* Process strings specially */ case '"': case '\'': quote = ch; if (ch == '"') token_set_cur_type(DQUOTE); else token_set_cur_type(SQUOTE); token_set_cur_lineno(lineno); STUFF(quote); ch2 = ch; ch = my_getc(fp); if ( ch==EOF ) fatal("premature EOF","in string/character constant");; while ( ch!=quote && ch!='\n') { STUFF(ch); ch2 = ch; ch = my_getc(fp); if ( ch==EOF ) fatal("premature EOF","in string/character constant");; if ( !isprint(ch) && ch!='\n' && ch!='\t' ) { warning("non-printing char","in string/character constant"); bogus_character_in_string = 1; } if ( ch2=='\\' ) { STUFF(ch); ch2 = 0; ch = my_getc(fp); if ( ch==EOF ) fatal("premature EOF","in string/character constant");; } } bogus_character_errors += bogus_character_in_string; if (ch == '"') token_push(DQUOTE, ftell(fp)-1); else token_push(SQUOTE, ftell(fp)-1); STUFF(quote); break; case '+': STUFF(ch); ch = my_getc(fp); if ( ch==EOF ) fatal("premature EOF","in string/character constant"); if (ch == '=') { STUFF(ch); token_set_cur_type(SEPARATOR); token_set_cur_len(2); } else { my_ungetc(ch,fp); token_set_cur_type(PLUS); } break; case '(': STUFF(ch); token_set_cur_type(OPENPAREN); break; case ')': STUFF(ch); token_set_cur_type(CLOSEPAREN); break; case '{': STUFF(ch); token_set_cur_type(OPENCURLY); break; case '}': STUFF(ch); token_set_cur_type(CLOSECURLY); break; case ';': case ':': case '=': case '?': STUFF(ch); token_set_cur_type(SEPARATOR); break; case ',': STUFF(ch); token_set_cur_type(SEPARATOR); token_set_cur_is_comma(); break; case '.': STUFF(ch); token_set_cur_type(DOT); break; /* Process identifiers specially */ /* every other character is just a token */ default: STUFF(ch); if ( isalpha(ch) || ch=='_' ) { ch = my_getc(fp); while ( isalnum(ch) || ch=='_' ) { STUFF(ch); ch = my_getc(fp); if ( ch==EOF ) break; } if ( ch==EOF ) break; my_ungetc(ch,fp); token[count] = 0; /* need this before setting name */ if (strcmp(token, "return") == 0) { token_set_cur_type(SEPARATOR); } else { token_set_cur_type(ID); token_set_cur_name(find_name(token)); token_set_cur_len(count); } } break; } token_set_cur_lineno(lineno); token[count] = 0; return token; } static void do_call(FILE *fp, char *token, void (*emit)(char*)) { char str[MAX_TOKEN+1]; int pcount = 1; if ( emit!=NULL ) emit(token); if ( emit!=NULL ) emit("("); if ( get_token(fp,str)==NULL ) fatal("malformed call, missing arg or right paren",token); while(pcount>0){ if ( str[0]==')' ) pcount--; if ( str[0]=='(' ) pcount++; if ( emit!=NULL ) emit(str); if ( pcount>0 ) { if ( get_token(fp,str)==NULL) return; } } } static void emit(char *str) { fputs(str,fp_out); } static void process_file(FILE *fp) { int ch; char token[MAX_TOKEN+1]; char lookahead[MAX_TOKEN+1]; int parsing_package_statement = 0; while ( get_token(fp,token) ) { tryagain: switch ( token[0] ) { case '"': /* Filter out strings that don't need to be I18n'd */ if ( !ignore_string(token) ) { warning("unmarked i18n string/character constant",token); } break; default: if (parsing_package_statement) { if (strcmp(token, ".") == 0 || token[0] == '_' || isalpha(token[0])) { strcat(class_name, token); break; } else if (strcmp(token, ";") == 0) { parsing_package_statement = 0; } } else if ( strcmp("package", token) == 0 ) { parsing_package_statement = 1; } break; } } } static void checking_error(int oldfile, int pos, int len, int lineno, char *warn_text) { static char buf[MAX_SOURCE_LINE_LENGTH * 2 + 100]; static char buf2[MAX_SOURCE_LINE_LENGTH + 1]; int size_needed, size; char *ptr; if (pos < MAX_SOURCE_LINE_LENGTH) { lseek(oldfile, 0, SEEK_SET); size_needed = pos; } else { lseek(oldfile, pos - MAX_SOURCE_LINE_LENGTH, SEEK_SET); size_needed = MAX_SOURCE_LINE_LENGTH; } size = read(oldfile, buf, size_needed); if (size != size_needed) { formatted_error(lineno, "tried to read %d bytes but only got %d bytes", (void *)(size_needed), (void *)size, 0); } buf[size] = '\0'; /* now find BOL */ for (ptr = buf + size_needed - 1; ptr >= buf; ptr--) { if (*ptr == '\n') { break; } } ptr++; /* skip leading newline */ size = read(oldfile, buf2, len); if (size != len) { formatted_error(lineno, "tried to read %d bytes but only got %d bytes", (void *)(len), (void *)size, 0); } buf2[size] = '\0'; strcat(ptr, "-->"); strcat(ptr, buf2); strcat(ptr, "<--"); if (ptr < buf) { /* check for error condition */ formatted_warning(lineno, "line exceeds %d characters, losing errors for that line", (void *)lineno, (void *)MAX_SOURCE_LINE_LENGTH, 0, 0); return; } formatted_warning(lineno, "%s\n%s", (void *)warn_text, (void *)ptr, 0, 0); } static int i18n_string_counter = 0; static int i18n_string_skip = 0; static i18n_choice i18n_string(int disable_yes, int disable_no, char *prompt, int pos1, int pos2) { char ch='x'; char buf[MAXPATHLEN * 2]; static char *cwd = NULL; char choices[20]; int firsttime = 1; if (i18n_string_skip) { return SKIP; } if (!cwd) { cwd = getcwd(NULL, MAXPATHLEN + 1); } i18n_string_counter++; choices[0] = '\0'; if (!disable_yes) { strcat(choices, "y"); } if (!disable_no) { strcat(choices, "N"); } strcat(choices, "nseQ"); /* emacs character pos's are 1 before our's - add 1 to fix */ sprintf(buf, "%s '(i18n-hilight \"%s\" \"%s\" %d %d)' > /dev/null", "gnudoit", cwd, file, pos1 + 1, pos2 + 1); system(buf); while (!strchr(choices, ch)) { if (!firsttime) { system(buf); printf("\n"); if (!disable_yes) { printf("y\tTreat as localizable text and go to the next text.\n"); } if (!disable_no) { printf("N\tTreat as non-localizable text and go to the next text.\n"); } printf("n\tMark text as non-localizable with a comment and go to the next text.\n"); if (!disable_no && !disable_yes) { printf("\tNOTE: 'n' should only be used for program internals. Anything that\n\tmight remotely be seen by the user should get a 'y'.\n"); } printf("s\tDon't mark text and go to the next text.\n"); printf("e\tMark text as an error (will insert a syntax error into code) and\n\tgo to the next text. \n"); printf("Q\tSkip this and all remaining text in all files.\n"); } firsttime=0; printf("%s (%s?) ", prompt, choices); ch = getchar(); if (ch == EOF) { formatted_warning(0, "Unexpected EOF when talking to user. Skipping rest of files", 0, 0, 0, 0); ch = 'Q'; /* will cause us to skip rest of files */ } } switch (ch) { case 'y': printf("yes\n", 0); return I18N; case 'N': printf("No\n", 0); return NOI18N; case 'n': printf("no\n", 0); return COMMENT; case 's': printf("skip\n", 0); return SKIP; case 'e': printf("error\n", 0); return FIXUP; case 'Q': printf("Quit\n", 0); i18n_string_skip = 1; return SKIP; } /*NOTREACHED*/ } static char * translate(char *buf, translation_entry *translation_table, char *string, const int maxsize) { char *new = buf; char *old = string; int i, j; int no_maxsize = (maxsize == 0); static int counter = 0; for (i = 0; no_maxsize || i < maxsize ; i++) { char newtext[10]; if (*old == '\0') { break; } newtext[0] = *old; newtext[1] = '\0'; for (j = 0; translation_table[j].c != '\0'; j++) { if (newtext[0] == translation_table[j].c) { strcpy(newtext, translation_table[j].translation); break; } } strcpy(new, newtext); /* do the concatenation */ new += strlen(newtext); old++; } if (!no_maxsize && /* if we have a maxsize - check for overflow */ i >= maxsize) { /* overflow happened - need to use counter */ *new++ = '.'; *new = '\0'; strcat(new, lltostr(counter++, "")); } return buf; } static char * make_key(char *buf, char *prefix, char *string) { sprintf(buf, "%s.%s", prefix, start_time); translate(buf + strlen(buf), key_translation_table, string, MAX_KEY_STRING_SIZE); return buf; } static char * make_plus_prefix(FILE *propsfp, char *buf, char *function_name, int count) { int i; static int counter = 0; static char key[MAXPATHLEN + 100]; char *value; sprintf(key, "%s.FMT.%s%d", class_name, start_time, counter++); sprintf(buf, "%s(\"%s\", ", function_name, key); /* now update properties file */ switch (count - 1) { case 9: value = "{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}"; break; case 8: value = "{0}{1}{2}{3}{4}{5}{6}{7}{8}"; break; case 7: value = "{0}{1}{2}{3}{4}{5}{6}{7}"; break; case 6: value = "{0}{1}{2}{3}{4}{5}{6}"; break; case 5: value = "{0}{1}{2}{3}{4}{5}"; break; case 4: value = "{0}{1}{2}{3}{4}"; break; case 3: value = "{0}{1}{2}{3}"; break; case 2: value = "{0}{1}{2}"; break; case 1: value = "{0}{1}"; break; case 0: value = "{0}"; break; } fprintf(propsfp, "%s=%s\n", key, value); return buf; } static int curpos = 0; static char buf[1000000]; /* * this maintains a list of file inserts to do at a later time. * the list must be ordered or problems will occur */ typedef struct { int oldfile; int newfile1; int pos; int lineno; int skip; int priority; /* if true insert before other insert at same pos */ char *text1; } file_insert_args; #define UNUSED -1 static file_insert_args pending_inserts[10000]; static int num_pending_inserts = 0; static int cur_pending_insert = 0; /* to sort by position */ static int compare_by_pos(const void *pending1_arg, const void *pending2_arg) { file_insert_args *pending1 = (file_insert_args *)pending1_arg; file_insert_args *pending2 = (file_insert_args *)pending2_arg; if (pending1->pos == pending2->pos) { /* compare priorities if at same pos */ return pending1->priority - pending2->priority; } else { /* compare positions */ return pending1->pos - pending2->pos; } } static void file_reset() { int i; curpos = 0; num_pending_inserts = 0; cur_pending_insert = 0; } static void file_insert_at(int oldfile, int newfile1, int pos, int lineno, char *text1); static void file_insert_at_skip(int oldfile, int newfile1, int pos, int lineno, int skip, FILE *propsfp, char *text1) { int size; int size_needed; if (checking_mode_on) { return; } if (pos < curpos) { formatted_warning(lineno, "Confused about I18N'ing. Must fix by hand. Search for 'FIXUP I18N BY HAND'.\n\t(Debugging info: '%s')", (void *)text1, 0, 0, 0); file_insert_at(oldfile, newfile1, curpos, lineno, FIXUP_TEXT); return; } while ((cur_pending_insert < num_pending_inserts) && ((pending_inserts[cur_pending_insert].pos < pos) || ((pending_inserts[cur_pending_insert].pos == pos) && (pending_inserts[cur_pending_insert].priority == HIGH_PRIORITY)))) { /* * have to insert pending one before we do this one. * first must mark that this one is done so we don't have an * infinite loop */ int this_insert = cur_pending_insert++; file_insert_at_skip(pending_inserts[this_insert].oldfile, pending_inserts[this_insert].newfile1, pending_inserts[this_insert].pos, pending_inserts[this_insert].lineno, pending_inserts[this_insert].skip, NULL, pending_inserts[this_insert].text1); free(pending_inserts[this_insert].text1); } size_needed = pos - curpos; size = read(oldfile, buf, size_needed); if (size != size_needed) { formatted_error(lineno, "tried to read %d bytes but only got %d bytes", (void *)(size_needed), (void *)size, 0); } write(newfile1, buf, size); size = read(oldfile, buf, skip); if (size != skip) { formatted_error(lineno, "Tried to read %d bytes but only got %d bytes", (void *)(skip), (void *)size, 0); } if (propsfp) { /* going into props file */ char key[MAXPATHLEN + 100]; char *lead = ""; char *bufptr = buf; buf[size] = '\0'; make_key(key, class_name, buf); /* create key */ /* write key to props file handling leading whitespace*/ switch (buf[0]) { case ' ': /* space */ lead = "\\"; break; case ' ': /* tab */ lead = "\\t"; bufptr++; /* can't have \ must have \t */ } fprintf(propsfp, "%s=%s%s\n", key, lead, bufptr); write(newfile1, key, strlen(key)); /* insert key into src */ } write(newfile1, text1, strlen(text1)); curpos = pos + skip; } static void file_insert_at(int oldfile, int newfile1, int pos, int lineno, char *text1) { file_insert_at_skip(oldfile, newfile1, pos, lineno, 0, NULL, text1); } static void file_insert_at_skip_pending(int oldfile, int newfile1, int pos, int skip, int lineno, int priority, char *text1) { int i; if (checking_mode_on) { return; } if (pos >= curpos) { /* if false then error - don't buffer */ pending_inserts[num_pending_inserts].oldfile = oldfile; pending_inserts[num_pending_inserts].newfile1 = newfile1; pending_inserts[num_pending_inserts].pos = pos; pending_inserts[num_pending_inserts].skip = skip; pending_inserts[num_pending_inserts].lineno = lineno; pending_inserts[num_pending_inserts].priority = priority; pending_inserts[num_pending_inserts].text1 = strdup(text1); num_pending_inserts++; return; } /* reaching here means an error occurred - file_insert_at will print msg */ file_insert_at_skip(oldfile, newfile1, pos, skip, lineno, NULL, text1); } static void file_insert_at_pending(int oldfile, int newfile1, int pos, int lineno, int high_priority, char *text1) { file_insert_at_skip_pending(oldfile, newfile1, pos, 0, lineno, high_priority, text1); } static int check_ok_method(int file, int t, int skip_bogus_warnings) { int cur = t; token *t1 = NULL, *t1sep, *t2 = NULL, *t2sep; token *t3 = NULL, *t3sep, *t4 = NULL, *t4sep; ok_method_type rtn; char *errfmt; int len; t1 = token_peek(cur--); t1sep = token_peek(cur--); if (t1->type != ID || t1->u.name == NULL) { return 0; } if (t1sep->type != DOT) { /* only 1 arg name */ errfmt = "%s"; rtn = is_ok_method(t1->u.name, 0, 0, 0); len = t1->len; } else { t2 = token_peek(cur--); if (t2->type != ID || t2->u.name == NULL) { return 0; } t2sep = token_peek(cur--); if (t2sep->type != DOT) { /* just 2 arg names */ errfmt = "%2$s.%1$s"; rtn = is_ok_method(t1->u.name, t2->u.name, 0, 0); len = t2->position + t2->len - t1->position; } else { t3 = token_peek(cur--); if (t2->type != ID || t2->u.name == NULL) { return 0; } t3sep = token_peek(cur--); if (t3sep->type != DOT) { /* 3 arg names */ errfmt = "%3$s.%2$s.%s1$"; rtn = is_ok_method(t1->u.name, t2->u.name, t3->u.name, 0); len = t3->position + t3->len - t1->position; } else { t4 = token_peek(cur--); if (t4->type != ID || t4->u.name == NULL) { return 0; } t4sep = token_peek(cur--); if (t4sep->type != DOT) { /* 4 arg names */ errfmt = "%4$s.%3$s.%2$s.%s1$"; rtn = is_ok_method(t1->u.name, t2->u.name, t3->u.name, t4->u.name); len = t4->position + t4->len - t1->position; } } } } if (checking_mode_on && rtn == METHOD_BOGUS && !skip_bogus_warnings) { char errbuf[MAX_SOURCE_LINE_LENGTH]; char fmt2[100]; sprintf(fmt2, "can't use bogus function %s", errfmt); sprintf(errbuf, fmt2, t1->u.name->name, t2 && t2->type == ID ? t2->u.name->name : NULL, t3 && t3->type == ID ? t3->u.name->name : NULL, t3 && t4->type == ID ? t4->u.name->name : NULL); bogus_function_errors++; checking_error(file, t1->position, len, t1->lineno, errbuf); return 0; } return (rtn == METHOD_OK); } static void handle_plusses(int oldfd, int newfd1, int *i, int *j) { /* string concatenation is special */ int first, last, temp, count; int open_paren_count = 0; int init_paren_count = 0; token *cur; token *first_token, *last_token; token *plus_token = 0; int plus_count = 0; char buf[1024]; i18n_choice choice; char *txt1, *txt2; for (first = *i, cur = token_peek(first); !(cur->type == SEPARATOR && open_paren_count == 0); first--, cur = token_peek(first)) { if (cur->type == OPENPAREN) { if (open_paren_count == 0) { /* start of function arguments containing string */ break; } open_paren_count++; } else if (cur->type == CLOSEPAREN) { open_paren_count--; } } /* first now points to a SEPARATOR or a OPENPAREN */ first_token = token_peek(first+1); /* skip OPENPAREN or SEPARATOR */ if (cur->type == OPENPAREN) { init_paren_count = 1; open_paren_count = 1; } else { init_paren_count = 0; open_paren_count = 0; } for (last = first+1, cur = token_peek(last); !(cur->type == SEPARATOR && open_paren_count == 0); last++, cur = token_peek(last)) { if (cur->type == SEPARATOR && init_paren_count == 1 && open_paren_count == 1 && cur->u.is_comma) { /* * its a comma separating args in a function call * we subtract init_paren_count from last later which is wrong here * so we add 1 to it now then subtract it off later - yucchh! */ last++; break; } else if (cur->type == OPENPAREN) { open_paren_count++; } else if (cur->type == CLOSEPAREN) { if (open_paren_count == 0) { /* end of function arguments containing string */ break; } open_paren_count--; } else if (cur->type == PLUS) { if (open_paren_count == init_paren_count) { cur->u.top_level_plus = 1; plus_count++; if (!plus_token) { plus_token = cur; } } else { cur->u.top_level_plus = 0; } } } /* * last now points to a SEPARATOR or a CLOSEPAREN * skip SEPARATOR or CLOSEPAREN and any extra CLOSEPARENs */ last_token = token_peek(last - 1 - init_paren_count); /* setup return values */ *i = first; *j = last; if (checking_mode_on) { checking_error(oldfd, plus_token->position, plus_token->len, plus_token->lineno, PLUS_WARNING_TEXT); concat_errors++; return; } choice = i18n_string(0, no_non_translated_props, "Internationalize anything in this string expression? ", first_token->position, last_token->position + last_token->len); switch (choice) { case COMMENT: case NOI18N: /* don't process double quotes on next pass */ for (temp = first; temp <= last; temp++) { cur = token_peek(temp); if (cur->type == DQUOTE) { cur->type = SEPARATOR; } } if (choice == COMMENT) { txt1=noi18n_delimited_paren_comment; txt2=")"; break; /* BREAK here for COMMENT */ } txt1 = make_plus_prefix(non_translated_fp, buf, string_concat_notranslate_function, plus_count+1); txt2 = concat_end; /* FALLTHRU */ case I18N: /* THIS HANDLES BOTH NOI18N and I18N */ if (plus_count > 9) { formatted_warning(lineno, "Too many operands in string concatenation. Must fix by hand.", 0, 0, 0, 0); txt1 = FIXUP_TEXT_BEGIN; txt2 = FIXUP_TEXT_END; break; } /* change plusses to commas */ for (temp = first+1; temp < last; temp++) { cur = token_peek(temp); if (cur->type == PLUS && cur->u.top_level_plus) { file_insert_at_skip_pending(oldfd, newfd1, cur->position, cur->len, lineno, 0, concat_separator); } } if (choice == NOI18N) { break; /* BREAK FOR NOI18N */ } txt1 = make_plus_prefix(translated_fp, buf, string_concat_translate_function, plus_count+1); txt2 = concat_end; break; case FIXUP: txt1 = FIXUP_TEXT_BEGIN; txt2 = FIXUP_TEXT_END; break; case SKIP: return; default: formatted_error(0, "Unexpected choice returned (%d)", (void *)choice, 0, 0); } file_insert_at_pending(oldfd, newfd1, first_token->position, lineno, HIGH_PRIORITY, txt1); file_insert_at_pending(oldfd, newfd1, last_token->position + last_token->len, lineno, LOW_PRIORITY, txt2); } static int find_matching_paren(int i, token_type open_delim, token_type close_delim) { int j; int open_parens; token *t; for (j = i, t = token_peek(j), open_parens = 0; !(open_parens == 1 && t->type == close_delim); j++, t = token_peek(j)){ if (t->type == close_delim) { open_parens--; } else { if (t->type == open_delim) { open_parens++; } } } return j; } static int mangle_token(int oldfd, int newfd1, int i, int do_plusses) { token *t = token_peek(i); token *prev = token_peek(i-1); token *close = token_peek(i+1); token *succ = token_peek(i+2); if ((t->type == OPENPAREN) && (check_ok_method(oldfd, i-1, do_plusses) || prev->type == NOI18NCOMMENT)) { return find_matching_paren(i, OPENPAREN, CLOSEPAREN); } if (t->type == OPENCURLY && prev->type == NOI18NCOMMENT) { return find_matching_paren(i, OPENCURLY, CLOSECURLY); } if (t->type != DQUOTE && t->type != SQUOTE) { return i; } if (close->position == t->position + 1) { /* a null string */ return i + 1; /* skip closing quote too */ } if (close->type != t->type) { formatted_error(close->lineno, "Confused. Expected single/double quote, found %s", (void *)token_type_string(close->type), 0, 0); } /* skip over function and its args if this is a valid wrapper function */ if (prev->type == OPENPAREN && check_ok_method(oldfd, i-2, do_plusses)) { return find_matching_paren(i-1, OPENPAREN, CLOSEPAREN); } if (prev->type == NOI18NCOMMENT) { return i+1; /* closing quote */ } if (do_plusses) { if ((prev->type == PLUS || succ->type == PLUS) && t->type == DQUOTE) { /* handle plusses near strings, not chars */ int first = i; int last = i + 1; handle_plusses(oldfd, newfd1, &first, &last); i = last; } else { i++; /* gobble up closing quote */ } } else { /* not doing plusses */ /* found an unwrapped string */ if (checking_mode_on) { checking_error(oldfd, t->position, close->position + close->len - t->position, t->lineno, UNWRAPPED_WARNING_TEXT); string_errors += t->type == DQUOTE; character_errors += t->type == SQUOTE; } else { /* just wrap it */ int endpos = close->position + close->len; char *msg = t->type == DQUOTE ? "Internationalize this string literal? " : "Internationalize this character constant? "; i18n_choice choice = i18n_string(t->type != DQUOTE, no_non_translated_props, msg, t->position, endpos); char *text1, *text2; char key[MAX_KEY_SIZE+1]; FILE *propsfp = NULL; switch (choice) { case I18N: case NOI18N: if (choice == I18N) { text1 = string_translate_function; propsfp = translated_fp; } else { text1 = string_notranslate_function; propsfp = non_translated_fp; } text2 = translate_end; break; case COMMENT: text1 = noi18n_delimited_comment; text2 = NULL; break; case FIXUP: text1 = FIXUP_TEXT_BEGIN; text2 = FIXUP_TEXT_END; break; case SKIP: text1 = NULL; text2 = NULL; break; default: formatted_error(0, "Unexpected choice returned (%d)", (void *)choice, 0, 0); } if (text1) { file_insert_at(oldfd, newfd1, t->position, t->lineno, text1); } if (text2) { if (propsfp) { /* do insert with key (passing propsfp makes this happen) */ file_insert_at_skip(oldfd, newfd1, t->position + t->len, /* skip quote */ t->lineno, close->position - t->position - t->len, propsfp, ""); } file_insert_at(oldfd, newfd1, endpos, t->lineno, text2); } } i++; /* gobble up closing quote */ } return i; } static void mangle_file(char *oldfile, int filesize) { char *newfile1 = (char *)malloc(strlen(oldfile) + 10); int oldfd; int newfd1; int i; int size; file_reset(); sprintf(newfile1, i18n_file_format_string, oldfile); oldfd = open(oldfile, O_RDONLY); if (oldfd == -1) { formatted_error(0, " opening %s for reading: %s", oldfile, strerror(errno), 0); } if (!checking_mode_on) { unlink(newfile1); newfd1 = open(newfile1, O_WRONLY | O_CREAT); if (newfd1 == -1) { formatted_error(0, " opening %s for writing: %s", newfile1, strerror(errno), 0); } fchmod(newfd1, S_IRUSR | S_IRGRP | S_IROTH); } size = token_size(); /* * process the token list twice. the first time only update the * string concats with plusses, the second time only update * the strings themselves */ i18n_string_counter = 0; for (i = 0; i < size; i++) { i = mangle_token(oldfd, newfd1, i, 1); } qsort(pending_inserts, num_pending_inserts, sizeof(pending_inserts[0]), compare_by_pos); for (i = 0; i < size; i++) { i = mangle_token(oldfd, newfd1, i, 0); } /* copy rest of file - pass global lineno which is at eof*/ file_insert_at(oldfd, newfd1, filesize, lineno, ""); close(oldfd); if (!checking_mode_on) { close(newfd1); if (i18n_string_counter == 0 ) { printf("No unmarked strings or character constants in file %s.\n", oldfile); } } } static char * basename(char *name) { char buf[MAXPATHLEN + 1]; char *ptr; int size; /* first take off leading paths */ ptr = strrchr(name, '/'); if (ptr) { ptr++; /* skip slash */ } else { ptr = name; } strcpy(buf, ptr); /* now take off ending .java */ size = strlen(buf); if (strcmp(buf + size - 5, ".java") != 0) { if (strcmp(buf + size - 2, ".c") != 0) { formatted_error(0, "'%s' does not end in '.java' or '.c'\n", name, 0, 0); } else { /* drop .c */ buf[size -2] = '\0'; } } else { /* drop .java */ buf[size - 5] = '\0'; } return buf; } static struct termios old; void fixtty(int junk) { tcsetattr(0, TCSANOW, &old); exit (1); } void onexit(void) { fixtty(0); } static void process_sources(int count, char *list[]) { FILE *fp; int i; int j; int filesize; struct termios new; if (!checking_mode_on) { /* no need for cbreak in checking mode */ tcgetattr(0, &old); signal(SIGINT, fixtty); atexit(onexit); new = old; new.c_lflag &= ~ICANON; new.c_lflag &= ~ECHO; new.c_cc[VMIN] = 1; new.c_cc[VTIME] = 0; tcsetattr(0, TCSANOW, &new); } for(i=0; itype), t->position); } } mangle_file(list[i], filesize); } if (checking_mode_on) { fprintf(fp_err, "================================================================================\n\n"); if (concat_errors + string_errors + character_errors + bogus_character_errors + bogus_function_errors == 0) { fprintf(fp_err, "==> All files checked out OK!! <==\n"); } else { if (concat_errors) { fprintf(fp_err, "==> You had %d uses of \"+\" for string concatenation!! <==\nYou must wrap these either in \n\t%s()\nfor translated concatenation or\n\t%s()\nfor untranslated concatenation.\n\n", concat_errors, string_concat_translate_function, (no_non_translated_props ? noi18n_delimited_comment : string_concat_notranslate_function)); } if (string_errors) { if (no_non_translated_props) { fprintf(fp_err, "==> You had %d strings not wrapped in an appropriate wrapper function!! <==\nYou must wrap these either in \n\t%s)\nfor translatation or prefix them with\n\t%s\nfor no translation.\n\n", string_errors, string_translate_function, noi18n_delimited_comment); } else { fprintf(fp_err, "==> You had %d strings not wrapped in an appropriate wrapper function!! <==\nYou must wrap these either in \n\t%s)\nfor translatation or\n\t%s)\nfor no translation.\n\n", string_errors, string_translate_function, string_notranslate_function); }; } if (character_errors) { fprintf(fp_err, "==> You had %d unwrapped character constants!! <==\nYou must either convert these to a string property and wrap them in \n\t%s()\nif you want them translated, or prefix them with\n\t%s\nfor untranslated character constants.\n\n", character_errors, string_translate_function, noi18n_delimited_comment); } if (bogus_character_errors) { fprintf(fp_err, "==> You had %d strings with non-printing characters in them!! <==\n\n", bogus_character_errors); } if (bogus_function_errors) { fprintf(fp_err, "==> You had %d function calls to functions designated bogus!! <==\n\n", bogus_function_errors); } } fprintf(fp_err, "================================================================================\n"); } else { tcsetattr(0, TCSANOW, &old); } } static Name *names; static int names_count; static int names_size; static Name * find_name(char *name) { int i; int len; len = strlen(name); for ( i=0; i= OK_METHOD_NAMES) { formatted_error(0, "Too many dots in '%s'", name, 0, 0); } methods[methods_count].n[i] = add_name(ptr + 1); *ptr = NULL; i++; } /* now add the part before the first dot */ if (i >= OK_METHOD_NAMES) { formatted_error(0, "Too many dots in '%s'", name, 0, 0); } methods[methods_count].n[i] = add_name(myname); methods[methods_count].size = i + 1; methods[methods_count].is_error_method = is_error_method; methods_count++; } static void describe_arg(char *arg, char *description, char *current_value) { fprintf(stderr, "%-20s%s\n", arg, description); if (current_value) { fprintf(stderr, "%-20s(Current value '%s')\n", "", current_value); } } int main(int argc, char *argv[]) { extern char *optarg; extern int optind, opterr, optopt; int c; int errflg = 0; char *buf1, *buf2; while ((c = getopt(argc, argv, "w:b:o:e:E:s:S:p:P:c?")) != EOF) { switch (c) { case 'w': case 'b': add_ok_method(optarg, c == 'b'); break; case 'o': i18n_file_format_string = strdup(optarg); break; case 's': string_translate_function = strdup(optarg); break; case 'S': string_notranslate_function = strdup(optarg); break; case 'e': string_concat_translate_function = strdup(optarg); break; case 'E': string_concat_notranslate_function = strdup(optarg); break; case 'p': i18n_property_file = strdup(optarg); break; case 'P': noi18n_property_file = strdup(optarg); break; case 'c': checking_mode_on = 1; break; case '?': errflg++; } } if (errflg) { fprintf(stderr, "\n"); fprintf(stderr, "usage: %s \n", argv[0]); describe_arg("ARGUMENT", "DESCRIPTION", NULL); describe_arg("========", "===========", NULL); describe_arg("-o file", "Output to file. If it contains a %s that will be replaced by the file being processed.", i18n_file_format_string); describe_arg("-w func", "Specify that func is used as a wrapper function", NULL); describe_arg("-b func", "Specify that func is bogus and can't be used in program", NULL); describe_arg("-s func", "Wrap I18N strings in func.", string_translate_function); describe_arg("-S func", "Wrap non-I18N strings in func.", string_notranslate_function); describe_arg("-e func", "Wrap I18N string expr's in func.", string_concat_translate_function); describe_arg("-E func", "Wrap non-I18N string expr's in func.", string_concat_notranslate_function); describe_arg("-p file", "I18N messages file.", i18n_property_file); describe_arg("-P file", "Non-I18N messages file. Defaults to non-existant.", noi18n_property_file); describe_arg("-c", "Turn on checking-only mode.", NULL); describe_arg("-?", "See this message.", NULL); describe_arg("file ...", "Files to I18N.", NULL); fprintf(stderr, "\n"); exit (2); } add_ok_method(string_translate_function, 0); add_ok_method(string_notranslate_function, 0); add_ok_method(string_concat_notranslate_function, 0); add_ok_method(string_concat_translate_function, 0); buf1 = (char *)malloc(strlen(string_translate_function) + 10); strcpy(buf1, string_translate_function); strcat(buf1, "("); string_translate_function = buf1; buf2 = (char *)malloc(strlen(string_notranslate_function) + 10); strcpy(buf2, string_notranslate_function); strcat(buf2, "("); string_notranslate_function = buf2; sprintf(noi18n_delimited_comment, "/*%s*/", NOI18N_COMMENT); sprintf(noi18n_delimited_paren_comment, "/*%s*/(", NOI18N_COMMENT); /* sprintf(start_time, "%d.", time(0));*/ start_time[0] = '\0'; translated_fp = fopen(i18n_property_file, "w"); if (strlen(noi18n_property_file) == 0) { no_non_translated_props = 1; } else { no_non_translated_props = 0; non_translated_fp = fopen(noi18n_property_file, "w"); } process_sources(argc-optind,argv+optind); fclose(non_translated_fp); fclose(translated_fp); return exit_status; }