2 * Copyright (c) 2018, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
12 module Syntax
= Full_fidelity_positioned_syntax
13 module SyntaxKind
= Full_fidelity_syntax_kind
14 module SyntaxTree
= Full_fidelity_syntax_tree
15 .WithSyntax
(Full_fidelity_positioned_syntax
)
18 * A `SyntaxKind.t`. We can generate a string from a kind, but not the other way
19 * around, so we store the string value given to us in the input directly. Then,
20 * when examining a node to see if it's a match, we'll convert the node's kind
21 * to a string and simply use string comparison.
23 * (Note that multiple syntax kinds map to the same string anyways, so it would
24 * be hard to reverse the mapping.)
26 type node_kind
= NodeKind
of string
29 * An identifier in the pattern used to identify the this node when returning
30 * the results of a match to the caller. This identifier may not be unique in
31 * the list of returned results.
33 type match_name
= MatchName
of string
36 * The name of a child of a node.
38 * For example, a binary expression has three possible child names:
40 * * binary_left_operand
42 * * binary_right_operand
44 * See the FFP schema definition in `src/parser/schema/schema_definition.ml`.
45 * Note that `BinaryExpression` has the prefix of `binary` and children like
46 * `left_operand`. When combined, this forms the full name
47 * `binary_left_operand`.
49 type child_type
= ChildType
of string
52 * A query that can be run on the syntax tree. It will return the matched nodes
53 * of any matched `MatchPattern`s.
57 * Match any node with the given kind, and whose children match the given
64 * Mapping from child name to pattern that the child must satisfy.
65 * Children may be omitted from this map. No child type may be specified
68 children
: (child_type
* pattern
) list
;
72 * Return the given node in the result list, assuming that the pattern overall
73 * matches. (This pattern by itself always matches; it's often used with
77 match_name
: match_name
;
81 * Matches a given node if there is descendant node matching the given pattern
82 * anywhere in the subtree underneath the parent node.
84 | DescendantPattern
of {
89 match_name
: match_name
;
95 * The list of nodes for which a `MatchPattern` matched.
97 matched_nodes
: matched_node list
;
101 syntax_tree
: SyntaxTree.t
;
104 let empty_result: result
option = Some
{ matched_nodes
= [] }
106 let merge_results (lhs
: result
option) (rhs
: result
option): result
option =
107 Option.merge lhs rhs ~f
:(fun lhs rhs
->
108 { matched_nodes
= lhs
.matched_nodes
@ rhs
.matched_nodes
}
111 let find_child_with_type
113 (child_type
: child_type
)
116 Syntax.children_names node
117 |> List.findi ~f
:(fun _i actual_child_type
->
118 match child_type
with
119 | ChildType expected_child_type
->
120 expected_child_type
= actual_child_type
124 match child_index with
126 | Some index
-> List.nth
(Syntax.children node
) index
132 : env
* result
option =
134 | NodePattern
{ kind
; children
} ->
135 let kind = match kind with NodeKind
kind -> kind in
136 if (node
|> Syntax.kind |> SyntaxKind.to_string
) <> kind
139 let (env
, result
) = List.fold_left_env env children
141 ~f
:(fun env acc_result
(child_type
, pattern
) ->
142 match acc_result
with
143 (* We failed to match a previous child pattern; short-circuit. *)
144 | None
-> (env
, None
)
146 | Some _
as result
->
147 let child_node = find_child_with_type node child_type
in
148 let child_node = Option.value_exn
child_node in
149 let (env
, child_result
) =
150 search_node ~env ~pattern ~node
:child_node in
151 match child_result
with
152 | None
-> (env
, None
)
153 | Some _
as child_result
->
154 let result = merge_results result child_result
in
160 | MatchPattern
{ match_name
} ->
168 | DescendantPattern
{ pattern
} ->
169 search_descendants ~env ~pattern ~node
171 (* TODO: this will likely have to become more intelligent *)
172 and search_descendants
176 : env
* result option =
179 (Syntax.children node
)
181 ~f
:(fun env acc_result child
->
182 let (env
, child_result
) = search_node ~env ~pattern ~node
:child
in
183 let (env
, descendants_result
) =
184 search_descendants ~env ~pattern ~node
:child
in
185 let result = merge_results child_result descendants_result
in
186 (env
, (merge_results result acc_result
))
189 let compile_pattern (json
: Hh_json.json
): (pattern
, string) Core_result.t
=
190 let open Core_result
in
191 let open Core_result.Monad_infix
in
193 let wrap_json_accessor f
=
194 fun x
-> Core_result.map_error
(f x
)
195 ~f
:Hh_json.Access.access_failure_to_string
198 let get_string x
= wrap_json_accessor (Hh_json.Access.get_string x
) in
199 let get_obj x
= wrap_json_accessor (Hh_json.Access.get_obj x
) in
200 let keytrace_to_string = Hh_json.Access.keytrace_to_string in
201 let error_at_keytrace ~keytrace error_message
=
202 Error
(error_message ^
(keytrace_to_string keytrace
))
205 let rec compile_pattern ~json ~keytrace
: (pattern
, string) Core_result.t
=
206 get_string "pattern_type" (json
, keytrace
)
208 >>= fun (pattern_type
, pattern_type_keytrace
) ->
209 match pattern_type
with
211 compile_node_pattern ~json ~keytrace
213 compile_match_pattern ~json ~keytrace
214 | "descendant_pattern" ->
215 compile_descendant_pattern ~json ~keytrace
217 error_at_keytrace ~keytrace
:pattern_type_keytrace
218 (Printf.sprintf
"Unknown pattern type '%s'" pattern_type
)
220 and compile_node_pattern ~json ~keytrace
: (pattern
, string) Core_result.t
=
221 get_string "kind" (json
, keytrace
)
223 >>= fun (kind, kind_keytrace
) ->
224 let open Schema_definition
in
225 let kind_info = List.find schema ~f
:(fun schema_node
->
226 schema_node
.description
= kind
230 error_at_keytrace ~keytrace
:kind_keytrace
231 (Printf.sprintf
"Kind '%s' doesn't exist" kind)
232 | Some
kind_info -> Ok
kind_info
235 get_obj "children" (json
, keytrace
)
237 >>= fun (children_json
, children_keytrace
) ->
238 (* This has already been verified to be an object above. *)
239 let children = Hh_json.get_object_exn children_json
in
243 : (child_type
, string) Core_result.t
=
244 (* We're given a field name like `binary_right_operand`, but the field
245 names in the schema are things like `right_operand`, and you have to
246 affix the prefix yourself. For consistency with other tooling, we want
247 to use `binary_right_operand` instead of just `right_operand`. *)
248 let get_prefixed_field_name field_name
=
249 kind_info.prefix ^
"_" ^ field_name
251 let field = List.find
kind_info.fields ~f
:(fun (field_name
, _
) ->
252 (get_prefixed_field_name field_name
) = child_name
)
256 let valid_types = List.map
kind_info.fields ~f
:(fun (field_name
, _
) ->
257 get_prefixed_field_name field_name
259 error_at_keytrace ~keytrace
:children_keytrace
261 ("Unknown child type '%s'; "^^
262 "valid child types for a node of kind '%s' are: %s")
265 (String.concat
", " valid_types))
266 | Some _
-> Ok
(ChildType child_name
)
268 let children_patterns =
269 List.map
children ~f
:(fun (child_name
, pattern_json
) ->
270 get_child_type child_name
>>= fun child_name
->
271 compile_pattern ~json
:pattern_json ~keytrace
:children_keytrace
273 (child_name
, pattern
)
276 all
children_patterns >>| fun children ->
278 kind = NodeKind
kind;
282 and compile_match_pattern ~json ~keytrace
=
283 get_string "match_name" (json
, keytrace
)
284 >>| fun (match_name
, _match_name_keytrace
) ->
286 match_name
= MatchName match_name
;
289 and compile_descendant_pattern ~json ~keytrace
=
290 get_obj "pattern" (json
, keytrace
) >>= fun (pattern
, pattern_keytrace
) ->
291 compile_pattern ~json
:pattern ~keytrace
:pattern_keytrace
>>| fun pattern
->
297 compile_pattern ~json ~keytrace
:[]
299 (* TODO(T28496995): This only converts a single result to JSON. We also need to
300 convert an entire response -- a mapping from file path to result -- to JSON. *)
301 let result_to_json (result: result option): Hh_json.json
=
306 let matched_nodes = List.map
result.matched_nodes ~f
:(fun matched_node
->
308 match matched_node
.match_name
309 with MatchName
match_name -> match_name
312 "match_name", JSON_String
match_name;
313 "node", Syntax.to_json matched_node
.node
;
317 "matched_nodes", JSON_Array
matched_nodes;
321 ~
(syntax_tree
: SyntaxTree.t
)
324 let env = { syntax_tree
} in
326 search_node ~
env ~pattern ~node
:(SyntaxTree.root
env.syntax_tree
) in
330 (acc
: (Relative_path.t
* result) list
)
331 (inputs
: (Relative_path.t
* pattern
) list
)
332 : (Relative_path.t
* result) list
=
335 ~f
:(fun acc
(path
, pattern
) ->
336 let source_text = Full_fidelity_source_text.from_file path
in
337 let syntax_tree = SyntaxTree.make
source_text in
338 match search ~
syntax_tree pattern
with
339 | Some
result -> (path
, result) :: acc
344 ~
(workers
: MultiWorker.worker list
option)
345 (files
: Relative_path.t list
)
346 (input
: Hh_json.json
)
347 : (Hh_json.json
, string) Core_result.t
349 let open Core_result.Monad_infix
in
350 compile_pattern input
>>| fun pattern
->
351 let inputs = List.map files ~f
:(fun path
-> (path
, pattern
)) in
352 let results = MultiWorker.call
356 ~merge
:List.rev_append
357 ~next
:(MultiWorker.next workers
inputs)
360 Hh_json.JSON_Object
(List.map
results ~f
:(fun (path
, result) ->
361 (Relative_path.to_absolute path
, result_to_json (Some
result))