[OCaml] Org mode parser.
refactor replace parse_bridge with Parse module
Rename parse_bridge to parse and give it clearer entry points: - Parse.document: structural lexer → Menhir → Raw_ast.document - Parse.inline: inline lexer → Menhir → Ast.inline list The Menhir lexbuf wiring boilerplate lives here (shared dummy_pos, position threading). Lexers remain focused on tokenization only.
lib/normalize.ml
@@ -102,7 +102,7 @@
102
102
in
103
103
(* Parse remaining text as inline content *)
104
104
let title_inline =
105
Removed:
if trim rest3 = "" then [] else Parse_bridge.parse_inline (trim rest3)
105
Added:
if trim rest3 = "" then [] else Parse.inline (trim rest3)
106
106
in
107
107
(todo, priority, commented, title_inline, tags)
108
108
@@ -237,12 +237,12 @@
237
237
| Some t -> (Some t, body_text)
238
238
| None -> extract_list_tag body_text
239
239
in
240
Removed:
let tag_inline = Option.map (fun t -> Parse_bridge.parse_inline t) tag_opt in
240
Added:
let tag_inline = Option.map (fun t -> Parse.inline t) tag_opt in
241
241
(* Build contents: paragraph from text + sub-list if any *)
242
242
let contents =
243
243
let text_elements =
244
244
if trim content_text = "" then []
245
Removed:
else [ Ast.Paragraph (Parse_bridge.parse_inline content_text, []) ]
245
Added:
else [ Ast.Paragraph (Parse.inline content_text, []) ]
246
246
in
247
247
let sub_list_elements =
248
248
if sub_items = [] then []
@@ -280,7 +280,7 @@
280
280
else s
281
281
in
282
282
let parts = String.split_on_char '|' s in
283
Removed:
List.map (fun cell -> Parse_bridge.parse_inline (trim cell)) parts
283
Added:
List.map (fun cell -> Parse.inline (trim cell)) parts
284
284
285
285
let normalize_table_row (row : Raw_ast.raw_table_row) : Ast.table_row =
286
286
match row with
@@ -375,8 +375,7 @@
375
375
match raw.rb_body with
376
376
| Raw_ast.Recursive_body elems -> normalize_recursive_body elems config
377
377
| Raw_ast.Opaque_body s ->
378
Removed:
if trim s = "" then []
379
Removed:
else [ Ast.Paragraph (Parse_bridge.parse_inline s, []) ]
378
Added:
if trim s = "" then [] else [ Ast.Paragraph (Parse.inline s, []) ]
380
379
in
381
380
Ast.Quote_block { qt_contents = contents; qt_affiliated = affiliated }
382
381
| "center" ->
@@ -384,14 +383,13 @@
384
383
match raw.rb_body with
385
384
| Raw_ast.Recursive_body elems -> normalize_recursive_body elems config
386
385
| Raw_ast.Opaque_body s ->
387
Removed:
if trim s = "" then []
388
Removed:
else [ Ast.Paragraph (Parse_bridge.parse_inline s, []) ]
386
Added:
if trim s = "" then [] else [ Ast.Paragraph (Parse.inline s, []) ]
389
387
in
390
388
Ast.Center_block { cn_contents = contents; cn_affiliated = affiliated }
391
389
| "verse" ->
392
390
let inline =
393
391
match raw.rb_body with
394
Removed:
| Raw_ast.Opaque_body s -> Parse_bridge.parse_inline s
392
Added:
| Raw_ast.Opaque_body s -> Parse.inline s
395
393
| Raw_ast.Recursive_body _ -> []
396
394
in
397
395
Ast.Verse_block { vs_contents = inline; vs_affiliated = affiliated }
@@ -400,8 +398,7 @@
400
398
match raw.rb_body with
401
399
| Raw_ast.Recursive_body elems -> normalize_recursive_body elems config
402
400
| Raw_ast.Opaque_body s ->
403
Removed:
if trim s = "" then []
404
Removed:
else [ Ast.Paragraph (Parse_bridge.parse_inline s, []) ]
401
Added:
if trim s = "" then [] else [ Ast.Paragraph (Parse.inline s, []) ]
405
402
in
406
403
Ast.Custom_block
407
404
{
@@ -463,7 +460,7 @@
463
460
| Raw_ast.Raw_paragraph [] -> None
464
461
| Raw_ast.Raw_paragraph lines ->
465
462
let text = String.concat " " lines in
466
Removed:
let inline = Parse_bridge.parse_inline text in
463
Added:
let inline = Parse.inline text in
467
464
Some (Ast.Paragraph (inline, affiliated))
468
465
| Raw_ast.Raw_list_items items ->
469
466
let kind = determine_list_kind_with_tags items in
@@ -500,7 +497,7 @@
500
497
(** Extract a timestamp from a raw planning entry. Uses the inline parser which
501
498
handles timestamps. *)
502
499
let extract_timestamp ts_str =
503
Removed:
let inlines = Parse_bridge.parse_inline ts_str in
500
Added:
let inlines = Parse.inline ts_str in
504
501
let rec find_ts = function
505
502
| [] -> None
506
503
| Ast.Timestamp ts :: _ -> Some ts
lib/org.ml
@@ -22,9 +22,10 @@
22
22
23
23
module Private = struct
24
24
module Lexer = Lexer
25
Added:
module Inline_lexer = Inline_lexer
26
Added:
module Parse = Parse
25
27
module Parser = Parser
26
28
module Token = Token
27
Removed:
module Parse_bridge = Parse_bridge
28
29
module Raw_ast = Raw_ast
29
30
module Prescan = Prescan
30
31
end
@@ -50,5 +51,5 @@
50
51
for #+TODO declarations automatically. *)
51
52
let parse ?config input =
52
53
let config = match config with Some c -> c | None -> Prescan.scan input in
53
Removed:
let raw = Parse_bridge.parse ~config input in
54
Added:
let raw = Parse.document ~config input in
54
55
Normalize.normalize config raw
lib/parse.ml
@@ -0,0 +1,31 @@
1
Added:
(** Parser orchestration.
2
Added:
3
Added:
Wires the handwritten lexers into the Menhir-generated parsers. *)
4
Added:
5
Added:
let dummy_pos =
6
Added:
{ Lexing.pos_fname = ""; pos_lnum = 0; pos_bol = 0; pos_cnum = 0 }
7
Added:
8
Added:
(** Parse input text into a [Raw_ast.document]. *)
9
Added:
let document ~config input =
10
Added:
let st = Lexer.create ~config input in
11
Added:
let dummy_lexbuf = Lexing.from_string "" in
12
Added:
dummy_lexbuf.Lexing.lex_start_p <- dummy_pos;
13
Added:
dummy_lexbuf.Lexing.lex_curr_p <- dummy_pos;
14
Added:
let token_fun _lexbuf =
15
Added:
let tok = Lexer.next_token st in
16
Added:
let ln = Lexer.line_number st in
17
Added:
dummy_lexbuf.Lexing.lex_start_p <- { dummy_pos with Lexing.pos_lnum = ln };
18
Added:
dummy_lexbuf.Lexing.lex_curr_p <- { dummy_pos with Lexing.pos_lnum = ln };
19
Added:
tok
20
Added:
in
21
Added:
Parser.document token_fun dummy_lexbuf
22
Added:
23
Added:
(** Parse inline text into a list of [Ast.inline] objects. *)
24
Added:
let inline input =
25
Added:
let st = Inline_lexer.create input in
26
Added:
let dummy_lexbuf = Lexing.from_string "" in
27
Added:
dummy_lexbuf.Lexing.lex_start_p <- dummy_pos;
28
Added:
dummy_lexbuf.Lexing.lex_curr_p <- dummy_pos;
29
Added:
let token_fun _lexbuf = Inline_lexer.next_token st in
30
Added:
try Inline_parser.inline_content token_fun dummy_lexbuf
31
Added:
with Inline_parser.Error -> [ Ast.Plain input ]
lib/parse_bridge.ml
@@ -1,37 +0,0 @@
1
Removed:
(** Bridge between the handwritten lexers and the Menhir parsers.
2
Removed:
3
Removed:
Provides entry points that wire lexer state into the Menhir-generated parser
4
Removed:
functions. No token conversion is needed since the lexers produce parser
5
Removed:
tokens directly. *)
6
Removed:
7
Removed:
(** A dummy lexbuf position for Menhir (we track positions ourselves). *)
8
Removed:
let dummy_pos =
9
Removed:
{ Lexing.pos_fname = ""; pos_lnum = 0; pos_bol = 0; pos_cnum = 0 }
10
Removed:
11
Removed:
(** Parse input text into a Raw_ast.document using the structural lexer and
12
Removed:
Menhir parser. *)
13
Removed:
let parse ~config input =
14
Removed:
let lexer_state = Lexer.create ~config input in
15
Removed:
let dummy_lexbuf = Lexing.from_string "" in
16
Removed:
dummy_lexbuf.Lexing.lex_start_p <- dummy_pos;
17
Removed:
dummy_lexbuf.Lexing.lex_curr_p <- dummy_pos;
18
Removed:
let token_fun _lexbuf =
19
Removed:
let tok = Lexer.next_token lexer_state in
20
Removed:
let ln = Lexer.line_number lexer_state in
21
Removed:
dummy_lexbuf.Lexing.lex_start_p <- { dummy_pos with Lexing.pos_lnum = ln };
22
Removed:
dummy_lexbuf.Lexing.lex_curr_p <- { dummy_pos with Lexing.pos_lnum = ln };
23
Removed:
tok
24
Removed:
in
25
Removed:
Parser.document token_fun dummy_lexbuf
26
Removed:
27
Removed:
(** Parse inline text into a list of Ast.inline objects. *)
28
Removed:
let parse_inline input =
29
Removed:
let st = Inline_lexer.create input in
30
Removed:
let dummy_lexbuf = Lexing.from_string "" in
31
Removed:
dummy_lexbuf.Lexing.lex_start_p <- dummy_pos;
32
Removed:
dummy_lexbuf.Lexing.lex_curr_p <- dummy_pos;
33
Removed:
let token_fun _lexbuf = Inline_lexer.next_token st in
34
Removed:
try Inline_parser.inline_content token_fun dummy_lexbuf
35
Removed:
with Inline_parser.Error ->
36
Removed:
(* Fallback: if inline parsing fails, return plain text *)
37
Removed:
[ Ast.Plain input ]
test/test_main.ml
@@ -173,12 +173,11 @@
173
173
check_tok "property" "Property(\"CUSTOM_ID\", \"my-id\")" (List.nth toks 0)
174
174
175
175
(* Parser tests *)
176
Removed:
module P = Org.Private.Parse_bridge
177
176
module R = Org.Private.Raw_ast
178
177
module A = Org.Ast
179
178
180
Removed:
let parse input = P.parse ~config input
181
Removed:
let parse_inline input = P.parse_inline input
179
Added:
let parse input = Org.Private.Parse.document ~config input
180
Added:
let parse_inline input = Org.Private.Parse.inline input
182
181
183
182
let test_parse_empty () =
184
183
let doc = parse "" in