1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
|
(* Preprocesses stdcompat modules *)
{
let error message =
Printf.eprintf "%s\n%!" message;
exit 2
let unknown what arg = Printf.sprintf "Unknown %s %s" what arg |> error
type source_type = OCaml | C
let source_types = [
"c", C;
"ocaml", OCaml;
]
let start_comment language =
(if language = OCaml then "(*" else "#if 0") |> print_string
let end_comment language =
(if language = OCaml then "*)" else "#endif") |> print_string
type options = {
compiler_version : Compiler_version.t;
source_type : source_type;
debug : bool;
}
let init_options () = {
compiler_version = Compiler_version.of_string Sys.ocaml_version;
source_type = OCaml;
debug = false;
}
let dprintf options fmt =
let printer =
if options.debug then Printf.fprintf else Printf.ifprintf
in
printer stderr fmt
let set_compiler_version opts version =
let compiler_version =
try Compiler_version.of_string version with
_ -> unknown "compiler version" version
in
opts := { !opts with compiler_version }
let set_source_type opts source_type =
let source_type =
try List.assoc source_type source_types with
Not_found -> unknown "source type" source_type
in
opts := { !opts with source_type }
let set_debug opts =
opts := { !opts with debug = true }
let parse_commandline options =
let opts = ref options in
let args =
[
("--compiler-version", Arg.String (set_compiler_version opts),
" Set compiler version");
("--source-type", Arg.String (set_source_type opts),
" Set source type (ocaml or c)");
("--debug", Arg.Unit (fun () -> set_debug opts),
" Turn on debugging");
] in
Arg.parse args (unknown "argument") Sys.argv.(0);
!opts
type cst = Before | From | After
type block_info = {
start_lineno : int; (* Line number where the current block starts *)
tag : string; (* tag used to start this block *)
keep : bool;
}
type lexer_state = {
lineno : int;
blocks : block_info list;
}
let next_line state = { state with lineno = state.lineno + 1 }
type lexer =
options -> lexer_state -> Lexing.lexbuf -> unit
let initial_lexer_state () = {
lineno = 1;
blocks = [];
}
let cst_of_string = function
| "BEFORE" -> Before
| "FROM" -> From
| "AFTER" -> After
| c -> error ("Unknown constraint " ^ c)
let tag_mismatch (start_lineno, start_tag) (end_lineno, end_tag) =
Printf.sprintf
"Line %d: expected END_%s, got END_%s, block starts at line %d"
end_lineno start_tag end_tag start_lineno
|> error
let check_block_end state end_tag = match state.blocks with
| [] ->
Printf.sprintf
"Line %d: the %s block has not been started"
state.lineno end_tag
|> error
| { start_lineno; tag = start_tag; _ } :: _ ->
if start_tag <> end_tag then
tag_mismatch (start_lineno, start_tag) (state.lineno, end_tag)
let known_tags compiler_version = [
"BIG_ENDIAN", string_of_bool Sys.big_endian;
"OCAML_DEVELOPMENT_VERSION", "false";
"OCAML_RELEASE_EXTRA",
Compiler_version.ocaml_of_extra_info_opt
(Compiler_version.extra_info compiler_version);
"OCAML_VERSION_MAJOR",
string_of_int (Compiler_version.major compiler_version);
"OCAML_VERSION_MINOR",
string_of_int (Compiler_version.minor compiler_version);
"OCAML_VERSION_PATCHLEVEL",
string_of_int (Compiler_version.patch_level compiler_version);
]
let features = [
"CYGWIN", Sys.os_type = "Cygwin";
"FLAMBDA2", false;
"MAGIC", true;
"RESULT_PKG", true;
"SEQ_PKG", true;
"UCHAR_PKG", true;
"UNIX", Sys.os_type = "Unix";
"WIN32", Sys.os_type = "Win32";
]
}
let cst = "BEFORE"|"FROM"|"AFTER"
let digit = ['0'-'9']
let number = digit+
let tag_char = ['A'-'Z''a'-'z''0'-'9''_']
let version = (number as major)'_'(number as minor)('_'(number as patch_level))?
let compiler_version_constraint_block = (cst as cst) '_' (version as _version) as _tag
let compiler_version_constraint_block_begin = "@BEGIN_" compiler_version_constraint_block '@'
let compiler_version_constraint_c_block_begin = "@C_BEGIN_" compiler_version_constraint_block '@'
let compiler_version_constraint_block_end = "@END_" compiler_version_constraint_block '@'
let compiler_version_constraint_c_block_end = "@C_END_" compiler_version_constraint_block '@'
let with = ("WITH"|"WITHOUT") as _enabled
let feature = ((tag_char+) as _feature)
let begin_feature_block = "@BEGIN_" ((with '_' feature) as _tag) '@'
let end_feature_block = "@END_" ((with '_' feature) as _tag) '@'
let other_tag = '@' ((tag_char+) as tag) '@'
rule ocaml_lexer options state = parse
| compiler_version_constraint_block_begin
{
dprintf options "block_begin\n%!";
let patch_level =
match patch_level with
| None -> "0"
| Some pl -> pl
in
dprintf options "Line %d: start of block with cst=%s, version=%s major=%s minor=%s patch_level=%s\n%!"
state.lineno cst _version major minor patch_level;
let cst = cst_of_string cst in
let f c = if c = '_' then '.' else c in
let version = _version |> (String.map f) |> Compiler_version.of_string in
let cmp = Compiler_version.compare options.compiler_version version in
let keep =
match cst with
| Before -> cmp < 0
| From -> cmp >= 0
| After -> cmp > 0
in
dprintf options "keep=%b\n%!" keep;
if not keep then start_comment OCaml;
let block = { start_lineno = state.lineno; tag = _tag; keep } in
let state = { state with blocks = block::state.blocks } in
ocaml_lexer options state lexbuf
}
| compiler_version_constraint_block_end
{
dprintf options "block_end\n%!";
let patch_level =
match patch_level with
| None -> "0"
| Some pl -> pl
in
dprintf options "Line %d: end of block with cst=%s, version=%s major=%s minor=%s patch_level=%s\n%!"
state.lineno cst _version major minor patch_level;
check_block_end state _tag;
let (keep, state) = match state.blocks with
| [] -> (true, state)
| block::blocks -> (block.keep, {state with blocks})
in
if not keep then end_comment OCaml;
ocaml_lexer options state lexbuf
}
| begin_feature_block
{
dprintf options "begin_feature_block\n%!";
if not (List.mem_assoc _feature features) then
Printf.sprintf "Line %d: unknown feature %s" state.lineno _feature |> error;
let keep = List.assoc _feature features in
let keep = if _enabled = "WITH" then keep else not keep in
if not keep then start_comment OCaml;
let block = { start_lineno = state.lineno; tag = _tag; keep } in
let state = { state with blocks = block::state.blocks } in
ocaml_lexer options state lexbuf
}
| end_feature_block
{
dprintf options "end_feature_block\n%!";
check_block_end state _tag;
let (keep, state) = match state.blocks with
| [] -> (true, state)
| block::blocks -> (block.keep, {state with blocks})
in
if not keep then end_comment OCaml;
ocaml_lexer options state lexbuf
}
| other_tag as quoted_tag
{
dprintf options "other_tag: %s\n%!" quoted_tag;
(try (List.assoc tag (known_tags options.compiler_version) |> print_string)
with Not_found -> Printf.sprintf "Line %d: unknknown tag %s " state.lineno quoted_tag |> error);
ocaml_lexer options state lexbuf
}
| _ as ch
{
dprintf options "character: '%s'\n%!" (Char.escaped ch);
print_char ch;
let state = if ch='\n' then next_line state else state in
ocaml_lexer options state lexbuf
}
| eof
{
dprintf options "eof\n%!";
match state.blocks with
| [] -> ()
| block::_ ->
begin
let message =
Printf.sprintf "The %s block started at line %d has not been ended"
block.tag block.start_lineno
in
error message
end
}
and c_lexer options state = parse
| compiler_version_constraint_c_block_begin
{
dprintf options "c_block_begin\n%!";
let patch_level =
match patch_level with
| None -> "0"
| Some pl -> pl
in
dprintf options "Line %d: start of block with cst=%s, version=%s major=%s minor=%s patch_level=%s\n%!"
state.lineno cst _version major minor patch_level;
let cst = cst_of_string cst in
let f c = if c = '_' then '.' else c in
let version = _version |> (String.map f) |> Compiler_version.of_string in
let cmp = Compiler_version.compare options.compiler_version version in
let keep =
match cst with
| Before -> cmp < 0
| From -> cmp >= 0
| After -> cmp > 0
in
if not keep then start_comment C;
let block = { start_lineno = state.lineno; tag = _tag; keep } in
let state = { state with blocks = block::state.blocks } in
c_lexer options state lexbuf
}
| compiler_version_constraint_c_block_end
{
dprintf options "c_block_end\n%!";
let patch_level =
match patch_level with
| None -> "0"
| Some pl -> pl
in
dprintf options "Line %d: end of block with cst=%s, version=%s major=%s minor=%s patch_level=%s\n%!"
state.lineno cst _version major minor patch_level;
check_block_end state _tag;
let (keep, state) = match state.blocks with
| [] -> (true, state)
| block::blocks -> (block.keep, {state with blocks})
in
if not keep then end_comment C;
c_lexer options state lexbuf
}
| other_tag as quoted_tag
{
dprintf options "other_tag: %s\n%!" quoted_tag;
try (List.assoc tag (known_tags options.compiler_version)) |> print_string
with Not_found -> Printf.sprintf "Line %d: unknknown tag %s " state.lineno quoted_tag |> error
}
| _ as ch
{
dprintf options "char: '%s'\n%!" (Char.escaped ch);
print_char ch;
let state = if ch='\n' then next_line state else state in
c_lexer options state lexbuf
}
| eof
{
dprintf options "eof\n%!";
match state.blocks with
| [] -> ()
| block::_ ->
begin
let message =
Printf.sprintf "The %s block started at line %d has not been ended"
block.tag block.start_lineno
in
error message
end
}
{
let main () =
let options = init_options () |> parse_commandline in
let lexer = match options.source_type with
| OCaml -> ocaml_lexer
| C -> c_lexer
in
let state = initial_lexer_state () in
Lexing.from_channel stdin |> lexer options state
let _ = main ()
}
|