// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library _fe_analyzer_shared.scanner;

import 'dart:convert' show unicodeReplacementCharacterRune;
import 'dart:typed_data' show Uint8List;

import 'abstract_scanner.dart'
    show LanguageVersionChanged, ScannerConfiguration;
import 'string_scanner.dart' show StringScanner;
import 'token.dart' show Token;
import 'utf8_bytes_scanner.dart' show Utf8BytesScanner;

export 'abstract_scanner.dart'
    show LanguageVersionChanged, ScannerConfiguration;
export 'error_token.dart' show ErrorToken, buildUnexpectedCharacterToken;
export 'string_scanner.dart' show StringScanner;
export 'token.dart'
    show LanguageVersionToken, Keyword, Token, TokenIsAExtension;
export 'token_constants.dart' show EOF_TOKEN;
export 'token_impl.dart'
    show
        StringTokenImpl,
        isBinaryOperator,
        isMinusOperator,
        isTernaryOperator,
        isUnaryOperator,
        isUserDefinableOperator;
export 'utf8_bytes_scanner.dart' show Utf8BytesScanner;

const int unicodeReplacementCharacter = unicodeReplacementCharacterRune;

abstract class Scanner {
  /// Returns true if an error occurred during [tokenize].
  bool get hasErrors;

  List<int> get lineStarts;

  /// Configure which tokens are produced.
  set configuration(ScannerConfiguration config);

  Token tokenize();
}

class ScannerResult {
  final Token tokens;
  final List<int> lineStarts;
  final bool hasErrors;

  ScannerResult(this.tokens, this.lineStarts, this.hasErrors);
}

/// Scan/tokenize the given UTF8 [bytes].
ScannerResult scan(Uint8List bytes,
    {ScannerConfiguration? configuration,
    bool includeComments = false,
    LanguageVersionChanged? languageVersionChanged,
    bool allowLazyStrings = true}) {
  Utf8BytesScanner scanner = new Utf8BytesScanner(bytes,
      configuration: configuration,
      includeComments: includeComments,
      languageVersionChanged: languageVersionChanged,
      allowLazyStrings: allowLazyStrings);
  Token tokens = scanner.tokenize();
  if (scanner.hasErrors) {
    // If there was a single missing `}` and the scanner can identify a good
    // candidate for better recovery, create a new scanner and instruct it to
    // do that recovery.
    int? offsetForCurlyBracketRecoveryStart =
        scanner.getOffsetForCurlyBracketRecoveryStart();
    if (offsetForCurlyBracketRecoveryStart != null) {
      scanner = new Utf8BytesScanner(bytes,
          configuration: configuration,
          includeComments: includeComments,
          languageVersionChanged: languageVersionChanged,
          allowLazyStrings: allowLazyStrings);
      scanner.offsetForCurlyBracketRecoveryStart =
          offsetForCurlyBracketRecoveryStart;
      tokens = scanner.tokenize();
    }
  }
  return new ScannerResult(tokens, scanner.lineStarts, scanner.hasErrors);
}

/// Scan/tokenize the given [source].
ScannerResult scanString(String source,
    {ScannerConfiguration? configuration,
    bool includeComments = false,
    LanguageVersionChanged? languageVersionChanged}) {
  StringScanner scanner = new StringScanner(source,
      configuration: configuration,
      includeComments: includeComments,
      languageVersionChanged: languageVersionChanged);
  Token tokens = scanner.tokenize();
  if (scanner.hasErrors) {
    // If there was a single missing `}` and the scanner can identify a good
    // candidate for better recovery, create a new scanner and instruct it to
    // do that recovery.
    int? offsetForCurlyBracketRecoveryStart =
        scanner.getOffsetForCurlyBracketRecoveryStart();
    if (offsetForCurlyBracketRecoveryStart != null) {
      scanner = new StringScanner(source,
          configuration: configuration,
          includeComments: includeComments,
          languageVersionChanged: languageVersionChanged);
      scanner.offsetForCurlyBracketRecoveryStart =
          offsetForCurlyBracketRecoveryStart;
      tokens = scanner.tokenize();
    }
  }
  return new ScannerResult(tokens, scanner.lineStarts, scanner.hasErrors);
}
