mirror of
https://github.com/cheat-engine/cheat-engine
synced 2026-08-15 02:26:08 -04:00
1021 lines
28 KiB
C++
1021 lines
28 KiB
C++
|
|
/* 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 <ctype.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#ifdef _USE_READLINE
|
||
|
|
#include <readline/readline.h>
|
||
|
|
#include <readline/history.h>
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#pragma warning(disable:4786)
|
||
|
|
|
||
|
|
// disable 'forcing value to bool' (Microsoft specific)
|
||
|
|
#pragma warning(disable:4800)
|
||
|
|
|
||
|
|
#include "tokens.h"
|
||
|
|
#include "utils.h"
|
||
|
|
#include <map>
|
||
|
|
|
||
|
|
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<string,PMEntry> 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 <int,MAX_FILE_DEPTH> 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;
|
||
|
|
}
|
||
|
|
|
||
|
|
|