input: def perform_sentiment_analysis(data: pd.DataFrame) -> str:
      """ Perform sentiment analysis on a DataFrame of product reviews. The function
      uses natural language processing techniques such as tokenization, stopword
      removal, lemmatization, and n-gram generation to create features, which are
      then used to train a Multinomial Naive Bayes classifier. The classifier
      predicts sentiments on a test set and returns a classification report.
  
      >>> mock_df = pd.DataFrame({'review': ['I love this product!', 'Terrible experience.'], 'sentiment': ['positive', 'negative']})
      >>> from io import StringIO
      >>> output = perform_sentiment_analysis(mock_df)
      >>> print(output)
                  precision    recall  f1-score   support
          positive       1.00      1.00      1.00         1
          negative       1.00      1.00      1.00         1
      accuracy                           1.00         2
      macro avg       1.00      1.00      1.00         2
      weighted avg    1.00      1.00      1.00         2
      """
      import numpy as np
      import pandas as pd
      import nltk
      from nltk.tokenize import word_tokenize
      from nltk.corpus import stopwords
      from nltk.stem import WordNetLemmatizer
      from sklearn.feature_extraction.text import CountVectorizer
      from sklearn.model_selection import train_test_split
      from sklearn.naive_bayes import MultinomialNB
      from sklearn.metrics import classification_report
      
      # Tokenise the texts
      data['tokenised'] = data['review'].apply(word_tokenize)
      
      # Remove stopwords
      stop_words = set(stopwords.words('english'))
      data['filtered'] = data['tokenised'].apply(lambda x: [val for val in x if val not in stop_words])
      
      # Normalize words
      wordnet_lemmatizer = WordNetLemmatizer()
      data['normalised'] = data['filtered'].apply(lambda x: [wordnet_lemmatizer.lemmatize(val) for val in x])
      
      # Generate features using CountVectorizer
      cv = CountVectorizer()
      data['features'] = cv.fit_transform(data['normalised'].astype('str')).toarray()
      
      # Split into training and test sets
      X = data['features']
      y = data['sentiment']
      X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
      
      # Train model
      model = MultinomialNB(alpha=0.1)
      model.fit(X_train, y_train)
      
      # Predict using the test set
      predictions = model.predict(X_test)
      
      # Generate a classification report
      report = classification_report(y_test, predictions)
      return report
output:     return report