source/parser/generator.mly (1,796 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ %{ open Core open Ast open Statement open Pyre open ParserExpression (* This weird-looking empty module definition is to work around a nasty issue when *) (* using menhir infer mode with dune: https://github.com/ocaml/dune/issues/2450 *) [@@@warning "-60"] module PyreParser = struct end [@@@warning "+60"] let with_decorators decorators decoratee = let decorators = List.map decorators ~f:convert in match decoratee with | { Node.location; value = Statement.Class value } -> let decorated = { value with Class.decorators; } in { Node.location; value = Statement.Class decorated } | { Node.location; value = Define value } -> let signature = { value.signature with Define.Signature.decorators } in let decorated = { value with signature } in { Node.location; value = Define decorated } | _ -> raise (ParserError "Cannot decorate statement") type entry = | Entry of Dictionary.Entry.t | Item of Expression.t | Keywords of Expression.t | Comprehension of Comprehension.Generator.t type entries = { entries: Dictionary.Entry.t list; items: Expression.t list; keywords: Expression.t list; comprehensions: Comprehension.Generator.t list; } let add_entry so_far = function | Entry entry -> { so_far with entries = entry :: so_far.entries } | Item item -> { so_far with items = item :: so_far.items } | Keywords keyword -> { so_far with keywords = keyword :: so_far.keywords } | Comprehension comprehension -> { so_far with comprehensions = comprehension :: so_far.comprehensions } (* Helper function to combine a start position of type Lexing.position and * stop position of type Location.position. *) let location_create_with_stop ~start ~stop = let position = Location.create ~start ~stop:start in { position with Location.stop = stop } type binary_operator = | Add | At | BitAnd | BitOr | BitXor | Divide | FloorDivide | LeftShift | Modulo | Multiply | Power | RightShift | Subtract let binary_operator ~compound ~left:({ Node.location; _ } as left) ~operator ~right:({ Node.location = { Location.stop; _ }; _ } as right) = let name = let name = match operator with | Add -> "add" | At -> "matmul" | BitAnd -> "and" | BitOr -> "or" | BitXor -> "xor" | Divide -> "truediv" | FloorDivide -> "floordiv" | LeftShift -> "lshift" | Modulo -> "mod" | Multiply -> "mul" | Power -> "pow" | RightShift -> "rshift" | Subtract -> "sub" in Format.asprintf "__%s%s__" (if compound then "i" else "") name in let callee = Expression.Name (Name.Attribute { base = left; attribute = name; special = true }) |> Node.create ~location in Expression.Call { callee; arguments = [{ Call.Argument.name = None; value = right }] } |> Node.create ~location:{ location with stop } let slice ~lower ~upper ~step ~bound_colon ~step_colon = let increment ({ Location.start; stop; _ } as location) = let increment ({ Location.column; _ } as position) = { position with Location.column = column + 1 } in { location with Location.start = increment start; stop = increment stop } in let lower_location = match lower with | Some lower -> lower.Node.location | None -> Location.create ~start:bound_colon ~stop:bound_colon in let upper_location = match upper with | Some upper -> upper.Node.location | None -> Location.create ~start:bound_colon ~stop:bound_colon |> increment in let step_location = match step with | Some step -> step.Node.location | None -> begin match step_colon with | Some colon -> Location.create ~start:colon ~stop:colon |> increment | None -> begin match upper with | Some { Node.location = ({ stop; _ } as location); _ } -> { location with start = stop } | None -> Location.create ~start:bound_colon ~stop:bound_colon |> increment end end in let slice_location = { lower_location with Location.stop = step_location.Location.stop } in let arguments = let argument argument location = let none = Node.create ~location (Expression.Constant AstExpression.Constant.NoneLiteral) in Option.value argument ~default:none in [ { Call.Argument.name = None; value = argument lower lower_location }; { Call.Argument.name = None; value = argument upper upper_location }; { Call.Argument.name = None; value = argument step step_location }; ] in let callee = Expression.Name (Name.Identifier "slice") |> Node.create ~location:slice_location in Expression.Call { callee; arguments } |> Node.create ~location:slice_location let create_ellipsis (start, stop) = let location = Location.create ~start ~stop in Node.create (Expression.Constant AstExpression.Constant.Ellipsis) ~location let create_ellipsis_after { Node.location; _ } = Node.create (Expression.Constant AstExpression.Constant.Ellipsis) ~location:{ location with start = location.stop } let subscript_argument subscripts = let value = match subscripts with | [] -> failwith "subscript can never be empty" | [subscript] -> subscript | subscripts -> let { Node.location = { Location.start; _ }; _ } = List.hd_exn subscripts in let { Node.location = { Location.stop; _ }; _ } = List.last_exn subscripts in { Node.location = { Location.start; stop }; value = Expression.Tuple subscripts } in { Call.Argument.name = None; value } let subscript_access subscript = let head, subscripts, subscript_location = subscript in let location = Node.location head in let callee = Expression.Name (Name.Attribute { base = head; attribute = "__getitem__"; special = true }) |> Node.create ~location:(Node.location head) in Expression.Call { callee; arguments = [subscript_argument subscripts] } |> Node.create ~location:{ subscript_location with start = location.start } let subscript_mutation ~subscript ~value ~annotation:_ = let head, subscripts, subscript_location = subscript in let callee = let location = { head.Node.location with Location.stop = subscript_location.Location.stop } in Expression.Name (Name.Attribute { base = head; attribute = "__setitem__"; special = true }) |> Node.create ~location in let location = { head.Node.location with Location.stop = value.Node.location.Location.stop } in Expression.Call { callee; arguments = [subscript_argument subscripts; { Call.Argument.name = None; value }]; } |> Node.create ~location |> fun expression -> Statement.Expression (convert expression) |> Node.create ~location let with_annotation ~parameter ~annotation = let value = let { Node.value = { Parameter.annotation = existing; _ } as value; _ } = parameter in let annotation = match existing, annotation with | None, Some annotation -> Some annotation | _ -> existing in { value with Parameter.annotation } in { parameter with Node.value } let create_literal_substring (string_position, (start, stop), value) = string_position, { Substring.kind = Substring.Kind.Literal; location = Location.create ~start ~stop; value; } let create_raw_format_substring (string_position, (start, stop), value) = string_position, { Substring.kind = Substring.Kind.RawFormat; location = Location.create ~start ~stop; value; } let create_mixed_string = function | [] -> Expression.Constant (AstExpression.Constant.String { AstExpression.StringLiteral.value = ""; kind = String }) | [ { Substring.kind = Substring.Kind.Literal; value; _ } ] -> Expression.Constant (AstExpression.Constant.String { AstExpression.StringLiteral.value; kind = String }) | _ as pieces -> let is_all_literal = List.for_all ~f:(fun { Substring.kind; _ } -> match kind with | Substring.Kind.Literal -> true | Substring.Kind.RawFormat -> false ) in if is_all_literal pieces then let value = List.map pieces ~f:(fun { Substring.value; _ } -> value) |> String.concat ~sep:"" in Expression.Constant (AstExpression.Constant.String { AstExpression.StringLiteral.value; kind = String }) else Expression.FormatString pieces %} (* The syntactic junkyard. *) %token <Lexing.position * Lexing.position> ASTERIKS %token <Lexing.position> AWAIT %token <Lexing.position> COLON %token <Lexing.position> DEDENT %token <Lexing.position * Lexing.position> DOT %token <Lexing.position> LEFTBRACKET %token <Lexing.position> LEFTCURLY %token <Lexing.position> LEFTPARENS %token <Lexing.position> MINUS %token <Lexing.position> NEWLINE %token <Lexing.position> NOT %token <Lexing.position> PLUS %token <Lexing.position> SLASH (* the RIGHT* lexemes only contain the end position. *) %token <Lexing.position> RIGHTBRACKET %token <Lexing.position> RIGHTCURLY %token <Lexing.position> RIGHTPARENS %token <Lexing.position> TILDE %token <(Lexing.position * Lexing.position) * string list * string> SIGNATURE_COMMENT %token <(Lexing.position * Lexing.position) * string> ANNOTATION_COMMENT %token AMPERSAND %token AMPERSANDEQUALS %token AND %token ASTERIKSASTERIKSEQUALS %token ASTERIKSEQUALS %token AT %token ATEQUALS %token BAR %token BAREQUALS %token COMMA %token COLONEQUALS %token DOUBLEEQUALS %token EOF %token EQUALS %token EXCLAMATIONMARK %token HAT %token HATEQUALS %token INDENT %token IS %token ISNOT %token LEFTANGLE %token LEFTANGLEEQUALS %token LEFTANGLELEFTANGLE %token LEFTANGLELEFTANGLEEQUALS %token MINUSEQUALS %token OR %token PERCENT %token PERCENTEQUALS %token PLUSEQUALS %token RIGHTANGLE %token RIGHTANGLEEQUALS %token RIGHTANGLERIGHTANGLE %token RIGHTANGLERIGHTANGLEEQUALS %token SEMICOLON %token SLASHEQUALS %token SLASHSLASHEQUALS (* Declarations. *) %token <Lexing.position * Lexing.position> ASYNC %token <Lexing.position> CLASS %token <Lexing.position> DEFINE %token <Lexing.position> LAMBDA (* Values. *) %token <(Lexing.position * Lexing.position) * float> FLOAT %token <(Lexing.position * Lexing.position) * float> COMPLEX %token <(Lexing.position * Lexing.position) * int> INTEGER %token <(Lexing.position * Lexing.position) * (Lexing.position * Lexing.position) * string> BYTES %token <(Lexing.position * Lexing.position) * (Lexing.position * Lexing.position) * string> FORMAT %token <(Lexing.position * Lexing.position) * string> IDENTIFIER %token <(Lexing.position * Lexing.position) * (Lexing.position * Lexing.position) * string> STRING %token <(Lexing.position * Lexing.position)> ELLIPSES %token <(Lexing.position * Lexing.position)> FALSE %token <(Lexing.position * Lexing.position)> TRUE %token <(Lexing.position * Lexing.position)> NONE (* Control. *) %token <Lexing.position> ASSERT %token <Lexing.position * Lexing.position> BREAK %token <Lexing.position * Lexing.position> CONTINUE %token <Lexing.position> DELETE %token <Lexing.position> ELSEIF %token <Lexing.position> FOR %token <Lexing.position> FROM %token <Lexing.position> GLOBAL %token <Lexing.position> IF %token <Lexing.position> IMPORT %token <Lexing.position> NONLOCAL %token <Lexing.position * Lexing.position> PASS %token <Lexing.position * Lexing.position> RAISE %token <Lexing.position * Lexing.position> RETURN %token <Lexing.position> TRY %token <Lexing.position> WITH %token <Lexing.position> WHILE %token <Lexing.position * Lexing.position> YIELD %token AS %token ELSE %token <Lexing.position> EXCEPT %token FINALLY %token IN %left LEFTANGLELEFTANGLE RIGHTANGLERIGHTANGLE %left NOT %left BAR %left HAT %left AMPERSAND %left PLUS MINUS %left ASTERIKS PERCENT SLASH %left AWAIT %left TILDE %left AT %left DOT %nonassoc LEFTPARENS %start <Ast.Statement.t list> parse %% parse: | statements = statements; EOF { snd statements } ; (* Statements. *) statements: | { Location.any, [] } | NEWLINE; statements = statements { statements } | statement = statement; statements = statements { (* The recursion always terminates in the empty statement case. This logic avoids * propagating the end location information from there. *) let location = match (snd statements) with | [] -> fst statement | _ -> {(fst statement) with Location.stop = (fst statements).Location.stop;} in location, (snd statement)@(snd statements) } ; statement: | statements = simple_statement { statements } | statement = compound_statement { statement.Node.location, [statement] } | statement = decorated_statement { statement.Node.location, [statement] } | statement = async_statement { statement.Node.location, [statement] } simple_statement: | statements = parser_generator_separated_nonempty_list_of_lists(SEMICOLON, small_statement); NEWLINE { let flattened_statements = List.concat statements in let head = List.hd_exn flattened_statements in let last = List.last_exn flattened_statements in let location = {head.Node.location with Location.stop = Node.stop last} in location, flattened_statements } ; small_statement: | subscript = subscript; compound = compound_operator; value = value { let value = binary_operator ~compound:true ~left:(subscript_access subscript) ~operator:compound ~right:value in [subscript_mutation ~subscript ~value ~annotation:None] } | target = test_list; compound = compound_operator; value = value { let value = binary_operator ~compound:true ~left:target ~operator:compound ~right:value in [{ Node.location = { target.Node.location with Location.stop = value.Node.location.Location.stop; }; value = Statement.Assign { Assign.target = convert target; annotation = None; value = convert value; }; }] } | target = test_list; annotation = annotation { [{ Node.location = { target.Node.location with Location.stop = annotation.Node.location.Location.stop; }; value = Statement.Assign { Assign.target = convert target; annotation = Some annotation >>| convert; value = create_ellipsis_after annotation |> convert; }; }] } | target = test_list; annotation = comment_annotation { [{ Node.location = { target.Node.location with Location.stop = annotation.Node.location.Location.stop; }; value = Statement.Assign { Assign.target = convert target; annotation = Some annotation >>| convert; value = create_ellipsis_after annotation |> convert; }; }] } | target = test_list; annotation = annotation; EQUALS; value = test_list { [{ Node.location = { target.Node.location with Location.stop = value.Node.location.Location.stop; }; value = Statement.Assign { Assign.target = convert target; annotation = Some annotation >>| convert; value = convert value; }; }] } | target = test_list; annotation = annotation; EQUALS; value = value { [{ Node.location = { target.Node.location with Location.stop = value.Node.location.Location.stop; }; value = Statement.Assign { Assign.target = convert target; annotation = Some annotation >>| convert; value = convert value; }; }] } | targets = targets; value = value; annotation = comment_annotation? { List.map ~f:(fun target -> target ~value ~annotation) targets } | targets = targets; ellipsis = ELLIPSES { let value = create_ellipsis ellipsis in List.map ~f:(fun target -> target ~value ~annotation:None) targets } | target = test_list; annotation = annotation; EQUALS; ellipsis = ELLIPSES { let ellipsis = create_ellipsis ellipsis in [{ Node.location = { target.Node.location with Location.stop = ellipsis.Node.location.Location.stop; }; value = Statement.Assign { Assign.target = convert target; annotation = Some annotation >>| convert; value = convert ellipsis; }; }] } | start = ASSERT; test = test { [{ Node.location = location_create_with_stop ~start ~stop:(Node.stop test); value = Statement.Assert { Assert.test = convert test; message = None; origin = Assert.Origin.Assertion } }] } | start = ASSERT; test = test; COMMA; message = test { [{ Node.location = location_create_with_stop ~start ~stop:(Node.stop test); value = Statement.Assert { Assert.test = convert test; message = Some message >>| convert; origin = Assert.Origin.Assertion } }] } | position = BREAK { let start, stop = position in [{ Node.location = Location.create ~start ~stop; value = Statement.Break }] } | position = CONTINUE { let start, stop = position in [{ Node.location = Location.create ~start ~stop; value = Statement.Continue }] } | test = test_list { [{ Node.location = test.Node.location; value = Statement.Expression (convert test) }] } | value = value { [{ Node.location = value.Node.location; value = Statement.Expression (convert value) }] } | start = GLOBAL; globals = parser_generator_separated_nonempty_list(COMMA, identifier) { let last = List.last_exn globals in let stop = (fst last).Location.stop in [{ Node.location = location_create_with_stop ~start ~stop; value = Statement.Global (List.map globals ~f:snd); }] } | start = IMPORT; imports = imports; { [{ Node.location = location_create_with_stop ~start ~stop:((fst imports).Location.stop); value = Statement.Import { Import.from = None; imports = snd imports }; }] } | start = FROM; from = from; IMPORT; imports = imports { [{ Node.location = location_create_with_stop ~start ~stop:((fst imports).Location.stop); value = Statement.Import { Import.from; imports = snd imports; }; }] } | start = NONLOCAL; nonlocals = parser_generator_separated_nonempty_list(COMMA, identifier) { let stop = (fst (List.last_exn nonlocals)).Location.stop in [{ Node.location = location_create_with_stop ~start ~stop; value = Statement.Nonlocal (List.map nonlocals ~f:snd); }] } | position = PASS { let start, stop = position in [{ Node.location = Location.create ~start ~stop; value = Statement.Pass }] } | position = RAISE; test = test_list?; raise_from = raise_from? { let start, stop = position in let location = match (test, raise_from) with | None, None -> Location.create ~start ~stop | Some node, None -> location_create_with_stop ~start ~stop:(Node.stop node) | _, Some { Node.location; _ } -> location_create_with_stop ~start ~stop:(location.Location.stop) in [{ Node.location; value = Statement.Raise { Raise.expression = test >>| convert; from = raise_from >>| convert }; }] } | return = RETURN; test = test_list? { let start, stop = return in let location = match test with | None -> Location.create ~start ~stop | Some node -> location_create_with_stop ~start ~stop:(Node.stop node) in [{ Node.location; value = Statement.Return { Return.expression = test >>| convert; is_implicit = false }; }] } | delete = DELETE; expressions = separated_nonempty_list(COMMA, expression) { let stop = Node.stop (List.last_exn expressions) in [{ Node.location = location_create_with_stop ~start:delete ~stop; value = Statement.Delete (List.map expressions ~f:convert); }] } ; raise_from: | FROM; test_list = test_list { test_list } ; compound_statement: | definition = CLASS; name = reference; bases = bases; colon_position = COLON; body = block_or_stub_body { let location = Location.create ~start:definition ~stop:colon_position in let body_location, body = body in let location = { location with Location.stop = body_location.Location.stop } in let _, name = name in let body = let rec transform_toplevel_statements = function | { Node.location; value = Statement.Define define } -> let signature = { define.signature with Define.Signature.parent = Some name } in { Node.location; value = Statement.Define { define with signature }; } | { Node.location; value = If { If.test; body; orelse; }; } -> { Node.location; value = If { If.test; body = List.map ~f:transform_toplevel_statements body; orelse = List.map ~f:transform_toplevel_statements orelse; }; } | statement -> statement in List.map ~f:transform_toplevel_statements body in { Node.location; value = Statement.Class { Class.name = name; base_arguments = List.map ~f:convert_argument bases; body; decorators = []; top_level_unbound_names = []; }; } } | definition = DEFINE; name = reference; LEFTPARENS; parameters = define_parameters; RIGHTPARENS; return_annotation = return_annotation?; COLON; signature_comment = SIGNATURE_COMMENT?; body = block_or_stub_body { let body_location, body = body in let location = location_create_with_stop ~start:definition ~stop:body_location.Location.stop in let annotation = match return_annotation with | Some return_annotation -> Some return_annotation | None -> signature_comment >>= (fun ((start, stop), _, return_annotation) -> Some { Node.location = Location.create ~start ~stop; value = Expression.Constant ( AstExpression.Constant.String (AstExpression.StringLiteral.create return_annotation) ); } ) in let parameters = match signature_comment with | Some ((start, stop), parameter_annotations, _) when not (List.is_empty parameter_annotations) -> let add_annotation ({ Node.value = parameter; _ } as parameter_node) annotation = match annotation with | None -> parameter_node | Some annotation -> { parameter_node with Node.value = { parameter with Parameter.annotation = Some { Node.location = Location.create ~start ~stop; value = Expression.Constant ( AstExpression.Constant.String (AstExpression.StringLiteral.create annotation) ); }; } } in (* We don't know whether a define is a method at this point, and mypy's documentation specifies that a method's self should NOT be annotated: `https://mypy.readthedocs.io/en/latest/python2.html`. Because we don't know whether we are parsing a method at this point or whether there's any decorators that mean a function doesn't have a self parameter, we make the angelic assumption that annotations lacking a single annotation knowingly elided the self annotation. *) let unannotated_parameter_count = List.length parameters - List.length parameter_annotations in if unannotated_parameter_count = 0 || unannotated_parameter_count = 1 then let parameter_annotations = List.init ~f:(fun _ -> None) unannotated_parameter_count @ List.map ~f:Option.some parameter_annotations in List.map2_exn ~f:add_annotation parameters parameter_annotations else parameters | _ -> parameters in let _, name = name in { Node.location; value = Statement.Define { signature = { name = name; parameters = List.map ~f:convert_parameter parameters; decorators = []; return_annotation = annotation >>| convert; async = false; generator = Ast.Statement.is_generator body; parent = None; nesting_define = None; }; captures = []; unbound_names = []; body }; } } | start = FOR; target = expression_list; IN; iterator = test_list; COLON; ANNOTATION_COMMENT?; body = block; orelse = named_optional_block(ELSE) { let stop = begin match orelse with | _, [] -> (fst body).Location.stop | location, _ -> location.Location.stop end in { Node.location = location_create_with_stop ~start ~stop; value = Statement.For { For.target = convert target; iterator = convert iterator; body = snd body; orelse = snd orelse; async = false }; } } | start = IF; value = conditional { let value_location, value = value in { Node.location = location_create_with_stop ~start ~stop:value_location.Location.stop; value } } | start = TRY; COLON; body = block; handlers = list(handler); orelse = named_optional_block(ELSE); finally = named_optional_block(FINALLY) { let stop = begin match handlers, snd orelse, snd finally with | _, _, (_::_) -> fst finally | _, (_::_), [] -> fst orelse | (_::_), [], [] -> (fst (List.last_exn handlers)) | _ -> (fst body) end.Location.stop in { Node.location = location_create_with_stop ~start ~stop; value = Statement.Try { Try.body = snd body; handlers = List.map ~f:snd handlers; orelse = snd orelse; finally = snd finally }; } } | start = WITH; items = parser_generator_separated_nonempty_list(COMMA, with_item); COLON; ANNOTATION_COMMENT?; body = block { let convert_item (expression, expression_option) = (convert expression, expression_option >>| convert) in { Node.location = location_create_with_stop ~start ~stop:(fst body).Location.stop; value = Statement.With { With.items = List.map ~f:convert_item items; body = snd body; async = false; }; } } | start = WHILE; test = test_list; COLON; body = block; orelse = named_optional_block(ELSE) { let stop = match orelse with | _, [] -> (fst body).Location.stop | location, _ -> location.Location.stop in { Node.location = location_create_with_stop ~start ~stop; value = Statement.While { While.test = convert test; body = snd body; orelse = snd orelse }; } } ; decorated_statement: | decorators = decorator+; statement = compound_statement { with_decorators decorators statement } | decorators = decorator+; statement = async_statement { with_decorators decorators statement } ; async_statement: | position = ASYNC; statement = compound_statement { let location = location_create_with_stop ~start:(fst position) ~stop:(Node.stop statement) in match statement with | { Node.value = Statement.Define value; _ } -> let signature = { value.signature with Define.Signature.async = true } in let decorated = { value with signature } in { Node.location; value = Statement.Define decorated; } | { Node.value = For value; _ } -> let with_async = { value with For.async = true } in { Node.location; value = For with_async; } | { Node.value = With value; _ } -> let with_async = { value with With.async = true } in { Node.location; value = With with_async; } | _ -> raise (ParserError "Async not supported on statement.") } ; block_or_stub_body: | ellipsis = ELLIPSES; NEWLINE | NEWLINE+; INDENT; ellipsis = ELLIPSES; NEWLINE; DEDENT; NEWLINE* { let location = Location.create ~start:(fst ellipsis) ~stop:(snd ellipsis) in let body = [ Node.create ~location (Statement.Expression (Node.create ~location (AstExpression.Expression.Constant AstExpression.Constant.Ellipsis) ) ) ] in location, body } | statements = block { statements } ; block: | simple_statement = simple_statement; { simple_statement } | NEWLINE+; INDENT; statements = statements; DEDENT; NEWLINE* { statements } ; named_optional_block(NAME): | { Location.any, [] } | NAME; COLON; block = block { block } ; conditional: | test = test_list; COLON; body = block; orelse = named_optional_block(ELSE) { { test.Node.location with Location.stop = match orelse with | _, [] -> (fst body).Location.stop | location, _ -> location.Location.stop; }, Statement.If { If.test = convert test; body = snd body; orelse = snd orelse } } | test = test_list; COLON; body = block; else_start = ELSEIF; value = conditional { let stop = (fst value).Location.stop in { test.Node.location with Location.stop }, Statement.If { If.test = convert test; body = (snd body); orelse = [{ Node.location = location_create_with_stop ~start:else_start ~stop; value = snd value }]; } } ; bases: | { [] } | LEFTPARENS; bases = parser_generator_separated_list(COMMA, argument); RIGHTPARENS { bases } ; decorator: | AT; expression = expression; NEWLINE+ { expression } ; identifier: | identifier = IDENTIFIER { let start, stop = fst identifier in Location.create ~start ~stop, snd identifier } | position = ASYNC { Location.create ~start:(fst position) ~stop:(snd position), "async" } ; reference: | identifiers = parser_generator_separated_nonempty_list(DOT, identifier) { let location = let (start, _) = List.hd_exn identifiers in let (stop, _) = List.last_exn identifiers in { start with Location.stop = stop.Location.stop } in let reference = List.map ~f:snd identifiers |> Reference.create_from_list in location, reference } ; define_parameters: | parameter = define_parameter; COMMA; annotation = comment_annotation?; parameters = define_parameters { (with_annotation ~parameter ~annotation) :: parameters } | parameter = define_parameter; annotation = comment_annotation? { [with_annotation ~parameter ~annotation] } | { [] } %inline define_parameter: (* `*` itself is a valid parameter... *) | asteriks = ASTERIKS { { Node.location = Location.create ~start:(fst asteriks) ~stop:(snd asteriks); value = { Parameter.name = "*"; value = None; annotation = None; }; } } | slash = SLASH { { Node.location = Location.create ~start:slash ~stop:slash; value = { Parameter.name = "/"; value = None; annotation = None; }; } } | name = name; annotation = annotation? { let location = let name_location = fst name in match annotation with | None -> name_location | Some { Node.location = { Location.stop; _ }; _ } -> { name_location with stop } in { Node.location; value = { Parameter.name = snd name; value = None; annotation }; } } | name = name; annotation = annotation?; EQUALS; value = test { let location = let name_location = fst name in match annotation with | None -> name_location | Some { Node.location = { Location.stop; _ }; _ } -> { name_location with stop } in { Node.location; value = { Parameter.name = snd name; value = Some value; annotation }; } } ; %inline lambda_parameter: (* `*` is a valid parameter for lambdas as well. *) | asteriks = ASTERIKS { { Node.location = Location.create ~start:(fst asteriks) ~stop:(snd asteriks); value = { Parameter.name = "*"; value = None; annotation = None; }; } } | name = name { { Node.location = fst name; value = { Parameter.name = snd name; value = None; annotation = None } } } | name = name; EQUALS; value = test { { Node.location = { (fst name) with Location.stop = value.Node.location.Location.stop }; value = { Parameter.name = snd name; value = Some value; annotation = None}; } } ; %inline name: | expression = expression { let rec identifier expression = match expression with | { Node.location; value = Expression.Name (Name.Identifier identifier) } -> (location, identifier) | { Node.location; value = Starred (Starred.Once expression) } -> location, identifier expression |> snd |> fun identifier -> "*" ^ identifier | { Node.location; value = Starred (Starred.Twice expression) } -> location, identifier expression |> snd |> fun identifier -> "**" ^ identifier | _ -> raise (ParserError "Unexpected parameters") in identifier expression } ; %inline annotation: | COLON; expression = expression { expression } ; %inline comment_annotation: | annotation = ANNOTATION_COMMENT { let (start, stop), annotation = annotation in annotation |> String.strip ~drop:(function | '\'' | '"' -> true | _ -> false) |> AstExpression.StringLiteral.create |> fun string -> Expression.Constant (AstExpression.Constant.String string) |> Node.create ~location:(Location.create ~start ~stop) } %inline return_annotation: | MINUS; RIGHTANGLE; expression = expression { expression } ; %inline subscript: | head = expression; left = LEFTBRACKET; subscripts = parser_generator_separated_nonempty_list(COMMA, subscript_key); right = RIGHTBRACKET { head, subscripts, Location.create ~start:left ~stop:right } ; with_item: | resource = test { resource, None } | resource = test; AS; target = expression { resource, Some target } ; handler: | start = EXCEPT; COLON; handler_body = block { location_create_with_stop ~start ~stop:(fst handler_body).Location.stop, { Try.Handler.kind = None; name = None; body = snd handler_body } } | start = EXCEPT; kind = expression; COLON; handler_body = block { location_create_with_stop ~start ~stop:(fst handler_body).Location.stop, { Try.Handler.kind = Some kind >>| convert; name = None; body = snd handler_body } } | start = EXCEPT; kind = expression; AS; name = identifier; COLON; handler_body = block | start = EXCEPT; kind = expression; COMMA; name = identifier; COLON; handler_body = block { location_create_with_stop ~start ~stop:(fst handler_body).Location.stop, { Try.Handler.kind = Some kind >>| convert; name = Some (snd name); body = snd handler_body } } | start = EXCEPT; kind = or_test; COLON; handler_body = block { location_create_with_stop ~start ~stop:(fst handler_body).Location.stop, { Try.Handler.kind = Some kind >>| convert; name = None; body = snd handler_body } } | start = EXCEPT; kind = or_test; AS; name = identifier; COLON; handler_body = block { location_create_with_stop ~start ~stop:(fst handler_body).Location.stop, { Try.Handler.kind = Some kind >>| convert; name = Some (snd name); body = snd handler_body } } ; from: | from = from_string { Some (Reference.create from) } ; from_string: | identifier = identifier { snd identifier } | identifier = identifier; from_string = from_string { (snd identifier) ^ from_string } | relative = nonempty_list(ellipsis_or_dot) { String.concat relative } | relative = nonempty_list(ellipsis_or_dot); from_string = from_string { (String.concat relative) ^ from_string } ; ellipsis_or_dot: | DOT { "." } | ELLIPSES { "..." } ; imports: | imports = parser_generator_separated_nonempty_list(COMMA, import) { let location = let (start, _) = List.hd_exn imports in let (stop, _) = List.last_exn imports in { start with Location.stop = stop.Location.stop } in location, List.map imports ~f:(fun (location, value) -> { Node.value; location }) } | start = LEFTPARENS; imports = parser_generator_separated_nonempty_list(COMMA, import); stop = RIGHTPARENS { (Location.create ~start ~stop), List.map imports ~f:(fun (location, value) -> { Node.value; location }) } ; import: | position = ASTERIKS { let location = let start, stop = position in Location.create ~start ~stop in location, { Import.name = Reference.create "*"; alias = None; } } | name = reference { fst name, { Import.name = snd name; alias = None; } } | name = reference; AS; alias = identifier { {(fst name) with Location.stop = (fst alias).Location.stop}, { Import.name = snd name; alias = Some (snd alias); } } ; %inline target: | target = test_list { let assignment_with_annotation ~value ~annotation = { Node.location = { target.Node.location with Location.stop = value.Node.location.Location.stop; }; value = Statement.Assign { Assign.target = convert target; annotation = annotation >>| convert; value = convert value; }; } in assignment_with_annotation } | subscript = subscript { subscript_mutation ~subscript } targets: | target = target; EQUALS { [target] } | targets = targets; target = target; EQUALS { targets @ [target] } ; value: | test = test_list { test } | yield_form = yield_form { yield_form } ; (* Expressions. *) atom: | identifier = identifier { { Node.location = fst identifier; value = Expression.Name (Name.Identifier (snd identifier)); } } | ellipsis = ELLIPSES { let location = Location.create ~start:(fst ellipsis) ~stop:(snd ellipsis) in Node.create (Expression.Constant AstExpression.Constant.Ellipsis) ~location } | left = expression; operator = binary_operator; right = expression; { binary_operator ~compound:false ~left ~operator ~right } | bytes = BYTES+ { let (start, stop), _, _ = List.hd_exn bytes in { Node.location = Location.create ~start ~stop; value = Expression.Constant (AstExpression.Constant.String ( AstExpression.StringLiteral.create ~bytes:true (String.concat (List.map bytes ~f:(fun (_, _, value) -> value))) )); } } | format = FORMAT; mixed_string = mixed_string { let all_strings = create_raw_format_substring format :: mixed_string in let all_pieces = List.map all_strings ~f:snd in let (head, _), (last, _) = List.hd_exn all_strings, List.last_exn all_strings in let (start, _) = head in let (_, stop) = last in { Node.location = Location.create ~start ~stop; value = create_mixed_string all_pieces; } } | name = expression; start = LEFTPARENS; arguments = arguments; stop = RIGHTPARENS { let call_location = Location.create ~start ~stop in Expression.Call { callee = name; arguments } |> Node.create ~location:({ name.Node.location with Location.stop = call_location.Location.stop }) } | set_or_dictionary = set_or_dictionary { set_or_dictionary } | position = FALSE { let start, stop = position in { Node.location = Location.create ~start ~stop; value = Expression.Constant AstExpression.Constant.False; } } | number = COMPLEX { let start, stop = fst number in { Node.location = Location.create ~start ~stop; value = Expression.Constant (AstExpression.Constant.Complex (snd number)); } } | number = FLOAT { let start, stop = fst number in { Node.location = Location.create ~start ~stop; value = Expression.Constant (AstExpression.Constant.Float (snd number)); } } | number = INTEGER { let start, stop = fst number in { Node.location = Location.create ~start ~stop; value = Expression.Constant (AstExpression.Constant.Integer (snd number)); } } | position = NONE { let start, stop = position in { Node.location = Location.create ~start ~stop; value = Expression.Constant AstExpression.Constant.NoneLiteral; } } | start = LEFTBRACKET; items = parser_generator_separated_list(COMMA, test); stop = RIGHTBRACKET { { Node.location = Location.create ~start ~stop; value = Expression.List items; } } | start = LEFTBRACKET; element = test; generators = comprehension+; stop = RIGHTBRACKET { { Node.location = Location.create ~start ~stop; value = Expression.ListComprehension { Comprehension.element; generators }; } } | start = LEFTCURLY; element = test; generators = comprehension+; stop = RIGHTCURLY { { Node.location = Location.create ~start ~stop; value = Expression.SetComprehension { Comprehension.element; generators }; } } | position = ASTERIKS; test = expression { let start, _ = position in let location = location_create_with_stop ~start ~stop:(Node.stop test) in match test with | { Node.value = Expression.Starred (Starred.Once test); _; } -> { Node.location; value = Expression.Starred (Starred.Twice test); } | _ -> { Node.location; value = Expression.Starred (Starred.Once test); } } | string = STRING; mixed_string = mixed_string { let all_strings = create_literal_substring string :: mixed_string in let all_pieces = List.map all_strings ~f:snd in let (head, _), (last, _) = List.hd_exn all_strings, List.last_exn all_strings in let (start, _) = head in let (_, stop) = last in { Node.location = Location.create ~start ~stop; value = create_mixed_string all_pieces; } } | position = TRUE { let start, stop = position in { Node.location = Location.create ~start ~stop; value = Expression.Constant AstExpression.Constant.True } } | operator = unary_operator; operand = expression { let start, operator = operator in let { Node.value; _ } = operand in let location = location_create_with_stop ~start ~stop:(Node.stop operand) in match operator, value with | AstExpression.UnaryOperator.Negative, Expression.Constant (AstExpression.Constant.Integer literal) -> { Node.location; value = Expression.Constant (AstExpression.Constant.Integer (-1 * literal)); } | _, _ -> { Node.location; value = Expression.UnaryOperator { UnaryOperator.operator = operator; operand; }; } } ; expression: | atom = atom { atom } | start = LEFTPARENS; stop = RIGHTPARENS { { Node.location = Location.create ~start ~stop; value = Expression.Tuple []; } } | start = LEFTPARENS; test = test_list; stop = RIGHTPARENS { { Node.location = Location.create ~start ~stop; value = Expression.Parenthesis test; } } | expression = expression; DOT; identifier = identifier { let location = { expression.Node.location with Location.stop = Location.stop (fst identifier) } in { Node.location; value = Expression.Name ( Name.Attribute { base = expression; attribute = snd identifier; special = false } ) } } | subscript = subscript { subscript_access subscript } | start = AWAIT; expression = expression { { Node.location = location_create_with_stop ~start ~stop:(Node.stop expression); value = Expression.Await expression; } } | LEFTPARENS; generator = generator; RIGHTPARENS { generator } | LEFTPARENS; yield_form = yield_form; RIGHTPARENS { yield_form } ; expression_list: | items = separated_nonempty_list_indicator(COMMA, expression) { match items with | head::[], has_trailing_comma -> if has_trailing_comma then { Node.location = head.Node.location; value = Expression.Tuple [head]; } else head | (head :: _) as items, _ -> let last = List.last_exn items in { Node.location = { head.Node.location with Location.stop = Node.stop last }; value = Expression.Tuple items; } | _ -> raise (ParserError "invalid atom") } ; mixed_string: | { [] } | first_string = FORMAT; rest = mixed_string { create_raw_format_substring first_string :: rest } | first_string = STRING; rest = mixed_string { create_literal_substring first_string :: rest } ; comparison: | expression = expression { expression } | left = expression; comparisons = nonempty_list(comparison_operator) { let rec comparison ({ Node.location; _ } as left) comparisons = match comparisons with | (operator, right) :: comparisons when List.length comparisons > 0 -> let left = Expression.ComparisonOperator { ComparisonOperator.left; operator; right } |> Node.create ~location:({ location with Location.stop = Node.stop right }) in let right = comparison right comparisons in Expression.BooleanOperator { BooleanOperator.left; operator = AstExpression.BooleanOperator.And; right; } |> Node.create ~location; | [operator, right] -> Expression.ComparisonOperator { ComparisonOperator.left; operator; right } |> Node.create ~location:({ location with Location.stop = Node.stop right }) | _ -> failwith "The parser is a lie! Did not get a non-empty comparison list." in comparison left comparisons } ; not_test: | comparison = comparison { comparison } | start = NOT; not_test = not_test { let location = location_create_with_stop ~start ~stop:(Node.stop not_test) in { Node.location; value = Expression.UnaryOperator { UnaryOperator.operator = AstExpression.UnaryOperator.Not; operand = not_test; } } } ; and_test: | not_test = not_test { not_test } | left = not_test; AND; right = and_test { let location = { (Node.location left) with Location.stop = Node.stop right } in { Node.location; value = Expression.BooleanOperator { BooleanOperator.left; operator = AstExpression.BooleanOperator.And; right; } } } ; or_test: | and_test = and_test { and_test } | left = and_test; OR; right = or_test { let location = { (Node.location left) with Location.stop = Node.stop right } in { Node.location; value = Expression.BooleanOperator { BooleanOperator.left; operator = AstExpression.BooleanOperator.Or; right; } } } ; test_with_generator: | generator = generator { generator } | test = test { test } ; test: | or_test = or_test { or_test } | target = identifier; COLONEQUALS; value = test { { Node.location = { (fst target) with Location.stop = Node.stop value }; value = Expression.WalrusOperator { target = Expression.Name (Name.Identifier (snd target)) |> Node.create ~location:(fst target); value = value; } } } | target = or_test; IF; test = test_list; ELSE; alternative = test { { Node.location = { target.Node.location with Location.stop = Node.stop alternative }; value = Expression.Ternary { Ternary.target; test; alternative }; } } | start = LAMBDA; parameters = parser_generator_separated_list(COMMA, lambda_parameter); COLON; body = test { { Node.location = location_create_with_stop ~start ~stop:(Node.stop body); value = Expression.Lambda { Lambda.parameters; body } } } ; test_list: | items = separated_nonempty_list_indicator(COMMA, test) { match items with | head :: [], has_trailing_comma -> if has_trailing_comma then { Node.location = head.Node.location; value = Expression.Tuple [head]; } else head | (head :: _ as items), _ -> let last = List.last_exn items in { Node.location = { head.Node.location with Location.stop = Node.stop last }; value = Expression.Tuple items; } | _ -> raise (ParserError "invalid atom") } ; yield_form: | yield_token = YIELD; test = test_list?; { let start, stop = yield_token in let location = Option.map ~f:(fun test -> location_create_with_stop ~start ~stop:(Node.stop test)) test |> Option.value ~default:(Location.create ~start ~stop) in { Node.location; value = Expression.Yield test; } } | yield_token = YIELD; FROM; test = test_list; { let start, _ = yield_token in let location = location_create_with_stop ~start ~stop:(Node.stop test) in { Node.location; value = Expression.YieldFrom test; } } ; %inline binary_operator: | PLUS { Add } | AT { At } | AMPERSAND { BitAnd } | BAR { BitOr } | HAT { BitXor } | SLASH; SLASH { FloorDivide } | SLASH { Divide } | LEFTANGLELEFTANGLE { LeftShift } | PERCENT { Modulo } | ASTERIKS; ASTERIKS { Power } | ASTERIKS { Multiply } | RIGHTANGLERIGHTANGLE { RightShift } | MINUS { Subtract } ; comparison_operator: | DOUBLEEQUALS; operand = expression { AstExpression.ComparisonOperator.Equals, operand } | RIGHTANGLE; operand = expression { AstExpression.ComparisonOperator.GreaterThan, operand } | RIGHTANGLEEQUALS; operand = expression { AstExpression.ComparisonOperator.GreaterThanOrEquals, operand } | IN; operand = expression { AstExpression.ComparisonOperator.In, operand } | IS; operand = expression { AstExpression.ComparisonOperator.Is, operand } | ISNOT; operand = expression { AstExpression.ComparisonOperator.IsNot, operand } | LEFTANGLE; operand = expression { AstExpression.ComparisonOperator.LessThan, operand } | LEFTANGLEEQUALS; operand = expression { AstExpression.ComparisonOperator.LessThanOrEquals, operand } | EXCLAMATIONMARK; EQUALS; operand = expression { AstExpression.ComparisonOperator.NotEquals, operand } | NOT; IN; operand = expression { AstExpression.ComparisonOperator.NotIn, operand } ; %inline compound_operator: | PLUSEQUALS { Add } | ATEQUALS { At } | AMPERSANDEQUALS { BitAnd } | BAREQUALS { BitOr } | HATEQUALS { BitXor } | SLASHSLASHEQUALS { FloorDivide } | SLASHEQUALS { Divide } | LEFTANGLELEFTANGLEEQUALS { LeftShift } | PERCENTEQUALS { Modulo } | ASTERIKSASTERIKSEQUALS { Power } | ASTERIKSEQUALS { Multiply } | RIGHTANGLERIGHTANGLEEQUALS { RightShift } | MINUSEQUALS { Subtract } ; %inline unary_operator: | position = TILDE { position, AstExpression.UnaryOperator.Invert } | position = MINUS { position, AstExpression.UnaryOperator.Negative } | position = NOT { position, AstExpression.UnaryOperator.Not } | position = PLUS { position, AstExpression.UnaryOperator.Positive } ; arguments: | arguments = parser_generator_separated_list(COMMA, argument) { arguments } | test = test_with_generator { [{ Call.Argument.name = None; value = test }] } | test = generator; COMMA { [{ Call.Argument.name = None; value = test }] } ; argument: | identifier = identifier; EQUALS; value = test { let location = let identifier_location = fst identifier in let value_location = Node.location value in { identifier_location with Location.stop = value_location.stop } in { Call.Argument.name = Some { Node.location; value = snd identifier }; value; } } | value = test { { Call.Argument.name = None; value } } ; subscript_key: | index = test { index } | lower = test?; bound_colon = COLON; upper = test? { slice ~lower ~upper ~step:None ~bound_colon ~step_colon:None } | lower = test?; bound_colon = COLON; upper = test?; step_colon = COLON; step = test? { slice ~lower ~upper ~step ~bound_colon ~step_colon:(Some step_colon) } ; (* Collections. *) set_or_dictionary_entry: | test = test { match test with | { Node.value = Expression.Starred (Starred.Twice keywords); _ } -> Keywords keywords | _ -> Item test } | key = test; COLON; value = test { Entry { Dictionary.Entry.key = key; value = value; } } ; set_or_dictionary_maker: | entry = set_or_dictionary_entry { add_entry { entries = []; comprehensions = []; items = []; keywords = [] } entry } | items = set_or_dictionary_maker; COMMA; entry = set_or_dictionary_entry { add_entry items entry } | items = set_or_dictionary_maker; comprehension = comprehension { add_entry items (Comprehension comprehension) } ; set_or_dictionary: | start = LEFTCURLY; stop = RIGHTCURLY { { Node.location = Location.create ~start ~stop; value = Expression.Dictionary { Dictionary.entries = []; keywords = [] }; } } | start = LEFTCURLY; items = set_or_dictionary_maker; COMMA?; stop = RIGHTCURLY { let value = match items with | { entries; keywords; comprehensions = []; items = [] } -> Expression.Dictionary { Dictionary.entries = List.rev entries; keywords = List.rev keywords } | { entries = [entry]; keywords = []; items = []; comprehensions } -> Expression.DictionaryComprehension { Comprehension.element = entry; generators = List.rev comprehensions; } | { items; entries = []; comprehensions = []; keywords = [] } -> Expression.Set (List.rev items) | { items = [item]; comprehensions; keywords = []; entries = [] } -> Expression.SetComprehension { Comprehension.element = item; generators = List.rev comprehensions; } | _ -> failwith "Invalid dictionary or set" in { Node.location = Location.create ~start ~stop; value } } generator: | element = test; generators = comprehension+ { let stop = let { Comprehension.Generator.iterator; conditions; _ } = List.last_exn generators in match List.rev conditions with | [] -> Node.stop iterator | condition :: _ -> Node.stop condition in { Node.location = { element.Node.location with Location.stop }; value = Expression.Generator { Comprehension.element; generators }; } } ; comprehension: | ASYNC; FOR; target = expression_list; IN; iterator = or_test; conditions = list(condition) { { Comprehension.Generator.target; iterator; conditions; async = true } } | FOR; target = expression_list; IN; iterator = or_test; conditions = list(condition) { { Comprehension.Generator.target; iterator; conditions; async = false } } ; condition: | IF; test = or_test { test } ; (* Helper rule dumping ground. *) parser_generator_separated_list(SEPARATOR, item): | { [] } | item = item { [item] } | item = item; SEPARATOR; rest = parser_generator_separated_list(SEPARATOR, item) { item::rest } ; separated_nonempty_list_indicator_tail(SEPARATOR, item): | { [], false } | SEPARATOR { [], true } | SEPARATOR; item = item; rest = separated_nonempty_list_indicator_tail(SEPARATOR, item) { let rest, has_trailing = rest in item :: rest, has_trailing } ; separated_nonempty_list_indicator(SEPARATOR, item): | item = item; rest = separated_nonempty_list_indicator_tail(SEPARATOR, item) { let rest, has_trailing = rest in item :: rest, has_trailing } ; parser_generator_separated_nonempty_list(SEPARATOR, item): | item = item { [item] } | item = item; SEPARATOR; rest = parser_generator_separated_list(SEPARATOR, item) { item::rest } ; parser_generator_separated_list_of_lists(SEPARATOR, list_item): | { [] } | list_item = list_item { [list_item] } | list_item = list_item; SEPARATOR; rest = parser_generator_separated_list_of_lists(SEPARATOR, list_item) { list_item::rest } ; parser_generator_separated_nonempty_list_of_lists(SEPARATOR, list_item): | list_item = list_item { [list_item] } | list_item = list_item; SEPARATOR; rest = parser_generator_separated_list_of_lists(SEPARATOR, list_item) { list_item::rest } ;