/* TOKENS.CPP * The TokenStream class (a C/C++ preprocessor/tokenizer) * UnderC C++ interpreter * Steve Donovan, 2001 * This is GPL'd software, and the usual disclaimers apply. * See LICENCE */ #include #include #include #include #ifdef _USE_READLINE #include #include #endif #pragma warning(disable:4786) // disable 'forcing value to bool' (Microsoft specific) #pragma warning(disable:4800) #include "tokens.h" #include "utils.h" #include bool tok_dbg = false; // *TEMPORARRY** // from SUBST.CPP char *copy_chars(char* q, char* p); char *copy_token(char *p, char *tok); char *massage_subst_string(char *buff, char **args, char *str); char *substitute(TokenStream& tok, char *buff, char **actual_args, char *subst); char *copy_str(char *tok, char *start, char *end); void insert(char *str, char *ins); long convert_int(char *buff, int base); static int mNoIncludePaths = 0; static string mIncludeDir[MAX_INCLUDE_PATHS]; static char lbuff[LINESIZE]; static char sbuff[STRSIZE]; static char obuff[10]; static char tbuff[STRSIZE]; static char abuff[STRSIZE]; typedef TokenStream& TS; char *last_line() { return lbuff; } // note: cmsg is defined in classlib.h as being cout for plain ol console. void warning(TS tok, string msg, bool is_error = false) { tok.on_error(msg.c_str(),is_error); } void fatal_error(TS tok, string msg) { warning(tok,msg,true); } //--------------Macro table stuff---------------------- typedef std::map MacroTable; static MacroTable mMacroTable; void macro_cleanup() { mMacroTable.clear(); } static PMEntry macro_lookup(char *name) { static string nm = "-------------------------------------------"; nm = name; MacroTable::iterator imt = mMacroTable.find(nm); return (imt != mMacroTable.end()) ? imt->second : NULL; } static PMEntry macro_new(const char *name) { PMEntry pme = new MEntry; mMacroTable[name] = pme; pme->is_alias = false; // by default return pme; } bool TokenStream::macro_delete(const char *name) { PMEntry pme = mMacroTable[name]; if (!pme) return false; // wasn't there in the first place! delete pme; mMacroTable[name] = NULL; return true; } void TokenStream::macro_builtin(const char *name, int id) { PMEntry pme = macro_new(name); pme->subst = NULL; pme->nargs = id; } void TokenStream::macro_subst(const char *name, const char *subst) { PMEntry pme = macro_new(name); pme->subst = (char *)subst; pme->nargs = 0; } void TokenStream::quote_str(char *dest, const char *src) { strcpy(dest,"\""); strcat(dest,src); strcat(dest,"\""); } //-----------------TokenStream class------------------------ TokenStream::TokenStream(const char *fname, UserCommand cmd) { inf = NULL; filename = "DUD"; m_C_str = true; m_skip = false; in_comment = false; if(fname) open(fname); m_cmd = cmd; m_prompter = NULL; m_line_feed = false; m_cwd = ""; } static char mPromptBuffer[MAX_PROMPT_SIZE]; char* TokenStream::get_prompt_buffer() { return mPromptBuffer; } void TokenStream::on_error(const char *msg, bool is_error) { cerr << file() << '(' << lineno() << ") " << msg << endl; if (is_error) exit(-1); } void TokenStream::set_str(char *str) { *str = 0; // just in case... start = P = start_P = str; } TokenStream::~TokenStream() { close(); } #ifdef _WCON static WConIstream win; static istream *con_in = &win; #else static istream *con_in = &cin; #endif // *add 1.2.6 exported as uc_include_path int _uc_include_path(const char *fname, char* buff, int sz) { int knt = 0; string path = fname; while(! Utils::can_access(path)) { if (knt == mNoIncludePaths) return false; path = mIncludeDir[knt++] + fname; } strncpy(buff,path.c_str(),sz); return true; } // *change 1.1.4 // Now supports multiple include paths! // Normal include will now also check these paths... bool TokenStream::open(const char *fname, bool system_include) { //..try to allocate and open a file stream istream* is = NULL; string path,new_cwd,dir; int knt = 0; bool true_system_include = system_include; while (is == NULL) { if (system_include) { path = mIncludeDir[knt++] + fname; } else path = fname; bool is_relative = Utils::extract_relative_path(path,dir); // *change 1.0.0 open() keeps track of working directory if (! system_include) { if (is_relative && m_cwd.size() != 0) { path = m_cwd; path += fname; } else path = fname; } // *fix 1.0.0L 'CON' is of course not a dev name in Linux! // *fix 1.2.1 (Eric) Don't create the file if it can't be found! if (path == "CON") is = con_in; else is = new ifstream(path.c_str(),IOS_IN_FLAGS); if (!(*is)) { delete is; is = NULL; // if we have failed on our first try, keep going... system_include = true; // bail out if we have run out of include paths... if (knt == mNoIncludePaths) break; } // *fix 1.2.8 only append the path part if it isn't absolute if (dir.size() != 0) { if (Utils::is_absolute_path(dir)) new_cwd = dir; else new_cwd = m_cwd + dir; } else new_cwd = ""; } if (is == NULL) fatal_error(*this,"Cannot open " + (true_system_include ? path : fname /*Utils::full_path(path)*/)); return insert_stream(is,path.c_str() /*fname*/,0,new_cwd); } static Stack mOldSkip; bool TokenStream::close() { if (inf) { if (inf != con_in) delete inf; inf = NULL; } //..pop the filestack! if (fstack.empty()) return false; FileStruct fs = fstack.pop(); filename = fs.filename; line = fs.lineno; inf = fs.fin; m_cwd = fs.cwd; if (fs.save_buff != NULL) { strcpy(buff,fs.save_buff); //*LEAK* !! P = fs.save_P; } else set_str(buff); // any operations that must occur when files are closed... // *add 1.2.4 Check that the skip-stack level is the same as it was at entry if (mOldSkip.depth() > 0 && mOldSkip.pop() != sstack.depth()) { warning(*this,"mismatched #if/#endif"); set_skip(false); } on_restore(); return inf != NULL; } void TokenStream::clear() { mOldSkip.clear(); sstack.clear(); set_skip(false); if (fstack.depth() > 1) close(); while (fstack.depth() > 1) { on_clear(filename.c_str(),line); close(); } set_str(buff); // and clear buffer! } bool TokenStream::insert_stream(istream *is, const char *name, int start_line, const string& new_cwd) { if (! is || !(*is) || is->eof()) return false; //..push our previous state onto the file stack FileStruct fs; fs.filename = filename; fs.fin = inf; fs.lineno = line; fs.cwd = m_cwd; if (start_line > 0) { fs.save_buff = strdup(buff); fs.save_P = P; } else fs.save_buff = NULL; fstack.push(fs); filename = name; inf = is; line = start_line; set_str(buff); if (new_cwd.size() > 0) m_cwd = new_cwd; // any operations on open - save the initial skip-stack level mOldSkip.push(sstack.depth()); on_open(); return true; } bool TokenStream::is_interactive() { return inf == con_in; // *change 1.0.0 Should be more efficient } int grab_macro_args(TokenStream& tok, char **args) { int nargs = 0; char ch = tok.next(); if (ch != '(') fatal_error(tok,"absurd!"); ch = tok.next(); while (ch != ')') { if (ch == T_TOKEN) { *args++ = strdup(tok.get_token()); nargs++; } else if (ch == T_END) fatal_error(tok,"end of file in macro arguments"); else if (ch != ',' && ch != ')') fatal_error(tok,"illegal char in macro argument"); ch = tok.next(); } *args = NULL; return nargs; } int grab_actual_args(TokenStream& tok, char **args) { // *fix 1.2.2b (Eric) 1: quoted macro arguments are grabbed verbatim; 2: code cleanup char ch, *q = tbuff; bool in_quote = false; int nargs = 0, level = 1; if (tok.next() != '(') return 0; while (ch = tok.getch()) { if (ch == '"') in_quote = !in_quote; if (in_quote) { if (ch == '\\' && tok.peek_ahead(0) != 0) { // it's escaped - grab two (one below) *q++ = ch; ch = tok.getch(); } } else if (ch == '(') ++level; else if (ch == ',' || ch == ')') { if (level == 1) { // trim off surrounding whitespace while (isspace(q[-1])) q--; *q = 0; q = tbuff; while (isspace(q[ 0])) q++; // save off the arg and prepare for next time through args[nargs++] = strdup(q); q = tbuff; // either go get the next arg or bail out cause we're done if (ch == ',') continue; else break; } if (ch == ')') --level; } // accumulate the current arg *q++ = ch; } if (ch == 0) fatal_error(tok,"Unterminated macro arg list"); args[nargs] = NULL; return nargs; } void expecting_macro_name(TS tok) { fatal_error(tok,"expecting macro name"); } void expecting_string(TS tok) { fatal_error(tok,"expecting string"); } // *add 1.1.0 ensure that the on_hash_cmd() method is always called, however we return! struct Restore { TokenStream& tok; Restore(TokenStream& t) : tok(t) { } ~Restore() { tok.on_hash_cmd(); } }; const uchar NO_SKIP = 0, // prepro is not skipping statements SKIP = 1, // it is skipping normally BLOCK_SKIP = 2, // finished with a #if/elsif/../endif NESTED_SKIP = 3; // we are within a block which is skipping static uchar do_else(TokenStream& tok) { if (tok.skip_stack_empty()) fatal_error(tok,"misplaced #else/#elif"); uchar skipping = tok.get_skip(); if (skipping == NO_SKIP || skipping == SKIP) tok.set_skip(skipping==SKIP ? NO_SKIP : BLOCK_SKIP); return tok.get_skip(); } bool do_prepro_directive(TokenStream& tok) { Restore after(tok); string ppd,path; // *add 1.2.6 null directive - also ignores shell script (e.g. #!/bin/ucc -f....) char ch = *tok.current(); if (ch == '\0' || ch == '!') return true; // fetch the directive name int t = tok.next(); if (t != T_TOKEN) { fatal_error(tok,"expecting preprocessor directive"); return true; } ppd = tok.get_token(); uchar skipping = tok.get_skip(); if (ppd == "include") { bool is_sys_include = false; if (skipping) return true; t = tok.next(); if (t == '<') { char *str = tok.get_upto('>',true); if (!str) fatal_error(tok,"expecting '>' in #include"); is_sys_include = true; path = str; } else path = tok.get_str(tbuff); tok.open(path.c_str(),is_sys_include); } else if (ppd == "define" || ppd == "alias") { char *subst, *sym; PMEntry pme; int nargs; char *args[MAX_MACRO_ARGS]; if (skipping) return true; t = tok.next(); if (t != T_TOKEN) expecting_macro_name(tok); sym = tok.get_str(tbuff); pme = macro_lookup(sym); if (pme) warning(tok,"redefining " + string(sym)); pme = macro_new(sym); pme->is_alias = ppd == "alias"; tok.on_add_macro(sym,pme); if (tok.look_ahead() == '(') { nargs = grab_macro_args(tok,args); } else nargs = 0; pme->nargs = nargs; subst = tok.get_upto(0,false); if (nargs > 0) { massage_subst_string(abuff,args,subst); pme->subst = strdup(abuff); } else pme->subst = strdup(subst); } else if (ppd == "ifdef" || ppd == "ifndef" || ppd == "if" || ppd == "elif") { if (ppd == "elif") skipping = do_else(tok); // *add 1.2.6 implement #elif else tok.push_skip(); // if (ppd == "if" || ppd == "elif") { // *add 1.2.5 implement #if char* line = tok.get_upto(0,false); if (! line) fatal_error(tok,"expecting #if expression"); if (! skipping) { // when we are not skipping, then we can change the skip state! tok.c_string_mode(true); tok.expecting_defined(true); int res = tok.eval_const_expr(line); tok.c_string_mode(false); tok.expecting_defined(false); tok.set_skip(res ? NO_SKIP : SKIP); } else if (ppd == "if") tok.set_skip(NESTED_SKIP); } else { // #ifdef, #ifndef t = tok.next(); if (t != T_TOKEN) expecting_macro_name(tok); if (! skipping) { bool sym_exists = macro_lookup(tok.get_str(tbuff)) != NULL; if(ppd == "ifdef") { if(! sym_exists) tok.set_skip(SKIP); } else { if(sym_exists) tok.set_skip(SKIP); } } else tok.set_skip(NESTED_SKIP); } } else if (ppd == "endif") { if (tok.skip_stack_empty()) fatal_error(tok,"misplaced #endif"); // throws an exception tok.pop_skip(); } else if (ppd == "else") { do_else(tok); } else if (ppd == "undef") { if (skipping) return true; t = tok.next(); if (t != T_TOKEN) expecting_macro_name(tok); TokenStream::macro_delete(tok.get_str(tbuff)); } else if (ppd == "warning" || ppd == "error") { // *add 1.2.5 #error, 1.2.6 #warning if (! skipping) { char* line = tok.get_upto(0,false); warning(tok,line, ppd=="error"); } } else { /// an interactive command! if (skipping) return true; if (!tok.user_cmd(ppd)) return false; } return true; } void TokenStream::grab_next() { // *hack 1.1.4 a cheap & nasty way of looking ahead in the stream.... inf->getline(lbuff,LINESIZE); strcat(buff,lbuff); } bool TokenStream::look_ahead_more(char ch) { char *p = P; while (true) { while (*p && isspace(*p)) p++; if (*p) { return (*p == ch); } grab_next(); } } bool TokenStream::fetch_line() { int len; bool continuation = true; *buff = '\0'; while (true) { // looking for non-blank, non-preprocessor lines set_str(buff); do { //...if we have run out of file, close and try to continue if (!inf || inf->eof()) { if(!close()){ set_str(buff); return false; // we have run out of open files // restored old file; can continue } } do_prompt(); // really only need to call this when in interactive mode... #ifndef _USE_READLINE inf->getline(lbuff,LINESIZE); #else if (inf != con_in) inf->getline(lbuff,LINESIZE); else { char *pl = readline(get_prompt_buffer()); if (pl == NULL) return false; // *fix 1.2.2 (Dean) Stopping process with ctrl-D strcpy(lbuff,pl); free(pl); add_history(lbuff); } #endif // *fix 1.1.0 spaces after \ messes with the preprocessor's little mind! char *endp = lbuff + strlen(lbuff) - 1; while (*endp && isspace(*endp)) --endp; continuation = (*endp == '\\'); // if (continuation) *endp = '\0'; // lop off '\' if (continuation) { *(endp+1) = '\0'; *endp = '\n'; } strcat(buff,lbuff); line++; } while (continuation); start = P = start_P = buff; while(isspace(*P)) P++; if (*P == 0) continue; // can ignore empty lines! if (*P == '#' && ! in_comment) { m_C_str = false; P++; try { if (!do_prepro_directive(*this)) { close(); m_C_str = true; return false; // bail out! } } catch(string msg) { // *fix 1.2.7 fatal errors must stop parsing, unless in interactive mode. // *fix 1.2.8 true, but that isn't the same as 'inf != con_in'! // is_interactive_mode() is a virtual function overriden by UCTokenStream. if (! is_interactive_mode()) return false; } m_C_str = true; } else if (! m_skip) { // ensure that there's a final line feed! if (m_line_feed) { len = strlen(buff); buff[len] = '$'; buff[len+1] = '\0'; } return true; } } // while looking for non-blank, non-preprocessor lines } char *TokenStream::get_upto(char ch, bool discard_ch) // Grab characters from the stream, upto (and optionally including) ch. // if anything goes wrong we throw a wobbly. // *this routine (for now) just works for the prepro* // *fix 1.2.1 (Peter) Strip out C++ line comments if we're grabbing the whole line { bool found_end; start_P = P; while(*P && *P != ch) P++; found_end = *P == ch; if (! discard_ch) P++; end_P = P; if (discard_ch) P++; if (found_end) { char *str = get_str(tbuff); if (ch=='\0') { // special case of grabbing the whole line // *fix 1.2.2 (Eric) strip out any C++ comments properly // *fix 1.2.4 Discard any \r found at end of line (DOS files in Linux) for (char *p = str; *p; ++p) { if ((p[0] == '/' && p[1] == '/') || p[0] == '\r') { *p = '\0'; break; } } } return str; } else return NULL; } void TokenStream::discard_line() { *P = 0; skip_whitespace(); } // *fix 0.9.3 This was ignoring blank lines & generally messing // up the template line number diagnostics. But it's still // giving grief - need to handle the case where there's something // still in the input buffer (as is usually the case) void TokenStream::grab_line(char *buff) { skip_whitespace(); start_P = P; P += strlen(P); end_P = P; get_str(buff); //char lbuff[LINESIZE]; //inf->getline(lbuff,LINESIZE); //strcpy(buff,lbuff); } void TokenStream::insert_string(char *str) // stuff characters into the stream! // for now, it effectively discards what was _in_ the buffer; // this is fine for its current application, which is to execute // commands.. { set_str(buff); strcpy(P,str); } bool no_new_line = false; bool TokenStream::skip_whitespace() { top: while(*P && isspace(*P)) P++; if (*P == 0) { if(no_new_line || !fetch_line()) return false; // EOF will pass through as T_END goto top; } else if (*P == '/') { //...filter out comments at this level!! if (*(P+1)=='/') { *P = '\0'; goto top; } else if (*(P+1)=='*') { P++; in_comment = true; while (true){ if (*P++=='*' && *P=='/') { P++; in_comment = false; goto top; } if (*P == 0) if(!fetch_line()) { fatal_error(*this,"unexpected end of file in comment"); return false; } } } } return true; } int grab_alias_args(char *ptr, char **args) { int nargs = 0; char *tok = strtok(ptr," "); //Utils::quote_strtok(ptr); while (tok != NULL) { *args++ = tok; ++nargs; tok = strtok(NULL," "); //Utils::quote_strtok(NULL); } return nargs; } void separate_alias_commands(TokenStream& tok) { // approved way to fetch the whole line char *line = strdup(tok.get_upto(0,true)); char *cmds[10], buff[80]; int k = 0; // break up into individual @-commands (need to do this separately) char *cmd = strtok(line,"@"); while (cmd) { cmds[k++] = cmd; cmd = strtok(NULL,"@"); } // insert the commands back into the stream // shifting the current position is necessary to prevent // runaway substition of 'cd' etc. for(int i = 0; i < k; i++) { sprintf(buff,"# %s",cmds[i]); tok.insert_string(buff); tok.current(tok.current()+1); // *fix 1.2.0 NB to switch off C string mode when calling do_prepro_directive() tok.c_string_mode(false); try { do_prepro_directive(tok); } catch(string msg) { } tok.c_string_mode(true); } } void TokenStream::skip_digits() { while(isdigit(*P)) P++; } static bool first_token_in(char *buff, char *P) { if (P == buff) return true; P--; while (P != buff && isspace(*P)) P--; return P == buff && isspace(*P); } // *change 1.2.2 I've separated out the macro substitution code from next() // and broken it into the two cases, C macros and aliases, explicitly. bool TokenStream::macro_attempt_process(char*& p, char *out, char *tok) { PMEntry pme = macro_lookup(tok); *out = '\0'; if (! pme || pme->is_alias) return false; else { char *old_P = current(); current(p); macro_process(pme,out); p = current(); current(old_P); } return true; } void TokenStream::macro_process(PMEntry pme, char *out) { char *args[MAX_MACRO_ARGS]; char temp_buff[TT_BUFFSIZE]; int nargs; char *subst; if (pme->nargs > 0) { if (!pme->subst) { // Builtin macro handle_builtin_macro(tbuff,pme->nargs); subst = tbuff; } else { nargs = grab_actual_args(*this,args); // regular C-style macro if (nargs != pme->nargs) fatal_error(*this,"wrong no. of arguments for this macro"); substitute(*this,temp_buff, args, pme->subst); subst = temp_buff; } } else subst = pme->subst; if (! out) insert(P,subst); else strcpy(out,subst); } void TokenStream::alias_process(PMEntry pme) { char *args[MAX_MACRO_ARGS]; char temp_buff[TT_BUFFSIZE]; int nargs; if (pme->nargs > 0) { nargs = grab_alias_args(P,args); set_str(buff); if (nargs != pme->nargs) fatal_error(*this,"wrong no. of arguments for this macro"); substitute(*this,temp_buff, args, pme->subst); insert(P,temp_buff); } else insert(P,pme->subst); skip_whitespace(); if(*P=='@') { ++P; separate_alias_commands(*this); discard_line(); } } int TokenStream::next() { try { // *fix 1.01 fatal_error() will throw a string! do_it_again: if (! skip_whitespace()) return 0; // means: finis, end of file, bail out. if (iscsymf(*P)) { //--------------------- TOKENS -------------- start_P = P; while (iscsym(*P)) P++; end_P = P; copy_str(tbuff,start_P,end_P); if (m_C_str) { // ie. suppress macro lookup in preprocessor directives PMEntry pme; // *add 1.2.4 Support for defined(MACRO) in #if directives // *fix 1.2.5 'defined MACRO' is also acceptable if (m_expecting_defined && strcmp(tbuff,"defined")==0) { m_C_str = false; int t = next(); char mname[MAX_IDEN_SIZE]; bool ok = (t == '(' || t == T_TOKEN); if (ok) { if (t == '(') ok = (next() == T_TOKEN); // skip the '(' if (ok) { get_str(mname); // pick up the macro name if (t == '(') next(); // skip the ')' insert(P,(char*)(macro_lookup(mname) ? "1" : "0")); m_C_str = true; goto do_it_again; } } m_C_str = true; if (! ok) fatal_error(*this,"defined takes one macro argument"); } else { pme = macro_lookup(tbuff); if (pme) { if (pme->is_alias) { if (! first_token_in(buff,start_P)) return T_TOKEN; else alias_process(pme); } else macro_process(pme,NULL); goto do_it_again; } // *fix 1.2.7 in #if expressions, all non-macros evaluate as 0 else if (m_expecting_defined) { insert(P,"0"); goto do_it_again; } } } return T_TOKEN; } else if (isdigit(*P) || *P == '.' && isdigit(*(P+1))) { //------- NUMBERS ------------------ int ntype = int_type = T_INT; // until proved otherwise! start_P = P; if (*P != '.') { if (*P == '0') { // actual verification of hex or octal constants must happen in lexer if (*(P+1) == 'x') { // hex constant while (isalnum(*P)) P++; // a preliminary check! ntype = int_type = T_HEX; } else if (isdigit(*(P+1))) { // octal constant skip_digits(); ntype = int_type = T_OCT; } else skip_digits(); // plain zero! } else { P++; // skip first - might be '-' skip_digits(); } } if (*P == '.') { // (opt) fractional part P++; skip_digits(); ntype = T_DOUBLE; } if (*P == 'e' || *P == 'E') { // (opt) exponent part P++; if (*P == '+' || *P == '-') P++; // (opt) exp sign skip_digits(); ntype = T_DOUBLE; } if (*P == 'f' || *P == 'F') { P++; ntype = T_FLOAT; } // *fix 1.2.6 long integer constants ending with 'L' are now acceptable else if (*P == 'l' || *P == 'L') { P++; ntype = T_INT; } end_P = P; return ntype; } else if (*P == '\"' || *P == '\'') { //------------CHAR OR STRING CONSTANT------- char ch, endch = *P++; char *p = sbuff; start_P = sbuff; next_string: while (*P && *P != endch) { if (*P == '\\' && m_C_str) { P++; switch(*P) { case '\\': ch = '\\'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'b': ch = '\b'; break; case '\"': ch = '\"'; break; case '\'': ch = '\''; break; case '0': { //..collecting OCTAL constant char *start_oct = P; skip_digits(); copy_str(obuff,start_oct,P); ch = (char)convert_int(obuff,8); P--; // leave us on last digit } break; // *fix 1.1.2 We were not letting non-escape sequences through... default: *p++ = '\\'; ch = *P; break; } // switch *p++ = ch; P++; } else *p++ = *P++; } if (! *P) fatal_error(*this,"Unterminated string constant"); P++; // skip the endch *p = '\0'; end_P = p; // *add 1.1.1 adjacent quoted text // *fix 1.1.4 gave preprocessor directives the heebies if (endch=='\"') { if (m_C_str) { skip_whitespace(); if (*P == '\"') { P++; // will be concatenated! goto next_string; // so go back & keep grabbing... } } return T_STRING; } else return T_CHAR; } else return *P++; } catch(...) { return ' '; } } int TokenStream::look_ahead(bool skip_wspace) //*OPT* Can inline this! { if(skip_wspace) skip_whitespace(); return *P; } int TokenStream::peek_ahead(int count) { return *(P+count); } char *TokenStream::get_string() { return sbuff; } char *TokenStream::peek_next_token() { // This is a hack and only use it if you are slowly going mad with frustration. // It will return an empty buffer if there's no more tokens on the current line. char *ptr = P; while (*ptr && !iscsym(*ptr)) ptr++; char *start_p = ptr; if (ptr) { while (iscsym(*ptr)) ptr++; } copy_str(tbuff,start_p,ptr); return tbuff; } char *TokenStream::get_str(char *tok) { if (tok==NULL) tok = tbuff; copy_str(tok,start_P,end_P); return tok; } char *TokenStream::get_token() { return tbuff; } double TokenStream::get_float() { char buff[20]; return atof(get_str(buff)); } int TokenStream::get_int() { char buff[20]; return convert_int(get_str(buff),int_type == T_INT ? 10 : 16); } double TokenStream::next_float() { int t; do { t = next(); if (t == T_NUMBER || t == T_END) return get_float(); } while (t != T_END); return 0.0; } void TokenStream::set_include_dir(const char *s) { // *fix 1.2.2 Check for too many include paths if (mNoIncludePaths+1 >= MAX_INCLUDE_PATHS) { cerr << "Out of room for include paths!" << endl; return; } string path = s; Utils::check_path_end(path); mIncludeDir[mNoIncludePaths] = path; ++mNoIncludePaths; }