cpsa-3.6.3: src/ocaml/expand.ml
(* Expand macros as is done by CPSA *)
open Lexing
open Sexpr
open Reader
open Main
(* The macro expand loop limit *)
let limit = 1000
(* Include depth bound *)
let bound = 16
type macro = {
name: string;
args: string list;
body: position sexpr;
}
(**** Define a macro ****)
let symbol = function
| S (_, s) -> s
| x -> failwith_msg (annotation x) "Expecting a symbol"
let defmacro pos = function
| [L (_, name :: args); body] ->
{
name = symbol name;
args = List.map symbol args;
body = body;
}
| _ -> failwith_msg pos "Malformed macro"
(**** Expand macros ****)
(* lookup key in association list with default *)
let rec lookup default x = function
| [] -> default
| (s, y) :: _ when s = x -> y
| _ :: xs -> lookup default x xs
let rec subst env = function
| S (_, s) as x -> lookup x s env
| L (pos, xs) -> L (pos, List.map (subst env) xs)
| x -> x
let rec zip xs ys =
match (xs, ys) with
| (x :: xs, y :: ys) -> (x, y) :: zip xs ys
| _ -> []
let apply mac args =
subst (zip mac.args args) mac.body
let rec expand_one macs sym xs =
match macs with
| [] -> None
| mac :: _ when mac.name = sym &&
List.length mac.args = List.length xs ->
Some (apply mac xs)
| _ :: macs -> expand_one macs sym xs
let rec limited_expand macs pos limit = function
| L (_, S (_, sym) :: xs) as x when limit > 0 ->
(match expand_one macs sym xs with
| Some y -> limited_expand macs pos (limit - 1) y
| None -> x)
| _ when limit <= 0 ->
failwith_msg pos "Expansion limit exceeded"
| x -> x
(* Splice sexprs that start with splice symbol. The splice symbol is
^. *)
let rec splice = function
| [] -> []
| L (_, S (_, keyword) :: xs) :: ys when keyword = "^" ->
let rec loop xs ys =
match xs with
| [] -> splice ys
| x :: xs -> x :: loop xs ys in
loop xs ys
| x :: xs -> x :: splice xs
let rec expand_all macs x =
match limited_expand macs (annotation x) limit x with
| L (pos, xs) ->
let ys = List.map (expand_all macs) xs in
L (pos, splice ys)
| x -> x
let need_newline = ref false
let expand o macs x =
if !need_newline
then output_char o '\n';
print_sexpr o (expand_all macs x);
need_newline := true
(**** Include the contents of a file ****)
let read_sexprs ch =
let rec loop xs =
try
let x = read_sexpr ch in
loop (x :: xs)
with
| End_of_file ->
List.rev xs in
loop []
(**** Main loop ****)
(* o is the output channel
bound is the include bound
macs is the current list of defined macros *)
let rec loop o bound macs = function
| [] -> macs (* Return the collected macros *)
| x :: xs -> dispatch o bound macs xs x
and dispatch o bound macs xs = function
| L (pos, (S (_, keyword) :: rest)) when keyword = "defmacro" ->
loop o bound (defmacro pos rest :: macs) xs
| L (pos, (S (_, keyword) :: rest)) when keyword = "include" ->
loop o bound (incl o bound macs pos rest) xs
| x ->
expand o macs x;
loop o bound macs xs
and incl o bound macs pos = function
| [Q (_, f)] when bound > 0 ->
let i =
try
open_in f
with Sys_error s -> failwith s
in
let lb = read_lexbuf f i in
let xs = read_sexprs lb in
loop o (bound - 1) macs xs
| [Q (_, f)] when bound <= 0 ->
failwith_msg pos ("Include depth exceeded with file " ^ f)
| _ -> failwith_msg pos "Malformed include directive"
let start o xs =
let _ = loop o bound [] xs in
()
let () =
main start