(*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *)

open Parser_env
open Flow_ast

type pattern_errors = {
  if_expr: (Loc.t * Parse_error.t) list;
  if_patt: (Loc.t * Parse_error.t) list;
}

type pattern_cover =
  | Cover_expr of (Loc.t, Loc.t) Expression.t
  | Cover_patt of (Loc.t, Loc.t) Expression.t * pattern_errors

module type PARSER = sig
  val program : env -> (Loc.t, Loc.t) Program.t

  val statement : env -> (Loc.t, Loc.t) Statement.t

  val statement_list_item :
    ?decorators:(Loc.t, Loc.t) Class.Decorator.t list -> env -> (Loc.t, Loc.t) Statement.t

  val statement_list : term_fn:(Token.t -> bool) -> env -> (Loc.t, Loc.t) Statement.t list

  val statement_list_with_directives :
    term_fn:(Token.t -> bool) -> env -> (Loc.t, Loc.t) Statement.t list * bool

  val module_body : term_fn:(Token.t -> bool) -> env -> (Loc.t, Loc.t) Statement.t list

  val expression : env -> (Loc.t, Loc.t) Expression.t

  val expression_or_pattern : env -> pattern_cover

  val conditional : env -> (Loc.t, Loc.t) Expression.t

  val assignment : env -> (Loc.t, Loc.t) Expression.t

  val left_hand_side : env -> (Loc.t, Loc.t) Expression.t

  val object_initializer : env -> Loc.t * (Loc.t, Loc.t) Expression.Object.t * pattern_errors

  val identifier : ?restricted_error:Parse_error.t -> env -> (Loc.t, Loc.t) Identifier.t

  val identifier_with_type :
    env -> ?no_optional:bool -> Parse_error.t -> Loc.t * (Loc.t, Loc.t) Pattern.Identifier.t

  val block_body : env -> Loc.t * (Loc.t, Loc.t) Statement.Block.t

  val function_block_body :
    expression:bool -> env -> Loc.t * (Loc.t, Loc.t) Statement.Block.t * bool

  val jsx_element_or_fragment :
    env ->
    Loc.t * [ `Element of (Loc.t, Loc.t) JSX.element | `Fragment of (Loc.t, Loc.t) JSX.fragment ]

  val pattern : env -> Parse_error.t -> (Loc.t, Loc.t) Pattern.t

  val pattern_from_expr : env -> (Loc.t, Loc.t) Expression.t -> (Loc.t, Loc.t) Pattern.t

  val object_key : ?class_body:bool -> env -> Loc.t * (Loc.t, Loc.t) Expression.Object.Property.key

  val class_declaration : env -> (Loc.t, Loc.t) Class.Decorator.t list -> (Loc.t, Loc.t) Statement.t

  val class_expression : env -> (Loc.t, Loc.t) Expression.t

  val is_assignable_lhs : (Loc.t, Loc.t) Expression.t -> bool

  val number : env -> Token.number_type -> string -> float
end

let identifier_name_raw env =
  let open Token in
  let name =
    match Peek.token env with
    (* obviously, Identifier is a valid IdentifierName *)
    | T_IDENTIFIER { value; _ } -> value
    (* keywords are also IdentifierNames *)
    | T_AWAIT -> "await"
    | T_BREAK -> "break"
    | T_CASE -> "case"
    | T_CATCH -> "catch"
    | T_CLASS -> "class"
    | T_CONST -> "const"
    | T_CONTINUE -> "continue"
    | T_DEBUGGER -> "debugger"
    | T_DEFAULT -> "default"
    | T_DELETE -> "delete"
    | T_DO -> "do"
    | T_ELSE -> "else"
    | T_EXPORT -> "export"
    | T_EXTENDS -> "extends"
    | T_FINALLY -> "finally"
    | T_FOR -> "for"
    | T_FUNCTION -> "function"
    | T_IF -> "if"
    | T_IMPORT -> "import"
    | T_IN -> "in"
    | T_INSTANCEOF -> "instanceof"
    | T_NEW -> "new"
    | T_RETURN -> "return"
    | T_SUPER -> "super"
    | T_SWITCH -> "switch"
    | T_THIS -> "this"
    | T_THROW -> "throw"
    | T_TRY -> "try"
    | T_TYPEOF -> "typeof"
    | T_VAR -> "var"
    | T_VOID -> "void"
    | T_WHILE -> "while"
    | T_WITH -> "with"
    | T_YIELD -> "yield"
    (* FutureReservedWord *)
    | T_ENUM -> "enum"
    | T_LET -> "let"
    | T_STATIC -> "static"
    | T_INTERFACE -> "interface"
    | T_IMPLEMENTS -> "implements"
    | T_PACKAGE -> "package"
    | T_PRIVATE -> "private"
    | T_PROTECTED -> "protected"
    | T_PUBLIC -> "public"
    (* NullLiteral *)
    | T_NULL -> "null"
    (* BooleanLiteral *)
    | T_TRUE -> "true"
    | T_FALSE -> "false"
    (* Flow-specific stuff *)
    | T_DECLARE -> "declare"
    | T_TYPE -> "type"
    | T_OPAQUE -> "opaque"
    | T_ANY_TYPE -> "any"
    | T_MIXED_TYPE -> "mixed"
    | T_EMPTY_TYPE -> "empty"
    | T_BOOLEAN_TYPE BOOL -> "bool"
    | T_BOOLEAN_TYPE BOOLEAN -> "boolean"
    | T_NUMBER_TYPE -> "number"
    | T_BIGINT_TYPE -> "bigint"
    | T_STRING_TYPE -> "string"
    | T_VOID_TYPE -> "void"
    | T_SYMBOL_TYPE -> "symbol"
    (* Contextual stuff *)
    | T_OF -> "of"
    | T_ASYNC -> "async"
    (* punctuators, types, literals, etc are not identifiers *)
    | _ ->
      error_unexpected ~expected:"an identifier" env;
      ""
  in
  Eat.token env;
  name

(* IdentifierName - https://tc39.github.io/ecma262/#prod-IdentifierName *)
let identifier_name env =
  let loc = Peek.loc env in
  let leading = Peek.comments env in
  let name = identifier_name_raw env in
  let trailing = Eat.trailing_comments env in
  let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in
  (loc, { Identifier.name; comments })

(** PrivateIdentifier - https://tc39.es/ecma262/#prod-PrivateIdentifier

    N.B.: whitespace, line terminators, and comments are not allowed
    between the # and IdentifierName because PrivateIdentifier is a
    CommonToken which is considered a single token. See also
    https://tc39.es/ecma262/#prod-InputElementDiv *)
let private_identifier env =
  let start_loc = Peek.loc env in
  let leading = Peek.comments env in
  Expect.token env Token.T_POUND;
  let name_loc = Peek.loc env in
  let name = identifier_name_raw env in
  let trailing = Eat.trailing_comments env in
  let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in
  let loc = Loc.btwn start_loc name_loc in
  if not (Loc.equal_position start_loc.Loc._end name_loc.Loc.start) then
    error_at env (loc, Parse_error.WhitespaceInPrivateName);
  (loc, { PrivateName.name; comments })

(**
 * The abstract operation IsLabelledFunction
 *
 * https://tc39.github.io/ecma262/#sec-islabelledfunction
 *)
let rec is_labelled_function = function
  | (_, Flow_ast.Statement.Labeled { Flow_ast.Statement.Labeled.body; _ }) ->
    begin
      match body with
      | (_, Flow_ast.Statement.FunctionDeclaration _) -> true
      | _ -> is_labelled_function body
    end
  | _ -> false

let with_loc ?start_loc fn env =
  let start_loc =
    match start_loc with
    | Some x -> x
    | None -> Peek.loc env
  in
  let result = fn env in
  let loc =
    match last_loc env with
    | Some end_loc -> Loc.btwn start_loc end_loc
    | None -> start_loc
  in
  (loc, result)

let with_loc_opt ?start_loc fn env =
  match with_loc ?start_loc fn env with
  | (loc, Some x) -> Some (loc, x)
  | (_, None) -> None
