//------------------------------------------------------------------------------ // CLING - the C++ LLVM-based InterpreterG :) // author: Vassil Vassilev // // 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 "MetaParser.h" #include "MetaLexer.h" #include "MetaSema.h" #include "cling/Interpreter/Interpreter.h" #include "cling/Interpreter/InvocationOptions.h" #include "cling/Interpreter/StoredValueRef.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" namespace cling { MetaParser::MetaParser(MetaSema* Actions) { m_Lexer.reset(0); m_Actions.reset(Actions); const InvocationOptions& Opts = Actions->getInterpreter().getOptions(); MetaLexer metaSymbolLexer(Opts.MetaString); Token Tok; while(true) { metaSymbolLexer.Lex(Tok); if (Tok.is(tok::eof)) break; m_MetaSymbolCache.push_back(Tok); } } void MetaParser::enterNewInputLine(llvm::StringRef Line) { m_Lexer.reset(new MetaLexer(Line)); m_TokenCache.clear(); } void MetaParser::consumeToken() { if (m_TokenCache.size()) m_TokenCache.erase(m_TokenCache.begin()); lookAhead(0); } void MetaParser::consumeAnyStringToken(tok::TokenKind stopAt/*=tok::space*/) { consumeToken(); // we have to merge the tokens from the queue until we reach eof token or // space token skipWhitespace(); // Add the new token in which we will merge the others. Token& MergedTok = m_TokenCache.front(); if (MergedTok.is(stopAt) || MergedTok.is(tok::eof) || MergedTok.is(tok::comment)) return; Token Tok = lookAhead(1); while (Tok.isNot(stopAt) && Tok.isNot(tok::eof)){ //MergedTok.setLength(MergedTok.getLength() + Tok.getLength()); m_TokenCache.erase(m_TokenCache.begin() + 1); Tok = lookAhead(1); } MergedTok.setKind(tok::raw_ident); MergedTok.setLength(Tok.getBufStart() - MergedTok.getBufStart()); } const Token& MetaParser::lookAhead(unsigned N) { if (N < m_TokenCache.size()) return m_TokenCache[N]; for (unsigned C = N+1 - m_TokenCache.size(); C > 0; --C) { m_TokenCache.push_back(Token()); m_Lexer->Lex(m_TokenCache.back()); } return m_TokenCache.back(); } void MetaParser::skipWhitespace() { while(getCurTok().is(tok::space)) consumeToken(); } bool MetaParser::isMetaCommand(MetaSema::ActionResult& actionResult, StoredValueRef* resultValue) { return isCommandSymbol() && isCommand(actionResult, resultValue); } bool MetaParser::isQuitRequested() const { return m_Actions->isQuitRequested(); } bool MetaParser::isCommandSymbol() { for (size_t i = 0; i < m_MetaSymbolCache.size(); ++i) { if (getCurTok().getKind() != m_MetaSymbolCache[i].getKind()) return false; consumeToken(); } return true; } bool MetaParser::isCommand(MetaSema::ActionResult& actionResult, StoredValueRef* resultValue) { if (resultValue) *resultValue = StoredValueRef::invalidValue(); return isLCommand(actionResult) || isXCommand(actionResult, resultValue) || isqCommand() || isUCommand(actionResult) || isICommand() || isOCommand() || israwInputCommand() || isprintASTCommand() || isdynamicExtensionsCommand() || ishelpCommand() || isfileExCommand() || isfilesCommand() || isClassCommand() || isgCommand() || isTypedefCommand() || isprintIRCommand() || isShellCommand(actionResult, resultValue) || isstoreStateCommand() || iscompareStateCommand() || isundoCommand() || isRedirectCommand(actionResult); } // L := 'L' FilePath // FilePath := AnyString // AnyString := .*^(' ' | '\t') bool MetaParser::isLCommand(MetaSema::ActionResult& actionResult) { bool result = false; if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("L")) { consumeAnyStringToken(); if (getCurTok().is(tok::raw_ident)) { result = true; actionResult = m_Actions->actOnLCommand(getCurTok().getIdent()); consumeToken(); if (getCurTok().is(tok::comment)) { consumeAnyStringToken(); m_Actions->actOnComment(getCurTok().getIdent()); } } } // TODO: Some fine grained diagnostics return result; } // >RedirectCommand := '>' FilePath // FilePath := AnyString // AnyString := .*^(' ' | '\t') bool MetaParser::isRedirectCommand(MetaSema::ActionResult& actionResult) { unsigned constant_FD = 0; // Default redirect is stdout. MetaProcessor::RedirectionScope stream = MetaProcessor::kSTDOUT; if (getCurTok().is(tok::constant)) { // > or 1> the redirection is for stdout stream // 2> redirection for stderr stream constant_FD = getCurTok().getConstant(); if (constant_FD == 2) { stream = MetaProcessor::kSTDERR; // Wrong constant_FD, do not redirect. } else if (constant_FD != 1) { llvm::errs() << "cling::MetaParser::isRedirectCommand():" << "invalid file descriptor number " << constant_FD <<"\n"; return true; } consumeToken(); } // &> redirection for both stdout & stderr if (getCurTok().is(tok::ampersand)) { if (constant_FD != 2) { stream = MetaProcessor::kSTDBOTH; } consumeToken(); } if (getCurTok().is(tok::greater)) { bool append = false; consumeToken(); // check for syntax like: 2>&1 if (getCurTok().is(tok::ampersand)) { if (constant_FD != 2) { stream = MetaProcessor::kSTDBOTH; } consumeToken(); } else { // check whether we have >> if (getCurTok().is(tok::greater)) { append = true; consumeToken(); } } llvm::StringRef file; if (getCurTok().is(tok::constant) && getCurTok().getConstant() == 1) { file = llvm::StringRef("_IO_2_1_stdout_"); } else { if (getCurTok().is(tok::eof)) { file = llvm::StringRef(); } else { consumeAnyStringToken(); if (getCurTok().is(tok::raw_ident)) { file = getCurTok().getIdent(); consumeToken(); } else { file = llvm::StringRef(); } } } // Empty file means std. actionResult = m_Actions->actOnRedirectCommand(file/*file*/, stream/*which stream to redirect*/, append/*append mode*/); return true; } return false; } // XCommand := 'x' FilePath[ArgList] | 'X' FilePath[ArgList] // FilePath := AnyString // ArgList := (ExtraArgList) ' ' [ArgList] // ExtraArgList := AnyString [, ExtraArgList] bool MetaParser::isXCommand(MetaSema::ActionResult& actionResult, StoredValueRef* resultValue) { if (resultValue) *resultValue = StoredValueRef::invalidValue(); const Token& Tok = getCurTok(); if (Tok.is(tok::ident) && (Tok.getIdent().equals("x") || Tok.getIdent().equals("X"))) { // There might be ArgList consumeAnyStringToken(tok::l_paren); llvm::StringRef file(getCurTok().getIdent()); llvm::StringRef args; consumeToken(); if (getCurTok().is(tok::l_paren) && isExtraArgList()) { args = getCurTok().getIdent(); consumeToken(); // consume the closing paren } actionResult = m_Actions->actOnxCommand(file, args, resultValue); if (getCurTok().is(tok::comment)) { consumeAnyStringToken(); m_Actions->actOnComment(getCurTok().getIdent()); } return true; } return false; } // ExtraArgList := AnyString [, ExtraArgList] bool MetaParser::isExtraArgList() { // This might be expanded if we need better arg parsing. consumeAnyStringToken(tok::r_paren); return getCurTok().is(tok::raw_ident); } bool MetaParser::isqCommand() { bool result = false; if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("q")) { result = true; m_Actions->actOnqCommand(); } return result; } bool MetaParser::isUCommand(MetaSema::ActionResult& actionResult) { actionResult = MetaSema::AR_Failure; if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("U")) { actionResult = m_Actions->actOnUCommand(); return true; } return false; } bool MetaParser::isICommand() { if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("I")) { consumeAnyStringToken(); llvm::StringRef path; if (getCurTok().is(tok::raw_ident)) path = getCurTok().getIdent(); m_Actions->actOnICommand(path); return true; } return false; } bool MetaParser::isOCommand() { const Token& currTok = getCurTok(); if (currTok.is(tok::ident)) { llvm::StringRef ident = currTok.getIdent(); if (ident.startswith("O")) { if (ident.size() > 1) { int level = 0; if (!ident.substr(1).getAsInteger(10, level) && level >= 0) { consumeAnyStringToken(tok::eof); if (getCurTok().is(tok::raw_ident)) return false; //TODO: Process .OXXX here as .O with level XXX. return true; } } else { consumeAnyStringToken(tok::eof); const Token& lastStringToken = getCurTok(); if (lastStringToken.is(tok::raw_ident) && lastStringToken.getLength()) { int level = 0; if (!lastStringToken.getIdent().getAsInteger(10, level) && level >= 0) { //TODO: process .O XXX return true; } } else { //TODO: process .O return true; } } } } return false; } bool MetaParser::israwInputCommand() { if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("rawInput")) { MetaSema::SwitchMode mode = MetaSema::kToggle; consumeToken(); skipWhitespace(); if (getCurTok().is(tok::constant)) mode = (MetaSema::SwitchMode)getCurTok().getConstantAsBool(); m_Actions->actOnrawInputCommand(mode); return true; } return false; } bool MetaParser::isprintASTCommand() { if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("printAST")) { MetaSema::SwitchMode mode = MetaSema::kToggle; consumeToken(); skipWhitespace(); if (getCurTok().is(tok::constant)) mode = (MetaSema::SwitchMode)getCurTok().getConstantAsBool(); m_Actions->actOnprintASTCommand(mode); return true; } return false; } bool MetaParser::isprintIRCommand() { if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("printIR")) { MetaSema::SwitchMode mode = MetaSema::kToggle; consumeToken(); skipWhitespace(); if (getCurTok().is(tok::constant)) mode = (MetaSema::SwitchMode)getCurTok().getConstantAsBool(); m_Actions->actOnprintIRCommand(mode); return true; } return false; } bool MetaParser::isstoreStateCommand() { if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("storeState")) { //MetaSema::SwitchMode mode = MetaSema::kToggle; consumeToken(); skipWhitespace(); if (!getCurTok().is(tok::quote)) return false; // FIXME: Issue proper diagnostics consumeToken(); if (!getCurTok().is(tok::ident)) return false; // FIXME: Issue proper diagnostics std::string ident = getCurTok().getIdent(); consumeToken(); if (!getCurTok().is(tok::quote)) return false; // FIXME: Issue proper diagnostics m_Actions->actOnstoreStateCommand(ident); return true; } return false; } bool MetaParser::iscompareStateCommand() { if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("compareState")) { //MetaSema::SwitchMode mode = MetaSema::kToggle; consumeToken(); skipWhitespace(); if (!getCurTok().is(tok::quote)) return false; // FIXME: Issue proper diagnostics consumeToken(); if (!getCurTok().is(tok::ident)) return false; // FIXME: Issue proper diagnostics std::string ident = getCurTok().getIdent(); consumeToken(); if (!getCurTok().is(tok::quote)) return false; // FIXME: Issue proper diagnostics m_Actions->actOncompareStateCommand(ident); return true; } return false; } bool MetaParser::isundoCommand() { if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("undo")) { consumeToken(); skipWhitespace(); const Token& next = getCurTok(); if (next.is(tok::constant)) m_Actions->actOnUCommand(next.getConstant()); else m_Actions->actOnUCommand(); return true; } return false; } bool MetaParser::isdynamicExtensionsCommand() { if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("dynamicExtensions")) { MetaSema::SwitchMode mode = MetaSema::kToggle; consumeToken(); skipWhitespace(); if (getCurTok().is(tok::constant)) mode = (MetaSema::SwitchMode)getCurTok().getConstantAsBool(); m_Actions->actOndynamicExtensionsCommand(mode); return true; } return false; } bool MetaParser::ishelpCommand() { const Token& Tok = getCurTok(); if (Tok.is(tok::quest_mark) || (Tok.is(tok::ident) && Tok.getIdent().equals("help"))) { m_Actions->actOnhelpCommand(); return true; } return false; } bool MetaParser::isfileExCommand() { if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("fileEx")) { m_Actions->actOnfileExCommand(); return true; } return false; } bool MetaParser::isfilesCommand() { if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("files")) { m_Actions->actOnfilesCommand(); return true; } return false; } bool MetaParser::isClassCommand() { const Token& Tok = getCurTok(); if (Tok.is(tok::ident)) { if (Tok.getIdent().equals("class")) { consumeAnyStringToken(tok::eof); const Token& NextTok = getCurTok(); llvm::StringRef className; if (NextTok.is(tok::raw_ident)) className = NextTok.getIdent(); m_Actions->actOnclassCommand(className); return true; } else if (Tok.getIdent().equals("Class")) { m_Actions->actOnClassCommand(); return true; } } return false; } bool MetaParser::isgCommand() { if (getCurTok().is(tok::ident) && getCurTok().getIdent().equals("g")) { consumeToken(); skipWhitespace(); llvm::StringRef varName; if (getCurTok().is(tok::ident)) varName = getCurTok().getIdent(); m_Actions->actOngCommand(varName); return true; } return false; } bool MetaParser::isTypedefCommand() { const Token& Tok = getCurTok(); if (Tok.is(tok::ident)) { if (Tok.getIdent().equals("typedef")) { consumeAnyStringToken(tok::eof); const Token& NextTok = getCurTok(); llvm::StringRef typedefName; if (NextTok.is(tok::raw_ident)) typedefName = NextTok.getIdent(); m_Actions->actOnTypedefCommand(typedefName); return true; } } return false; } bool MetaParser::isShellCommand(MetaSema::ActionResult& actionResult, StoredValueRef* resultValue) { if (resultValue) *resultValue = StoredValueRef::invalidValue(); actionResult = MetaSema::AR_Failure; const Token& Tok = getCurTok(); if (Tok.is(tok::excl_mark)) { consumeAnyStringToken(tok::eof); const Token& NextTok = getCurTok(); if (NextTok.is(tok::raw_ident)) { llvm::StringRef commandLine(NextTok.getIdent()); if (!commandLine.empty()) actionResult = m_Actions->actOnShellCommand(commandLine, resultValue); } return true; } return false; } } // end namespace cling