View raw

1 (* Structural Menhir grammar for Org Mode documents. 2 Consumes tokens from the structural lexer and produces Raw_ast.document. *) 3 4 %{ 5 open Raw_ast 6 7 let convert_list_item (item : Token.list_item_data) : raw_list_item = 8 { rli_indent = item.Token.lid_indent; 9 rli_bullet = item.Token.lid_bullet; 10 rli_counter_set = item.Token.lid_counter_set; 11 rli_checkbox = item.Token.lid_checkbox; 12 rli_tag = None; 13 rli_body_lines = [item.Token.lid_rest]; 14 } 15 16 let parse_planning_entries (line : string) : raw_planning list = 17 let results = ref [] in 18 let keywords = ["DEADLINE"; "SCHEDULED"; "CLOSED"] in 19 List.iter (fun kw -> 20 let pat = kw ^ ":" in 21 let len = String.length line in 22 let plen = String.length pat in 23 let rec find_from i = 24 if i + plen > len then () 25 else if String.sub line i plen = pat then begin 26 let rest_start = i + plen in 27 let rest = String.trim (String.sub line rest_start (len - rest_start)) in 28 let ts_end = 29 let rec find_end j closer = 30 if j >= String.length rest then j 31 else if rest.[j] = closer then j + 1 32 else find_end (j + 1) closer 33 in 34 if String.length rest > 0 && rest.[0] = '<' then find_end 1 '>' 35 else if String.length rest > 0 && rest.[0] = '[' then find_end 1 ']' 36 else 0 37 in 38 if ts_end > 0 then 39 results := { rpl_keyword = kw; rpl_timestamp = String.sub rest 0 ts_end } :: !results 40 end 41 else find_from (i + 1) 42 in 43 find_from 0 44 ) keywords; 45 List.rev !results 46 %} 47 48 %token BLANK 49 %token <int * string> HEADING 50 %token <string * string> KEYWORD 51 %token <string * string option * string> AFFILIATED_KEYWORD 52 %token <string * string option> BEGIN_BLOCK 53 %token <string> END_BLOCK 54 %token <string> DRAWER_BEGIN 55 %token DRAWER_END 56 %token <string * string option> PROPERTY 57 %token <string> PLANNING 58 %token <Token.list_item_data> LIST_ITEM 59 %token <string> TABLE_ROW 60 %token <string> FIXED_WIDTH 61 %token <string> COMMENT_LINE 62 %token HORIZONTAL_RULE 63 %token <string> TEXT_LINE 64 %token EOF 65 66 %start <Raw_ast.document> document 67 68 %% 69 70 document: 71 | preamble = list(element) headings = list(heading) EOF 72 { { rd_preamble = preamble; rd_headings = headings } } 73 ; 74 75 heading: 76 | h = HEADING body = list(element) 77 { let (level, raw_title) = h in 78 { rh_level = level; rh_raw_title = raw_title; rh_body = body } } 79 ; 80 81 element: 82 | BLANK 83 { Raw_paragraph [] } 84 | lines = nonempty_list(TEXT_LINE) 85 { Raw_paragraph lines } 86 | items = nonempty_list(list_item_tok) 87 { Raw_list_items (List.map convert_list_item items) } 88 | rows = nonempty_list(table_row_tok) 89 { Raw_table rows } 90 | b = block 91 { Raw_block b } 92 | d = drawer 93 { Raw_drawer d } 94 | k = KEYWORD 95 { let (key, value) = k in Raw_keyword (key, value) } 96 | a = AFFILIATED_KEYWORD 97 { let (name, opt, value) = a in Raw_affiliated (name, opt, value) } 98 | comments = nonempty_list(COMMENT_LINE) 99 { Raw_comment comments } 100 | lines = nonempty_list(FIXED_WIDTH) 101 { Raw_fixed_width lines } 102 | HORIZONTAL_RULE 103 { Raw_horizontal_rule } 104 | p = PLANNING 105 { Raw_planning (parse_planning_entries p) } 106 ; 107 108 list_item_tok: 109 | item = LIST_ITEM { item } 110 ; 111 112 table_row_tok: 113 | row = TABLE_ROW 114 { if String.length row > 1 && row.[1] = '-' then Raw_table_rule 115 else Raw_table_standard row } 116 ; 117 118 block: 119 | b = BEGIN_BLOCK body = list(block_body_line) e = END_BLOCK 120 { let (name, params) = b in let _ = e in 121 { rb_name = name; rb_params = params; rb_body = Opaque_body (String.concat "\n" body) } } 122 | b = BEGIN_BLOCK body = list(block_body_line) EOF 123 { let (name, params) = b in 124 { rb_name = name; rb_params = params; rb_body = Opaque_body (String.concat "\n" body) } } 125 ; 126 127 block_body_line: 128 | t = TEXT_LINE { t } 129 | BLANK { "" } 130 | k = KEYWORD { let (key, value) = k in "#+" ^ key ^ ": " ^ value } 131 | a = AFFILIATED_KEYWORD { let (n, _, v) = a in "#+" ^ n ^ ": " ^ v } 132 | c = COMMENT_LINE { "# " ^ c } 133 | f = FIXED_WIDTH { ": " ^ f } 134 | HORIZONTAL_RULE { "-----" } 135 | item = LIST_ITEM { item.Token.lid_rest } 136 | row = TABLE_ROW { row } 137 | p = PLANNING { p } 138 | prop = PROPERTY { let (n, v) = prop in ":" ^ n ^ ": " ^ (Option.value ~default:"" v) } 139 | d = DRAWER_BEGIN { ":" ^ d ^ ":" } 140 | DRAWER_END { ":END:" } 141 ; 142 143 (* A drawer handles both property drawers and regular drawers. 144 Property drawers are distinguished during normalization by their name. *) 145 drawer: 146 | name = DRAWER_BEGIN contents = drawer_contents DRAWER_END 147 { { rd_name = name; rd_contents = contents } } 148 | name = DRAWER_BEGIN contents = drawer_contents EOF 149 { { rd_name = name; rd_contents = contents } } 150 ; 151 152 (* Drawer contents can include properties (for property drawers) 153 or regular elements (for other drawers). We parse them uniformly 154 as elements, with properties becoming text that normalization handles. *) 155 drawer_contents: 156 | items = list(drawer_item) 157 { items } 158 ; 159 160 drawer_item: 161 | BLANK { Raw_paragraph [] } 162 | lines = nonempty_list(TEXT_LINE) { Raw_paragraph lines } 163 | p = PROPERTY 164 { let (name, value) = p in Raw_keyword (name, Option.value ~default:"" value) } 165 | k = KEYWORD { let (key, value) = k in Raw_keyword (key, value) } 166 | a = AFFILIATED_KEYWORD { let (n, _, v) = a in Raw_affiliated (n, None, v) } 167 | items = nonempty_list(list_item_tok) 168 { Raw_list_items (List.map convert_list_item items) } 169 | rows = nonempty_list(table_row_tok) { Raw_table rows } 170 | comments = nonempty_list(COMMENT_LINE) { Raw_comment comments } 171 | lines = nonempty_list(FIXED_WIDTH) { Raw_fixed_width lines } 172 | HORIZONTAL_RULE { Raw_horizontal_rule } 173 | pl = PLANNING { Raw_planning (parse_planning_entries pl) } 174 ; 175