Writing a custom AST Provider¶
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.
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).
Either way, they must conform to the following interface:
module type PROVIDER = sig val name: string
val extensions: string list
val parse: filename -> source -> Absyn.absyn
val exports : (function_name * exported_function) list
end
With the following type aliases:
type exported_function = Ctx.ctx -> Rule.literal list -> Rule.literal
type filename = string
type source = string
type function_name = string
Using a custom provider¶
- Custom providers must be compiled to a
.cmxs
file, which is OCaml’s dynamic library format (as described here); - They must have
.provider
suffix before the.cmxs
extension, e.g.swift.provider.cmxs
; - You must tell NEAL the path to your provider (using the
-p
/--provider
configuration flag).
After that NEAL should already be able to parse files using your new provider and evaluate rules that target it.
## Examples
Here we’ll use the two providers currently available with NEAL to illustrate the two use cases:
Swift provider¶
Here’s the current implementation (we’ll discuss it below):
Provider.register(module struct
let name = "Swift"
let extensions = [".swift"]
let parse filename source = Parser.parse filename source
let exports = [
("inheritsFrom", inherits_from)
]
end)
What this provider is saying is: * Its name is Swift, i.e. the provider name you’ll use in a rule to reference to this provider is Swift. * It can parse files that have the .swift extension. (It should always include the .) * Parsing is delegate to Parser.parse, which in this case is a Swift parser implemented in OCaml. * It exports a function called inheritsFrom, and its implementation is the OCaml function inherits_from. (Which should have the exported_function type from above.)
Python provider¶
The actual Python provider module looks very similar to the Swift one:
let () = Provider.register(module struct
let name = "Python"
let extensions = [".py"]
let parse = parse
let exports = []
end)
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.
let exec_name = Neal.Utils.relative_path "providers/helpers/dump_python_ast.py"
let parse filename _ =
let cmd = Printf.sprintf "%s %s" exec_name filename in
let stdout = Unix.open_process_in cmd in
let json = Yojson.Safe.from_channel stdout in
let _ = Unix.close_process_in stdout in
absyn_of_json json
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, but here will ignore it as an implementation detail.