Files
bgfx/tools/shaderc/pp.cpp
2026-08-20 19:07:34 +00:00

3093 lines
57 KiB
C++

/*
* Copyright 2011-2026 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bx/blob/master/LICENSE
*/
#include "pp.h"
#include <bx/allocator.h>
#include <bx/filepath.h>
#include <bx/scanner.h>
#include <bx/sort.h>
#include <tinystl/vector.h>
namespace shaderc
{
namespace
{
namespace stl = tinystl;
static constexpr uint32_t kArenaBlockSize = 64<<10;
static constexpr uint32_t kMaxIncludeDepth = 200;
enum class Kind : uint8_t
{
Eof,
Newline,
Identifier,
Number,
String,
Char,
Punct,
Placemarker,
Count
};
struct HideNode
{
bx::StringView name;
const HideNode* next;
};
struct Token
{
bx::StringView lexeme;
SourceLocation location;
const HideNode* hide;
Kind kind;
bool spaceBefore;
};
Token makeToken(Kind _kind, const bx::StringView& _lexeme)
{
return
{
.lexeme = _lexeme,
.location = SourceLocation(),
.hide = NULL,
.kind = _kind,
.spaceBefore = false,
};
}
bool isPunct(const Token& _token, const bx::StringView& _lexeme)
{
return true
&& Kind::Punct == _token.kind
&& isEqual(_token.lexeme, _lexeme)
;
}
bool isIdent(const Token& _token, const bx::StringView& _lexeme)
{
return true
&& Kind::Identifier == _token.kind
&& isEqual(_token.lexeme, _lexeme)
;
}
Kind classify(const bx::StringView& _text)
{
if (_text.isEmpty() )
{
return Kind::Placemarker;
}
const char ch = *_text.getPtr();
if (bx::isIdentStart(ch) )
{
return Kind::Identifier;
}
if (bx::isNumeric(ch)
|| ('.' == ch && 1 < _text.getLength() && bx::isNumeric(_text.getPtr()[1]) ) )
{
return Kind::Number;
}
return Kind::Punct;
}
class Arena
{
BX_CLASS(Arena
, NO_DEFAULT_CTOR
, NO_COPY
);
public:
Arena(bx::AllocatorI* _allocator)
: m_allocator(_allocator)
, m_head(NULL)
{
}
bx::AllocatorI* getAllocator() const
{
return m_allocator;
}
~Arena()
{
reset();
}
void* allocate(uint32_t _size)
{
_size = bx::alignUp(_size, 16);
if (NULL == m_head
|| m_head->size < m_head->used + _size)
{
const uint32_t blockSize = bx::max(_size, kArenaBlockSize);
Block* block = (Block*)bx::alloc(m_allocator, sizeof(Block) + blockSize);
*block =
{
.prev = m_head,
.size = blockSize,
.used = 0,
};
m_head = block;
}
void* ptr = (char*)(m_head + 1) + m_head->used;
m_head->used += _size;
return ptr;
}
template<typename Ty>
Ty* allocate(uint32_t _num)
{
return (Ty*)allocate(_num * uint32_t(sizeof(Ty) ) );
}
bx::StringView intern(const bx::StringView& _str)
{
const uint32_t len = _str.getLength();
if (0 == len)
{
return bx::StringView();
}
char* dst = allocate<char>(len);
bx::memCopy(dst, _str.getPtr(), len);
return bx::StringView(dst, len);
}
bx::StringView concat(const bx::StringView* _parts, uint32_t _num)
{
uint32_t total = 0;
for (uint32_t ii = 0; ii < _num; ++ii)
{
total += _parts[ii].getLength();
}
if (0 == total)
{
return bx::StringView();
}
char* dst = allocate<char>(total);
uint32_t offset = 0;
for (uint32_t ii = 0; ii < _num; ++ii)
{
const uint32_t len = _parts[ii].getLength();
bx::memCopy(dst + offset, _parts[ii].getPtr(), len);
offset += len;
}
return bx::StringView(dst, total);
}
bx::StringView concat(const bx::StringView& _lhs, const bx::StringView& _rhs)
{
const bx::StringView parts[] = { _lhs, _rhs };
return concat(parts, BX_COUNTOF(parts) );
}
void reset()
{
while (NULL != m_head)
{
Block* prev = m_head->prev;
bx::free(m_allocator, m_head);
m_head = prev;
}
}
private:
struct Block
{
Block* prev;
uint32_t size;
uint32_t used;
};
bx::AllocatorI* m_allocator;
Block* m_head;
};
static const bx::StringLiteral s_puncts[] =
{
"<<=",
">>=",
"...",
"##",
"<<",
">>",
"<=",
">=",
"==",
"!=",
"&&",
"||",
"->",
"++",
"--",
"+=",
"-=",
"*=",
"/=",
"%=",
"&=",
"|=",
"^=",
"::",
};
bool isNumberPart(char _ch)
{
return bx::isIdentChar(_ch)
|| '.' == _ch
;
}
class Lexer
{
BX_CLASS(Lexer
, NO_DEFAULT_CTOR
, NO_COPY
);
public:
Lexer(Arena& _arena, const bx::StringView& _input, Token* _tokens, uint32_t _max)
: m_arena(_arena)
, m_scanner(_input)
, m_input(_input)
, m_tokens(_tokens)
, m_max(_max)
, m_num(0)
, m_unterminatedComment(false)
{
}
bool hadUnterminatedComment() const
{
return m_unterminatedComment;
}
uint32_t run()
{
for (;;)
{
const bool spaceBefore = skipSpace();
if (m_scanner.isDone() )
{
break;
}
const uint32_t line = m_scanner.getLine();
const bx::StringView newLine = m_scanner.accept('\n');
if (!newLine.isEmpty() )
{
emit(Kind::Newline, newLine, spaceBefore, line);
continue;
}
const char ch = peek();
if (bx::isIdentStart(ch) )
{
const bx::StringView ident = acceptSpliced(
m_scanner.accept(bx::Scanner::Class::Identifier)
, bx::isIdentChar
, [this]() { return m_scanner.acceptWhile(bx::isIdentChar); }
);
emit(Kind::Identifier, ident, spaceBefore, line);
continue;
}
if (bx::isNumeric(ch)
|| ('.' == ch && bx::isNumeric(peek(1) ) ) )
{
const bx::StringView number = acceptSpliced(
acceptNumber()
, isNumberPart
, [this]() { return acceptNumber(); }
);
emit(Kind::Number, number, spaceBefore, line);
continue;
}
if ('"' == ch)
{
emit(Kind::String, acceptQuoted('"'), spaceBefore, line);
continue;
}
if ('\'' == ch)
{
emit(Kind::Char, acceptQuoted('\''), spaceBefore, line);
continue;
}
emit(Kind::Punct, acceptPunct(), spaceBefore, line);
}
return m_num;
}
private:
char peek(int32_t _offset = 0) const
{
const char* ptr = m_scanner.getCursor().getPtr() + _offset;
return ptr < m_input.getTerm() ? *ptr : '\0';
}
bool skipSpace()
{
bool space = false;
for (;;)
{
if (!m_scanner.acceptWhile(bx::isSpaceHoriz).isEmpty() )
{
space = true;
continue;
}
if (skipLineContinuation()
|| skipComment() )
{
space = true;
continue;
}
break;
}
return space;
}
bool skipLineContinuation()
{
const bx::StringView cursor = m_scanner.getCursor();
if (m_scanner.accept('\\').isEmpty() )
{
return false;
}
m_scanner.accept('\r');
if (!m_scanner.accept('\n').isEmpty() )
{
return true;
}
m_scanner.seek(cursor);
return false;
}
bool skipComment()
{
if (!m_scanner.accept("//").isEmpty() )
{
m_scanner.acceptUntil(bx::Scanner::Class::EndOfLine);
return true;
}
if (m_scanner.accept("/*").isEmpty() )
{
return false;
}
uint32_t depth = 1;
while (!m_scanner.isDone() )
{
if (!m_scanner.accept("/*").isEmpty() )
{
++depth;
continue;
}
if (!m_scanner.accept("*/").isEmpty() )
{
if (0 == --depth)
{
break;
}
continue;
}
m_scanner.accept();
}
m_unterminatedComment = m_unterminatedComment || 0 != depth;
return true;
}
bool spliceJoins(bool (*_isPart)(char) )
{
const bx::StringView cursor = m_scanner.getCursor();
if (!skipLineContinuation() )
{
return false;
}
if (_isPart(peek() ) )
{
return true;
}
m_scanner.seek(cursor);
return false;
}
template<typename AcceptFn>
bx::StringView acceptSpliced(bx::StringView _lexeme, bool (*_isPart)(char), AcceptFn _accept)
{
while (spliceJoins(_isPart) )
{
const bx::StringView parts[] = { _lexeme, _accept() };
_lexeme = m_arena.concat(parts, BX_COUNTOF(parts) );
}
return _lexeme;
}
void emit(
Kind _kind
, const bx::StringView& _lexeme
, bool _spaceBefore
, uint32_t _line
)
{
if (m_num >= m_max)
{
return;
}
m_tokens[m_num++] =
{
.lexeme = _lexeme,
.location = SourceLocation(bx::StringView(), _line),
.hide = NULL,
.kind = _kind,
.spaceBefore = _spaceBefore,
};
}
bx::StringView acceptNumber()
{
const bx::StringView cursor = m_scanner.getCursor();
m_scanner.accept();
for (;;)
{
const char ch = peek();
if ('e' == ch || 'E' == ch
|| 'p' == ch || 'P' == ch)
{
m_scanner.accept();
m_scanner.accept('+', '-');
continue;
}
if (bx::isIdentChar(ch)
|| '.' == ch)
{
m_scanner.accept();
continue;
}
break;
}
return m_scanner.between(cursor);
}
bx::StringView acceptQuoted(char _quote)
{
const bx::StringView cursor = m_scanner.getCursor();
m_scanner.accept(_quote);
while (!m_scanner.isDone()
&& m_scanner.peek('\n').isEmpty() )
{
if (!m_scanner.accept('\\').isEmpty() )
{
m_scanner.accept();
continue;
}
if (!m_scanner.accept(_quote).isEmpty() )
{
break;
}
m_scanner.accept();
}
return m_scanner.between(cursor);
}
bx::StringView acceptPunct()
{
for (uint32_t ii = 0; ii < BX_COUNTOF(s_puncts); ++ii)
{
const bx::StringView punct = m_scanner.accept(s_puncts[ii]);
if (!punct.isEmpty() )
{
return punct;
}
}
return m_scanner.accept();
}
Arena& m_arena;
bx::Scanner m_scanner;
bx::StringView m_input;
Token* m_tokens;
uint32_t m_max;
uint32_t m_num;
bool m_unterminatedComment;
};
struct Macro
{
bx::StringView name;
stl::vector<bx::StringView> params;
const Token* body;
uint32_t bodyNum;
bool isFunction;
bool isVariadic;
};
struct Frame
{
bx::StringView name;
bx::StringView presumedName;
const Token* tokens;
uint32_t num;
uint32_t pos;
int32_t lineDelta;
bool isInclude;
};
static const bx::StringView& frameName(const Frame& _frame)
{
return _frame.presumedName.isEmpty()
? _frame.name
: _frame.presumedName
;
}
static bx::StringView pathOf(const bx::FilePath& _filePath)
{
return _filePath.isEmpty()
? bx::StringView()
: bx::StringView(_filePath)
;
}
struct Cond
{
bool active;
bool taken;
bool parent;
bool seenElse;
};
class Worklist
{
BX_CLASS(Worklist
, NO_DEFAULT_CTOR
, NO_COPY
);
public:
Worklist(const stl::vector<Token>& _tokens)
: m_tokens(_tokens)
, m_head(0)
{
}
bool isEmpty() const
{
return m_head >= m_tokens.size();
}
const Token& getFront() const
{
return m_tokens[m_head];
}
void popFront()
{
++m_head;
}
void prepend(const stl::vector<Token>& _tokens)
{
const uint32_t num = uint32_t(_tokens.size() );
if (0 == num)
{
return;
}
if (num <= m_head)
{
m_head -= num;
for (uint32_t ii = 0; ii < num; ++ii)
{
m_tokens[m_head + ii] = _tokens[ii];
}
return;
}
stl::vector<Token> tmp;
tmp.reserve(num + m_tokens.size() - m_head);
for (uint32_t ii = 0; ii < num; ++ii)
{
tmp.push_back(_tokens[ii]);
}
const uint32_t numTokens = uint32_t(m_tokens.size() );
for (uint32_t ii = m_head; ii < numTokens; ++ii)
{
tmp.push_back(m_tokens[ii]);
}
m_tokens.swap(tmp);
m_head = 0;
}
const Token* begin() const
{
return m_tokens.data() + m_head;
}
const Token* end() const
{
return m_tokens.data() + m_tokens.size();
}
private:
stl::vector<Token> m_tokens;
uint32_t m_head;
};
enum class Op : uint8_t
{
NotEq,
Mod,
BitAnd,
LogicalAnd,
Mul,
Add,
Sub,
Div,
Lt,
Shl,
LtEq,
Eq,
Gt,
GtEq,
Shr,
BitXor,
BitOr,
LogicalOr,
Count
};
struct OpInfo
{
bx::StringView lexeme;
int32_t precedence;
static int32_t cmpFn(const void* _lhs, const void* _rhs)
{
return bx::strCmp(
( (const OpInfo*)_lhs)->lexeme
, ( (const OpInfo*)_rhs)->lexeme
);
}
};
static const OpInfo s_ops[] =
{
{ "!=", 6 },
{ "%", 10 },
{ "&", 5 },
{ "&&", 2 },
{ "*", 10 },
{ "+", 9 },
{ "-", 9 },
{ "/", 10 },
{ "<", 7 },
{ "<<", 8 },
{ "<=", 7 },
{ "==", 6 },
{ ">", 7 },
{ ">=", 7 },
{ ">>", 8 },
{ "^", 4 },
{ "|", 3 },
{ "||", 1 },
};
static_assert(uint8_t(Op::Count) == BX_COUNTOF(s_ops), "s_ops must match Op.");
Op findOp(const bx::StringView& _lexeme)
{
const int32_t idx = bx::binarySearch(_lexeme, s_ops, BX_COUNTOF(s_ops), sizeof(OpInfo), OpInfo::cmpFn);
return 0 <= idx ? Op(idx) : Op::Count;
}
const OpInfo& getOpInfo(Op _op)
{
return s_ops[uint8_t(_op)];
}
int32_t toDigit(char _ch)
{
if (bx::isNumeric(_ch) )
{
return _ch - '0';
}
if (bx::isHexNum(_ch) )
{
return (bx::toLower(_ch) - 'a') + 10;
}
return -1;
}
}
struct PreprocessorImpl
{
BX_CLASS(PreprocessorImpl
, NO_DEFAULT_CTOR
, NO_COPY
);
PreprocessorImpl(PreprocessorCallbackI& _callback, bx::AllocatorI* _allocator)
: m_callback(_callback)
, m_arena(_allocator)
, m_out(NULL)
, m_includeDepth(0)
, m_outLocation(bx::StringView(), 1)
, m_emitLine(false)
, m_inCondition(false)
, m_ok(true)
{
}
void syncLine(const SourceLocation& _location)
{
if (!m_emitLine
|| 0 == _location.line)
{
return;
}
if (_location.line == m_outLocation.line
&& isEqual(_location.file, m_outLocation.file) )
{
return;
}
const bx::FilePath filePath(_location.file);
const bx::StringView file = pathOf(filePath);
char tmp[bx::kMaxFilePath + 64];
const int32_t total = bx::snprintf(tmp, BX_COUNTOF(tmp), "#line %u \"%.*s\"\n"
, _location.line
, file.getLength()
, file.getPtr()
);
append(bx::StringView(tmp, bx::min(total, int32_t(BX_COUNTOF(tmp) ) - 1) ) );
m_outLocation = _location;
}
void report(bool _isError, const bx::StringView& _message)
{
m_callback.message(_isError, m_location, _message);
if (_isError)
{
m_ok = false;
}
}
void error(const bx::StringView& _message)
{
report(true, _message);
}
void pushFrame(const bx::StringView& _name, const bx::StringView& _source, bool _isInclude)
{
const uint32_t max = _source.getLength() + 16;
Token* tokens = m_arena.allocate<Token>(max);
Lexer lexer(m_arena, _source, tokens, max);
const Frame frame =
{
.name = _name,
.presumedName = bx::StringView(),
.tokens = tokens,
.num = lexer.run(),
.pos = 0,
.lineDelta = 0,
.isInclude = _isInclude,
};
m_frames.push_back(frame);
m_location.file = _name;
if (lexer.hadUnterminatedComment() )
{
m_location.line = 0;
error("Unterminated comment");
}
}
bool next(Token& _outToken)
{
if (!m_pending.empty() )
{
_outToken = m_pending.back();
m_pending.pop_back();
return true;
}
while (!m_frames.empty() )
{
Frame& frame = m_frames.back();
if (frame.pos < frame.num)
{
_outToken = frame.tokens[frame.pos++];
m_location = SourceLocation(frameName(frame), uint32_t(int32_t(_outToken.location.line) + frame.lineDelta) );
_outToken.location = m_location;
return true;
}
const bool wasInclude = frame.isInclude;
m_frames.pop_back();
if (wasInclude)
{
--m_includeDepth;
}
if (!m_frames.empty() )
{
m_location.file = frameName(m_frames.back() );
}
}
return false;
}
void unget(const Token& _token)
{
m_pending.push_back(_token);
}
void readLine(stl::vector<Token>& _outTokens)
{
Token token;
while (next(token) )
{
if (Kind::Newline == token.kind)
{
break;
}
_outTokens.push_back(token);
}
}
bool readLineKeepNewline(stl::vector<Token>& _outTokens)
{
Token token;
while (next(token) )
{
_outTokens.push_back(token);
if (Kind::Newline == token.kind)
{
return true;
}
}
return false;
}
void skipLine()
{
Token token;
while (next(token) )
{
if (Kind::Newline == token.kind)
{
break;
}
}
}
bool condActive() const
{
return m_cond.empty() || m_cond.back().active;
}
const Macro* findMacro(const bx::StringView& _name) const
{
for (uint32_t ii = 0, num = uint32_t(m_macros.size() ); ii < num; ++ii)
{
if (isEqual(m_macros[ii].name, _name) )
{
return &m_macros[ii];
}
}
return NULL;
}
void removeMacro(const bx::StringView& _name)
{
for (uint32_t ii = 0, num = uint32_t(m_macros.size() ); ii < num; ++ii)
{
if (isEqual(m_macros[ii].name, _name) )
{
m_macros.erase(m_macros.begin() + ii);
return;
}
}
}
bool isDefined(const bx::StringView& _name) const
{
return false
|| NULL != findMacro(_name)
|| isEqual(_name, "__LINE__")
|| isEqual(_name, "__FILE__")
|| isEqual(_name, "__COUNTER__")
;
}
bool hideContains(const HideNode* _hide, const bx::StringView& _name) const
{
for (const HideNode* node = _hide; NULL != node; node = node->next)
{
if (isEqual(node->name, _name) )
{
return true;
}
}
return false;
}
const HideNode* hideAdd(const HideNode* _hide, const bx::StringView& _name)
{
if (hideContains(_hide, _name) )
{
return _hide;
}
HideNode* node = m_arena.allocate<HideNode>(1);
*node =
{
.name = _name,
.next = _hide,
};
return node;
}
const HideNode* hideUnion(const HideNode* _lhs, const HideNode* _rhs)
{
const HideNode* result = _lhs;
for (const HideNode* node = _rhs; NULL != node; node = node->next)
{
result = hideAdd(result, node->name);
}
return result;
}
const HideNode* hideIntersect(const HideNode* _lhs, const HideNode* _rhs)
{
const HideNode* result = NULL;
for (const HideNode* node = _lhs; NULL != node; node = node->next)
{
if (hideContains(_rhs, node->name) )
{
result = hideAdd(result, node->name);
}
}
return result;
}
int32_t findParam(const Macro& _macro, const bx::StringView& _name) const
{
for (uint32_t ii = 0, num = uint32_t(_macro.params.size() ); ii < num; ++ii)
{
if (isEqual(_macro.params[ii], _name) )
{
return int32_t(ii);
}
}
return -1;
}
bool isParam(const Macro& _macro, const Token& _token) const
{
if (Kind::Identifier != _token.kind)
{
return false;
}
if (_macro.isVariadic
&& isEqual(_token.lexeme, "__VA_ARGS__") )
{
return true;
}
return -1 != findParam(_macro, _token.lexeme);
}
void selectActual(
const Macro& _macro
, const stl::vector<stl::vector<Token> >& _actuals
, const bx::StringView& _name
, stl::vector<Token>& _outTokens
)
{
if (_macro.isVariadic
&& isEqual(_name, "__VA_ARGS__") )
{
const uint32_t first = uint32_t(_macro.params.size() );
for (uint32_t ii = first, num = uint32_t(_actuals.size() ); ii < num; ++ii)
{
if (ii > first)
{
_outTokens.push_back(makeToken(Kind::Punct, ",") );
}
const stl::vector<Token>& actual = _actuals[ii];
for (uint32_t jj = 0, numArg = uint32_t(actual.size() ); jj < numArg; ++jj)
{
_outTokens.push_back(actual[jj]);
}
}
return;
}
const int32_t idx = findParam(_macro, _name);
if (0 <= idx
&& uint32_t(idx) < _actuals.size() )
{
_outTokens = _actuals[idx];
}
}
bx::StringView stringize(const stl::vector<Token>& _tokens)
{
stl::vector<char> buffer;
buffer.push_back('"');
for (uint32_t ii = 0, num = uint32_t(_tokens.size() ); ii < num; ++ii)
{
const Token& token = _tokens[ii];
if (0 != ii
&& token.spaceBefore)
{
buffer.push_back(' ');
}
const bx::StringView& lexeme = token.lexeme;
const bool escape = false
|| Kind::String == token.kind
|| Kind::Char == token.kind
;
for (const char* ptr = lexeme.getPtr(); ptr != lexeme.getTerm(); ++ptr)
{
if (escape
&& ('"' == *ptr
|| '\\' == *ptr) )
{
buffer.push_back('\\');
}
buffer.push_back(*ptr);
}
}
buffer.push_back('"');
return m_arena.intern(bx::StringView(buffer.data(), int32_t(buffer.size() ) ) );
}
bx::StringView rebuild(const stl::vector<Token>& _tokens)
{
stl::vector<char> buffer;
for (uint32_t ii = 0, num = uint32_t(_tokens.size() ); ii < num; ++ii)
{
const Token& token = _tokens[ii];
if (0 != ii
&& token.spaceBefore)
{
buffer.push_back(' ');
}
const bx::StringView& lexeme = token.lexeme;
for (const char* ptr = lexeme.getPtr(); ptr != lexeme.getTerm(); ++ptr)
{
buffer.push_back(*ptr);
}
}
return m_arena.intern(bx::StringView(buffer.data(), int32_t(buffer.size() ) ) );
}
void paste(stl::vector<Token>& _inOutTokens, const Token& _rhs)
{
if (Kind::Placemarker == _rhs.kind)
{
return;
}
while (!_inOutTokens.empty()
&& Kind::Placemarker == _inOutTokens.back().kind)
{
_inOutTokens.pop_back();
}
if (_inOutTokens.empty() )
{
_inOutTokens.push_back(_rhs);
return;
}
Token& lhs = _inOutTokens.back();
if (Kind::Placemarker == lhs.kind)
{
lhs = _rhs;
return;
}
const bx::StringView glued = m_arena.concat(lhs.lexeme, _rhs.lexeme);
lhs.lexeme = glued;
lhs.kind = classify(glued);
}
bool hasVaOpt(const Token* _body, uint32_t _num) const
{
for (uint32_t ii = 0; ii < _num; ++ii)
{
if (Kind::Identifier == _body[ii].kind
&& isEqual(_body[ii].lexeme, "__VA_OPT__") )
{
return true;
}
}
return false;
}
void expandVaOpt(const Token* _body, uint32_t _num, bool _keep, stl::vector<Token>& _outTokens)
{
for (uint32_t ii = 0; ii < _num; )
{
const Token& token = _body[ii];
if (Kind::Identifier != token.kind
|| !isEqual(token.lexeme, "__VA_OPT__")
|| ii + 1 >= _num
|| !isPunct(_body[ii+1], "(") )
{
_outTokens.push_back(token);
++ii;
continue;
}
uint32_t depth = 1;
uint32_t jj = ii + 2;
for (; jj < _num && 0 != depth; ++jj)
{
if (isPunct(_body[jj], "(") ) { ++depth; }
else if (isPunct(_body[jj], ")") ) { --depth; }
}
const uint32_t first = ii + 2;
const uint32_t last = jj - 1;
if (_keep
&& last > first)
{
stl::vector<Token> inner;
expandVaOpt(_body + first, last - first, _keep, inner);
if (!inner.empty() )
{
inner[0].spaceBefore = token.spaceBefore;
}
for (uint32_t kk = 0, numInner = uint32_t(inner.size() ); kk < numInner; ++kk)
{
_outTokens.push_back(inner[kk]);
}
}
else
{
Token placemarker = makeToken(Kind::Placemarker, bx::StringView() );
placemarker.spaceBefore = token.spaceBefore;
_outTokens.push_back(placemarker);
}
ii = jj;
}
}
void subst(
const Macro& _macro
, const stl::vector<stl::vector<Token> >& _actuals
, const HideNode* _hide
, const SourceLocation& _location
, stl::vector<Token>& _outTokens
)
{
const Token* body = _macro.body;
uint32_t num = _macro.bodyNum;
stl::vector<Token> vaOptBody;
if (_macro.isVariadic
&& hasVaOpt(body, num) )
{
stl::vector<Token> va;
selectActual(_macro, _actuals, "__VA_ARGS__", va);
expandVaOpt(body, num, !va.empty(), vaOptBody);
body = vaOptBody.data();
num = uint32_t(vaOptBody.size() );
}
stl::vector<Token> result;
for (uint32_t ii = 0; ii < num; )
{
const Token& token = body[ii];
if (isPunct(token, ",")
&& ii + 2 < num
&& isPunct(body[ii+1], "##")
&& isParam(_macro, body[ii+2]) )
{
stl::vector<Token> arg;
selectActual(_macro, _actuals, body[ii+2].lexeme, arg);
if (!arg.empty() )
{
result.push_back(token);
for (uint32_t jj = 0, numArg = uint32_t(arg.size() ); jj < numArg; ++jj)
{
result.push_back(arg[jj]);
}
}
ii += 3;
continue;
}
if (isPunct(token, "#")
&& ii + 1 < num
&& isParam(_macro, body[ii+1]) )
{
stl::vector<Token> arg;
selectActual(_macro, _actuals, body[ii+1].lexeme, arg);
result.push_back(makeToken(Kind::String, stringize(arg) ) );
ii += 2;
continue;
}
if (isPunct(token, "##")
&& ii + 1 < num)
{
const Token& rhs = body[ii+1];
if (!isParam(_macro, rhs) )
{
paste(result, rhs);
}
else
{
stl::vector<Token> arg;
selectActual(_macro, _actuals, rhs.lexeme, arg);
if (arg.empty() )
{
paste(result, makeToken(Kind::Placemarker, bx::StringView() ) );
}
else
{
paste(result, arg[0]);
for (uint32_t jj = 1, numArg = uint32_t(arg.size() ); jj < numArg; ++jj)
{
result.push_back(arg[jj]);
}
}
}
ii += 2;
continue;
}
if (isParam(_macro, token)
&& ii + 1 < num
&& isPunct(body[ii+1], "##") )
{
stl::vector<Token> arg;
selectActual(_macro, _actuals, token.lexeme, arg);
if (arg.empty() )
{
result.push_back(makeToken(Kind::Placemarker, bx::StringView() ) );
}
else
{
for (uint32_t jj = 0, numArg = uint32_t(arg.size() ); jj < numArg; ++jj)
{
result.push_back(arg[jj]);
}
}
++ii;
continue;
}
if (isParam(_macro, token) )
{
stl::vector<Token> arg;
selectActual(_macro, _actuals, token.lexeme, arg);
stl::vector<Token> expanded;
expand(arg, expanded);
if (!expanded.empty() )
{
expanded[0].spaceBefore = token.spaceBefore;
}
for (uint32_t jj = 0, numArg = uint32_t(expanded.size() ); jj < numArg; ++jj)
{
result.push_back(expanded[jj]);
}
++ii;
continue;
}
result.push_back(token);
++ii;
}
for (uint32_t ii = 0, num2 = uint32_t(result.size() ); ii < num2; ++ii)
{
Token& token = result[ii];
if (Kind::Placemarker == token.kind)
{
continue;
}
token.hide = hideUnion(token.hide, _hide);
token.location = _location;
_outTokens.push_back(token);
}
}
static bool frontIsOpenParen(const Worklist& _work)
{
for (const Token* it = _work.begin(), *term = _work.end(); it != term; ++it)
{
if (Kind::Placemarker == it->kind
|| Kind::Newline == it->kind)
{
continue;
}
return isPunct(*it, "(");
}
return false;
}
bool collectArgs(
Worklist& _work
, stl::vector<stl::vector<Token> >& _outActuals
, const HideNode*& _outCloseHide
, uint32_t& _outSwallowedNewlines
)
{
while (!_work.isEmpty()
&& !isPunct(_work.getFront(), "(") )
{
if (Kind::Newline == _work.getFront().kind)
{
++_outSwallowedNewlines;
}
_work.popFront();
}
if (_work.isEmpty() )
{
return false;
}
_work.popFront();
stl::vector<Token> current;
int32_t depth = 0;
bool any = false;
for (;;)
{
if (_work.isEmpty() )
{
return false;
}
const Token token = _work.getFront();
_work.popFront();
if (Kind::Newline == token.kind)
{
++_outSwallowedNewlines;
continue;
}
if (isPunct(token, "(") )
{
++depth;
current.push_back(token);
continue;
}
if (isPunct(token, ")") )
{
if (0 == depth)
{
_outCloseHide = token.hide;
if (any
|| !current.empty() )
{
_outActuals.push_back(current);
}
return true;
}
--depth;
current.push_back(token);
continue;
}
if (isPunct(token, ",")
&& 0 == depth)
{
_outActuals.push_back(current);
current.clear();
any = true;
continue;
}
current.push_back(token);
}
}
bool checkArity(const Macro& _macro, const stl::vector<stl::vector<Token> >& _actuals)
{
const uint32_t num = uint32_t(_actuals.size() );
const uint32_t params = uint32_t(_macro.params.size() );
const uint32_t given = 0 == num && 1 == params ? 1 : num;
if (given < params)
{
error("Too few arguments in macro invocation");
return false;
}
if (!_macro.isVariadic
&& given > params)
{
error("Too many arguments in macro invocation");
return false;
}
return true;
}
void expand(const stl::vector<Token>& _tokens, stl::vector<Token>& _outTokens)
{
Worklist work(_tokens);
while (!work.isEmpty() )
{
const Token token = work.getFront();
work.popFront();
if (Kind::Identifier != token.kind)
{
if (Kind::Placemarker != token.kind)
{
_outTokens.push_back(token);
}
continue;
}
if (hideContains(token.hide, token.lexeme) )
{
_outTokens.push_back(token);
continue;
}
if (m_inCondition
&& isEqual(token.lexeme, "defined") )
{
_outTokens.push_back(token);
copyDefinedOperand(work, _outTokens);
continue;
}
if (isEqual(token.lexeme, "__LINE__") )
{
char tmp[16];
const int32_t len = bx::toString(tmp, BX_COUNTOF(tmp), token.location.line);
Token line = makeToken(Kind::Number, m_arena.intern(bx::StringView(tmp, len) ) );
line.spaceBefore = token.spaceBefore;
_outTokens.push_back(line);
continue;
}
if (isEqual(token.lexeme, "__FILE__") )
{
const bx::FilePath filePath(m_location.file);
const bx::StringView parts[] = { "\"", pathOf(filePath), "\"" };
const bx::StringView quoted = m_arena.concat(parts, BX_COUNTOF(parts) );
Token file = makeToken(Kind::String, quoted);
file.spaceBefore = token.spaceBefore;
_outTokens.push_back(file);
continue;
}
if (isEqual(token.lexeme, "__COUNTER__") )
{
char tmp[16];
const int32_t len = bx::toString(tmp, BX_COUNTOF(tmp), m_counter++);
Token count = makeToken(Kind::Number, m_arena.intern(bx::StringView(tmp, len) ) );
count.spaceBefore = token.spaceBefore;
_outTokens.push_back(count);
continue;
}
const Macro* macro = findMacro(token.lexeme);
if (NULL == macro)
{
_outTokens.push_back(token);
continue;
}
if (!macro->isFunction)
{
const HideNode* hide = hideAdd(token.hide, token.lexeme);
const stl::vector<stl::vector<Token> > noActuals;
stl::vector<Token> replacement;
subst(*macro, noActuals, hide, token.location, replacement);
if (!replacement.empty() )
{
replacement[0].spaceBefore = token.spaceBefore;
}
work.prepend(replacement);
continue;
}
if (!frontIsOpenParen(work) )
{
_outTokens.push_back(token);
continue;
}
stl::vector<stl::vector<Token> > actuals;
const HideNode* closeHide = NULL;
uint32_t swallowedNewlines = 0;
if (!collectArgs(work, actuals, closeHide, swallowedNewlines) )
{
error("Unterminated macro invocation");
_outTokens.push_back(token);
continue;
}
if (!checkArity(*macro, actuals) )
{
_outTokens.push_back(token);
continue;
}
const HideNode* hide = hideAdd(hideIntersect(token.hide, closeHide), token.lexeme);
stl::vector<Token> replacement;
subst(*macro, actuals, hide, token.location, replacement);
if (!replacement.empty() )
{
replacement[0].spaceBefore = token.spaceBefore;
}
for (uint32_t ii = 0; ii < swallowedNewlines; ++ii)
{
replacement.push_back(makeToken(Kind::Newline, "\n") );
}
work.prepend(replacement);
}
}
static bool needSpace(const Token& _lhs, const Token& _rhs)
{
if (_lhs.lexeme.isEmpty()
|| _rhs.lexeme.isEmpty() )
{
return false;
}
return bx::isIdentChar(_lhs.lexeme.getTerm()[-1])
&& bx::isIdentChar(*_rhs.lexeme.getPtr() )
;
}
void append(const bx::StringView& _str)
{
bx::write(m_out, _str.getPtr(), _str.getLength(), &m_err);
}
void append(char _ch)
{
bx::write(m_out, _ch, &m_err);
}
void emit(const stl::vector<Token>& _tokens, bool _atLineStart = true)
{
bool atLineStart = _atLineStart;
const Token* prev = NULL;
for (uint32_t ii = 0, num = uint32_t(_tokens.size() ); ii < num; ++ii)
{
const Token& token = _tokens[ii];
if (Kind::Placemarker == token.kind)
{
continue;
}
if (Kind::Newline == token.kind)
{
append('\n');
++m_outLocation.line;
atLineStart = true;
prev = NULL;
continue;
}
if (atLineStart)
{
syncLine(token.location);
}
else if (NULL != prev
&& (token.spaceBefore || needSpace(*prev, token) ) )
{
append(' ');
}
append(token.lexeme);
atLineStart = false;
prev = &token;
}
}
class Eval
{
BX_CLASS(Eval
, NO_DEFAULT_CTOR
, NO_COPY
);
public:
struct Value
{
int64_t value;
bool isUnsigned;
};
Eval(PreprocessorImpl& _impl, const stl::vector<Token>& _tokens)
: m_impl(_impl)
, m_tokens(_tokens)
, m_pos(0)
, m_evaluated(true)
{
}
Value parseTernary()
{
const Value cond = parseBinary(1);
if (!peekPunct("?") )
{
return cond;
}
advance();
const bool taken = 0 != cond.value;
const Value lhs = parseUnevaluatedUnless(taken);
if (peekPunct(":") )
{
advance();
}
else
{
m_impl.error("Missing ':' in #if expression");
}
const Value rhs = parseUnevaluatedUnless(!taken);
Value result = taken ? lhs : rhs;
result.isUnsigned = lhs.isUnsigned || rhs.isUnsigned;
return result;
}
private:
Value parseUnevaluatedUnless(bool _evaluated)
{
const bool old = m_evaluated;
m_evaluated = m_evaluated && _evaluated;
const Value value = parseTernary();
m_evaluated = old;
return value;
}
static Value makeValue(int64_t _value, bool _isUnsigned)
{
Value value;
value.value = _value;
value.isUnsigned = _isUnsigned;
return value;
}
const Token* peek() const
{
return m_pos < m_tokens.size() ? &m_tokens[m_pos] : NULL;
}
bool peekPunct(const bx::StringView& _lexeme) const
{
const Token* token = peek();
return NULL != token
&& isPunct(*token, _lexeme)
;
}
void advance()
{
++m_pos;
}
Value parsePrimary()
{
const Token* token = peek();
if (NULL == token)
{
m_impl.error("Unexpected end of #if expression");
return makeValue(0, false);
}
if (peekPunct("(") )
{
advance();
const Value value = parseTernary();
if (peekPunct(")") )
{
advance();
}
else
{
m_impl.error("Missing ')' in #if expression");
}
return value;
}
if (peekPunct("!") )
{
advance();
return makeValue(0 != parseUnary().value ? 0 : 1, false);
}
if (peekPunct("~") )
{
advance();
const Value value = parseUnary();
return makeValue(~value.value, value.isUnsigned);
}
if (peekPunct("-") )
{
advance();
const Value value = parseUnary();
return makeValue(int64_t(0ull - uint64_t(value.value) ), value.isUnsigned);
}
if (peekPunct("+") )
{
advance();
return parseUnary();
}
if (Kind::Number == token->kind)
{
advance();
return parseIntLiteral(token->lexeme);
}
if (Kind::Char == token->kind)
{
advance();
return makeValue(parseCharLiteral(token->lexeme), false);
}
advance();
return makeValue(0, false);
}
Value parseUnary()
{
return parsePrimary();
}
Value parseBinary(int32_t _minPrec)
{
Value lhs = parseUnary();
for (;;)
{
const Token* token = peek();
if (NULL == token
|| Kind::Punct != token->kind)
{
break;
}
const Op op = findOp(token->lexeme);
if (Op::Count == op
|| getOpInfo(op).precedence < _minPrec)
{
break;
}
advance();
const bool shortCircuit = false
|| (Op::LogicalAnd == op && 0 == lhs.value)
|| (Op::LogicalOr == op && 0 != lhs.value)
;
const bool old = m_evaluated;
m_evaluated = m_evaluated && !shortCircuit;
const Value rhs = parseBinary(getOpInfo(op).precedence + 1);
m_evaluated = old;
lhs = apply(op, lhs, rhs);
}
return lhs;
}
Value apply(Op _op, const Value& _lhs, const Value& _rhs)
{
const int32_t shift = int32_t(_rhs.value & 63);
switch (_op)
{
case Op::Shl:
return _lhs.isUnsigned
? makeValue(int64_t(uint64_t(_lhs.value) << shift), true)
: makeValue(_lhs.value << shift, false)
;
case Op::Shr:
return _lhs.isUnsigned
? makeValue(int64_t(uint64_t(_lhs.value) >> shift), true)
: makeValue(_lhs.value >> shift, false)
;
case Op::LogicalAnd:
return makeValue(0 != _lhs.value && 0 != _rhs.value ? 1 : 0, false);
case Op::LogicalOr:
return makeValue(0 != _lhs.value || 0 != _rhs.value ? 1 : 0, false);
default:
break;
}
if (_lhs.isUnsigned
|| _rhs.isUnsigned)
{
return applyTyped<uint64_t>(
_op
, uint64_t(_lhs.value)
, uint64_t(_rhs.value)
, true
);
}
return applyTyped<int64_t>(_op, _lhs.value, _rhs.value, false);
}
template<typename Ty>
Value applyTyped(Op _op, Ty _lhs, Ty _rhs, bool _isUnsigned)
{
switch (_op)
{
case Op::Mul:
return makeValue(int64_t(Ty(_lhs * _rhs) ), _isUnsigned);
case Op::Div:
return makeValue(int64_t(Ty(0 != _rhs ? _lhs / _rhs : checkedZero() ) ), _isUnsigned);
case Op::Mod:
return makeValue(int64_t(Ty(0 != _rhs ? _lhs % _rhs : checkedZero() ) ), _isUnsigned);
case Op::Add:
return makeValue(int64_t(Ty(_lhs + _rhs) ), _isUnsigned);
case Op::Sub:
return makeValue(int64_t(Ty(_lhs - _rhs) ), _isUnsigned);
case Op::BitAnd:
return makeValue(int64_t(Ty(_lhs & _rhs) ), _isUnsigned);
case Op::BitXor:
return makeValue(int64_t(Ty(_lhs ^ _rhs) ), _isUnsigned);
case Op::BitOr:
return makeValue(int64_t(Ty(_lhs | _rhs) ), _isUnsigned);
case Op::Lt:
return makeValue(_lhs < _rhs ? 1 : 0, false);
case Op::Gt:
return makeValue(_lhs > _rhs ? 1 : 0, false);
case Op::LtEq:
return makeValue(_lhs <= _rhs ? 1 : 0, false);
case Op::GtEq:
return makeValue(_lhs >= _rhs ? 1 : 0, false);
case Op::Eq:
return makeValue(_lhs == _rhs ? 1 : 0, false);
case Op::NotEq:
return makeValue(_lhs != _rhs ? 1 : 0, false);
default:
BX_ASSERT(false, "Bug, _op can't be %d!", uint8_t(_op) );
return makeValue(0, false);
}
}
static Value parseIntLiteral(const bx::StringView& _text)
{
bx::Scanner scanner(_text);
int32_t base = 10;
if (!scanner.accept(bx::StringView("0x") ).isEmpty()
|| !scanner.accept(bx::StringView("0X") ).isEmpty() )
{
base = 16;
}
else if (1 < _text.getLength()
&& !scanner.accept('0').isEmpty() )
{
base = 8;
}
uint64_t value = 0;
for (;;)
{
const bx::StringView next = scanner.peek();
if (next.isEmpty() )
{
break;
}
const int32_t digit = toDigit(*next.getPtr() );
if (0 > digit
|| digit >= base)
{
break;
}
scanner.accept();
value = value * base + digit;
}
const bx::StringView suffix = scanner.acceptAll();
const bool isUnsigned = false
|| !strFind(suffix, 'u').isEmpty()
|| !strFind(suffix, 'U').isEmpty()
;
return makeValue(int64_t(value), isUnsigned);
}
static int64_t parseCharLiteral(const bx::StringView& _text)
{
bx::Scanner scanner(_text);
scanner.accept('\'');
if (scanner.accept('\\').isEmpty() )
{
const bx::StringView ch = scanner.accept();
return ch.isEmpty() ? 0 : uint8_t(*ch.getPtr() );
}
const bx::StringView escape = scanner.accept();
if (escape.isEmpty() )
{
return 0;
}
const char ch = *escape.getPtr();
switch (ch)
{
case 'a': return '\a';
case 'b': return '\b';
case 'f': return '\f';
case 'n': return '\n';
case 'r': return '\r';
case 't': return '\t';
case 'v': return '\v';
case '\\': return '\\';
case '\'': return '\'';
case '"': return '"';
case '?': return '?';
default:
break;
}
if ('x' == ch
|| 'X' == ch)
{
const bx::StringView digits = scanner.acceptWhile(bx::isHexNum);
int64_t value = 0;
for (const char* ptr = digits.getPtr(); ptr != digits.getTerm(); ++ptr)
{
value = value * 16 + toDigit(*ptr);
}
return value;
}
if (bx::isOctNum(ch) )
{
int64_t value = ch - '0';
for (uint32_t ii = 0; ii < 2; ++ii)
{
const bx::StringView digit = scanner.accept(bx::isOctNum);
if (digit.isEmpty() )
{
break;
}
value = value * 8 + (*digit.getPtr() - '0');
}
return value;
}
return uint8_t(ch);
}
int32_t checkedZero()
{
if (m_evaluated)
{
m_impl.error("Division by zero in #if expression");
}
return 0;
}
PreprocessorImpl& m_impl;
const stl::vector<Token>& m_tokens;
uint32_t m_pos;
bool m_evaluated;
};
void reduceDefined(const stl::vector<Token>& _tokens, stl::vector<Token>& _outTokens)
{
for (uint32_t ii = 0, num = uint32_t(_tokens.size() ); ii < num; )
{
const Token& token = _tokens[ii];
if (!isIdent(token, "defined") )
{
_outTokens.push_back(token);
++ii;
continue;
}
uint32_t jj = ii + 1;
bool paren = false;
if (jj < num
&& isPunct(_tokens[jj], "(") )
{
paren = true;
++jj;
}
bx::StringView name;
if (jj < num
&& Kind::Identifier == _tokens[jj].kind)
{
name = _tokens[jj].lexeme;
++jj;
}
else
{
error("Operator 'defined' requires an identifier");
}
if (paren)
{
if (jj < num
&& isPunct(_tokens[jj], ")") )
{
++jj;
}
else
{
error("Missing ')' after 'defined'");
}
}
Token result = makeToken(Kind::Number, isDefined(name) ? "1" : "0");
result.spaceBefore = token.spaceBefore;
_outTokens.push_back(result);
ii = jj;
}
}
void copyDefinedOperand(Worklist& _work, stl::vector<Token>& _outTokens)
{
if (_work.isEmpty() )
{
return;
}
Token token = _work.getFront();
if (isPunct(token, "(") )
{
_work.popFront();
_outTokens.push_back(token);
if (_work.isEmpty() )
{
return;
}
token = _work.getFront();
}
if (Kind::Identifier == token.kind)
{
_work.popFront();
_outTokens.push_back(token);
}
}
bool evalCondition(const stl::vector<Token>& _args)
{
stl::vector<Token> reduced;
reduceDefined(_args, reduced);
const bool oldInCondition = m_inCondition;
m_inCondition = true;
stl::vector<Token> expanded;
expand(reduced, expanded);
m_inCondition = oldInCondition;
stl::vector<Token> settled;
reduceDefined(expanded, settled);
Eval eval(*this, settled);
return 0 != eval.parseTernary().value;
}
void pushCond(bool _value)
{
const bool parent = condActive();
const bool active = parent && _value;
const Cond cond =
{
.active = active,
.taken = active,
.parent = parent,
.seenElse = false,
};
m_cond.push_back(cond);
}
void parseDefine(const stl::vector<Token>& _args)
{
if (_args.empty()
|| Kind::Identifier != _args[0].kind)
{
error("Macro name missing");
return;
}
Macro macro =
{
.name = _args[0].lexeme,
.params = {},
.body = NULL,
.bodyNum = 0,
.isFunction = false,
.isVariadic = false,
};
const uint32_t num = uint32_t(_args.size() );
uint32_t ii = 1;
if (ii < num
&& isPunct(_args[ii], "(")
&& !_args[ii].spaceBefore)
{
macro.isFunction = true;
++ii;
bool expectParam = true;
for (; ii < num; ++ii)
{
const Token& token = _args[ii];
if (isPunct(token, ")") )
{
++ii;
break;
}
if (isPunct(token, ",") )
{
expectParam = true;
continue;
}
if (isPunct(token, "...") )
{
macro.isVariadic = true;
expectParam = false;
continue;
}
if (Kind::Identifier == token.kind
&& expectParam)
{
if (-1 != findParam(macro, token.lexeme) )
{
error("Duplicate macro parameter");
return;
}
macro.params.push_back(token.lexeme);
expectParam = false;
continue;
}
error("Invalid macro parameter list");
return;
}
}
const uint32_t bodyNum = num - ii;
if (0 < bodyNum)
{
Token* body = m_arena.allocate<Token>(bodyNum);
for (uint32_t jj = 0; jj < bodyNum; ++jj)
{
body[jj] = _args[ii + jj];
}
body[0].spaceBefore = false;
if (isPunct(body[0], "##")
|| isPunct(body[bodyNum - 1], "##") )
{
error("'##' cannot appear at either end of a macro replacement list");
return;
}
if (macro.isFunction)
{
for (uint32_t jj = 0; jj < bodyNum; ++jj)
{
if (!isPunct(body[jj], "#") )
{
continue;
}
if (jj + 1 >= bodyNum
|| Kind::Identifier != body[jj + 1].kind
|| (-1 == findParam(macro, body[jj + 1].lexeme)
&& !(macro.isVariadic && isEqual(body[jj + 1].lexeme, "__VA_ARGS__") ) ) )
{
error("'#' must be followed by a macro parameter");
return;
}
}
}
for (uint32_t jj = 0; jj < bodyNum; ++jj)
{
if (Kind::Identifier != body[jj].kind
|| !isEqual(body[jj].lexeme, "__VA_OPT__") )
{
continue;
}
if (!macro.isVariadic)
{
error("'__VA_OPT__' can only appear in a variadic macro");
return;
}
if (jj + 1 >= bodyNum
|| !isPunct(body[jj + 1], "(") )
{
error("'__VA_OPT__' must be followed by '('");
return;
}
uint32_t depth = 1;
for (uint32_t kk = jj + 2; kk < bodyNum && 0 != depth; ++kk)
{
if (isPunct(body[kk], "(") ) { ++depth; }
else if (isPunct(body[kk], ")") ) { --depth; }
}
if (0 != depth)
{
error("Unterminated '__VA_OPT__'");
return;
}
}
macro.body = body;
macro.bodyNum = bodyNum;
}
removeMacro(macro.name);
m_macros.push_back(macro);
}
void parseUndef(const stl::vector<Token>& _args)
{
if (_args.empty()
|| Kind::Identifier != _args[0].kind)
{
error("Macro name missing");
return;
}
removeMacro(_args[0].lexeme);
}
void parseLine(const stl::vector<Token>& _args, const Token& _directive)
{
if (m_frames.empty()
|| !isEqual(frameName(m_frames.back() ), _directive.location.file) )
{
return;
}
stl::vector<Token> expanded;
const stl::vector<Token>* args = &_args;
if (!_args.empty()
&& Kind::Number != _args[0].kind)
{
expand(_args, expanded);
args = &expanded;
}
int32_t line;
if (args->empty()
|| Kind::Number != (*args)[0].kind
|| !fromString(&line, (*args)[0].lexeme)
|| 0 > line)
{
error("#line expects a decimal line number");
return;
}
Frame& frame = m_frames.back();
frame.lineDelta += line - int32_t(_directive.location.line) - 1;
if (1 < args->size() )
{
const Token& name = (*args)[1];
if (Kind::String != name.kind)
{
error("#line expects a file name string");
return;
}
frame.presumedName = m_arena.intern(
bx::StringView(name.lexeme.getPtr() + 1, name.lexeme.getTerm() - 1)
);
}
}
bool isPragmaOnce(const bx::StringView& _name) const
{
for (uint32_t ii = 0, num = uint32_t(m_pragmaOnce.size() ); ii < num; ++ii)
{
if (isEqual(m_pragmaOnce[ii], _name) )
{
return true;
}
}
return false;
}
void markPragmaOnce()
{
if (m_frames.empty() )
{
return;
}
const bx::StringView& name = m_frames.back().name;
if (!isPragmaOnce(name) )
{
m_pragmaOnce.push_back(m_arena.intern(name) );
}
}
void doInclude(const stl::vector<Token>& _args)
{
if (_args.empty() )
{
error("Invalid #include");
return;
}
bx::StringView name;
bool isSystem = false;
if (Kind::String == _args[0].kind)
{
const bx::StringView lexeme = _args[0].lexeme;
if (2 <= lexeme.getLength() )
{
name.set(lexeme.getPtr() + 1, lexeme.getTerm() - 1);
}
}
else if (isPunct(_args[0], "<") )
{
isSystem = true;
stl::vector<Token> parts;
for (uint32_t ii = 1, num = uint32_t(_args.size() ); ii < num; ++ii)
{
if (isPunct(_args[ii], ">") )
{
break;
}
parts.push_back(_args[ii]);
}
name = rebuild(parts);
}
else
{
stl::vector<Token> expanded;
expand(_args, expanded);
if (!expanded.empty()
&& (Kind::String == expanded[0].kind || isPunct(expanded[0], "<") ) )
{
doInclude(expanded);
}
else
{
error("Invalid #include");
}
return;
}
if (kMaxIncludeDepth < m_includeDepth)
{
error("#include nested too deeply");
return;
}
bx::MemoryBlock mb(m_arena.getAllocator() );
bx::MemoryWriter writer(&mb);
bx::FilePath resolved;
bx::Error err;
if (!m_callback.include(name, isSystem, m_location.file, &writer, resolved, &err)
|| !err.isOk() )
{
char tmp[1024];
bx::snprintf(tmp, BX_COUNTOF(tmp), "Could not open include file '%.*s'"
, name.getLength()
, name.getPtr()
);
error(tmp);
return;
}
const bx::StringView path = isEqual(resolved, ".") ? bx::StringView() : bx::StringView(resolved);
m_callback.depend(path);
const uint32_t size = uint32_t(bx::seek(&writer) );
const bx::StringView arenaName = m_arena.intern(path.isEmpty() ? name : path);
if (isPragmaOnce(arenaName) )
{
return;
}
const bx::StringView arenaSource = m_arena.intern(
0 == size ? bx::StringView() : bx::StringView( (const char*)mb.more(0), int32_t(size) )
);
++m_includeDepth;
pushFrame(arenaName, arenaSource, true);
}
void handleDirective(const stl::vector<Token>& _line)
{
if (_line.empty()
|| Kind::Identifier != _line[0].kind)
{
return;
}
const bx::StringView directive = _line[0].lexeme;
stl::vector<Token> args;
args.assign(_line.data() + 1, _line.data() + _line.size() );
if (isEqual(directive, "if") )
{
pushCond(condActive() && evalCondition(args) );
return;
}
if (isEqual(directive, "ifdef")
|| isEqual(directive, "ifndef") )
{
bool value = !args.empty()
&& Kind::Identifier == args[0].kind
&& isDefined(args[0].lexeme)
;
if (isEqual(directive, "ifndef") )
{
value = !value;
}
pushCond(value);
return;
}
if (isEqual(directive, "elif")
|| isEqual(directive, "elifdef")
|| isEqual(directive, "elifndef") )
{
if (m_cond.empty() )
{
error("#elif without #if");
return;
}
Cond& cond = m_cond.back();
if (cond.seenElse)
{
error("#elif after #else");
return;
}
if (cond.taken
|| !cond.parent)
{
cond.active = false;
}
else
{
if (isEqual(directive, "elif") )
{
cond.active = evalCondition(args);
}
else
{
const bool defined = true
&& !args.empty()
&& Kind::Identifier == args[0].kind
&& isDefined(args[0].lexeme)
;
cond.active = isEqual(directive, "elifdef") ? defined : !defined;
}
cond.taken = cond.active;
}
return;
}
if (isEqual(directive, "else") )
{
if (m_cond.empty() )
{
error("#else without #if");
return;
}
Cond& cond = m_cond.back();
if (cond.seenElse)
{
error("Multiple #else");
return;
}
cond.seenElse = true;
cond.active = cond.parent && !cond.taken;
cond.taken = cond.taken || cond.active;
return;
}
if (isEqual(directive, "endif") )
{
if (m_cond.empty() )
{
error("#endif without #if");
return;
}
m_cond.pop_back();
return;
}
if (!condActive() )
{
return;
}
if (isEqual(directive, "define") )
{
parseDefine(args);
}
else if (isEqual(directive, "undef") )
{
parseUndef(args);
}
else if (isEqual(directive, "include") )
{
doInclude(args);
}
else if (isEqual(directive, "error") )
{
report(true, rebuild(args) );
}
else if (isEqual(directive, "warning") )
{
report(false, rebuild(args) );
}
else if (isEqual(directive, "pragma") )
{
if (!args.empty()
&& isIdent(args[0], "once") )
{
markPragmaOnce();
return;
}
syncLine(_line[0].location);
append("#pragma");
if (!args.empty() )
{
append(' ');
}
emit(args, false);
append('\n');
++m_outLocation.line;
}
else if (isEqual(directive, "line") )
{
parseLine(args, _line[0]);
}
else
{
char tmp[256];
bx::snprintf(tmp, BX_COUNTOF(tmp), "Unknown directive '#%.*s'"
, directive.getLength()
, directive.getPtr()
);
report(false, tmp);
}
}
bool run(const bx::StringView& _name, const bx::StringView& _source, bx::WriterI* _writer, bx::Error* _err)
{
BX_ASSERT(bx::isSorted(s_ops, BX_COUNTOF(s_ops), OpInfo::cmpFn), "s_ops must be sorted!");
m_out = _writer;
m_err.reset();
m_ok = true;
m_includeDepth = 0;
m_outLocation.line = 1;
m_inCondition = false;
m_counter = 0;
m_outLocation.file.clear();
m_pragmaOnce.clear();
pushFrame(m_arena.intern(_name), m_arena.intern(_source), false);
Token token;
while (next(token) )
{
if (Kind::Newline == token.kind)
{
if (condActive() )
{
append('\n');
++m_outLocation.line;
}
continue;
}
if (isPunct(token, "#") )
{
stl::vector<Token> directive;
readLine(directive);
handleDirective(directive);
continue;
}
if (!condActive() )
{
skipLine();
continue;
}
stl::vector<Token> text;
text.push_back(token);
bool more = readLineKeepNewline(text);
while (more)
{
Token lineStart;
if (!next(lineStart) )
{
break;
}
if (Kind::Newline == lineStart.kind)
{
text.push_back(lineStart);
continue;
}
if (isPunct(lineStart, "#") )
{
unget(lineStart);
break;
}
text.push_back(lineStart);
more = readLineKeepNewline(text);
}
stl::vector<Token> expanded;
expand(text, expanded);
emit(expanded);
}
if (!m_cond.empty() )
{
error("Unterminated #if");
}
m_frames.clear();
m_arena.reset();
m_out = NULL;
if (!m_err.isOk() )
{
m_ok = false;
if (NULL != _err)
{
_err->setError(m_err.get(), m_err.getMessage() );
}
}
return m_ok;
}
void runDefine(const bx::StringView& _source)
{
pushFrame("<define>", _source, false);
Token token;
while (next(token) )
{
if (isPunct(token, "#") )
{
stl::vector<Token> directive;
readLine(directive);
handleDirective(directive);
}
}
m_frames.clear();
}
PreprocessorCallbackI& m_callback;
Arena m_arena;
stl::vector<Macro> m_macros;
stl::vector<Frame> m_frames;
stl::vector<Cond> m_cond;
stl::vector<Token> m_pending;
stl::vector<bx::StringView> m_pragmaOnce;
bx::WriterI* m_out;
bx::Error m_err;
SourceLocation m_location;
uint32_t m_includeDepth;
SourceLocation m_outLocation;
bool m_emitLine;
uint32_t m_counter;
bool m_inCondition;
bool m_ok;
};
Preprocessor::Preprocessor(PreprocessorCallbackI& _callback, bx::AllocatorI* _allocator)
: m_impl(new PreprocessorImpl(_callback, _allocator) )
{
}
Preprocessor::~Preprocessor()
{
delete m_impl;
}
void Preprocessor::define(const bx::StringView& _define)
{
bx::Scanner scanner(_define);
bx::StringView name = scanner.acceptUntil("=");
bx::StringView value("1");
if (!scanner.accept('=').isEmpty() )
{
value = scanner.acceptAll();
}
else
{
name = scanner.acceptAll();
}
const bx::StringView parts[] = { "#define ", name, " ", value, "\n" };
m_impl->runDefine(m_impl->m_arena.concat(parts, BX_COUNTOF(parts) ) );
}
void Preprocessor::undefine(const bx::StringView& _name)
{
m_impl->removeMacro(_name);
}
void Preprocessor::setEmitLineDirectives(bool _emit)
{
m_impl->m_emitLine = _emit;
}
bool Preprocessor::preprocess(
const bx::StringView& _name
, const bx::StringView& _source
, bx::WriterI* _writer
, bx::Error* _err
)
{
return m_impl->run(_name, _source, _writer, _err);
}
}