void PHPSourceFile::ReadImplements(wxArrayString& impls) { wxString type; phpLexerToken token; while(NextToken(token)) { switch(token.type) { case kPHP_T_IDENTIFIER: case kPHP_T_NS_SEPARATOR: type << token.text; break; case ',': // More to come if(!type.IsEmpty()) { wxString fullyQualifiedType = MakeIdentifierAbsolute(type); if(impls.Index(fullyQualifiedType) == wxNOT_FOUND) { impls.Add(fullyQualifiedType); } type.clear(); } break; default: // unexpected token if(!type.IsEmpty()) { wxString fullyQualifiedType = MakeIdentifierAbsolute(type); if(impls.Index(fullyQualifiedType) == wxNOT_FOUND) { impls.Add(fullyQualifiedType); } type.clear(); } UngetToken(token); return; } } }
bool PHPSourceFile::ReadCommaSeparatedIdentifiers(int delim, wxArrayString& list) { phpLexerToken token; wxString temp; while(NextToken(token)) { if(token.IsAnyComment()) continue; if(token.type == delim) { if(!temp.IsEmpty() && list.Index(temp) == wxNOT_FOUND) { list.Add(MakeIdentifierAbsolute(temp)); } UngetToken(token); return true; } switch(token.type) { case ',': if(list.Index(temp) == wxNOT_FOUND) { list.Add(MakeIdentifierAbsolute(temp)); } temp.clear(); break; default: temp << token.text; break; } } return false; }
void PHPSourceFile::OnUseTrait() { PHPEntityBase::Ptr_t clas = CurrentScope(); if(!clas) return; // Collect the identifiers followed the 'use' statement wxArrayString identifiers; wxString tempname; phpLexerToken token; while(NextToken(token)) { switch(token.type) { case ',': { if(!tempname.IsEmpty()) { identifiers.Add(MakeIdentifierAbsolute(tempname)); } tempname.clear(); } break; case ';': { if(!tempname.IsEmpty()) { identifiers.Add(MakeIdentifierAbsolute(tempname)); } tempname.clear(); // add the traits as list of 'extends' clas->Cast<PHPEntityClass>()->SetTraits(identifiers); return; } break; default: tempname << token.text; break; } } }
void PHPSourceFile::OnClass(const phpLexerToken& tok) { // A "complex" example: class A extends BaseClass implements C, D {} // Read until we get the class name phpLexerToken token; while(NextToken(token)) { if(token.IsAnyComment()) continue; if(token.type != kPHP_T_IDENTIFIER) { // expecting the class name return; } break; } // create new class entity PHPEntityBase::Ptr_t klass(new PHPEntityClass()); klass->SetFilename(m_filename.GetFullPath()); PHPEntityClass* pClass = klass->Cast<PHPEntityClass>(); // Is the class an interface? pClass->SetIsInterface(tok.type == kPHP_T_INTERFACE); pClass->SetIsTrait(tok.type == kPHP_T_TRAIT); pClass->SetFullName(MakeIdentifierAbsolute(token.text)); pClass->SetLine(token.lineNumber); while(NextToken(token)) { if(token.IsAnyComment()) continue; switch(token.type) { case kPHP_T_EXTENDS: { // inheritance if(!ReadUntilFound(kPHP_T_IDENTIFIER, token)) return; pClass->SetExtends(MakeIdentifierAbsolute(token.text)); } break; case kPHP_T_IMPLEMENTS: { wxArrayString implements; if(!ReadCommaSeparatedIdentifiers('{', implements)) return; pClass->SetImplements(implements); } break; case '{': { // entering the class body // add the current class to the current scope CurrentScope()->AddChild(klass); m_scopes.push_back(klass); Parse(m_depth - 1); if(!m_reachedEOF) { m_scopes.pop_back(); } return; } default: break; } } }
wxString PHPSourceFile::ReadType() { bool cont = true; wxString type; phpLexerToken token; while(cont && NextToken(token)) { switch(token.type) { case kPHP_T_IDENTIFIER: type << token.text; break; case kPHP_T_NS_SEPARATOR: type << token.text; break; default: // restore the token so next call to NextToken // will pick it up again UngetToken(token); cont = false; break; } } type = MakeIdentifierAbsolute(type); return type; }
bool PHPSourceFile::ReadVariableInitialization(PHPEntityBase::Ptr_t var) { phpLexerToken token; if(!NextToken(token)) { return false; } if(token.type != '=') { // restore the token UngetToken(token); return false; } wxString expr; if(!ReadExpression(expr)) { return false; // EOF } // Optimize 'new ClassName(..)' expression if(expr.StartsWith("new")) { expr = expr.Mid(3); expr.Trim().Trim(false); expr = expr.BeforeFirst('('); expr.Trim().Trim(false); var->Cast<PHPEntityVariable>()->SetTypeHint(MakeIdentifierAbsolute(expr)); } else { // keep the expression var->Cast<PHPEntityVariable>()->SetExpressionHint(expr); } return true; }
wxString PHPSourceFile::ReadType() { bool cont = true; wxString type; phpLexerToken token; while(cont && NextToken(token)) { switch(token.type) { case kPHP_T_IDENTIFIER: type << token.text; break; case kPHP_T_NS_SEPARATOR: type << token.text; break; // special cases that must always be handled case '{': cont = false; break; // end of special cases default: cont = false; break; } } type = MakeIdentifierAbsolute(type); return type; }
void PHPSourceFile::OnUse() { wxString fullname, alias, temp; phpLexerToken token; bool cont = true; while(cont && NextToken(token)) { switch(token.type) { case ',': case ';': { if(fullname.IsEmpty()) { // no full name yet fullname.swap(temp); } else if(alias.IsEmpty()) { alias.swap(temp); } if(alias.IsEmpty()) { // no alias provided, use the last part of the fullname alias = fullname.AfterLast('\\'); } if(!fullname.IsEmpty() && !alias.IsEmpty()) { // Use namespace is alway refered as fullpath namespace // So writing: // use Zend\Mvc\Controll\Action; // is equal for writing: // use \Zend\Mvc\Controll\Action; // For simplicitiy, we change it to fully qualified path // so parsing is easier if(!fullname.StartsWith("\\")) { fullname.Prepend("\\"); } m_aliases.insert(std::make_pair(alias, MakeIdentifierAbsolute(fullname))); } temp.clear(); fullname.clear(); alias.clear(); if(token.type == ';') { cont = false; } } break; case kPHP_T_AS: { fullname.swap(temp); temp.clear(); } break; default: temp << token.text; break; } } }
wxString PHPSourceFile::ReadExtends() { wxString type; phpLexerToken token; while(NextToken(token)) { if(token.type == kPHP_T_IDENTIFIER || token.type == kPHP_T_NS_SEPARATOR) { type << token.text; } else { UngetToken(token); break; } } type = MakeIdentifierAbsolute(type); return type; }
void PHPSourceFile::OnCatch() { // Read until we find the kPHP_T_VARIABLE bool cont(true); phpLexerToken token; wxString typehint; wxString varname; while(cont && NextToken(token)) { switch(token.type) { case kPHP_T_VARIABLE: cont = false; varname = token.text; break; case kPHP_T_IDENTIFIER: case kPHP_T_NS_SEPARATOR: typehint << token.text; break; default: break; } } if(!varname.IsEmpty()) { // we found the variable PHPEntityBase::Ptr_t var(new PHPEntityVariable()); var->SetFullName(varname); var->SetFilename(m_filename.GetFullPath()); var->SetLine(token.lineNumber); var->Cast<PHPEntityVariable>()->SetTypeHint(MakeIdentifierAbsolute(typehint)); // add the variable to the current scope if(!CurrentScope()->FindChild(var->GetFullName(), true)) { CurrentScope()->AddChild(var); } } }
void PHPSourceFile::OnVariable(const phpLexerToken& tok) { phpLexerToken token; // Read until we find the ';' std::vector<phpLexerToken> tokens; PHPEntityBase::Ptr_t var(new PHPEntityVariable()); var->SetFullName(tok.text); var->SetFilename(m_filename.GetFullPath()); var->SetLine(tok.lineNumber); if(!CurrentScope()->FindChild(var->GetFullName(), true)) { CurrentScope()->AddChild(var); } if(!NextToken(token)) return; if(token.type != '=') { m_lookBackTokens.clear(); return; } wxString expr; if(!ReadExpression(expr)) return; // EOF // Optimize 'new ClassName(..)' expression if(expr.StartsWith("new")) { expr = expr.Mid(3); expr.Trim().Trim(false); expr = expr.BeforeFirst('('); expr.Trim().Trim(false); var->Cast<PHPEntityVariable>()->SetTypeHint(MakeIdentifierAbsolute(expr)); } else { // keep the expression var->Cast<PHPEntityVariable>()->SetExpressionHint(expr); } }
void PHPSourceFile::Parse(int exitDepth) { int retDepth = exitDepth; phpLexerToken token; while(NextToken(token)) { switch(token.type) { case '=': m_lookBackTokens.clear(); break; case '{': m_lookBackTokens.clear(); break; case '}': m_lookBackTokens.clear(); if(m_depth == retDepth) { return; } break; case ';': m_lookBackTokens.clear(); break; case kPHP_T_VARIABLE: if(!CurrentScope()->Is(kEntityTypeClass)) { // A global variable OnVariable(token); } break; case kPHP_T_CATCH: // found 'catch (...)' OnCatch(); break; case kPHP_T_PUBLIC: case kPHP_T_PRIVATE: case kPHP_T_PROTECTED: { int visibility = token.type; PHPEntityClass* cls = CurrentScope()->Cast<PHPEntityClass>(); if(cls) { /// keep the current token m_lookBackTokens.push_back(token); // Now we have a small problem here: // public can be a start for a member or a function // we let the lexer run forward until it finds kPHP_T_VARIABLE (for variable) // or kPHP_T_IDENTIFIER int what = ReadUntilFoundOneOf(kPHP_T_VARIABLE, kPHP_T_FUNCTION, token); if(what == kPHP_T_VARIABLE) { // A variable PHPEntityBase::Ptr_t member(new PHPEntityVariable()); member->SetFilename(m_filename.GetFullPath()); member->Cast<PHPEntityVariable>()->SetVisibility(visibility); member->Cast<PHPEntityVariable>()->SetFullName(token.text); size_t flags = LookBackForVariablesFlags(); member->Cast<PHPEntityVariable>()->SetFlag(kVar_Member); member->Cast<PHPEntityVariable>()->SetFlag(kVar_Const, flags & kVar_Const); member->Cast<PHPEntityVariable>()->SetFlag(kVar_Static, flags & kVar_Static); member->Cast<PHPEntityVariable>()->SetLine(token.lineNumber); CurrentScope()->AddChild(member); // Handle member assignment // public $memberVar = new Something(); // for such cases, assign $memberVar type of Something() phpLexerToken t; if(!NextToken(t)) { // EOF return; } if(t.type == '=') { // assignment wxString expr; if(!ReadExpression(expr)) { return; } // Optimize 'new ClassName(..)' expression if(expr.StartsWith("new")) { expr = expr.Mid(3); expr.Trim().Trim(false); expr = expr.BeforeFirst('('); expr.Trim().Trim(false); member->Cast<PHPEntityVariable>()->SetTypeHint(MakeIdentifierAbsolute(expr)); } else { // keep the expression member->Cast<PHPEntityVariable>()->SetExpressionHint(expr); } } else { // restore the token UngetToken(t); if(!ConsumeUntil(';')) return; } } else if(what == kPHP_T_FUNCTION) { // A function... OnFunction(); m_lookBackTokens.clear(); } } break; } case kPHP_T_DEFINE: // Define statement OnDefine(token); break; case kPHP_T_CONST: OnConstant(token); break; case kPHP_T_REQUIRE: case kPHP_T_REQUIRE_ONCE: case kPHP_T_INCLUDE: case kPHP_T_INCLUDE_ONCE: // Handle include files m_lookBackTokens.clear(); break; case kPHP_T_FOREACH: // found "foreach" statement OnForEach(); m_lookBackTokens.clear(); break; case kPHP_T_USE: // Found outer 'use' statement - construct the alias table if(Class()) { // inside a class, this means that this is a 'use <trait>;' OnUseTrait(); } else { // alias table OnUse(); } m_lookBackTokens.clear(); break; case kPHP_T_CLASS: case kPHP_T_INTERFACE: case kPHP_T_TRAIT: // Found class OnClass(token); m_lookBackTokens.clear(); break; case kPHP_T_NAMESPACE: // Found a namespace OnNamespace(); m_lookBackTokens.clear(); break; case kPHP_T_FUNCTION: // Found function OnFunction(); m_lookBackTokens.clear(); break; default: // Keep the token break; } } PhaseTwo(); }
void PHPSourceFile::ParseFunctionBody() { m_lookBackTokens.clear(); // when we reach the current depth-1 -> leave int exitDepth = m_depth - 1; phpLexerToken token; PHPEntityBase::Ptr_t var(NULL); while(NextToken(token)) { switch(token.type) { case '{': m_lookBackTokens.clear(); break; case '}': m_lookBackTokens.clear(); if(m_depth == exitDepth) { return; } break; case ';': m_lookBackTokens.clear(); break; case kPHP_T_CATCH: OnCatch(); break; case kPHP_T_VARIABLE: { var.Reset(new PHPEntityVariable()); var->SetFullName(token.text); var->SetFilename(m_filename.GetFullPath()); var->SetLine(token.lineNumber); CurrentScope()->AddChild(var); // Peek at the next token if(!NextToken(token)) return; // EOF if(token.type != '=') { m_lookBackTokens.clear(); var.Reset(NULL); UngetToken(token); } else { wxString expr; if(!ReadExpression(expr)) return; // EOF // Optimize 'new ClassName(..)' expression if(expr.StartsWith("new")) { expr = expr.Mid(3); expr.Trim().Trim(false); expr = expr.BeforeFirst('('); expr.Trim().Trim(false); var->Cast<PHPEntityVariable>()->SetTypeHint(MakeIdentifierAbsolute(expr)); } else { // keep the expression var->Cast<PHPEntityVariable>()->SetExpressionHint(expr); } } } break; default: break; } } }
void PHPSourceFile::ParseFunctionSignature(int startingDepth) { phpLexerToken token; if(startingDepth == 0) { // loop until we find the open brace while(NextToken(token)) { if(token.type == '(') { ++startingDepth; break; } } if(startingDepth == 0) return; } // at this point the 'depth' is 1, as we already read the open brace int depth = 1; wxString typeHint; wxString defaultValue; PHPEntityVariable* var(NULL); bool collectingDefaultValue = false; while(NextToken(token)) { switch(token.type) { case kPHP_T_VARIABLE: if(!var) { // var can be non null if we are parsing PHP-7 function arguments // with type-hinting var = new PHPEntityVariable(); } var->SetFullName(token.text); var->SetLine(token.lineNumber); var->SetFilename(m_filename); // Mark this variable as function argument var->SetFlag(kVar_FunctionArg); if(typeHint.EndsWith("&")) { var->SetIsReference(true); typeHint.RemoveLast(); } var->SetTypeHint(MakeIdentifierAbsolute(typeHint)); break; case '(': depth++; if(collectingDefaultValue) { defaultValue << "("; } break; case ')': depth--; // if the depth goes under 1 - we are done if(depth < 1) { if(var) { var->SetDefaultValue(defaultValue); CurrentScope()->AddChild(PHPEntityBase::Ptr_t(var)); } return; } else if(depth) { defaultValue << token.text; } break; case '=': // default value collectingDefaultValue = true; break; case ',': if(var) { var->SetDefaultValue(defaultValue); CurrentScope()->AddChild(PHPEntityBase::Ptr_t(var)); } var = NULL; typeHint.Clear(); defaultValue.Clear(); collectingDefaultValue = false; break; case kPHP_T_IDENTIFIER: if(!var) { // PHP-7 type hinting function arguments var = new PHPEntityVariable(); UngetToken(token); typeHint = ReadType(); if(!typeHint.IsEmpty()) { break; } } // all "else" cases simply fall into the default case default: if(collectingDefaultValue) { defaultValue << token.text; } else { typeHint << token.text; } break; } } }
void PHPSourceFile::ParseUseTraitsBody() { wxString fullname, alias, temp; phpLexerToken token; bool cont = true; while(cont && NextToken(token)) { switch(token.type) { case '}': { cont = false; } break; case ',': case ';': { if(fullname.IsEmpty()) { // no full name yet fullname.swap(temp); } else if(alias.IsEmpty()) { alias.swap(temp); } if(alias.IsEmpty()) { // no alias provided, use the last part of the fullname alias = fullname.AfterLast('\\'); } if(!fullname.IsEmpty() && !alias.IsEmpty()) { // Use namespace is alway refered as fullpath namespace // So writing: // use Zend\Mvc\Controll\Action; // is equal for writing: // use \Zend\Mvc\Controll\Action; // For simplicitiy, we change it to fully qualified path // so parsing is easier if(!fullname.StartsWith("\\")) { fullname.Prepend("\\"); } PHPEntityBase::Ptr_t funcAlias(new PHPEntityFunctionAlias()); funcAlias->Cast<PHPEntityFunctionAlias>()->SetRealname(MakeIdentifierAbsolute(fullname)); funcAlias->Cast<PHPEntityFunctionAlias>()->SetScope(CurrentScope()->GetFullName()); funcAlias->SetShortName(alias); funcAlias->SetFullName(CurrentScope()->GetFullName() + "\\" + alias); funcAlias->SetFilename(GetFilename()); funcAlias->SetLine(token.lineNumber); CurrentScope()->AddChild(funcAlias); } temp.clear(); fullname.clear(); alias.clear(); } break; case kPHP_T_PAAMAYIM_NEKUDOTAYIM: { // Convert "::" into "\\" temp << "\\"; } break; case kPHP_T_AS: { fullname.swap(temp); temp.clear(); } break; case kPHP_T_INSTEADOF: { // For now, we are not interested in // A insteadof b; statements, so just clear the collected data so far fullname.clear(); temp.clear(); alias.clear(); if(!ConsumeUntil(';')) return; } break; default: temp << token.text; break; } } }