(* Yoann Padioleau * * Copyright (C) 2010 Facebook * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation, with the * special exception on linking described in file license.txt. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file * license.txt for more details. *) open Common (*****************************************************************************) (* Prelude *) (*****************************************************************************) (* * PLEAC, the Programming Language Examples Alike Cookbook, * is a great resource when learning a new language. This module * provides some functions to parse pleac data and to generate * regular code that can then be analyzed and indexed and * then visualized like any other code. * * See http://pleac.sourceforge.net/ * * Important files: * - skeleton.sgml * - *.data, language implementations *) (*****************************************************************************) (* Types *) (*****************************************************************************) type code_excerpt = string list type section = string type comment_style = string (* comment_start *) * string (* comment_end *) type sections = (section, code_excerpt) Common.assoc type skeleton = (string (* section1 *) * ((string (* section2 title *) * section) list)) list (*****************************************************************************) (* Helpers *) (*****************************************************************************) let skip_no_heading xs = xs +> Common.exclude (fun (s, _) -> s =$= Common2.split_list_regexp_noheading) let mangle_to_generate_filename s = Str.global_replace (Str.regexp "[- /.,':()]") "_" s (*****************************************************************************) (* Parsing *) (*****************************************************************************) (* ex: (* @@PLEAC@@_1.0 *), or # @@PLEAC@@_1.0 *) let regexp_section_pleac_data = "\\(.*\\) @@PLEAC@@_\\([0-9\\.]+\\)\\(.*\\)" let parse_data_file file = file +> Common.cat +> Common2.split_list_regexp regexp_section_pleac_data +> skip_no_heading +> List.map (fun (s, group) -> if s =~ regexp_section_pleac_data then let (_, section, _) = Common.matched3 s in section, group else failwith ("Pleac.parse_data_file: impossible: " ^ s) ) let detect_comment_style file = file +> Common.cat +> Common2.return_when (fun s -> if s =~ regexp_section_pleac_data then let (s1, _s2, s3) = Common.matched3 s in Some (s1, s3) else None ) (* ex: Strings *) let regexp_skeleton_section1 = "\\(.*\\)" (* ex: Short Sleeps *) let regexp_skeleton_section2 = "\\(.*\\)" (* ex: PLEAC:3.9:CAELP *) let regexp_skeleton_section_number = "PLEAC:\\(.*\\):" (* It's a sgml file so we could parse it using pxp and then visiting it * but using regexps is probably ok. *) let parse_skeleton_file file = file +> Common.cat +> Common2.split_list_regexp regexp_skeleton_section1 +> skip_no_heading +> List.map (fun (s, group) -> if s =~ regexp_skeleton_section1 then let section1 = Common.matched1 s in section1, group +> Common2.split_list_regexp regexp_skeleton_section2 +> skip_no_heading +> List.map (fun (s2, group) -> if s2 =~ regexp_skeleton_section2 then let section2 = Common.matched1 s2 in section2, group +> Common2.return_when (fun s3 -> if s3 =~ regexp_skeleton_section_number then Some (Common.matched1 s3) else None ) else failwith ("Pleac.parse_data_file: impossible: " ^ s) ) else failwith ("Pleac.parse_data_file: impossible: " ^ s) ) (*****************************************************************************) (* Main entry point *) (*****************************************************************************) (* todo? could also split using class with sections and static methods with * subsections. So could use M-x Pleac_Strings::TAB :) *) type gen_mode = | OneFilePerSection | OneDirPerSection let gen_source_files skeleton sections (comment_start, comment_end) ~gen_mode ~output_dir ~ext_file ~hook_start_section2 ~hook_line_body ~hook_end_section2 = if not (Common2.command2_y_or_no("rm -rf " ^ output_dir)) then failwith "ok we stop"; Common.command2("mkdir -p " ^ output_dir); let hsections = Common.hash_of_list sections in let estet_sect1 = (Common2.repeat "*" 70) +> Common.join "" in let estet_sect2 = (Common2.repeat "-" 70) +> Common.join "" in (match gen_mode with | OneFilePerSection -> skeleton +> List.iter (fun (section1, xs) -> let file = Filename.concat output_dir (mangle_to_generate_filename section1) ^ "." ^ ext_file in Common.with_open_outfile file (fun (pr_no_nl, _chan) -> let pr s = pr_no_nl (s ^ "\n") in pr (spf "%s %s %s" comment_start estet_sect1 comment_end); pr (spf "%s %s %s" comment_start section1 comment_end); pr (spf "%s %s %s" comment_start estet_sect1 comment_end); xs +> List.iter (fun (section2, secnumber) -> let code_opt = try Some (Hashtbl.find hsections secnumber) with Not_found -> pr2 (spf "Section %s was not found in data file" secnumber); None in pr (spf "%s %s %s" comment_start estet_sect2 comment_end); pr (spf "%s %s %s" comment_start section2 comment_end); pr (spf "%s %s %s" comment_start estet_sect2 comment_end); code_opt +> Common.do_option (fun code -> code +> List.iter pr) ) ) ) | OneDirPerSection -> skeleton +> List.iter (fun (section1, xs) -> let dir = Filename.concat output_dir (mangle_to_generate_filename section1) in Common.command2("mkdir -p " ^ dir); xs +> List.iter (fun (section2, secnumber) -> let file = Filename.concat dir (mangle_to_generate_filename section2) ^ "." ^ ext_file in let code_opt = try Some (Hashtbl.find hsections secnumber) with Not_found -> pr2 (spf "Section %s was not found in data file" secnumber); None in code_opt +> Common.do_option (fun code -> Common.with_open_outfile file (fun (pr_no_nl, _chan) -> let pr s = pr_no_nl (s ^ "\n") in pr (spf "%s %s %s" comment_start estet_sect1 comment_end); pr (spf "%s %s %s" comment_start section2 comment_end); pr (spf "%s %s %s" comment_start estet_sect1 comment_end); pr (hook_start_section2 (mangle_to_generate_filename section2)); code +> List.iter (fun s -> pr (hook_line_body s) ); pr (hook_end_section2 (mangle_to_generate_filename section2)); ) ) ) ) )