229 lines
7.6 KiB
C++
229 lines
7.6 KiB
C++
//------------------------------------------------------------------------------
|
|
// CLING - the C++ LLVM-based InterpreterG :)
|
|
// author: Axel Naumann <axel@cern.ch>
|
|
//
|
|
// This file is dual-licensed: you can choose to license it under the University
|
|
// of Illinois Open Source License or the GNU Lesser General Public License. See
|
|
// LICENSE.TXT for details.
|
|
//------------------------------------------------------------------------------
|
|
|
|
#include "InputValidator.h"
|
|
#include "MetaLexer.h"
|
|
#include <algorithm>
|
|
|
|
namespace cling {
|
|
bool InputValidator::inBlockComment() const {
|
|
return std::find(m_ParenStack.begin(), m_ParenStack.end(), tok::slash)
|
|
!= m_ParenStack.end();
|
|
}
|
|
|
|
static int findNestedBlockComments(const char* startPos, const char* endPos) {
|
|
// While probably not standard compliant, it should work fine for the indent
|
|
// Let the real parser error if the balancing is incorrect
|
|
|
|
// search forward for //, then backward for block comments
|
|
// */ last, comment has ended, doesn't matter how many /* before
|
|
// /* last, comment has begun, doesn't matter if priors ended or not
|
|
char commentTok = 0;
|
|
while (startPos < endPos) {
|
|
if (*startPos == '/') {
|
|
if (++commentTok == 2) {
|
|
while (endPos > startPos) {
|
|
switch (*endPos) {
|
|
case '*':
|
|
if (commentTok == '*')
|
|
return -1;
|
|
else
|
|
commentTok = '/';
|
|
break;
|
|
case '/':
|
|
if (commentTok == '/')
|
|
return 1;
|
|
else
|
|
commentTok = '*';
|
|
break;
|
|
default:
|
|
commentTok = 0;
|
|
break;
|
|
}
|
|
--endPos;
|
|
}
|
|
return 0;
|
|
}
|
|
} else if (commentTok)
|
|
commentTok = 0; // need a new start to double slash
|
|
++startPos;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void unwindTokens(std::deque<int>& queue, int tok) {
|
|
assert(!queue.empty() && "Token stack is empty!");
|
|
while (queue.back() != tok) {
|
|
queue.pop_back();
|
|
assert(!queue.empty() && "Token stack is empty!");
|
|
}
|
|
queue.pop_back();
|
|
}
|
|
|
|
InputValidator::ValidationResult
|
|
InputValidator::validate(llvm::StringRef line) {
|
|
ValidationResult Res = kComplete;
|
|
|
|
Token Tok;
|
|
const char* curPos = line.data();
|
|
bool multilineComment = inBlockComment();
|
|
int commentTok = multilineComment ? tok::asterik : tok::slash;
|
|
|
|
if (!multilineComment && m_ParenStack.empty()) {
|
|
// Only check for 'template' if we're not already indented
|
|
MetaLexer Lex(curPos, true);
|
|
Lex.Lex(Tok);
|
|
curPos = Lex.getLocation();
|
|
if (Tok.is(tok::ident)) {
|
|
if (Tok.getIdent()=="template")
|
|
m_ParenStack.push_back(tok::greater);
|
|
} else
|
|
curPos -= Tok.getLength(); // Rewind buffer for LexPunctuatorAndAdvance
|
|
}
|
|
|
|
do {
|
|
const char* prevStart = curPos;
|
|
if (!MetaLexer::LexPunctuatorAndAdvance(curPos, Tok)) {
|
|
// there were tokens between the previous and this Tok.
|
|
commentTok = tok::slash;
|
|
}
|
|
const int kind = (int)Tok.getKind();
|
|
|
|
if (kind == commentTok) {
|
|
if (kind == tok::slash) {
|
|
if (multilineComment) {
|
|
// exiting a comment, unwind the stack
|
|
multilineComment = false;
|
|
commentTok = tok::slash;
|
|
unwindTokens(m_ParenStack, tok::slash);
|
|
}
|
|
// If we have a closing comment without a start it will be transformed
|
|
// to */; and clang reports an error for both the */ and the ;
|
|
// If we return kIncomplete, then just one error is printed, but too
|
|
// late: after the user has another expression which will always fail.
|
|
// So just deal with two errors for now
|
|
// else if (prevKind == tok::asterik) {
|
|
// Res = kIncomplete;
|
|
// break;
|
|
// }
|
|
else // wait for an asterik
|
|
commentTok = tok::asterik;
|
|
}
|
|
else {
|
|
assert(commentTok == tok::asterik && "Comment token not / or *");
|
|
if (!multilineComment) {
|
|
// entering a new comment
|
|
multilineComment = true;
|
|
m_ParenStack.push_back(tok::slash);
|
|
}
|
|
else // wait for closing / (must be next token)
|
|
commentTok = tok::slash;
|
|
}
|
|
}
|
|
else {
|
|
// If we're in a multiline, and waiting for the closing slash
|
|
// we gonna have to wait for another asterik first
|
|
if (multilineComment) {
|
|
if (kind == tok::eof) {
|
|
switch (findNestedBlockComments(prevStart, curPos)) {
|
|
case -1: unwindTokens(m_ParenStack, tok::slash);
|
|
case 1:
|
|
case 0: break;
|
|
default: assert(0 && "Nested block comment count"); break;
|
|
}
|
|
// eof, were done anyway
|
|
break;
|
|
}
|
|
else if (commentTok == tok::slash) {
|
|
// Cancel the wait for a slash, but only if current token isn't
|
|
// also an asterik.
|
|
if (kind != tok::asterik)
|
|
commentTok = tok::asterik;
|
|
}
|
|
}
|
|
|
|
if (kind >= (int)tok::l_square && kind <= (int)tok::r_brace) {
|
|
// The closing paren kind is open paren kind + 1 (i.e odd number)
|
|
if (kind % 2) {
|
|
int prev = m_ParenStack.empty() ? -1: m_ParenStack.back();
|
|
// closing the right one?
|
|
if (prev != kind - 1) {
|
|
if (multilineComment)
|
|
continue;
|
|
Res = kMismatch;
|
|
break;
|
|
}
|
|
m_ParenStack.pop_back();
|
|
|
|
// Right brace will pop a template if their is one
|
|
if (kind == tok::r_brace && m_ParenStack.size() == 1 ) {
|
|
if (m_ParenStack.back() == tok::greater)
|
|
m_ParenStack.pop_back();
|
|
}
|
|
}
|
|
else
|
|
m_ParenStack.push_back(kind);
|
|
}
|
|
else if (kind == tok::hash) {
|
|
MetaLexer Lex(curPos);
|
|
Lex.SkipWhitespace();
|
|
Lex.LexAnyString(Tok);
|
|
const llvm::StringRef PPtk = Tok.getIdent();
|
|
if (PPtk.startswith("endif")
|
|
&& (PPtk.size() > 5 ? PPtk[5]=='/' || isspace(PPtk[5]) : true)) {
|
|
if (m_ParenStack.empty() || m_ParenStack.back() != tok::hash) {
|
|
Res = kMismatch;
|
|
break;
|
|
}
|
|
m_ParenStack.pop_back();
|
|
}
|
|
else if (PPtk.startswith("if")) {
|
|
m_ParenStack.push_back(tok::hash);
|
|
}
|
|
}
|
|
else if (kind == tok::semicolon) {
|
|
// Template forward declatation
|
|
if (m_ParenStack.size() == 1 && m_ParenStack.back()==tok::greater)
|
|
m_ParenStack.pop_back();
|
|
}
|
|
else if (kind >= (int)tok::stringlit && kind <= (int)tok::charlit) {
|
|
MetaLexer::LexQuotedStringAndAdvance(curPos, Tok);
|
|
}
|
|
}
|
|
} while (Tok.isNot(tok::eof));
|
|
|
|
if (!m_ParenStack.empty() && Res != kMismatch)
|
|
Res = kIncomplete;
|
|
|
|
if (!m_Input.empty()) {
|
|
if (!m_ParenStack.empty() && (m_ParenStack.back() == tok::stringlit
|
|
|| m_ParenStack.back() == tok::charlit))
|
|
m_Input.append("\\n");
|
|
else
|
|
m_Input.append("\n");
|
|
}
|
|
else
|
|
m_Input = "";
|
|
|
|
m_Input.append(line);
|
|
|
|
return Res;
|
|
}
|
|
|
|
void InputValidator::reset(std::string* input) {
|
|
if (input) {
|
|
assert(input->empty() && "InputValidator::reset got non empty argument");
|
|
input->swap(m_Input);
|
|
} else
|
|
std::string().swap(m_Input);
|
|
|
|
std::deque<int>().swap(m_ParenStack);
|
|
}
|
|
} // end namespace cling
|