|
| 1 | +Writing a custom AST Provider |
| 2 | +============================= |
| 3 | + |
| 4 | +AST Providers in NEAL are responsible for adding support for a new language. They must conform to a small interface, and have complete ownership over the analysis to be done. |
| 5 | + |
| 6 | +They can either do take care of parsing internally, e.g. using a parser written in OCaml (like the `Swift provider`_) or call out to an external parser (like the `Python provider`_). |
| 7 | + |
| 8 | +Either way, they must conform to the following interface: |
| 9 | + |
| 10 | +.. code-block:: ocaml |
| 11 | +
|
| 12 | + module type PROVIDER = sig val name: string |
| 13 | + val extensions: string list |
| 14 | + val parse: filename -> source -> Absyn.absyn |
| 15 | + val exports : (function_name * exported_function) list |
| 16 | + end |
| 17 | +
|
| 18 | +With the following type aliases: |
| 19 | + |
| 20 | +.. code-block:: ocaml |
| 21 | +
|
| 22 | + type exported_function = Ctx.ctx -> Rule.literal list -> Rule.literal |
| 23 | + type filename = string |
| 24 | + type source = string |
| 25 | + type function_name = string |
| 26 | +
|
| 27 | +Using a custom provider |
| 28 | +----------------------- |
| 29 | + |
| 30 | +* Custom providers must be compiled to a ``.cmxs`` file, which is OCaml's dynamic library format (as described `here <../components#plugins>`_); |
| 31 | +* They must have ``.provider`` suffix before the ``.cmxs`` extension, e.g. ``swift.provider.cmxs``; |
| 32 | +* You must tell NEAL the path to your provider (using the ``-p``/``--provider`` `configuration flag <../configuration.html#flags>`_). |
| 33 | + |
| 34 | +After that NEAL should already be able to parse files using your new provider and evaluate rules that target it. |
| 35 | + |
| 36 | +## Examples |
| 37 | + |
| 38 | +Here we'll use the two providers currently available with NEAL to illustrate the two use cases: |
| 39 | + |
| 40 | +`Swift provider`_ |
| 41 | ++++++++++++++++++ |
| 42 | + |
| 43 | +Here's the current implementation (we'll discuss it below): |
| 44 | + |
| 45 | +.. code-block:: ocaml |
| 46 | +
|
| 47 | + Provider.register(module struct |
| 48 | + let name = "Swift" |
| 49 | + let extensions = [".swift"] |
| 50 | + let parse filename source = Parser.parse filename source |
| 51 | + let exports = [ |
| 52 | + ("inheritsFrom", inherits_from) |
| 53 | + ] |
| 54 | + end) |
| 55 | +
|
| 56 | +What this provider is saying is: |
| 57 | +* Its name is `Swift`, i.e. the provider name you'll use in a `rule <../rules.html>`_ to reference to this provider is `Swift`. |
| 58 | +* It can parse files that have the `.swift` extension. (It should always include the `.`) |
| 59 | +* Parsing is delegate to `Parser.parse`, which in this case is a Swift parser implemented in OCaml. |
| 60 | +* It exports a function called `inheritsFrom`, and its implementation is the OCaml function `inherits_from`. (Which should have the `exported_function` type from above.) |
| 61 | + |
| 62 | +`Python provider`_ |
| 63 | ++++++++++++++++++++ |
| 64 | + |
| 65 | +The actual Python provider module looks very similar to the Swift one: |
| 66 | + |
| 67 | +.. code-block:: ocaml |
| 68 | +
|
| 69 | + let () = Provider.register(module struct |
| 70 | + let name = "Python" |
| 71 | + let extensions = [".py"] |
| 72 | + let parse = parse |
| 73 | + let exports = [] |
| 74 | + end) |
| 75 | +
|
| 76 | +The main different is the ``parse`` function. Here, instead of actually parsing the file and generating the AST all in OCaml, it calls out to a script which uses the parser built into the python interpreter to generate the AST and dumps it as JSON. |
| 77 | + |
| 78 | +.. code-block:: ocaml |
| 79 | +
|
| 80 | + let exec_name = Neal.Utils.relative_path "providers/helpers/dump_python_ast.py" |
| 81 | +
|
| 82 | + let parse filename _ = |
| 83 | + let cmd = Printf.sprintf "%s %s" exec_name filename in |
| 84 | + let stdout = Unix.open_process_in cmd in |
| 85 | + let json = Yojson.Safe.from_channel stdout in |
| 86 | + let _ = Unix.close_process_in stdout in |
| 87 | + absyn_of_json json |
| 88 | +
|
| 89 | +The implementation of ``absyn_of_json`` simply takes the JSON parsed using yojson_ and converts it to NEAL's AST type. You can check the `source code <Python provider>`_, but here will ignore it as an implementation detail. |
| 90 | + |
| 91 | +.. _`Swift provider`: https://github.com/uber/NEAL/blob/master/src/providers/swift/swift_provider.ml |
| 92 | +.. _`Python provider`: https://github.com/uber/NEAL/blob/master/src/providers/python/python.ml |
| 93 | +.. _yojson: https://github.com/mjambon/yojson |
0 commit comments