188 Commits

Author SHA1 Message Date
Tom Alexander
59222c58b1 Publish version 0.1.13.
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-12-15 21:10:21 -05:00
Tom Alexander
4d95a7f244 Merge branch 'post_blank'
All checks were successful
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-15 20:11:20 -05:00
Tom Alexander
5a8159eed7 Fix clippy.
All checks were successful
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-12-15 19:57:35 -05:00
Tom Alexander
e24fcb9ded Add dummy values for new fields for plaintext. 2023-12-15 19:54:03 -05:00
Tom Alexander
4b94dc60d2 Fix handling of documents containing only whitespace. 2023-12-15 19:49:12 -05:00
Tom Alexander
2046603d01 Fix handling post blank for org documents. 2023-12-15 19:42:43 -05:00
Tom Alexander
30412361e1 Fix handling fixed width area post-blank inside a list. 2023-12-15 19:37:33 -05:00
Tom Alexander
e846c85188 Fix handling fixed width areas with empty lines in the middle. 2023-12-15 19:17:16 -05:00
Tom Alexander
99b74095e6 Fix heading post-blank. 2023-12-15 19:10:14 -05:00
Tom Alexander
6b802d36bf Implement the new fields for target. 2023-12-15 18:57:19 -05:00
Tom Alexander
33ca43ca40 Remove the old Paragraph::of_text function.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-15 18:06:48 -05:00
Tom Alexander
f5280a3090 Implement the new fields for bullshitium broken dynamic block. 2023-12-15 18:04:42 -05:00
Tom Alexander
c28d8ccea4 Fix post-blank for headlines containing only whitespace. 2023-12-15 17:59:47 -05:00
Tom Alexander
9690545901 Fix setting contents for broken end bullshitium when there is a paragraph present.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has failed
2023-12-15 17:40:08 -05:00
Tom Alexander
eba4fb94cf Implement the new fields for dynamic block. 2023-12-15 17:26:01 -05:00
Tom Alexander
565978225a Implement the new fields for table.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 17:16:49 -05:00
Tom Alexander
cce9ca87fa Fix handling of leading blank lines in greater blocks.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 16:55:47 -05:00
Tom Alexander
683c523ece Implement the new fields for greater block. 2023-12-15 16:15:22 -05:00
Tom Alexander
7a4dc20dc9 Implement the new fields for plain list.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 15:59:00 -05:00
Tom Alexander
022dda06eb Implement the new fields for plain list item. 2023-12-15 15:52:53 -05:00
Tom Alexander
7b88a2d248 Implement the new fields for broken end bullshitium. 2023-12-15 15:40:17 -05:00
Tom Alexander
fce5b92091 Remove leading blank lines from document contents. 2023-12-15 15:30:46 -05:00
Tom Alexander
45a506334c Remove leading blank lines from heading contents. 2023-12-15 15:20:31 -05:00
Tom Alexander
e47901a67f Implement the new fields for node property.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has failed
2023-12-15 15:05:03 -05:00
Tom Alexander
7430daa768 Fix handling of property drawers containing only whitespace. 2023-12-15 15:05:03 -05:00
Tom Alexander
6ce25c8a3b Update property drawer empty test to include a variety of whitespace. 2023-12-15 14:25:01 -05:00
Tom Alexander
7b8fa1eb4a Fix get_contents for sections. 2023-12-15 13:21:58 -05:00
Tom Alexander
ffa5349f25 Fix get_contents for headlines. 2023-12-15 13:14:49 -05:00
Tom Alexander
bb472b63cc Implement the new fields for property drawer. 2023-12-15 13:03:42 -05:00
Tom Alexander
57f566a7a1 Implement the new fields for planning. 2023-12-15 12:55:05 -05:00
Tom Alexander
2181993246 Implement the new fields for horizontal rule. 2023-12-15 12:50:01 -05:00
Tom Alexander
60d1ecfa75 Fix fixed width area to not consume trailing line break so it can be part of the post-blank.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 12:45:51 -05:00
Tom Alexander
3962db12a8 Implement the new fields for fixed width area. 2023-12-15 12:29:46 -05:00
Tom Alexander
f192507cd9 Implement the new fields for diary sexp.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 12:10:34 -05:00
Tom Alexander
252be3e001 Fix post blank for timestamp date ranges. 2023-12-15 11:38:52 -05:00
Tom Alexander
28f12a04f7 Implement the new fields for drawer.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 11:22:27 -05:00
Tom Alexander
d6232dc49c Implement the new fields for clock. 2023-12-15 10:49:04 -05:00
Tom Alexander
68a220aa1c Implement the new fields for babel call.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 10:45:16 -05:00
Tom Alexander
2e7db0f8bd Implement the new fields for lesser block.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 15:59:56 -05:00
Tom Alexander
175ff1e6c4 Implement the new fields for LaTeX environment.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 14:49:18 -05:00
Tom Alexander
0b42139393 Implement the new fields for inline babel call and inline source block. 2023-12-11 14:47:22 -05:00
Tom Alexander
67a9103b07 Implement the new fields for export snippet. 2023-12-11 14:41:49 -05:00
Tom Alexander
f141a4e186 Implement the new fields for citation.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 14:38:14 -05:00
Tom Alexander
aba29df34c Implement the new fields for org macro. 2023-12-11 14:22:56 -05:00
Tom Alexander
87ce7d7432 Implement the new fields for timestamp. 2023-12-11 14:18:04 -05:00
Tom Alexander
68dccd54b1 Implement the new fields for radio link. 2023-12-11 14:10:27 -05:00
Tom Alexander
4753f4c7c6 Implement the new fields for plain link.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 13:08:53 -05:00
Tom Alexander
13c62bf29f Implement the new fields for angle link.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 13:00:43 -05:00
Tom Alexander
670209e9fc Fix post blank for comment. 2023-12-11 12:58:05 -05:00
Tom Alexander
4af0d3141f Implement the new fields for statistics cookie. 2023-12-11 12:51:07 -05:00
Tom Alexander
ab281de3c6 Implement the new fields for latex fragment.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 12:46:20 -05:00
Tom Alexander
d556d28f49 Implement the new fields for entity. 2023-12-11 12:44:42 -05:00
Tom Alexander
9cfb2fa052 Implement the new fields for keywords.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 12:38:13 -05:00
Tom Alexander
30c03b5529 Implement the new fields for radio target. 2023-12-11 12:27:35 -05:00
Tom Alexander
b943f90766 Implement the new fields for regular link.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 12:19:49 -05:00
Tom Alexander
0108f5b0b1 Implement the new fields for subscript and superscript.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 12:04:59 -05:00
Tom Alexander
50145c6cf2 Implement the new fields for line break.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-08 16:20:58 -05:00
Tom Alexander
4a8607726c Implement the new fields for comment. 2023-12-08 16:20:58 -05:00
Tom Alexander
9bcba4020d Implement the new fields for verbatim and code. 2023-12-08 16:20:58 -05:00
Tom Alexander
8fd9ff3848 Implement the new fields for bold, italic, underline, and strike-through. 2023-12-08 15:51:38 -05:00
Tom Alexander
3fb7cb82cd Implement get_contents for document.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-11-01 01:22:43 -04:00
Tom Alexander
e0ec5c115f Need a constant value for generic numbers. 2023-11-01 00:49:22 -04:00
Tom Alexander
f0868ba3ed Add a post blank implementation to document. 2023-10-31 23:56:40 -04:00
Tom Alexander
425bc12353 Add implementations to calculate the new fields for heading. 2023-10-31 23:46:53 -04:00
Tom Alexander
03754be71e Implement the new fields for section. 2023-10-31 23:16:57 -04:00
Tom Alexander
70002800c2 Implement the new fields for footnote definitions. 2023-10-31 23:12:04 -04:00
Tom Alexander
281c35677b Implement the new fields for paragraph. 2023-10-31 23:06:43 -04:00
Tom Alexander
92d15c3d91 Fix clippy. 2023-10-31 22:58:17 -04:00
Tom Alexander
b1773ac90e Get post blank for footnote references. 2023-10-31 22:58:17 -04:00
Tom Alexander
645d9abf9c Support nil contents. 2023-10-31 22:58:17 -04:00
Tom Alexander
d2f2bdf88d Implement get_contents for footnote references. 2023-10-31 22:58:17 -04:00
Tom Alexander
90ba17b68c Switch to a numeric post-blank.
Turns out post-blank has different meanings to different object types so we need to return a number to properly do the compare.
2023-10-31 22:32:01 -04:00
Tom Alexander
31406fd520 Fix clippy.
Some checks failed
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-10-31 22:19:39 -04:00
Tom Alexander
49bc51ba89 Compare post-blank. 2023-10-31 22:18:28 -04:00
Tom Alexander
92592104a4 Compare contents begin/end. 2023-10-31 22:11:38 -04:00
Tom Alexander
33f4614d28 Make get_rust_byte_offsets more generic so it can be used for contents.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-31 21:59:58 -04:00
Tom Alexander
6c197c376a Add todo implementations of the new standard property functions. 2023-10-31 21:49:33 -04:00
Tom Alexander
bcf1b49db2 Remove the GetStandardProperties trait.
This was using dynamic dispatch to deal with enums to avoid the repetitive typing.
2023-10-31 21:26:00 -04:00
Tom Alexander
49f6e70a19 Use RPIT to get static dispatch GetStandardProperties. 2023-10-31 21:20:46 -04:00
Tom Alexander
31fb815681 Add a function for getting the post blank. 2023-10-31 21:20:46 -04:00
Tom Alexander
7dfe24ff98 Merge branch 'lazy_parse_lesser_block_contents'
All checks were successful
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-31 20:54:01 -04:00
Tom Alexander
a5627d0cee Do not parse the lesser block contents during parsing, but rather only if the contents are requested.
Some checks failed
rust-test Build rust-test has failed
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
This seemed like an unnecessary allocation during parsing, especially considering we throw away some parses based on whether or not we found radio targets in the source.
2023-10-31 20:43:08 -04:00
Tom Alexander
93cfa71df2 Merge branch 'foreign_document_literate_build_emacs'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-31 19:49:15 -04:00
Tom Alexander
78320d3265 Fix clippy errors.
All checks were successful
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-31 18:32:01 -04:00
Tom Alexander
9e908935f8 Add special case to delete invalid org-mode file.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-31 17:26:13 -04:00
Tom Alexander
b18a703529 Handle nil values for compare_property_object_tree. 2023-10-31 17:20:35 -04:00
Tom Alexander
ea52dc60be Add a literate tutorial for building emacs to the foreign documents test. 2023-10-31 16:33:11 -04:00
Tom Alexander
f5699ce830 Remove PartialEq from Object.
Some checks failed
rustfmt Build rustfmt has succeeded
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-10-31 16:33:10 -04:00
Tom Alexander
10aa0956ee Merge branch 'lesser_block_memory_optimization'
All checks were successful
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-27 22:13:25 -04:00
Tom Alexander
816c164996 Only allocate memory if removing text for lesser blocks.
All checks were successful
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-27 21:50:08 -04:00
Tom Alexander
ee201e1336 Merge branch 'explicit_all_node_iter'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-23 18:39:16 -04:00
Tom Alexander
4897952330 Make creating AllAstNodeIter explicit.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
This is to remove the ambiguity between calling iter on the specific structs like Document and calling iter on an AstNode by having an explicitly-named function to create the iterator.
2023-10-23 18:25:59 -04:00
Tom Alexander
e1d85c6dc2 Merge branch 'remove_set_source'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-23 18:06:56 -04:00
Tom Alexander
c420ccd029 Fix clippy errors.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-23 17:43:43 -04:00
Tom Alexander
a880629831 Make clippy not write to the host git repo. 2023-10-23 17:43:32 -04:00
Tom Alexander
5e2dea1f28 Remove the SetSource trait.
It was only being used for creating paragraphs of specific text, so I just adjusted the of_text function to handle it.
2023-10-23 17:43:32 -04:00
Tom Alexander
f47d688be4 Remove owned String from CustomError.
Some checks failed
rustfmt Build rustfmt has failed
rust-test Build rust-test has failed
clippy Build clippy has failed
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
This is a 15% performance improvement.
2023-10-21 14:29:37 -04:00
Tom Alexander
acfc5e5e68 Only allocate memory when unquoting sexp string that contains escapes.
All checks were successful
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
If the quoted string contains no escape sequences, then unquoting the string can be done by simply shaving off the leading and trailing quotation marks which can be a slice operation. By returning Cow, we can return either a borrowed slice or an owned String.
2023-10-20 12:53:27 -04:00
Tom Alexander
503db94b2c Publish version 0.1.12.
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-18 19:01:43 -04:00
Tom Alexander
a4381e5e39 Merge branch 'keyword_constants' 2023-10-18 18:48:52 -04:00
Tom Alexander
e11de60def Clippy fixes.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-18 18:39:04 -04:00
Tom Alexander
b2479e9de8 Remove Debug from the context variables.
Now that entities are stored in the settings struct, these variables are massive which makes them balloon trace sizes while being mostly unreadable. This removes Debug from them to serve as a static-analysis check that context is ALWAYS ignored in tracing calls.
2023-10-18 18:36:25 -04:00
Tom Alexander
49d1cef7ae Remove context from functions that no longer need it. 2023-10-18 18:28:24 -04:00
Tom Alexander
ba72cc1b29 The variables for keywords are actually constants.
These settings do not need to exist in GlobalSettings because they are actually constants in upstream Org-Mode.
2023-10-18 18:22:01 -04:00
Tom Alexander
c58b0e7c35 Add a script to dump an AST using docker.
All checks were successful
rust-test Build rust-test has succeeded
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
2023-10-18 15:39:52 -04:00
Tom Alexander
f19d262825 Merge branch 'bullshitium'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-18 13:04:16 -04:00
Tom Alexander
68f3f2e159 Clippy fixes.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-18 12:42:09 -04:00
Tom Alexander
269e23c1b1 No more expect-fail tests! 2023-10-18 12:41:12 -04:00
Tom Alexander
e111b8b9b8 Performance optimization. 2023-10-18 12:39:08 -04:00
Tom Alexander
353ff07420 Handle bullshitium for broken dynamic blocks. 2023-10-18 12:32:48 -04:00
Tom Alexander
94dec31130 Consuming trailing whitespace for 🔚 bullshitium. 2023-10-18 12:17:57 -04:00
Tom Alexander
cf5d3ed745 Add tests for the 🔚 bullshitium. 2023-10-18 11:59:55 -04:00
Tom Alexander
b0b287cd47 Handle bullshitium for 🔚. 2023-10-18 11:57:39 -04:00
Tom Alexander
bcdf1f5e9d Merge branch 'entity_special_case'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-18 08:53:57 -04:00
Tom Alexander
17d8e76e05 Do not match POST for entities that end with a space.
This is a special case for en-spaces.
2023-10-18 08:52:18 -04:00
Tom Alexander
8db9038c53 Merge branch 'list_perf_improvement'
Some checks failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has failed
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
2023-10-18 08:40:19 -04:00
Tom Alexander
a276ba70e0 Fix empty content items with final item whitespace cut-off before headlines.
Some checks failed
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-17 15:56:02 -04:00
Tom Alexander
b7442c1e92 Do not match headlines as plain list items. 2023-10-17 15:35:43 -04:00
Tom Alexander
364ba79517 It actually worked on trailing whitespace ownership test case 2. 2023-10-17 15:22:31 -04:00
Tom Alexander
47408763e5 A first stab at a final item whitespace cut-off exit matcher. 2023-10-17 15:08:36 -04:00
Tom Alexander
bd187ebfe7 Remove re-parsing of the final list child. 2023-10-17 14:17:47 -04:00
Tom Alexander
59cb3c2bbf Remove unnecessary closures in plain lists. 2023-10-17 13:59:33 -04:00
Tom Alexander
44f7412a5c Merge branch 'perf_improvement'
Some checks failed
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has succeeded
2023-10-17 13:54:44 -04:00
Tom Alexander
01464057ad Remove unused event types. 2023-10-17 13:43:33 -04:00
Tom Alexander
0208020e3e Also print byte offset.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-17 13:35:40 -04:00
Tom Alexander
a2f53361eb Record element start events and report them when the event_count feature is enabled. 2023-10-17 13:32:01 -04:00
Tom Alexander
17db05c2c7 Unify more error handling. 2023-10-17 12:42:34 -04:00
Tom Alexander
6139ea328d Unify some more error handling. 2023-10-17 12:22:52 -04:00
Tom Alexander
d20b4a410b Remove pointless map_err calls. 2023-10-17 11:56:36 -04:00
Tom Alexander
05c64f53b1 Remove boxed error from CustomError. 2023-10-17 11:40:11 -04:00
Tom Alexander
f65d0bb82d Remove redundant call to space0. 2023-10-17 11:33:26 -04:00
Tom Alexander
50d2831081 Cleanup. 2023-10-17 11:30:23 -04:00
Tom Alexander
bc9bd4f97b Eliminate some closures. 2023-10-17 11:10:18 -04:00
Tom Alexander
369d3e8c50 Add a full-document parse benchmark. 2023-10-17 10:57:04 -04:00
Tom Alexander
7d73eb6bd4 Merge branch 'error_rework'
Some checks failed
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has failed
rustfmt Build rustfmt has failed
rust-test Build rust-test has succeeded
2023-10-17 10:40:46 -04:00
Tom Alexander
f59f153ee7 Clean up. 2023-10-17 10:39:21 -04:00
Tom Alexander
20c4a0f8f7 Continue removing MyError. 2023-10-17 10:35:33 -04:00
Tom Alexander
e776a051ad Continue removing MyError. 2023-10-17 10:13:00 -04:00
Tom Alexander
77e6c22ad8 Continue removing MyError. 2023-10-17 10:09:37 -04:00
Tom Alexander
c9d7251e3b Begin removing the MyError type. 2023-10-17 09:45:18 -04:00
Tom Alexander
8417b5fc9d Add an owned string entry for CustomError. 2023-10-17 09:27:15 -04:00
Tom Alexander
acc29e7977 Publish version 0.1.11.
Some checks failed
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has succeeded
2023-10-16 19:53:46 -04:00
Tom Alexander
ebc0a30035 Merge branch 'clippy_ci_job' 2023-10-16 19:50:01 -04:00
Tom Alexander
e2d55e13d3 Fix some clippy errors that didn't appear on my host version of clippy.
Some checks failed
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-16 19:43:15 -04:00
Tom Alexander
e4d9c5f467 Add makefile command to run clippy through docker. 2023-10-16 19:38:45 -04:00
Tom Alexander
d8e3a85ef7 We need to add dependencies so we are now building a container. 2023-10-16 19:34:53 -04:00
Tom Alexander
464685b52b Use a cargo cache for the clippy CI job.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-16 19:27:15 -04:00
Tom Alexander
5fed4e80a7 Add a CI job to run clippy for every push. 2023-10-16 19:22:59 -04:00
Tom Alexander
e53140426f Merge branch 'clippy'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-16 19:14:19 -04:00
Tom Alexander
9a4d290cf8 Apply more suggestions. 2023-10-16 19:12:25 -04:00
Tom Alexander
acd24d6198 Apply more suggestions. 2023-10-16 19:02:34 -04:00
Tom Alexander
880b00ef3f Apply more suggestions. 2023-10-16 18:54:41 -04:00
Tom Alexander
3069711447 Apply more suggestions. 2023-10-16 18:29:21 -04:00
Tom Alexander
4b6c717812 Apply more suggestions. 2023-10-16 17:58:52 -04:00
Tom Alexander
1d329cc310 Apply more suggestions. 2023-10-16 17:28:28 -04:00
Tom Alexander
b4f9a3b9b6 Apply more suggestions. 2023-10-16 17:14:44 -04:00
Tom Alexander
2dd5246506 Apply more suggestions. 2023-10-16 17:03:39 -04:00
Tom Alexander
4ba0e3611b Apply more suggestions. 2023-10-16 17:03:39 -04:00
Tom Alexander
728f79b86c Apply more suggestions. 2023-10-16 17:03:39 -04:00
Tom Alexander
192a4a2891 Remove unnecessary lifetimes. 2023-10-16 17:03:39 -04:00
Tom Alexander
fafd85fb30 Apply some clippy fixes. 2023-10-16 17:03:39 -04:00
Tom Alexander
1c23065329 Add a clippy command to the makefile. 2023-10-16 17:03:39 -04:00
Tom Alexander
ed105b04ad Merge branch 'cargo_bench' 2023-10-16 16:03:56 -04:00
Tom Alexander
f10efec21d No performance change switching affiliated_key to using element macro. 2023-10-16 15:57:18 -04:00
Tom Alexander
72b4cf8e71 Add the first use of the rust benchmark tests. 2023-10-16 15:50:08 -04:00
Tom Alexander
547fc40dbe No measurable performance improvement with native builds over LTO release builds.
Some checks failed
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
Leaving the code for this commented out because it involved an unstable cargo feature without showing any benefit. I would like to revisit this later.
2023-10-16 15:21:36 -04:00
Tom Alexander
9f1671658d Merge branch 'object_parser_perf'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-16 15:04:04 -04:00
Tom Alexander
18d0676fad Clean up. 2023-10-16 15:03:23 -04:00
Tom Alexander
7833a58461 Apply a similar optimization to the detect element parser but also unify detection of affiliated keywords. 2023-10-16 14:55:40 -04:00
Tom Alexander
0020d71089 Extend that optimization to more object parsers. 2023-10-16 14:41:12 -04:00
Tom Alexander
cfdf39d1fa Significantly reduce the use of closures in the object parsers. 2023-10-16 14:25:02 -04:00
Tom Alexander
26f1eae9a1 Merge branch 'planning_before_property_drawer'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-16 13:48:03 -04:00
Tom Alexander
3eff85059a Add support for planning before property drawer when calculating additional properties for headlines. 2023-10-16 13:35:03 -04:00
Tom Alexander
d2d0e9e5dd Merge branch 'optval_affiliated_keywords'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-16 13:16:44 -04:00
Tom Alexander
c86d1000c0 Do not clear values in lists of strings.
Some checks failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
This is a hold-over from when I had list of single string which was a misunderstanding of the optional pair type.
2023-10-16 12:58:20 -04:00
Tom Alexander
911634cb42 Attr_ affiliated keywords should be lists of strings. 2023-10-16 12:55:18 -04:00
Tom Alexander
0aa746fb1e Implement comparison for object tree. 2023-10-16 12:50:53 -04:00
Tom Alexander
33800c4a88 Implement comparison for optional pair. 2023-10-16 12:05:36 -04:00
Tom Alexander
909ccadfa1 Beginning update to compare_affiliated_keywords. 2023-10-16 11:45:54 -04:00
Tom Alexander
e352deb989 Update parse_affiliated_keywords for handling optional pairs. 2023-10-16 11:42:20 -04:00
Tom Alexander
f5a6a26c43 Disable the existing handling of affiliated keywords. 2023-10-15 20:31:14 -04:00
Tom Alexander
dd7184da54 Add analysis from test. 2023-10-15 20:22:46 -04:00
Tom Alexander
1168ddb1fe Start an investigation into affiliated keyword behavior. 2023-10-15 17:38:56 -04:00
Tom Alexander
77ab636e6a Merge branch 'unify_keyword_constants' 2023-10-15 15:59:21 -04:00
Tom Alexander
f5dcacc79d Do not match keyword name if a longer keyword name would match. 2023-10-15 15:55:19 -04:00
Tom Alexander
e7c3c7aab6 Switch the keyword parsers over to using the settings from GlobalSettings. 2023-10-15 15:17:08 -04:00
Tom Alexander
7603b0a1cc Add a test showing we are not handling optval properly. 2023-10-15 15:16:23 -04:00
Tom Alexander
dea3721b1c Fix reporting errors in tests.
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has succeeded
2023-10-15 15:16:06 -04:00
117 changed files with 4437 additions and 2048 deletions

View File

@@ -0,0 +1,191 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: clippy
spec:
pipelineSpec:
params:
- name: image-name
description: The name for the built image
type: string
- name: path-to-image-context
description: The path to the build context
type: string
- name: path-to-dockerfile
description: The path to the Dockerfile
type: string
- name: GIT_USER_NAME
description: The username for git
type: string
default: "fluxcdbot"
- name: GIT_USER_EMAIL
description: The email for git
type: string
default: "fluxcdbot@users.noreply.github.com"
tasks:
- name: do-stuff
taskSpec:
metadata: {}
stepTemplate:
image: alpine:3.18
name: ""
resources:
requests:
cpu: 10m
memory: 600Mi
workingDir: /workspace/source
steps:
- image: alpine:3.18
name: do-stuff-step
script: |
#!/usr/bin/env sh
echo "hello world"
- name: report-pending
taskRef:
name: gitea-set-status
runAfter:
- fetch-repository
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has started"
- name: STATE
value: pending
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: fetch-repository
taskRef:
name: git-clone
workspaces:
- name: output
workspace: git-source
params:
- name: url
value: $(params.REPO_URL)
- name: revision
value: $(params.PULL_BASE_SHA)
- name: deleteExisting
value: "true"
- name: build-image
taskRef:
name: kaniko
params:
- name: IMAGE
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: CONTEXT
value: $(params.path-to-image-context)
- name: DOCKERFILE
value: $(params.path-to-dockerfile)
- name: BUILDER_IMAGE
value: "gcr.io/kaniko-project/executor:v1.12.1"
- name: EXTRA_ARGS
value:
- --cache=true
- --cache-copy-layers
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
- --use-new-run # Should result in a speed-up
- --reproducible # To remove timestamps so layer caching works.
- --snapshot-mode=redo
- --skip-unused-stages=true
- --registry-mirror=dockerhub.dockerhub.svc.cluster.local
workspaces:
- name: source
workspace: git-source
- name: dockerconfig
workspace: docker-credentials
runAfter:
- fetch-repository
- name: clippy
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- build-image
params:
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
finally:
- name: report-success
when:
- input: "$(tasks.status)"
operator: in
values: ["Succeeded", "Completed"]
taskRef:
name: gitea-set-status
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has succeeded"
- name: STATE
value: success
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: report-failure
when:
- input: "$(tasks.status)"
operator: in
values: ["Failed"]
taskRef:
name: gitea-set-status
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has failed"
- name: STATE
value: failure
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
workspaces:
- name: git-source
- name: docker-credentials
workspaces:
- name: git-source
volumeClaimTemplate:
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
subPath: rust-source
- name: cargo-cache
persistentVolumeClaim:
claimName: organic-cargo-cache-clippy
- name: docker-credentials
secret:
secretName: harbor-plain
serviceAccountName: build-bot
timeout: 240h0m0s
params:
- name: image-name
value: "harbor.fizz.buzz/private/organic-clippy"
- name: path-to-image-context
value: docker/organic_clippy/
- name: path-to-dockerfile
value: docker/organic_clippy/Dockerfile

View File

@@ -30,3 +30,10 @@ spec:
skip_branches:
# We already run on every commit, so running when the semver tags get pushed is causing needless double-processing.
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
- name: clippy
source: "pipeline-clippy.yaml"
# Override https-based url from lighthouse events.
clone_uri: "git@code.fizz.buzz:talexander/organic.git"
skip_branches:
# We already run on every commit, so running when the semver tags get pushed is causing needless double-processing.
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$"

View File

@@ -1,6 +1,8 @@
# cargo-features = ["profile-rustflags"]
[package]
name = "organic"
version = "0.1.10"
version = "0.1.13"
authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser."
edition = "2021"
@@ -57,6 +59,7 @@ default = []
compare = ["tokio/process", "tokio/macros"]
foreign_document_test = ["compare", "dep:futures", "tokio/sync", "dep:walkdir", "tokio/process"]
tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"]
event_count = []
# Optimized build for any sort of release.
[profile.release-lto]
@@ -64,6 +67,13 @@ inherits = "release"
lto = true
strip = "symbols"
# Optimized build for local execution.
# [profile.native]
# inherits = "release"
# lto = true
# strip = "symbols"
# rustflags = ["-C", "target-cpu=native"]
# Profile for performance testing with the "perf" tool. Notably keeps debug enabled and does not strip symbols to make reading the perf output easier.
[profile.perf]
inherits = "release"

View File

@@ -37,6 +37,14 @@ clean:
format:
> $(MAKE) -C docker/cargo_fmt run
.PHONY: dockerclippy
dockerclippy:
> $(MAKE) -C docker/organic_clippy run
.PHONY: clippy
clippy:
> cargo clippy --no-deps --all-targets --all-features -- -D warnings
.PHONY: test
test:
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)

View File

@@ -14,7 +14,7 @@ use walkdir::WalkDir;
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let destination = Path::new(&out_dir).join("tests.rs");
let mut test_file = File::create(&destination).unwrap();
let mut test_file = File::create(destination).unwrap();
// Re-generate the tests if any org-mode files change
println!("cargo:rerun-if-changed=org_mode_samples");
@@ -51,7 +51,7 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
.to_lowercase()
.strip_suffix(".org")
.expect("Should have .org extension")
.replace("/", "_");
.replace('/', "_");
write!(
test_file,
@@ -66,10 +66,6 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
}
#[cfg(feature = "compare")]
fn is_expect_fail(name: &str) -> Option<&str> {
match name {
"greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
"element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
_ => None,
}
fn is_expect_fail(_name: &str) -> Option<&str> {
None
}

View File

@@ -0,0 +1,5 @@
FROM rustlang/rust:nightly-alpine3.17
RUN apk add --no-cache musl-dev
ENTRYPOINT ["cargo", "clippy", "--no-deps", "--all-targets", "--all-features", "--", "-D", "warnings"]

View File

@@ -0,0 +1,37 @@
IMAGE_NAME:=organic-clippy
# REMOTE_REPO:=harbor.fizz.buzz/private
.PHONY: all
all: build push
.PHONY: build
build:
docker build -t $(IMAGE_NAME) -f Dockerfile .
.PHONY: push
push:
ifdef REMOTE_REPO
docker tag $(IMAGE_NAME) $(REMOTE_REPO)/$(IMAGE_NAME)
docker push $(REMOTE_REPO)/$(IMAGE_NAME)
else
@echo "REMOTE_REPO not defined, not pushing to a remote repo."
endif
.PHONY: clean
clean:
docker rmi $(IMAGE_NAME)
ifdef REMOTE_REPO
docker rmi $(REMOTE_REPO)/$(IMAGE_NAME)
else
@echo "REMOTE_REPO not defined, not removing from remote repo."
endif
docker volume rm rust-cache cargo-cache
# NOTE: This target will write to folders underneath the git-root
.PHONY: run
run: build
docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)
.PHONY: shell
shell: build
docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)

View File

@@ -93,6 +93,12 @@ ARG WORG_PATH=/foreign_documents/worg
ARG WORG_REPO=https://git.sr.ht/~bzg/worg
RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD
ARG LITERATE_BUILD_EMACS_VERSION=e3ac1afe1e40af601be7af12c1d13d96308ab209
ARG LITERATE_BUILD_EMACS_PATH=/foreign_documents/literate_build_emacs
ARG LITERATE_BUILD_EMACS_REPO=https://gitlab.com/spudlyo/orgdemo2.git
RUN mkdir -p $LITERATE_BUILD_EMACS_PATH && git -C $LITERATE_BUILD_EMACS_PATH init --initial-branch=main && git -C $LITERATE_BUILD_EMACS_PATH remote add origin $LITERATE_BUILD_EMACS_REPO && git -C $LITERATE_BUILD_EMACS_PATH fetch origin $LITERATE_BUILD_EMACS_VERSION && git -C $LITERATE_BUILD_EMACS_PATH checkout FETCH_HEAD
# unused/aws.org contains invalid paths for setupfile which causes both upstream org-mode and Organic to error out.
RUN rm $LITERATE_BUILD_EMACS_PATH/unused/aws.org
FROM tester as foreign-document-test
RUN apk add --no-cache bash coreutils
@@ -100,6 +106,7 @@ RUN mkdir /foreign_documents
COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams
COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs
COPY --from=foreign-document-gather /foreign_documents/worg /foreign_documents/worg
COPY --from=foreign-document-gather /foreign_documents/literate_build_emacs /foreign_documents/literate_build_emacs
COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode
COPY --from=build-emacs /root/emacs /foreign_documents/emacs
ENTRYPOINT ["cargo", "run", "--bin", "foreign_document_test", "--features", "compare,foreign_document_test", "--profile", "release-lto"]

View File

@@ -0,0 +1,42 @@
* Elisp Structure
| Keyword | Single | Double | Single Optval | Double Optval |
|---------+---------------+---------------+---------------+---------------|
| CAPTION | objtree | objtree | objtree | objtree |
| DATA | quoted(:name) | quoted(:name) | - | - |
| HEADER | list(quoted) | list(quoted) | - | - |
| NAME | quoted(:name) | quoted(:name) | - | - |
| PLOT | quoted(:plot) | quoted(:plot) | - | - |
| RESULTS | optional pair | optional pair | optional pair | optional pair |
* types
** objtree
Outer list: 1 per keyword
next list: first entry = list of objects for value. remaining entries = optval
** list(quoted)
List of quoted strings, 1 per keyword
** quoted(NAME)
Quoted string under the NAME property (for example quoted(:name))
** optional pair
When optval is supplied this is an alist with the field value being the real value and the 3nd value being the optval.
#+begin_src elisp
("*f*" . "*bar*")
#+end_src
When optval is not supplied this is a list containing a single string of the last occurrence of this keyword.
#+begin_src elisp
("*c*")
#+end_src
* Default settings
#+begin_src text
org-element-dual-keywords ("CAPTION" "RESULTS")
org-element-parsed-keywords ("CAPTION")
org-element-multiple-keywords ("CAPTION" "HEADER")
org-babel-results-keyword "RESULTS"
#+end_src
* Analysis
We don't have an example of a parsed non-dual keyword
Looks like multiple triggers list 1 per keyword
dual triggers support for optval
parsed triggers objects

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
#
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
file_path="${DIR}/test_document.org"
for TARGET_VARIABLE in RESULTS CAPTION HEADER DATA NAME PLOT; do
INIT_SCRIPT=$(cat <<EOF
(progn
(erase-buffer)
(require 'org)
(defun org-table-align () t)
(setq vc-handled-backends nil)
(find-file "/input/${file_path}")
(org-mode)
(replace-regexp-in-region "foo" "${TARGET_VARIABLE}")
(message "%s" (pp-to-string (org-element-parse-buffer)))
)
EOF
)
docker run --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -w /input --entrypoint "" organic-test emacs -q --no-site-file --no-splash --batch --eval "$INIT_SCRIPT" 2> "${DIR}/${TARGET_VARIABLE}"
done
# exec docker run --init --rm -i -t --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -w /input --entrypoint "" organic-test emacs -q --no-site-file --no-splash --eval "$INIT_SCRIPT"
# org-element-dual-keywords ("CAPTION" "RESULTS")
# org-element-parsed-keywords ("CAPTION")
# org-element-multiple-keywords ("CAPTION" "HEADER")
# org-babel-results-keyword "RESULTS"

View File

@@ -0,0 +1,25 @@
# Single instance
#+foo: *a*
#+begin_example
#+end_example
# Two instances
#+foo: *b*
#+foo: *c*
#+begin_example
#+end_example
# Single with optval
#+foo[*bar*]: *d*
#+begin_example
#+end_example
# Two with optval
#+foo[*bar*]: *e*
#+foo[*bar*]: *f*
#+begin_example
#+end_example

View File

@@ -0,0 +1,5 @@
#+caption:
#+caption: *foo*
#+caption[bar]:
#+begin_src bash
#+end_src

View File

@@ -0,0 +1,3 @@
foo
:end:
bar

View File

@@ -0,0 +1,2 @@
foo
:end:

View File

View File

@@ -0,0 +1,4 @@

View File

@@ -0,0 +1,5 @@
* foo

View File

@@ -1,3 +1,32 @@
* Empty
:PROPERTIES:
:END:
* Single new line
:PROPERTIES:
:END:
* Single line with spaces
:PROPERTIES:
:END:
* Many lines, first line without spaces
:PROPERTIES:
:END:
* Many lines, first line with spaces
:PROPERTIES:
:END:
* Many lines, first line with spaces, later line with spaces
:PROPERTIES:
:END:

View File

@@ -0,0 +1,7 @@
** Foo
DEADLINE: <2023-10-16 Mon>
:PROPERTIES:
:foo: *a*
:Bar: *b*
:BAZ: *c*
:END:

View File

@@ -5,3 +5,5 @@
#+call: dolar cat(dog)
#+call: (bat)
#+call:

View File

@@ -0,0 +1,3 @@
: foo
:
: bar

View File

@@ -0,0 +1,6 @@
1. foo
#+begin_src text
#+end_src
2. baz

View File

@@ -0,0 +1,16 @@
#+results[foo]: bar
#+results[lorem]: ipsum
#+begin_example
baz
#+end_example
#+caption[lorem]: ipsum
#+caption[foo]: bar
#+begin_example
baz
#+end_example
#+header[foo]: bar
#+begin_example
baz
#+end_example

View File

@@ -1,3 +1,3 @@
foo <<bar>> baz
<<FOO>> bar
lorem << ipsum >> dolar
[[FOO][baz]]

View File

@@ -0,0 +1,5 @@
* foo
** bar
* baz

58
scripts/dump_ast.bash Executable file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env bash
#
# Dump the AST of an org-mode document from emacs
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
REALPATH=$(command -v uu-realpath || command -v realpath)
MAKE=$(command -v gmake || command -v make)
############## Setup #########################
function die {
local status_code="$1"
shift
(>&2 echo "${@}")
exit "$status_code"
}
function log {
(>&2 echo "${@}")
}
############## Program #########################
function main {
if [ $# -eq 0 ]; then
dump_ast_stdin "${@}"
else
dump_ast_file "${@}"
fi
}
function dump_ast_stdin {
# Until we can find a good way to encode stdin as an elisp string in bash, I cannot operate on stdin.
die 1 "This script only works on files."
}
function dump_ast_file {
local target_file mounted_file elisp_script
target_file=$($REALPATH "$1")
mounted_file="/input${target_file}"
elisp_script=$(cat <<EOF
(progn
(erase-buffer)
(require 'org)
(defun org-table-align () t)
(find-file-read-only "${mounted_file}")
(org-mode)
(message "%s" (pp-to-string (org-element-parse-buffer)))
)
EOF
)
exec docker run --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" --entrypoint "" organic-test emacs -q --no-site-file --no-splash --batch --eval "$elisp_script"
}
main "${@}"

View File

@@ -14,7 +14,7 @@ function main {
additional_flags+=(--profile "$PROFILE")
fi
(cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}")
perf record --freq=2000 --call-graph dwarf --output="$DIR/../perf.data" "$DIR/../target/${PROFILE}/parse" "${@}"
perf record --freq=70000 --call-graph dwarf --output="$DIR/../perf.data" "$DIR/../target/${PROFILE}/parse" "${@}"
# Convert to a format firefox will read
# flags to consider --show-info

View File

@@ -15,23 +15,21 @@ mod init_tracing;
#[cfg(not(feature = "tracing"))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
let result = rt.block_on(async {
rt.block_on(async {
let main_body_result = main_body().await;
main_body_result
});
result
})
}
#[cfg(feature = "tracing")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
let result = rt.block_on(async {
rt.block_on(async {
init_telemetry()?;
let main_body_result = main_body().await;
shutdown_telemetry()?;
main_body_result
});
result
})
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@@ -21,23 +21,21 @@ mod init_tracing;
#[cfg(not(feature = "tracing"))]
fn main() -> Result<ExitCode, Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
let result = rt.block_on(async {
rt.block_on(async {
let main_body_result = main_body().await;
main_body_result
});
result
})
}
#[cfg(feature = "tracing")]
fn main() -> Result<ExitCode, Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
let result = rt.block_on(async {
rt.block_on(async {
init_telemetry()?;
let main_body_result = main_body().await;
shutdown_telemetry()?;
main_body_result
});
result
})
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -55,6 +53,9 @@ async fn main_body() -> Result<ExitCode, Box<dyn std::error::Error>> {
let layer = layer.chain(compare_group("doomemacs", || {
compare_all_org_document("/foreign_documents/doomemacs")
}));
let layer = layer.chain(compare_group("literate_build_emacs", || {
compare_all_org_document("/foreign_documents/literate_build_emacs")
}));
let running_tests: Vec<_> = layer.map(|c| tokio::spawn(c.run_test())).collect();
let mut any_failed = false;
@@ -108,10 +109,9 @@ fn compare_howard_abrams() -> impl Iterator<Item = TestConfig> {
let layer = layer.chain(compare_group("clojure-yesql-xp", || {
compare_all_org_document("/foreign_documents/howardabrams/clojure-yesql-xp")
}));
let layer = layer.chain(compare_group("veep", || {
layer.chain(compare_group("veep", || {
compare_all_org_document("/foreign_documents/howardabrams/veep")
}));
layer
}))
}
fn compare_group<N: Into<String>, F: Fn() -> I, I: Iterator<Item = TestConfig>>(
@@ -195,7 +195,6 @@ struct ResultLayer {
#[derive(Debug)]
struct SingleFileResult {
name: String,
file_path: PathBuf,
status: TestStatus,
}
@@ -225,7 +224,6 @@ impl SingleFile {
let result = silent_compare_on_file(&self.file_path).await;
Ok(SingleFileResult {
name: self.name,
file_path: self.file_path,
status: if let Ok(true) = result {
TestStatus::Pass
} else {

View File

@@ -1,3 +1,5 @@
use std::borrow::Borrow;
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::fmt::Debug;
use std::str::FromStr;
@@ -16,7 +18,6 @@ use super::util::get_property_unquoted_atom;
use crate::types::AstNode;
use crate::types::CharOffsetInLine;
use crate::types::LineNumber;
use crate::types::Object;
use crate::types::RetainLabels;
use crate::types::SwitchNumberLines;
@@ -57,11 +58,11 @@ impl<'b, 's> ComparePropertiesResult<'b, 's> {
/// Do no comparison.
///
/// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values.
pub(crate) fn compare_noop<'b, 's, 'x, R, RG>(
pub(crate) fn compare_noop<'b, 's, R, RG>(
_source: &'s str,
_emacs: &'b Token<'s>,
_rust_node: R,
_emacs_field: &'x str,
_emacs_field: &str,
_rust_value_getter: RG,
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
Ok(ComparePropertiesResult::NoChange)
@@ -70,18 +71,16 @@ pub(crate) fn compare_noop<'b, 's, 'x, R, RG>(
/// Do no comparison.
///
/// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values.
pub(crate) fn compare_identity() -> () {
()
}
pub(crate) fn compare_identity() {}
/// Assert that the emacs value is always nil or absent.
///
/// This is usually used for fields which, in my testing, are always nil. Using this compare function instead of simply doing a compare_noop will enable us to be alerted when we finally come across an org-mode document that has a value other than nil for the property.
pub(crate) fn compare_property_always_nil<'b, 's, 'x, R, RG>(
pub(crate) fn compare_property_always_nil<'b, 's, R, RG>(
_source: &'s str,
emacs: &'b Token<'s>,
_rust_node: R,
emacs_field: &'x str,
emacs_field: &str,
_rust_value_getter: RG,
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
let value = get_property(emacs, emacs_field)?;
@@ -100,7 +99,6 @@ pub(crate) fn compare_property_always_nil<'b, 's, 'x, R, RG>(
pub(crate) fn compare_property_quoted_string<
'b,
's,
'x,
R,
RV: AsRef<str> + std::fmt::Debug,
RG: Fn(R) -> Option<RV>,
@@ -108,12 +106,12 @@ pub(crate) fn compare_property_quoted_string<
_source: &'s str,
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
emacs_field: &str,
rust_value_getter: RG,
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
let value = get_property_quoted_string(emacs, emacs_field)?;
let rust_value = rust_value_getter(rust_node);
if rust_value.as_ref().map(|s| s.as_ref()) != value.as_ref().map(String::as_str) {
if rust_value.as_ref().map(|s| s.as_ref()) != value.as_deref() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
@@ -181,7 +179,6 @@ where
pub(crate) fn compare_property_list_of_quoted_string<
'b,
's,
'x,
R,
RV: AsRef<str> + std::fmt::Debug,
RI: Iterator<Item = RV>,
@@ -190,7 +187,7 @@ pub(crate) fn compare_property_list_of_quoted_string<
_source: &'s str,
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
emacs_field: &str,
rust_value_getter: RG,
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
let value = get_property(emacs, emacs_field)?
@@ -267,15 +264,12 @@ pub(crate) fn compare_property_set_of_quoted_string<
.iter()
.map(|e| e.as_atom())
.collect::<Result<Vec<_>, _>>()?;
let value: Vec<String> = value
let value: Vec<Cow<'_, str>> = value
.into_iter()
.map(unquote)
.collect::<Result<Vec<_>, _>>()?;
let value: BTreeSet<&str> = value.iter().map(|e| e.as_str()).collect();
let mismatched: Vec<_> = value
.symmetric_difference(&rust_value)
.map(|val| *val)
.collect();
let value: BTreeSet<&str> = value.iter().map(|e| e.borrow()).collect();
let mismatched: Vec<_> = value.symmetric_difference(&rust_value).copied().collect();
if !mismatched.is_empty() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
@@ -288,11 +282,111 @@ pub(crate) fn compare_property_set_of_quoted_string<
Ok(ComparePropertiesResult::NoChange)
}
pub(crate) fn compare_property_boolean<'b, 's, 'x, R, RG: Fn(R) -> bool>(
pub(crate) fn compare_property_optional_pair<
'b,
's,
R,
RV: AsRef<str> + std::fmt::Debug,
ROV: AsRef<str> + std::fmt::Debug,
RG: Fn(R) -> Option<(Option<ROV>, RV)>,
>(
_source: &'s str,
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
emacs_field: &str,
rust_value_getter: RG,
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
let value = get_property(emacs, emacs_field)?
.map(Token::as_list)
.map_or(Ok(None), |r| r.map(Some))?;
let rust_value = rust_value_getter(rust_node);
match (value, &rust_value) {
(None, None) => {}
(None, Some(_)) | (Some(_), None) => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(el), Some((Some(_), _))) if el.len() != 3 => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(el), Some((None, _))) if el.len() != 1 => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(el), Some((Some(orl), rl))) => {
let e = el
.first()
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.map(unquote)
.map_or(Ok(None), |r| r.map(Some))?
.expect("Above match proved length to be 3.");
let oe = el
.get(2)
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.map(unquote)
.map_or(Ok(None), |r| r.map(Some))?
.expect("Above match proved length to be 3.");
let r = rl.as_ref();
let or = orl.as_ref();
if e != r {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}",
emacs_field, e, r, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
if oe != or {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}",
emacs_field, e, r, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
}
(Some(el), Some((None, rl))) => {
let e = el
.first()
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.map(unquote)
.map_or(Ok(None), |r| r.map(Some))?
.expect("Above match proved length to be 1.");
let r = rl.as_ref();
if e != r {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}",
emacs_field, e, r, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
}
}
Ok(ComparePropertiesResult::NoChange)
}
pub(crate) fn compare_property_boolean<'b, 's, R, RG: Fn(R) -> bool>(
_source: &'s str,
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &str,
rust_value_getter: RG,
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
// get_property already converts nil to None.
@@ -376,14 +470,7 @@ where
match (value, rust_value) {
(None, None) => {}
(Some(el), None)
if el.len() == 1
&& el.into_iter().all(|t| {
if let Ok(r#""""#) = t.as_atom() {
true
} else {
false
}
}) => {}
if el.len() == 1 && el.iter().all(|t| matches!(t.as_atom(), Ok(r#""""#))) => {}
(None, rv @ Some(_)) | (Some(_), rv @ None) => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
@@ -412,118 +499,161 @@ where
Ok(ComparePropertiesResult::NoChange)
}
/// Special compare used for affiliate keywords that are parsed as objects.
///
/// Org-mode seems to store these as a 3-deep list:
/// - Outer list with 1 element per #+caption keyword (or other parsed keyword).
/// - Middle list which has:
/// - first element is a list of objects representing the value after the colon.
/// - every additional element is a list of objects from inside the square brackets (the optional value).
pub(crate) fn compare_property_list_of_list_of_list_of_ast_nodes<
pub(crate) fn compare_property_object_tree<
'b,
's,
'x,
R,
RG: Fn(R) -> Option<&'b Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>>,
RV: std::fmt::Debug + 'b,
ROV: std::fmt::Debug + 'b,
RI: Iterator<Item = &'b (Option<Vec<ROV>>, Vec<RV>)> + ExactSizeIterator + std::fmt::Debug,
RG: Fn(R) -> Option<RI>,
>(
source: &'s str,
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
rust_value_getter: RG,
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
// TODO: Replace Object<'s> with generics. I hard-coded Object in to make lifetimes easier.
let rust_value = rust_value_getter(rust_node);
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>>
where
AstNode<'b, 's>: From<&'b RV>,
AstNode<'b, 's>: From<&'b ROV>,
{
let value = get_property(emacs, emacs_field)?
.map(Token::as_list)
.map_or(Ok(None), |r| r.map(Some))?;
let (value, rust_value) = match (value, rust_value) {
let rust_value = rust_value_getter(rust_node);
let (outer_emacs_list, outer_rust_list) = match (value, rust_value) {
(None, None) => {
return Ok(ComparePropertiesResult::NoChange);
}
(None, Some(_)) | (Some(_), None) => {
(None, rv @ Some(_)) | (Some(_), rv @ None) => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
emacs_field, value, rv
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(value), Some(rust_value)) if value.len() != rust_value.len() => {
(Some(el), Some(rl)) if el.len() != rl.len() => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
emacs_field, el, rl
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(value), Some(rust_value)) => (value, rust_value),
(Some(el), Some(rl)) => (el, rl),
};
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(outer_rust_list.len());
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
// Iterate the outer lists
for (value, (rust_optional, rust_value)) in value.iter().zip(rust_value.iter()) {
let mut middle_value = value.as_list()?.iter();
// First element of middle list is the mandatory value (the value past the colon).
let mandatory_value = middle_value.next();
let mandatory_value = match mandatory_value {
Some(mandatory_value) => mandatory_value,
None => {
for (kw_e, kw_r) in outer_emacs_list.iter().zip(outer_rust_list) {
match (kw_e.as_atom(), kw_r) {
(Ok("nil"), (None, mandatory_value)) if mandatory_value.is_empty() => {
// If its an empty keyword then it becomes nil in the elisp.
continue;
}
(Ok("nil"), _) => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
emacs_field, kw_e, kw_r
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
};
// Compare optional value
if let Some(rust_optional) = rust_optional {
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
if rust_optional.len() != middle_value.len() {
_ => {}
}
let kw_e = kw_e.as_list()?;
let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0);
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length);
if let Some(or) = &kw_r.0 {
// if optional value
let mut kw_e = kw_e.iter();
// First element is a list representing the mandatory value.
if let Some(val_e) = kw_e.next() {
match (val_e.as_atom(), kw_r) {
(Ok("nil"), (_, mandatory_value)) if mandatory_value.is_empty() => {}
(Ok("nil"), _) => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, kw_e, kw_r
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
_ => {
let el = val_e.as_list()?;
if el.len() != kw_r.1.len() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, kw_e, kw_r
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
for (e, r) in el.iter().zip(kw_r.1.iter()) {
child_status.push(compare_ast_node(source, e, r.into())?);
}
}
};
} else {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} optional value length mismatch (emacs != rust) {} != {} | {:?}",
emacs_field,
middle_value.len(),
rust_optional.len(),
rust_optional
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, kw_e, kw_r
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
for (e, r) in middle_value.zip(rust_optional) {
// Remaining elements are the optional value.
if kw_e.len() != or.len() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, kw_e, kw_r
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
for (e, r) in kw_e.zip(or.iter()) {
child_status.push(compare_ast_node(source, e, r.into())?);
}
if !child_status.is_empty() {
let diff_scope = artificial_diff_scope("optional value", child_status)?;
full_status.push(diff_scope);
} else {
// if no optional value
if !kw_e.len() == 1 {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, kw_e, kw_r
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
}
// Compare mandatory value
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
let mandatory_value = mandatory_value.as_list()?;
if rust_value.len() != mandatory_value.len() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mandatory value length mismatch (emacs != rust) {} != {} | {:?}",
emacs_field,
mandatory_value.len(),
rust_value.len(),
rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
for (e, r) in mandatory_value.iter().zip(rust_value) {
child_status.push(compare_ast_node(source, e, r.into())?);
let e = kw_e
.first()
.map(Token::as_list)
.map_or(Ok(None), |r| r.map(Some))?
.expect("The above if-statement proves this will be Some.")
.iter();
let r = kw_r.1.iter();
if e.len() != r.len() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, kw_e, kw_r
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
for (e, r) in e.zip(r) {
child_status.push(compare_ast_node(source, e, r.into())?);
}
}
if !child_status.is_empty() {
let diff_scope = artificial_diff_scope("mandatory value", child_status)?;
full_status.push(diff_scope);
}
}
if full_status.is_empty() {
Ok(ComparePropertiesResult::NoChange)
} else {
@@ -553,7 +683,7 @@ pub(crate) fn compare_property_number_lines<
(Some(number_lines), Some(rust_number_lines)) => {
let token_list = number_lines.as_list()?;
let number_type = token_list
.get(0)
.first()
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.ok_or(":number-lines should have a type.")?;

View File

@@ -57,7 +57,6 @@ use crate::types::FixedWidthArea;
use crate::types::FootnoteDefinition;
use crate::types::FootnoteReference;
use crate::types::FootnoteReferenceType;
use crate::types::GetStandardProperties;
use crate::types::Heading;
use crate::types::HorizontalRule;
use crate::types::Hour;
@@ -128,7 +127,7 @@ pub struct DiffResult<'b, 's> {
emacs_token: &'b Token<'s>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub(crate) enum DiffStatus {
Good,
Bad,
@@ -164,7 +163,7 @@ impl<'b, 's> DiffEntry<'b, 's> {
fn is_immediately_bad(&self) -> bool {
match self {
DiffEntry::DiffResult(diff) => diff.status == DiffStatus::Bad,
DiffEntry::DiffResult(diff) => matches!(diff.status, DiffStatus::Bad),
DiffEntry::DiffLayer(_) => false,
}
}
@@ -227,7 +226,7 @@ impl<'b, 's> DiffResult<'b, 's> {
status_text = status_text,
name = self.name,
char_offset = preceding_text.chars().count() + 1,
message = self.message.as_ref().map(|m| m.as_str()).unwrap_or("")
message = self.message.as_deref().unwrap_or("")
);
for child in self.children.iter() {
child.print_indented(indentation + 1, original_document)?;
@@ -330,8 +329,8 @@ pub(crate) fn artificial_diff_scope<'b, 's>(
.into())
}
pub(crate) fn artificial_owned_diff_scope<'b, 's, 'x>(
name: &'x str,
pub(crate) fn artificial_owned_diff_scope<'b, 's>(
name: &str,
children: Vec<DiffEntry<'b, 's>>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
Ok(DiffLayer {
@@ -413,7 +412,7 @@ pub(crate) fn compare_ast_node<'b, 's>(
name: rust.get_elisp_fact().get_elisp_name(),
message: Some(e.to_string()),
children: Vec::new(),
rust_source: rust.get_standard_properties().get_source(),
rust_source: rust.get_source(),
emacs_token: emacs,
}
.into()
@@ -426,14 +425,9 @@ pub(crate) fn compare_ast_node<'b, 's>(
// PlainText is a special case because upstream Org-Mode uses relative values for the bounds in plaintext rather than absolute so the below checks do not account for that.
if let AstNode::PlainText(_) = rust {
} else {
match compare_standard_properties(source, emacs, &rust) {
Err(err) => {
compare_result.status = DiffStatus::Bad;
compare_result.message = Some(err.to_string())
}
Ok(_) => {}
}
} else if let Err(err) = compare_standard_properties(source, emacs, &rust) {
compare_result.status = DiffStatus::Bad;
compare_result.message = Some(err.to_string())
}
Ok(compare_result.into())
@@ -495,7 +489,7 @@ fn _compare_document<'b, 's>(
.map(EmacsField::Required),
(
EmacsField::Required(":path"),
|r| r.path.as_ref().map(|p| p.to_str()).flatten(),
|r| r.path.as_ref().and_then(|p| p.to_str()),
compare_property_quoted_string
),
(
@@ -1581,7 +1575,7 @@ fn compare_example_block<'b, 's>(
[],
(
EmacsField::Required(":value"),
|r| Some(r.contents.as_str()),
|r| Some(r.get_value()),
compare_property_quoted_string
),
(
@@ -1659,7 +1653,7 @@ fn compare_export_block<'b, 's>(
),
(
EmacsField::Required(":value"),
|r| Some(r.contents.as_str()),
|r| Some(r.get_value()),
compare_property_quoted_string
)
) {
@@ -1707,7 +1701,7 @@ fn compare_src_block<'b, 's>(
),
(
EmacsField::Required(":value"),
|r| Some(r.contents.as_str()),
|r| Some(r.get_value()),
compare_property_quoted_string
),
(
@@ -2158,7 +2152,7 @@ fn compare_plain_text<'b, 's>(
let text = emacs.as_text()?;
let start_ind: usize = text
.properties
.get(0)
.first()
.expect("Should have start index.")
.as_atom()?
.parse()?;

View File

@@ -1,3 +1,4 @@
#[allow(clippy::module_inception)]
mod compare;
mod compare_field;
mod diff;

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::collections::HashMap;
use nom::branch::alt;
@@ -36,12 +37,6 @@ pub struct TextWithProperties<'s> {
pub(crate) properties: Vec<Token<'s>>,
}
enum ParseState {
Normal,
Escape,
Octal(Vec<u8>),
}
impl<'s> Token<'s> {
pub(crate) fn as_vector<'p>(
&'p self,
@@ -113,70 +108,175 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
debug_assert!(is_slice_of(input, remaining));
let source = {
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
&input[..offset]
};
source.into()
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
&input[..offset]
}
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
let mut out: Vec<u8> = Vec::with_capacity(text.len());
if !text.starts_with(r#"""#) {
#[derive(Debug)]
enum UnquoteState {
Normal,
Escape,
HasEscape {
out: Vec<u8>,
},
HasEscapeEscape {
out: Vec<u8>,
},
Octal {
octal_begin_offset: usize,
octal: Vec<u8>,
},
HasEscapeOctal {
out: Vec<u8>,
octal: Vec<u8>,
},
}
pub(crate) fn unquote(text: &str) -> Result<Cow<'_, str>, Box<dyn std::error::Error>> {
if !text.starts_with('"') {
return Err("Quoted text does not start with quote.".into());
}
if !text.ends_with(r#"""#) {
if !text.ends_with('"') {
return Err("Quoted text does not end with quote.".into());
}
let interior_text = &text[1..(text.len() - 1)];
let mut state = ParseState::Normal;
for current_char in interior_text.bytes().into_iter() {
let mut state = UnquoteState::Normal;
for (offset, current_char) in interior_text.bytes().enumerate() {
// Check to see if octal finished
state = match (state, current_char) {
(ParseState::Octal(octal), b'0'..=b'7') if octal.len() < MAX_OCTAL_LENGTH => {
ParseState::Octal(octal)
(
UnquoteState::Octal {
octal_begin_offset,
octal,
},
b'0'..=b'7',
) if octal.len() < MAX_OCTAL_LENGTH => UnquoteState::Octal {
octal_begin_offset,
octal,
},
(
UnquoteState::Octal {
octal_begin_offset,
octal,
},
_,
) => {
let octal_number_string = String::from_utf8(octal)?;
let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?;
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
out.extend_from_slice(&interior_text.as_bytes()[..octal_begin_offset]);
out.push(decoded_byte);
UnquoteState::HasEscape { out }
}
(ParseState::Octal(octal), _) => {
(UnquoteState::HasEscapeOctal { out, octal }, b'0'..=b'7')
if octal.len() < MAX_OCTAL_LENGTH =>
{
UnquoteState::HasEscapeOctal { out, octal }
}
(UnquoteState::HasEscapeOctal { mut out, octal }, _) => {
let octal_number_string = String::from_utf8(octal)?;
let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?;
out.push(decoded_byte);
ParseState::Normal
UnquoteState::HasEscape { out }
}
(state, _) => state,
};
state = match (state, current_char) {
(ParseState::Normal, b'\\') => ParseState::Escape,
(ParseState::Normal, _) => {
(UnquoteState::Normal, b'\\') => UnquoteState::Escape,
(UnquoteState::Normal, _) => UnquoteState::Normal,
(UnquoteState::HasEscape { out }, b'\\') => UnquoteState::HasEscapeEscape { out },
(UnquoteState::HasEscape { mut out }, _) => {
out.push(current_char);
ParseState::Normal
UnquoteState::HasEscape { out }
}
(ParseState::Escape, b'n') => {
(UnquoteState::Escape, b'n') => {
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
// Subtract 1 from offset to account for backslash.
out.extend_from_slice(&interior_text.as_bytes()[..(offset - 1)]);
out.push(b'\n');
ParseState::Normal
UnquoteState::HasEscape { out }
}
(ParseState::Escape, b'\\') => {
(UnquoteState::HasEscapeEscape { mut out }, b'n') => {
out.push(b'\n');
UnquoteState::HasEscape { out }
}
(UnquoteState::Escape, b'\\') => {
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
// Subtract 1 from offset to account for backslash.
out.extend_from_slice(&interior_text.as_bytes()[..(offset - 1)]);
out.push(b'\\');
ParseState::Normal
UnquoteState::HasEscape { out }
}
(ParseState::Escape, b'"') => {
(UnquoteState::HasEscapeEscape { mut out }, b'\\') => {
out.push(b'\\');
UnquoteState::HasEscape { out }
}
(UnquoteState::Escape, b'"') => {
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
// Subtract 1 from offset to account for backslash.
out.extend_from_slice(&interior_text.as_bytes()[..(offset - 1)]);
out.push(b'"');
ParseState::Normal
UnquoteState::HasEscape { out }
}
(ParseState::Escape, b'0'..=b'7') => {
(UnquoteState::HasEscapeEscape { mut out }, b'"') => {
out.push(b'"');
UnquoteState::HasEscape { out }
}
(UnquoteState::Escape, b'0'..=b'7') => {
let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH);
octal.push(current_char);
ParseState::Octal(octal)
// Substract 1 from offset to account for backslash
UnquoteState::Octal {
octal_begin_offset: offset - 1,
octal,
}
}
(ParseState::Octal(mut octal), b'0'..=b'7') => {
(UnquoteState::HasEscapeEscape { out }, b'0'..=b'7') => {
let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH);
octal.push(current_char);
ParseState::Octal(octal)
// Substract 1 from offset to account for backslash
UnquoteState::HasEscapeOctal { out, octal }
}
_ => panic!("Invalid state unquoting string."),
(
UnquoteState::Octal {
octal_begin_offset,
mut octal,
},
b'0'..=b'7',
) => {
octal.push(current_char);
UnquoteState::Octal {
octal_begin_offset,
octal,
}
}
(UnquoteState::HasEscapeOctal { out, mut octal }, b'0'..=b'7') => {
octal.push(current_char);
UnquoteState::HasEscapeOctal { out, octal }
}
(state, _) => panic!(
"Invalid state unquoting string: {:?} | {} | {:?}",
state, offset, interior_text
),
};
}
Ok(String::from_utf8(out)?)
match state {
UnquoteState::Normal | UnquoteState::Escape | UnquoteState::Octal { .. } => {
Ok(Cow::Borrowed(interior_text))
}
UnquoteState::HasEscape { out } => Ok(Cow::Owned(String::from_utf8(out)?)),
UnquoteState::HasEscapeEscape { mut out } => {
out.push(b'\\');
Ok(Cow::Owned(String::from_utf8(out)?))
}
UnquoteState::HasEscapeOctal { mut out, octal } => {
out.push(b'\\');
out.extend(octal);
Ok(Cow::Owned(String::from_utf8(out)?))
}
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -229,11 +329,9 @@ fn atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, body) = take_till1(|c| match c {
' ' | '\t' | '\r' | '\n' | ')' | ']' => true,
_ => false,
})(input)?;
Ok((remaining, Token::Atom(body.into())))
let (remaining, body) =
take_till1(|c| matches!(c, ' ' | '\t' | '\r' | '\n' | ')' | ']'))(input)?;
Ok((remaining, Token::Atom(body)))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -264,22 +362,19 @@ fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
}
let (remaining, _) = tag(r#"""#)(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Token::Atom(source.into())))
Ok((remaining, Token::Atom(source)))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn hash_notation<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag("#<")(input)?;
let (remaining, _body) = take_till1(|c| match c {
'>' => true,
_ => false,
})(remaining)?;
let (remaining, _body) = take_till1(|c| matches!(c, '>'))(remaining)?;
let (remaining, _) = tag(">")(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Token::Atom(source.into())))
Ok((remaining, Token::Atom(source)))
}
fn text_with_properties<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
fn text_with_properties(input: &str) -> Res<&str, Token<'_>> {
let (remaining, _) = tag("#(")(input)?;
let (remaining, (text, props)) = delimited(
multispace0,
@@ -348,10 +443,7 @@ mod tests {
let input = r#" (foo "b(a)r" baz ) "#;
let (remaining, parsed) = sexp(input).expect("Parse the input");
assert_eq!(remaining, "");
assert!(match parsed {
Token::List(_) => true,
_ => false,
});
assert!(matches!(parsed, Token::List(_)));
let children = match parsed {
Token::List(children) => children,
_ => panic!("Should be a list."),
@@ -364,14 +456,14 @@ mod tests {
r#"foo"#
);
assert_eq!(
match children.iter().nth(1) {
match children.get(1) {
Some(Token::Atom(body)) => *body,
_ => panic!("Second child should be an atom."),
},
r#""b(a)r""#
);
assert_eq!(
match children.iter().nth(2) {
match children.get(2) {
Some(Token::Atom(body)) => *body,
_ => panic!("Third child should be an atom."),
},

View File

@@ -1,7 +1,9 @@
use std::borrow::Cow;
use std::str::FromStr;
use super::compare_field::compare_property_list_of_list_of_list_of_ast_nodes;
use super::compare_field::compare_property_list_of_quoted_string;
use super::compare_field::compare_property_object_tree;
use super::compare_field::compare_property_optional_pair;
use super::compare_field::compare_property_quoted_string;
use super::compare_field::ComparePropertiesResult;
use super::diff::DiffEntry;
@@ -13,7 +15,6 @@ use crate::compare::sexp::unquote;
use crate::types::AffiliatedKeywordValue;
use crate::types::AstNode;
use crate::types::GetAffiliatedKeywords;
use crate::types::GetStandardProperties;
use crate::types::StandardProperties;
/// Check if the child string slice is a slice of the parent string slice.
@@ -28,33 +29,30 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
/// Get the byte offset into source that the rust object exists at.
///
/// These offsets are zero-based unlike the elisp ones.
fn get_rust_byte_offsets<'b, 's, S: StandardProperties<'s> + ?Sized>(
original_document: &'s str,
rust_ast_node: &'b S,
) -> (usize, usize) {
let rust_object_source = rust_ast_node.get_source();
debug_assert!(is_slice_of(original_document, rust_object_source));
let offset = rust_object_source.as_ptr() as usize - original_document.as_ptr() as usize;
let end = offset + rust_object_source.len();
fn get_rust_byte_offsets(original_document: &str, subset: &str) -> (usize, usize) {
debug_assert!(is_slice_of(original_document, subset));
let offset = subset.as_ptr() as usize - original_document.as_ptr() as usize;
let end = offset + subset.len();
(offset, end)
}
pub(crate) fn compare_standard_properties<
'b,
's,
S: GetStandardProperties<'s> + GetElispFact<'s> + ?Sized,
S: StandardProperties<'s> + GetElispFact<'s> + ?Sized,
>(
original_document: &'s str,
emacs: &'b Token<'s>,
rust: &'b S,
) -> Result<(), Box<dyn std::error::Error>> {
assert_name(emacs, rust.get_elisp_fact().get_elisp_name())?;
assert_bounds(original_document, emacs, rust.get_standard_properties())?;
assert_bounds(original_document, emacs, rust)?;
assert_post_blank(emacs, rust)?;
Ok(())
}
pub(crate) fn assert_name<'b, 's, S: AsRef<str>>(
emacs: &'b Token<'s>,
fn assert_name<S: AsRef<str>>(
emacs: &Token<'_>,
name: S,
) -> Result<(), Box<dyn std::error::Error>> {
let name = name.as_ref();
@@ -76,24 +74,72 @@ pub(crate) fn assert_name<'b, 's, S: AsRef<str>>(
/// Assert that the character ranges defined by upstream org-mode's :standard-properties match the slices in Organic's StandardProperties.
///
/// This does **not** handle plain text because plain text is a special case.
pub(crate) fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>(
fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>(
original_document: &'s str,
emacs: &'b Token<'s>,
rust: &'b S,
) -> Result<(), Box<dyn std::error::Error>> {
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
let (begin, end) = (
standard_properties
.begin
.ok_or("Token should have a begin.")?,
standard_properties.end.ok_or("Token should have an end.")?,
);
let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust); // 0-based
let rust_begin_char_offset = (&original_document[..rust_begin]).chars().count() + 1; // 1-based
let rust_end_char_offset =
rust_begin_char_offset + (&original_document[rust_begin..rust_end]).chars().count(); // 1-based
if rust_begin_char_offset != begin || rust_end_char_offset != end {
Err(format!("Rust bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
// Check begin/end
{
let (begin, end) = (
standard_properties
.begin
.ok_or("Token should have a begin.")?,
standard_properties.end.ok_or("Token should have an end.")?,
);
let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust.get_source()); // 0-based
let rust_begin_char_offset = original_document[..rust_begin].chars().count() + 1; // 1-based
let rust_end_char_offset =
rust_begin_char_offset + original_document[rust_begin..rust_end].chars().count(); // 1-based
if rust_begin_char_offset != begin || rust_end_char_offset != end {
Err(format!("Rust bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
}
}
// Check contents-begin/contents-end
{
if let Some(rust_contents) = rust.get_contents() {
let (begin, end) = (
standard_properties
.contents_begin
.ok_or("Token should have a contents-begin.")?,
standard_properties
.contents_end
.ok_or("Token should have an contents-end.")?,
);
let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust_contents); // 0-based
let rust_begin_char_offset = original_document[..rust_begin].chars().count() + 1; // 1-based
let rust_end_char_offset =
rust_begin_char_offset + original_document[rust_begin..rust_end].chars().count(); // 1-based
if rust_begin_char_offset != begin || rust_end_char_offset != end {
Err(format!("Rust contents bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs contents bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
}
} else if standard_properties.contents_begin.is_some()
|| standard_properties.contents_end.is_some()
{
Err(format!("Rust contents is None but emacs contents bounds are ({emacs_begin:?}, {emacs_end:?})", emacs_begin=standard_properties.contents_begin, emacs_end=standard_properties.contents_end))?;
}
}
Ok(())
}
/// Assert that the post blank matches between emacs and organic.
///
/// This does **not** handle plain text because plain text is a special case.
fn assert_post_blank<'b, 's, S: StandardProperties<'s> + ?Sized>(
emacs: &'b Token<'s>,
rust: &'b S,
) -> Result<(), Box<dyn std::error::Error>> {
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
let rust_post_blank = rust.get_post_blank();
let emacs_post_blank = standard_properties
.post_blank
.ok_or("Token should have a post-blank.")?;
if rust_post_blank as usize != emacs_post_blank {
Err(format!("Rust post-blank {rust_post_blank} does not match emacs post-blank ({emacs_post_blank})", rust_post_blank = rust_post_blank, emacs_post_blank = emacs_post_blank))?;
}
Ok(())
@@ -112,21 +158,18 @@ struct EmacsStandardProperties {
post_blank: Option<usize>,
}
fn get_emacs_standard_properties<'b, 's>(
emacs: &'b Token<'s>,
fn get_emacs_standard_properties(
emacs: &Token<'_>,
) -> Result<EmacsStandardProperties, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_child = children.get(1).ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let standard_properties = attributes_map.get(":standard-properties");
Ok(if standard_properties.is_some() {
let mut std_props = standard_properties
.expect("if statement proves its Some")
.as_vector()?
.into_iter();
.iter();
let begin = maybe_token_to_usize(std_props.next())?;
let post_affiliated = maybe_token_to_usize(std_props.next())?;
let contents_begin = maybe_token_to_usize(std_props.next())?;
@@ -142,16 +185,13 @@ fn get_emacs_standard_properties<'b, 's>(
post_blank,
}
} else {
let begin = maybe_token_to_usize(attributes_map.get(":begin").map(|token| *token))?;
let end = maybe_token_to_usize(attributes_map.get(":end").map(|token| *token))?;
let contents_begin =
maybe_token_to_usize(attributes_map.get(":contents-begin").map(|token| *token))?;
let contents_end =
maybe_token_to_usize(attributes_map.get(":contents-end").map(|token| *token))?;
let post_blank =
maybe_token_to_usize(attributes_map.get(":post-blank").map(|token| *token))?;
let begin = maybe_token_to_usize(attributes_map.get(":begin").copied())?;
let end = maybe_token_to_usize(attributes_map.get(":end").copied())?;
let contents_begin = maybe_token_to_usize(attributes_map.get(":contents-begin").copied())?;
let contents_end = maybe_token_to_usize(attributes_map.get(":contents-end").copied())?;
let post_blank = maybe_token_to_usize(attributes_map.get(":post-blank").copied())?;
let post_affiliated =
maybe_token_to_usize(attributes_map.get(":post-affiliated").map(|token| *token))?;
maybe_token_to_usize(attributes_map.get(":post-affiliated").copied())?;
EmacsStandardProperties {
begin,
post_affiliated,
@@ -169,62 +209,57 @@ fn maybe_token_to_usize(
Ok(token
.map(|token| token.as_atom())
.map_or(Ok(None), |r| r.map(Some))?
.map(|val| {
.and_then(|val| {
if val == "nil" {
None
} else {
Some(val.parse::<usize>())
}
})
.flatten() // Outer option is whether or not the param exists, inner option is whether or not it is nil
.map_or(Ok(None), |r| r.map(Some))?)
}
/// Get a named property from the emacs token.
///
/// Returns Ok(None) if value is nil or absent.
pub(crate) fn get_property<'b, 's, 'x>(
pub(crate) fn get_property<'b, 's>(
emacs: &'b Token<'s>,
key: &'x str,
key: &str,
) -> Result<Option<&'b Token<'s>>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_child = children.get(1).ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let prop = attributes_map.get(key).map(|token| *token);
match prop.map(|token| token.as_atom()) {
Some(Ok("nil")) => return Ok(None),
_ => {}
};
let prop = attributes_map.get(key).copied();
if let Some(Ok("nil")) = prop.map(Token::as_atom) {
return Ok(None);
}
Ok(prop)
}
/// Get a named property containing an unquoted atom from the emacs token.
///
/// Returns None if key is not found.
pub(crate) fn get_property_unquoted_atom<'b, 's, 'x>(
emacs: &'b Token<'s>,
key: &'x str,
pub(crate) fn get_property_unquoted_atom<'s>(
emacs: &Token<'s>,
key: &str,
) -> Result<Option<&'s str>, Box<dyn std::error::Error>> {
Ok(get_property(emacs, key)?
get_property(emacs, key)?
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?)
.map_or(Ok(None), |r| r.map(Some))
}
/// Get a named property containing an quoted string from the emacs token.
///
/// Returns None if key is not found.
pub(crate) fn get_property_quoted_string<'b, 's, 'x>(
emacs: &'b Token<'s>,
key: &'x str,
) -> Result<Option<String>, Box<dyn std::error::Error>> {
Ok(get_property(emacs, key)?
pub(crate) fn get_property_quoted_string<'s>(
emacs: &Token<'s>,
key: &str,
) -> Result<Option<Cow<'s, str>>, Box<dyn std::error::Error>> {
get_property(emacs, key)?
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.map(unquote)
.map_or(Ok(None), |r| r.map(Some))?)
.map_or(Ok(None), |r| r.map(Some))
}
/// Get a named property containing an unquoted numeric value.
@@ -250,7 +285,7 @@ where
pub(crate) fn compare_children<'b, 's, 'x, RC>(
source: &'s str,
emacs: &'b Token<'s>,
rust_children: &'x Vec<RC>,
rust_children: &'x [RC],
child_status: &mut Vec<DiffEntry<'b, 's>>,
this_status: &mut DiffStatus,
message: &mut Option<String>,
@@ -301,8 +336,8 @@ where
Ok(())
}
pub(crate) fn assert_no_children<'b, 's>(
emacs: &'b Token<'s>,
pub(crate) fn assert_no_children(
emacs: &Token<'_>,
this_status: &mut DiffStatus,
message: &mut Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
@@ -331,7 +366,7 @@ where
let rust_key = rust_key.as_ref();
let rust_value = rust_value.as_ref();
let emacs_value = get_property_quoted_string(emacs, rust_key)?;
if Some(rust_value) != emacs_value.as_ref().map(String::as_str) {
if Some(rust_value) != emacs_value.as_deref() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
@@ -376,13 +411,23 @@ where
)?;
ret.push(diff);
}
AffiliatedKeywordValue::ListOfListsOfObjects(rust_value) => {
let diff = compare_property_list_of_list_of_list_of_ast_nodes(
AffiliatedKeywordValue::OptionalPair { optval, val } => {
let diff = compare_property_optional_pair(
source,
emacs,
rust,
emacs_property_name.as_str(),
|_| Some(rust_value),
|_| Some((*optval, *val)),
)?;
ret.push(diff);
}
AffiliatedKeywordValue::ObjectTree(rust_value) => {
let diff = compare_property_object_tree(
source,
emacs,
rust,
emacs_property_name.as_str(),
|_| Some(rust_value.iter()),
)?;
ret.push(diff);
}

View File

@@ -1,15 +1,27 @@
use super::global_settings::EntityDefinition;
pub(crate) const DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS: [&'static str; 1] = ["CAPTION"];
/// Keywords that contain the standard set of objects (excluding footnote references).
///
/// Corresponds to org-element-parsed-keywords elisp variable.
pub(crate) const ORG_ELEMENT_PARSED_KEYWORDS: [&str; 1] = ["CAPTION"];
pub(crate) const DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["CAPTION", "RESULTS"];
/// Keywords that can have a secondary value in square brackets.
///
/// Corresponds to org-element-dual-keywords elisp variable.
pub(crate) const ORG_ELEMENT_DUAL_KEYWORDS: [&str; 2] = ["CAPTION", "RESULTS"];
pub(crate) const DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
/// Keywords that can be affiliated with an element.
///
/// Corresponds to org-element-affiliated-keywords elisp variable.
pub(crate) const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&str; 13] = [
"CAPTION", "DATA", "HEADER", "HEADERS", "LABEL", "NAME", "PLOT", "RESNAME", "RESULT",
"RESULTS", "SOURCE", "SRCNAME", "TBLNAME",
];
pub(crate) const DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST: [(&'static str, &'static str); 8] = [
/// Mapping of keyword names.
///
/// Corresponds to org-element-keyword-translation-alist elisp variable.
pub(crate) const ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST: [(&str, &str); 8] = [
("DATA", "NAME"),
("LABEL", "NAME"),
("RESNAME", "NAME"),
@@ -20,7 +32,7 @@ pub(crate) const DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST: [(&'static str,
("HEADERS", "HEADER"),
];
pub(crate) const DEFAULT_ORG_LINK_PARAMETERS: [&'static str; 23] = [
pub(crate) const DEFAULT_ORG_LINK_PARAMETERS: [&str; 23] = [
"id",
"eww",
"rmail",

View File

@@ -9,11 +9,9 @@ use super::list::List;
use super::DynContextMatcher;
use super::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::OrgSource;
#[derive(Debug)]
pub(crate) enum ContextElement<'r, 's> {
/// Stores a parser that indicates that children should exit upon matching an exit matcher.
ExitMatcherNode(ExitMatcherNode<'r>),
@@ -35,15 +33,6 @@ pub(crate) struct ExitMatcherNode<'r> {
pub(crate) class: ExitClass,
}
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut formatter = f.debug_struct("ExitMatcherNode");
formatter.field("class", &self.class.to_string());
formatter.finish()
}
}
#[derive(Debug)]
pub(crate) struct Context<'g, 'r, 's> {
global_settings: &'g GlobalSettings<'g, 's>,
tree: List<'r, &'r ContextElement<'r, 's>>,
@@ -108,27 +97,22 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
pub(crate) fn check_exit_matcher(
&'r self,
i: OrgSource<'s>,
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> {
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError> {
let mut current_class_filter = ExitClass::Gamma;
for current_node in self.iter_context() {
let context_element = current_node.get_data();
match context_element {
ContextElement::ExitMatcherNode(exit_matcher) => {
if exit_matcher.class as u32 <= current_class_filter as u32 {
current_class_filter = exit_matcher.class;
let local_result = (exit_matcher.exit_matcher)(&current_node, i);
if local_result.is_ok() {
return local_result;
}
if let ContextElement::ExitMatcherNode(exit_matcher) = context_element {
if exit_matcher.class as u32 <= current_class_filter as u32 {
current_class_filter = exit_matcher.class;
let local_result = (exit_matcher.exit_matcher)(&current_node, i);
if local_result.is_ok() {
return local_result;
}
}
_ => {}
};
}
}
// TODO: Make this a specific error instead of just a generic MyError
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoExit".into(),
))));
return Err(nom::Err::Error(CustomError::Static("NoExit")));
}
/// Indicates if elements should consume the whitespace after them.
@@ -140,11 +124,8 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
fn _should_consume_trailing_whitespace(&self) -> Option<bool> {
for current_node in self.iter() {
match current_node {
ContextElement::ConsumeTrailingWhitespace(should) => {
return Some(*should);
}
_ => {}
if let ContextElement::ConsumeTrailingWhitespace(should) = current_node {
return Some(*should);
}
}
None

View File

@@ -1,13 +1,7 @@
#[derive(Debug, Copy, Clone)]
#[derive(Copy, Clone)]
pub(crate) enum ExitClass {
Document = 1,
Alpha = 2,
Beta = 3,
Gamma = 4,
}
impl std::fmt::Display for ExitClass {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}

View File

@@ -1,17 +1,16 @@
use std::fmt::Debug;
use std::path::PathBuf;
#[cfg(any(feature = "compare", feature = "foreign_document_test"))]
pub trait FileAccessInterface: Sync + Debug {
pub trait FileAccessInterface: Sync {
fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
}
#[cfg(not(any(feature = "compare", feature = "foreign_document_test")))]
pub trait FileAccessInterface: Debug {
pub trait FileAccessInterface {
fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
}
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct LocalFileAccessInterface {
pub working_directory: Option<PathBuf>,
}
@@ -20,10 +19,9 @@ impl FileAccessInterface for LocalFileAccessInterface {
fn read_file(&self, path: &str) -> Result<String, std::io::Error> {
let final_path = self
.working_directory
.as_ref()
.map(PathBuf::as_path)
.as_deref()
.map(|pb| pb.join(path))
.unwrap_or_else(|| PathBuf::from(path));
Ok(std::fs::read_to_string(final_path)?)
std::fs::read_to_string(final_path)
}
}

View File

@@ -5,16 +5,12 @@ use super::constants::DEFAULT_ORG_ENTITIES;
use super::constants::DEFAULT_ORG_LINK_PARAMETERS;
use super::FileAccessInterface;
use super::LocalFileAccessInterface;
use crate::context::constants::DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS;
use crate::context::constants::DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS;
use crate::context::constants::DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST;
use crate::context::constants::DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS;
use crate::types::IndentationLevel;
use crate::types::Object;
// TODO: Ultimately, I think we'll need most of this: https://orgmode.org/manual/In_002dbuffer-Settings.html
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct GlobalSettings<'g, 's> {
pub radio_targets: Vec<&'g Vec<Object<'s>>>,
pub file_access: &'g dyn FileAccessInterface,
@@ -58,26 +54,6 @@ pub struct GlobalSettings<'g, 's> {
///
/// Corresponds to org-entities elisp variable.
pub entities: &'g [EntityDefinition<'s>],
/// Keywords that contain the standard set of objects (excluding footnote references).
///
/// Corresponds to org-element-parsed-keywords elisp variable.
pub element_parsed_keywords: &'g [&'s str],
/// Keywords that can have a secondary value in square brackets.
///
/// Corresponds to org-element-dual-keywords elisp variable.
pub element_dual_keywords: &'g [&'s str],
/// Keywords that can be affiliated with an element.
///
/// Corresponds to org-element-affiliated-keywords elisp variable.
pub element_affiliated_keywords: &'g [&'s str],
/// Mapping of keyword names.
///
/// Corresponds to org-element-keyword-translation-alist elisp variable.
pub element_keyword_translation_alist: &'g [(&'s str, &'s str)],
}
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
@@ -112,10 +88,6 @@ impl<'g, 's> GlobalSettings<'g, 's> {
link_parameters: &DEFAULT_ORG_LINK_PARAMETERS,
link_templates: BTreeMap::new(),
entities: &DEFAULT_ORG_ENTITIES,
element_parsed_keywords: &DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS,
element_dual_keywords: &DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS,
element_affiliated_keywords: &DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS,
element_keyword_translation_alist: &DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST,
}
}
}
@@ -126,14 +98,10 @@ impl<'g, 's> Default for GlobalSettings<'g, 's> {
}
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Clone, PartialEq, Default)]
pub enum HeadlineLevelFilter {
Odd,
#[default]
OddEven,
}
impl Default for HeadlineLevelFilter {
fn default() -> Self {
HeadlineLevelFilter::OddEven
}
}

View File

@@ -50,7 +50,7 @@ impl<'a, T> Iterator for Iter<'a, T> {
fn next(&mut self) -> Option<Self::Item> {
let ret = self.next.map(|link| link.get_data());
self.next = self.next.map(|link| link.get_parent()).flatten();
self.next = self.next.and_then(|link| link.get_parent());
ret
}
}
@@ -64,7 +64,7 @@ impl<'a, T> Iterator for IterList<'a, T> {
fn next(&mut self) -> Option<Self::Item> {
let ret = self.next;
self.next = self.next.map(|this| this.get_parent()).flatten();
self.next = self.next.and_then(|link| link.get_parent());
ret
}
}

View File

@@ -1,7 +1,8 @@
use crate::error::Res;
use crate::parser::OrgSource;
mod constants;
pub(crate) mod constants;
#[allow(clippy::module_inception)]
mod context;
mod exiting;
mod file_access_interface;
@@ -30,4 +31,5 @@ pub use global_settings::GlobalSettings;
pub use global_settings::HeadlineLevelFilter;
pub use global_settings::DEFAULT_TAB_WIDTH;
pub(crate) use list::List;
pub(crate) use parser_with_context::bind_context;
pub(crate) use parser_with_context::parser_with_context;

View File

@@ -4,3 +4,10 @@ macro_rules! parser_with_context {
};
}
pub(crate) use parser_with_context;
macro_rules! bind_context {
($target:expr, $context:expr) => {
|i| $target($context, i)
};
}
pub(crate) use bind_context;

View File

@@ -2,22 +2,18 @@ use nom::error::ErrorKind;
use nom::error::ParseError;
use nom::IResult;
pub(crate) type Res<T, U> = IResult<T, U, CustomError<T>>;
pub(crate) type Res<T, U> = IResult<T, U, CustomError>;
#[derive(Debug)]
pub enum CustomError<I> {
MyError(MyError<&'static str>),
Nom(I, ErrorKind),
pub enum CustomError {
Static(&'static str),
IO(std::io::Error),
BoxedError(Box<dyn std::error::Error>),
Parser(ErrorKind),
}
#[derive(Debug)]
pub struct MyError<I>(pub(crate) I);
impl<I> ParseError<I> for CustomError<I> {
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
CustomError::Nom(input, kind)
impl<I: std::fmt::Debug> ParseError<I> for CustomError {
fn from_error_kind(_input: I, kind: ErrorKind) -> Self {
CustomError::Parser(kind)
}
fn append(_input: I, _kind: ErrorKind, /*mut*/ other: Self) -> Self {
@@ -26,20 +22,14 @@ impl<I> ParseError<I> for CustomError<I> {
}
}
impl<I> From<std::io::Error> for CustomError<I> {
impl From<std::io::Error> for CustomError {
fn from(value: std::io::Error) -> Self {
CustomError::IO(value)
}
}
impl<I> From<&'static str> for CustomError<I> {
impl From<&'static str> for CustomError {
fn from(value: &'static str) -> Self {
CustomError::MyError(MyError(value))
}
}
impl<I> From<Box<dyn std::error::Error>> for CustomError<I> {
fn from(value: Box<dyn std::error::Error>) -> Self {
CustomError::BoxedError(value)
CustomError::Static(value)
}
}

View File

@@ -1,4 +1,4 @@
#[allow(clippy::module_inception)]
mod error;
pub(crate) use error::CustomError;
pub(crate) use error::MyError;
pub(crate) use error::Res;

View File

@@ -0,0 +1,43 @@
use std::collections::HashMap;
use std::sync::Mutex;
use super::EventType;
use crate::parser::OrgSource;
#[derive(Debug, Eq, Hash, PartialEq)]
struct EventKey {
event_type: EventType,
byte_offset: usize,
}
pub(crate) type EventCount = usize;
static GLOBAL_DATA: Mutex<Option<HashMap<EventKey, EventCount>>> = Mutex::new(None);
pub(crate) fn record_event(event_type: EventType, input: OrgSource<'_>) {
let mut db = GLOBAL_DATA.lock().unwrap();
let db = db.get_or_insert_with(HashMap::new);
let key = EventKey {
event_type,
byte_offset: input.get_byte_offset(),
};
*db.entry(key).or_insert(0) += 1;
}
pub fn report(original_document: &str) {
let mut db = GLOBAL_DATA.lock().unwrap();
let db = db.get_or_insert_with(HashMap::new);
let mut results: Vec<_> = db.iter().collect();
results.sort_by_key(|(_k, v)| *v);
// This would put the most common at the top, but that is a pain when there is already a lot of output from the parser.
// results.sort_by(|(_ak, av), (_bk, bv)| bv.cmp(av));
for (key, count) in results {
println!(
"{:?} {} character offset: {} byte offset: {}",
key.event_type,
count,
original_document[..key.byte_offset].chars().count() + 1,
key.byte_offset
)
}
}

View File

@@ -0,0 +1,4 @@
#[derive(Debug, Eq, Hash, PartialEq)]
pub(crate) enum EventType {
ElementStart,
}

6
src/event_count/mod.rs Normal file
View File

@@ -0,0 +1,6 @@
mod database;
mod event_type;
pub(crate) use database::record_event;
pub use database::report;
pub(crate) use event_type::EventType;

View File

@@ -5,7 +5,7 @@ use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
#[cfg(feature = "tracing")]
use tracing_subscriber::util::SubscriberInitExt;
const SERVICE_NAME: &'static str = "organic";
const SERVICE_NAME: &str = "organic";
// Despite the obvious verbosity that fully-qualifying everything causes, in these functions I am fully-qualifying everything relating to tracing. This is because the tracing feature involves multiple libraries working together and so I think it is beneficial to see which libraries contribute which bits.

View File

@@ -90,12 +90,11 @@ impl<'r, 's> Iterator for AllAstNodeIter<'r, 's> {
}
}
impl<'r, 's> IntoIterator for AstNode<'r, 's> {
type Item = AstNode<'r, 's>;
type IntoIter = AllAstNodeIter<'r, 's>;
fn into_iter(self) -> Self::IntoIter {
impl<'r, 's> AstNode<'r, 's> {
/// Iterate all AST nodes.
///
/// This is different from the iter/into_iter functions which iterate a single level of the children. This iterates the entire tree including returning the root node itself.
pub fn iter_all_ast_nodes(self) -> AllAstNodeIter<'r, 's> {
AllAstNodeIter {
root: Some(self),
queue: VecDeque::new(),

View File

@@ -2,13 +2,19 @@
#![feature(trait_alias)]
#![feature(path_file_prefix)]
#![feature(is_sorted)]
#![feature(test)]
// TODO: #![warn(missing_docs)]
#![allow(clippy::bool_assert_comparison)] // Sometimes you want the long form because its easier to see at a glance.
extern crate test;
#[cfg(feature = "compare")]
pub mod compare;
mod context;
mod error;
#[cfg(feature = "event_count")]
pub mod event_count;
mod iter;
pub mod parser;
pub mod types;

View File

@@ -23,13 +23,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(feature = "tracing")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
let result = rt.block_on(async {
rt.block_on(async {
init_telemetry()?;
let main_body_result = main_body();
shutdown_telemetry()?;
main_body_result
});
result
})
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -55,8 +54,11 @@ fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
}
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
let rust_parsed = parse(org_contents.as_ref())?;
let org_contents = org_contents.as_ref();
let rust_parsed = parse(org_contents)?;
println!("{:#?}", rust_parsed);
#[cfg(feature = "event_count")]
organic::event_count::report(org_contents);
Ok(())
}
@@ -70,12 +72,13 @@ fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::err
let file_access_interface = LocalFileAccessInterface {
working_directory: Some(parent_directory.to_path_buf()),
};
let global_settings = {
let mut global_settings = GlobalSettings::default();
global_settings.file_access = &file_access_interface;
global_settings
let global_settings = GlobalSettings {
file_access: &file_access_interface,
..Default::default()
};
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
println!("{:#?}", rust_parsed);
#[cfg(feature = "event_count")]
organic::event_count::report(org_contents);
Ok(())
}

View File

@@ -14,17 +14,46 @@ use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::object_parser::standard_set_object;
use super::util::confine_context;
use crate::context::parser_with_context;
use super::OrgSource;
use crate::context::bind_context;
use crate::context::constants::ORG_ELEMENT_DUAL_KEYWORDS;
use crate::context::constants::ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST;
use crate::context::constants::ORG_ELEMENT_PARSED_KEYWORDS;
use crate::context::Context;
use crate::context::ContextElement;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::error::Res;
use crate::types::AffiliatedKeywordValue;
use crate::types::AffiliatedKeywords;
use crate::types::Keyword;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn affiliated_keywords<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> {
let mut ret = Vec::new();
let mut remaining = input;
loop {
let result = affiliated_keyword(remaining);
match result {
Ok((remain, kw)) => {
remaining = remain;
ret.push(kw);
}
Err(_) => {
break;
}
}
}
Ok((remaining, ret))
}
pub(crate) fn parse_affiliated_keywords<'g, 's, AK>(
global_settings: &'g GlobalSettings<'g, 's>,
input: AK,
@@ -34,86 +63,98 @@ where
{
let mut ret = BTreeMap::new();
for kw in input {
let translated_name = translate_name(global_settings, kw.key);
if is_single_string_keyword(global_settings, translated_name.as_str()) {
ret.insert(
translated_name,
AffiliatedKeywordValue::SingleString(kw.value),
);
} else if is_list_of_single_string_keyword(global_settings, translated_name.as_str()) {
let list_of_strings = ret
.entry(translated_name)
.or_insert_with(|| AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1)));
match list_of_strings {
AffiliatedKeywordValue::ListOfStrings(list_of_strings)
if list_of_strings.is_empty() =>
{
list_of_strings.push(kw.value);
}
AffiliatedKeywordValue::ListOfStrings(list_of_strings) => {
list_of_strings.clear();
list_of_strings.push(kw.value);
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
let translated_name = translate_name(kw.key);
let keyword_type = identify_keyword_type(translated_name.as_str());
match keyword_type {
AffiliatedKeywordType::SingleString => {
ret.insert(
translated_name,
AffiliatedKeywordValue::SingleString(kw.value),
);
}
} else if is_list_of_objects_keyword(global_settings, translated_name.as_str()) {
let initial_context = ContextElement::document_context();
let initial_context = Context::new(global_settings, List::new(&initial_context));
let (_remaining, optional_objects) = opt(all_consuming(map(
tuple((
take_until("["),
tag("["),
map_parser(
AffiliatedKeywordType::ListOfStrings => {
let list_of_strings = ret.entry(translated_name).or_insert_with(|| {
AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1))
});
match list_of_strings {
AffiliatedKeywordValue::ListOfStrings(list_of_strings) => {
list_of_strings.push(kw.value);
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
}
}
AffiliatedKeywordType::OptionalPair => {
let (_remaining, optional_string) = opt(all_consuming(map(
tuple((
take_until::<_, &str, nom::error::Error<_>>("["),
tag("["),
recognize(many_till(anychar, peek(tuple((tag("]"), eof))))),
confine_context(|i| {
all_consuming(many0(parser_with_context!(standard_set_object)(
&initial_context,
)))(i)
}),
),
tag("]"),
eof,
)),
|(_, _, objects, _, _)| objects,
)))(kw.key.into())
.expect("Object parser should always succeed.");
tag("]"),
eof,
)),
|(_, _, objects, _, _)| objects,
)))(kw.key)
.expect("Parser should always succeed.");
ret.insert(
translated_name,
AffiliatedKeywordValue::OptionalPair {
optval: optional_string,
val: kw.value,
},
);
}
AffiliatedKeywordType::ObjectTree => {
let initial_context = ContextElement::document_context();
let initial_context = Context::new(global_settings, List::new(&initial_context));
// TODO: This should be omitting footnote references
let (_remaining, objects) = all_consuming(many0(parser_with_context!(
standard_set_object
)(&initial_context)))(kw.value.into())
.expect("Object parser should always succeed.");
let list_of_lists = ret.entry(translated_name).or_insert_with(|| {
AffiliatedKeywordValue::ListOfListsOfObjects(Vec::with_capacity(1))
});
match list_of_lists {
AffiliatedKeywordValue::ListOfListsOfObjects(list_of_lists) => {
list_of_lists.push((optional_objects, objects));
let (_remaining, optional_objects) = opt(all_consuming(map(
tuple((
take_until("["),
tag("["),
map_parser(
recognize(many_till(anychar, peek(tuple((tag("]"), eof))))),
confine_context(|i| {
all_consuming(many0(bind_context!(
standard_set_object,
&initial_context
)))(i)
}),
),
tag("]"),
eof,
)),
|(_, _, objects, _, _)| objects,
)))(kw.key.into())
.expect("Object parser should always succeed.");
// TODO: This should be omitting footnote references
let (_remaining, objects) = all_consuming(many0(bind_context!(
standard_set_object,
&initial_context
)))(kw.value.into())
.expect("Object parser should always succeed.");
let entry_per_keyword_list = ret
.entry(translated_name)
.or_insert_with(|| AffiliatedKeywordValue::ObjectTree(Vec::with_capacity(1)));
match entry_per_keyword_list {
AffiliatedKeywordValue::ObjectTree(entry_per_keyword_list) => {
entry_per_keyword_list.push((optional_objects, objects));
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
}
} else {
let list_of_strings = ret
.entry(translated_name)
.or_insert_with(|| AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1)));
match list_of_strings {
AffiliatedKeywordValue::ListOfStrings(list_of_strings) => {
list_of_strings.push(kw.value);
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
}
}
};
}
AffiliatedKeywords { keywords: ret }
}
fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s str) -> String {
fn translate_name(name: &str) -> String {
let name_until_optval = name
.split_once("[")
.split_once('[')
.map(|(before, _after)| before)
.unwrap_or(name);
for (src, dst) in global_settings.element_keyword_translation_alist {
for (src, dst) in ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST {
if name_until_optval.eq_ignore_ascii_case(src) {
return dst.to_lowercase();
}
@@ -121,40 +162,32 @@ fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s
name_until_optval.to_lowercase()
}
fn is_single_string_keyword<'g, 's>(
_global_settings: &'g GlobalSettings<'g, 's>,
name: &'s str,
) -> bool {
// TODO: Is this defined by an elisp variable?
for single_string_name in ["plot", "name"] {
if name.eq_ignore_ascii_case(single_string_name) {
return true;
}
}
false
enum AffiliatedKeywordType {
SingleString,
ListOfStrings,
OptionalPair,
ObjectTree,
}
fn is_list_of_single_string_keyword<'g, 's>(
_global_settings: &'g GlobalSettings<'g, 's>,
name: &'s str,
) -> bool {
// TODO: Is this defined by an elisp variable?
for single_string_name in ["results"] {
if name.eq_ignore_ascii_case(single_string_name) {
return true;
}
fn identify_keyword_type(name: &str) -> AffiliatedKeywordType {
let is_multiple = ["CAPTION", "HEADER"]
.into_iter()
.any(|candidate| name.eq_ignore_ascii_case(candidate))
|| name.to_lowercase().starts_with("attr_");
let is_parsed = ORG_ELEMENT_PARSED_KEYWORDS
.iter()
.any(|candidate| name.eq_ignore_ascii_case(candidate));
let can_have_optval = ORG_ELEMENT_DUAL_KEYWORDS
.iter()
.any(|candidate| name.eq_ignore_ascii_case(candidate));
match (is_multiple, is_parsed, can_have_optval) {
(true, true, true) => AffiliatedKeywordType::ObjectTree,
(true, true, false) => unreachable!("Nothing like this exists in upstream org-mode."),
(true, false, true) => unreachable!("Nothing like this exists in upstream org-mode."),
(true, false, false) => AffiliatedKeywordType::ListOfStrings,
(false, true, true) => unreachable!("Nothing like this exists in upstream org-mode."),
(false, true, false) => unreachable!("Nothing like this exists in upstream org-mode."),
(false, false, true) => AffiliatedKeywordType::OptionalPair,
(false, false, false) => AffiliatedKeywordType::SingleString,
}
false
}
fn is_list_of_objects_keyword<'g, 's>(
global_settings: &'g GlobalSettings<'g, 's>,
name: &'s str,
) -> bool {
for parsed_keyword in global_settings.element_parsed_keywords {
if name.eq_ignore_ascii_case(parsed_keyword) {
return true;
}
}
false
}

View File

@@ -47,7 +47,7 @@ pub(crate) fn angle_link<'b, 'g, 'r, 's>(
parser_with_context!(parse_angle_link)(context),
))(remaining)?;
let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -59,6 +59,7 @@ pub(crate) fn angle_link<'b, 'g, 'r, 's>(
raw_link: raw_link.into(),
search_option: parsed_link.search_option,
application: parsed_link.application,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -4,7 +4,6 @@ use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
@@ -21,7 +20,6 @@ use super::OrgSource;
use crate::context::Matcher;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::parser::util::org_line_ending;
@@ -44,33 +42,10 @@ where
start_of_line(remaining)?;
let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(remaining)?;
if let Ok((remaining, (_, line_break))) = tuple((space0, org_line_ending))(remaining) {
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
return Ok((
remaining,
BabelCall {
source: Into::<&str>::into(source),
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
value: Into::<&str>::into(line_break.take(0)),
call: None,
inside_header: None,
arguments: None,
end_header: None,
},
));
}
let (remaining, _ws) = space0(remaining)?;
let (remaining, (value, (call, inside_header, arguments, end_header))) =
consumed(babel_call_value)(remaining)?;
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, babel_call_value) = babel_call_value(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -82,46 +57,87 @@ where
context.get_global_settings(),
affiliated_keywords,
),
value: Into::<&str>::into(value).trim_end(),
call: call.map(Into::<&str>::into),
inside_header: inside_header.map(Into::<&str>::into),
arguments: arguments.map(Into::<&str>::into),
end_header: end_header.map(Into::<&str>::into),
value: Into::<&str>::into(babel_call_value.value),
call: babel_call_value.call.map(Into::<&str>::into),
inside_header: babel_call_value.inside_header.map(Into::<&str>::into),
arguments: babel_call_value.arguments.map(Into::<&str>::into),
end_header: babel_call_value.end_header.map(Into::<&str>::into),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[derive(Debug)]
struct BabelCallValue<'s> {
/// The entire string to the right of "#+call: " without the trailing line break.
value: OrgSource<'s>,
/// The function name which may contain a line break if there are no headers/arguments.
call: Option<OrgSource<'s>>,
inside_header: Option<OrgSource<'s>>,
arguments: Option<OrgSource<'s>>,
end_header: Option<OrgSource<'s>>,
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
alt((
babel_call_value_without_headers,
babel_call_value_with_headers,
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value_without_headers<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
let (remaining, value) = babel_call_call_with_headers(input)?;
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
let call = get_consumed(input, remaining);
Ok((
remaining,
BabelCallValue {
value,
call: Some(call),
inside_header: None,
arguments: None,
end_header: None,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value<'s>(
fn babel_call_value_with_headers<'s>(
input: OrgSource<'s>,
) -> Res<
OrgSource<'s>,
(
Option<OrgSource<'s>>,
Option<OrgSource<'s>>,
Option<OrgSource<'s>>,
Option<OrgSource<'s>>,
),
> {
let (remaining, call) = opt(babel_call_call)(input)?;
) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
let (remaining, call) = opt(babel_call_call_with_headers)(input)?;
let (remaining, inside_header) = opt(inside_header)(remaining)?;
let (remaining, arguments) = opt(arguments)(remaining)?;
let (remaining, end_header) = opt(end_header)(remaining)?;
let value = get_consumed(input, remaining);
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
Ok((
remaining,
(call, inside_header, arguments.flatten(), end_header),
BabelCallValue {
value,
call,
inside_header,
arguments: arguments.flatten(),
end_header,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_call<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
fn babel_call_call_with_headers<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// When babel call contains no arguments or headers (for example: "#+call: lorem ipsum\n") then the trailing line break is part of the call. Otherwise, it is not.
verify(
recognize(many_till(
anychar,
alt((
peek(recognize(one_of("[("))),
peek(alt((
recognize(one_of("[(")),
recognize(tuple((space0, org_line_ending))),
)),
))),
)),
|s| s.len() > 0,
)(input)
@@ -215,9 +231,7 @@ fn impl_balanced_bracket<
}
if fail_parser(remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Fail parser matched.",
))));
return Err(nom::Err::Error(CustomError::Static("Fail parser matched.")));
}
let (remain, _) = anychar(remaining)?;
@@ -226,26 +240,10 @@ fn impl_balanced_bracket<
let contents_end = remaining;
let (remaining, _) = end_parser(remaining)?;
let contents = if contents_start != contents_end {
let contents = if Into::<&str>::into(contents_start) != Into::<&str>::into(contents_end) {
Some(contents_start.get_until(contents_end))
} else {
None
};
Ok((remaining, contents))
}
#[cfg(test)]
mod tests {
use nom::combinator::opt;
use super::*;
#[test]
fn simple_call() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("()");
let (remaining, call) = opt(babel_call_call)(input)?;
assert_eq!(Into::<&str>::into(remaining), "()");
assert_eq!(call, None);
Ok(())
}
}

167
src/parser/bullshitium.rs Normal file
View File

@@ -0,0 +1,167 @@
use nom::branch::alt;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::paragraph::paragraph;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
use super::util::start_of_line;
use super::OrgSource;
use crate::context::bind_context;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::macros::element;
use crate::types::Object;
use crate::types::Paragraph;
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn bullshitium<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
alt((
bind_context!(broken_end, context),
bind_context!(broken_dynamic_block, context),
))(input)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn detect_bullshitium<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
element!(detect_broken_end, context, input);
element!(detect_broken_dynamic_block, context, input);
Err(nom::Err::Error(CustomError::Static("No bullshitium.")))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn broken_end<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
start_of_line(input)?;
let (remaining, _) = space0(input)?;
let (remaining, _) = tag_no_case(":end:")(remaining)?;
let (lead_in_remaining, _) = tuple((space0, org_line_ending))(remaining)?;
if let Ok((remaining, mut paragraph)) =
paragraph(std::iter::empty(), lead_in_remaining, context, input)
{
match paragraph.children.first_mut() {
Some(Object::PlainText(plain_text)) => {
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
paragraph.contents = Some(input.get_until_end_of_str(plain_text.source).into());
}
Some(obj) => {
panic!("Unhandled first object type inside bullshitium {:?}", obj);
}
None => {
unreachable!("Paragraph must have children.");
}
};
Ok((remaining, paragraph))
} else {
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
let body = Into::<&str>::into(input.get_until(lead_in_remaining));
Ok((
remaining,
Paragraph::of_text(
input.get_until(remaining).into(),
body,
if !body.is_empty() { Some(body) } else { None },
post_blank.map(Into::<&str>::into),
),
))
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))
)]
pub(crate) fn detect_broken_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
start_of_line(input)?;
let (remaining, _) = space0(input)?;
let (remaining, _) = tag_no_case(":end:")(remaining)?;
let (_remaining, _) = tuple((space0, org_line_ending))(remaining)?;
Ok((input, ()))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn broken_dynamic_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
start_of_line(input)?;
let (remaining, _) = space0(input)?;
let (remaining, _) = tag_no_case("#+BEGIN:")(remaining)?;
let (lead_in_remaining, _) = many_till(anychar, org_line_ending)(remaining)?;
if let Ok((remaining, mut paragraph)) =
paragraph(std::iter::empty(), lead_in_remaining, context, input)
{
match paragraph.children.first_mut() {
Some(Object::PlainText(plain_text)) => {
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
paragraph.contents = Some(input.get_until_end_of_str(plain_text.source).into());
}
Some(obj) => {
panic!("Unhandled first object type inside bullshitium {:?}", obj);
}
None => {
unreachable!("Paragraph must have children.");
}
};
Ok((remaining, paragraph))
} else {
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
let body = Into::<&str>::into(input.get_until(lead_in_remaining));
Ok((
remaining,
Paragraph::of_text(
input.get_until(remaining).into(),
body,
if !body.is_empty() { Some(body) } else { None },
post_blank.map(Into::<&str>::into),
),
))
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))
)]
pub(crate) fn detect_broken_dynamic_block<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
start_of_line(input)?;
let (remaining, _) = space0(input)?;
let (_remaining, _) = tag_no_case("#+BEGIN:")(remaining)?;
Ok((input, ()))
}

View File

@@ -46,16 +46,22 @@ pub(crate) fn citation<'b, 'g, 'r, 's>(
let (remaining, prefix) =
must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?;
let contents_begin = remaining;
let (remaining, references) =
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
let contents_end = {
let (rem, _) = opt(tag(";"))(remaining)?;
rem
};
let (remaining, suffix) = must_balance_bracket(opt(map(
tuple((tag(";"), parser_with_context!(global_suffix)(context))),
|(_, suffix)| suffix,
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let contents = contents_begin.get_until(contents_end);
Ok((
remaining,
Citation {
@@ -64,6 +70,8 @@ pub(crate) fn citation<'b, 'g, 'r, 's>(
prefix: prefix.unwrap_or(Vec::new()),
suffix: suffix.unwrap_or(Vec::new()),
children: references,
contents: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -137,7 +145,7 @@ fn _global_prefix_end<'b, 'g, 'r, 's>(
unreachable!("Exceeded citation global prefix bracket depth.")
}
if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() {
return close_bracket;
}
@@ -191,7 +199,7 @@ fn _global_suffix_end<'b, 'g, 'r, 's>(
unreachable!("Exceeded citation global suffix bracket depth.")
}
if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() {
return close_bracket;
}
@@ -205,49 +213,54 @@ fn _global_suffix_end<'b, 'g, 'r, 's>(
#[cfg(test)]
mod tests {
use super::*;
use crate::context::bind_context;
use crate::context::Context;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::parser::element_parser::element;
use crate::types::CitationReference;
use crate::types::Element;
use crate::types::GetStandardProperties;
use crate::types::StandardProperties;
#[test]
fn citation_simple() {
fn citation_simple() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("[cite:@foo]");
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
let paragraph_matcher = bind_context!(element(true), &initial_context);
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
let first_paragraph = match first_paragraph {
Element::Paragraph(paragraph) => paragraph,
_ => panic!("Should be a paragraph!"),
};
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(
first_paragraph.get_standard_properties().get_source(),
"[cite:@foo]"
);
assert_eq!(first_paragraph.get_source(), "[cite:@foo]");
assert_eq!(first_paragraph.children.len(), 1);
assert_eq!(
first_paragraph
.children
.get(0)
.expect("Len already asserted to be 1"),
&Object::Citation(Citation {
source: "[cite:@foo]",
style: None,
prefix: vec![],
suffix: vec![],
children: vec![CitationReference {
source: "@foo",
key: "foo",
prefix: vec![],
suffix: vec![]
}]
})
);
match first_paragraph
.children
.first()
.expect("Len already asserted to be 1.")
{
Object::Citation(inner) => {
assert_eq!(inner.get_source(), "[cite:@foo]");
assert_eq!(inner.children.len(), 1);
assert!(inner.prefix.is_empty());
assert!(inner.suffix.is_empty());
assert!(inner.style.is_none());
let citation_reference = inner
.children
.first()
.expect("Len already asserted to be 1.");
assert_eq!(citation_reference.get_source(), "@foo");
assert_eq!(citation_reference.key, "foo");
assert!(citation_reference.prefix.is_empty());
assert!(citation_reference.suffix.is_empty());
}
_ => {
return Err("Child should be a citation.".into());
}
};
Ok(())
}
}

View File

@@ -20,7 +20,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::object_parser::minimal_set_object;
use crate::parser::util::exit_matcher_parser;
@@ -151,7 +150,7 @@ fn _key_prefix_end<'b, 'g, 'r, 's>(
unreachable!("Exceeded citation key prefix bracket depth.")
}
if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() {
return close_bracket;
}
@@ -181,7 +180,7 @@ fn _key_suffix_end<'b, 'g, 'r, 's>(
unreachable!("Exceeded citation key suffix bracket depth.")
}
if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() {
return close_bracket;
}
@@ -199,9 +198,7 @@ where
let pre_bracket_depth = input.get_bracket_depth();
let (remaining, output) = inner(input)?;
if remaining.get_bracket_depth() - pre_bracket_depth != 0 {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"UnbalancedBrackets".into(),
))));
return Err(nom::Err::Error(CustomError::Static("UnbalancedBrackets")));
}
Ok((remaining, output))
}

View File

@@ -40,7 +40,7 @@ pub(crate) fn clock<'b, 'g, 'r, 's>(
let (remaining, (timestamp, duration)) = clock_timestamp(context, remaining)?;
let (remaining, _) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -54,6 +54,7 @@ pub(crate) fn clock<'b, 'g, 'r, 's>(
} else {
ClockStatus::Running
},
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -81,7 +82,7 @@ fn clock_timestamp<'b, 'g, 'r, 's>(
|(timestamp, duration)| (timestamp, duration.map(Into::<&str>::into)),
),
map(
parser_with_context!(inactive_timestamp)(context),
parser_with_context!(inactive_timestamp(true))(context),
|timestamp| (timestamp, None),
),
))(input)

View File

@@ -19,7 +19,6 @@ use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::immediate_in_section;
@@ -35,9 +34,9 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Comment<'s>> {
if immediate_in_section(context, "comment") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element",
)));
}
let parser_context = ContextElement::Context("comment");
let parser_context = context.with_additional_node(&parser_context);
@@ -47,7 +46,7 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
let (remaining, mut remaining_lines) =
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
@@ -68,6 +67,7 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
Comment {
source: source.into(),
value,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -104,6 +104,7 @@ pub(crate) fn detect_comment<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()>
#[cfg(test)]
mod tests {
use super::*;
use crate::context::bind_context;
use crate::context::Context;
use crate::context::ContextElement;
use crate::context::GlobalSettings;
@@ -119,7 +120,7 @@ mod tests {
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let comment_matcher = parser_with_context!(comment)(&initial_context);
let comment_matcher = bind_context!(comment, &initial_context);
let (remaining, first_comment) = comment_matcher(input).expect("Parse first comment");
assert_eq!(
Into::<&str>::into(remaining),

View File

@@ -1,11 +1,9 @@
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
@@ -33,7 +31,7 @@ where
let (remaining, value) = recognize(tuple((tag("%%("), is_not("\r\n"))))(remaining)?;
let (remaining, _eol) = org_line_ending(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -45,13 +43,24 @@ where
affiliated_keywords,
),
value: Into::<&str>::into(value),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_diary_sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
tuple((start_of_line, tag("%%(")))(input)?;
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
)]
pub(crate) fn detect_diary_sexp<'b, 'g, 'r, 's, AK>(
_affiliated_keywords: AK,
remaining: OrgSource<'s>,
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
tuple((start_of_line, tag("%%(")))(remaining)?;
Ok((input, ()))
}

View File

@@ -3,6 +3,7 @@ use std::path::Path;
use nom::combinator::all_consuming;
use nom::combinator::opt;
use nom::multi::many0;
use nom::InputTake;
use super::headline::heading;
use super::in_buffer_settings::apply_in_buffer_settings;
@@ -11,6 +12,7 @@ use super::in_buffer_settings::scan_for_in_buffer_settings;
use super::org_source::OrgSource;
use super::section::zeroth_section;
use super::util::get_consumed;
use crate::context::bind_context;
use crate::context::parser_with_context;
use crate::context::Context;
use crate::context::ContextElement;
@@ -18,9 +20,7 @@ use crate::context::GlobalSettings;
use crate::context::List;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::org_source::convert_error;
use crate::parser::util::blank_line;
use crate::types::AstNode;
use crate::types::Document;
@@ -30,7 +30,7 @@ use crate::types::Object;
///
/// This is a main entry point for Organic. It will parse the full contents of the input string as an org-mode document without an underlying file attached.
#[allow(dead_code)]
pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, Box<dyn std::error::Error>> {
pub fn parse(input: &str) -> Result<Document<'_>, Box<dyn std::error::Error>> {
parse_file_with_settings::<&Path>(input, &GlobalSettings::default(), None)
}
@@ -40,10 +40,10 @@ pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, Box<dyn std::error::Err
///
/// file_path is not used for reading the file contents. It is only used for determining the document category and filling in the path attribute on the Document.
#[allow(dead_code)]
pub fn parse_file<'s, P: AsRef<Path>>(
input: &'s str,
pub fn parse_file<P: AsRef<Path>>(
input: &str,
file_path: Option<P>,
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
) -> Result<Document<'_>, Box<dyn std::error::Error>> {
parse_file_with_settings(input, &GlobalSettings::default(), file_path)
}
@@ -77,7 +77,7 @@ pub fn parse_file_with_settings<'g, 's, P: AsRef<Path>>(
let initial_context = Context::new(global_settings, List::new(&initial_context));
let wrapped_input = OrgSource::new(input);
let mut doc =
all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input)
all_consuming(bind_context!(document_org_source, &initial_context))(wrapped_input)
.map_err(|err| err.to_string())
.map(|(_remaining, parsed_document)| parsed_document)?;
if let Some(file_path) = file_path {
@@ -101,11 +101,8 @@ pub fn parse_file_with_settings<'g, 's, P: AsRef<Path>>(
///
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
#[allow(dead_code)]
fn document<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: &'s str,
) -> Res<&'s str, Document<'s>> {
let (remaining, doc) = document_org_source(context, input.into()).map_err(convert_error)?;
fn document<'s>(context: RefContext<'_, '_, '_, 's>, input: &'s str) -> Res<&'s str, Document<'s>> {
let (remaining, doc) = document_org_source(context, input.into())?;
Ok((Into::<&str>::into(remaining), doc))
}
@@ -129,29 +126,16 @@ fn document_org_source<'b, 'g, 'r, 's>(
.get_global_settings()
.file_access
.read_file(setup_file)
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))
.map_err(|err| nom::Err::<CustomError>::Failure(err.into()))
})
.collect::<Result<Vec<_>, _>>()?;
for setup_file in setup_files.iter().map(String::as_str) {
let (_, setup_file_settings) =
scan_for_in_buffer_settings(setup_file.into()).map_err(|err| {
eprintln!("{}", err);
nom::Err::Error(CustomError::MyError(MyError(
"TODO: make this take an owned string so I can dump err.to_string() into it."
.into(),
)))
})?;
let (_, setup_file_settings) = scan_for_in_buffer_settings(setup_file.into())?;
final_settings.extend(setup_file_settings);
}
final_settings.extend(document_settings);
let new_settings = apply_in_buffer_settings(final_settings, context.get_global_settings())
.map_err(|err| {
eprintln!("{}", err);
nom::Err::Error(CustomError::MyError(MyError(
"TODO: make this take an owned string so I can dump err.to_string() into it."
.into(),
)))
})?;
.map_err(nom::Err::Error)?;
let new_context = context.with_global_settings(&new_settings);
let context = &new_context;
@@ -160,7 +144,7 @@ fn document_org_source<'b, 'g, 'r, 's>(
{
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
let all_radio_targets: Vec<&Vec<Object<'_>>> = Into::<AstNode>::into(&document)
.into_iter()
.iter_all_ast_nodes()
.filter_map(|ast_node| {
if let AstNode::RadioTarget(ast_node) = ast_node {
Some(ast_node)
@@ -176,15 +160,13 @@ fn document_org_source<'b, 'g, 'r, 's>(
let parser_context = context.with_global_settings(&new_global_settings);
let (remaining, mut document) = _document(&parser_context, input)
.map(|(rem, out)| (Into::<&str>::into(rem), out))?;
apply_post_parse_in_buffer_settings(&mut document)
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
apply_post_parse_in_buffer_settings(&mut document);
return Ok((remaining.into(), document));
}
}
// Find final in-buffer settings that do not impact parsing
apply_post_parse_in_buffer_settings(&mut document)
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
apply_post_parse_in_buffer_settings(&mut document);
Ok((remaining.into(), document))
}
@@ -200,8 +182,10 @@ fn _document<'b, 'g, 'r, 's>(
let zeroth_section_matcher = parser_with_context!(zeroth_section)(context);
let heading_matcher = parser_with_context!(heading(0))(context);
let (remaining, _blank_lines) = many0(blank_line)(input)?;
let contents_begin = remaining;
let (remaining, zeroth_section) = opt(zeroth_section_matcher)(remaining)?;
let (remaining, children) = many0(heading_matcher)(remaining)?;
let contents = get_consumed(contents_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -211,6 +195,25 @@ fn _document<'b, 'g, 'r, 's>(
path: None,
zeroth_section,
children,
contents: if contents.len() > 0 {
Into::<&str>::into(contents)
} else {
Into::<&str>::into(remaining.take(0))
},
},
))
}
#[cfg(test)]
mod tests {
use test::Bencher;
use super::*;
#[bench]
fn bench_full_document(b: &mut Bencher) {
let input = include_str!("../../org_mode_samples/element_container_priority/README.org");
b.iter(|| assert!(parse(input).is_ok()));
}
}

View File

@@ -4,6 +4,7 @@ use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::recognize;
@@ -12,17 +13,17 @@ use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource;
use super::paragraph::empty_paragraph;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::bind_context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::element_parser::element;
use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
@@ -31,8 +32,6 @@ use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::types::Drawer;
use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr(
feature = "tracing",
@@ -48,9 +47,9 @@ where
AK: IntoIterator<Item = Keyword<'s>>,
{
if immediate_in_section(context, "drawer") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element",
)));
}
start_of_line(remaining)?;
let (remaining, _leading_whitespace) = space0(remaining)?;
@@ -72,30 +71,12 @@ where
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let (remaining, (contents, children)) =
consumed(parser_with_context!(children)(&parser_context))(remaining)?;
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, children) = match tuple((
not(exit_matcher),
blank_line,
many_till(blank_line, exit_matcher),
))(remaining)
{
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
let source = get_consumed(remaining, remain);
element.set_source(source.into());
(remain, vec![element])
}
Err(_) => {
let (remaining, (children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
(remaining, children)
}
};
let (remaining, _end) = drawer_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -109,10 +90,34 @@ where
),
drawer_name: drawer_name.into(),
children,
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
fn children<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Element<'s>>> {
let element_matcher = parser_with_context!(element(true))(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
if let Ok((remaining, (_not_exit, empty_para))) =
tuple((not(exit_matcher), bind_context!(empty_paragraph, context)))(input)
{
return Ok((remaining, vec![Element::Paragraph(empty_para)]));
}
let (remaining, (children, _exit_contents)) = many_till(element_matcher, exit_matcher)(input)?;
Ok((remaining, children))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input)

View File

@@ -6,30 +6,28 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::greater_block::leading_blank_lines_end;
use super::org_source::OrgSource;
use super::paragraph::empty_paragraph;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::bind_context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::element_parser::element;
use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
@@ -37,8 +35,6 @@ use crate::parser::util::start_of_line;
use crate::types::DynamicBlock;
use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr(
feature = "tracing",
@@ -54,9 +50,9 @@ where
AK: IntoIterator<Item = Keyword<'s>>,
{
if immediate_in_section(context, "dynamic block") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element",
)));
}
start_of_line(remaining)?;
@@ -79,32 +75,29 @@ where
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters),
None => None,
};
let parameters = parameters.map(|(_ws, parameters)| parameters);
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
not(exit_matcher)(remaining)?;
let (remaining, leading_blank_lines) = opt(consumed(tuple((
blank_line,
many0(preceded(not(exit_matcher), blank_line)),
))))(remaining)?;
let leading_blank_lines =
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
element.set_source(source.into());
element
});
let contents_begin = remaining;
let blank_line_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha,
exit_matcher: &leading_blank_lines_end,
});
let blank_line_context = parser_context.with_additional_node(&blank_line_context);
let (remaining, leading_blank_lines) =
opt(bind_context!(empty_paragraph, &blank_line_context))(remaining)?;
let (remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
if let Some(lines) = leading_blank_lines {
children.insert(0, lines);
children.insert(0, Element::Paragraph(lines));
}
let contents = get_consumed(contents_begin, remaining);
let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -118,6 +111,12 @@ where
block_name: name.into(),
parameters: parameters.map(|val| val.into()),
children,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -1,6 +1,3 @@
use nom::branch::alt;
use nom::multi::many0;
use super::babel_call::babel_call;
use super::clock::clock;
use super::comment::comment;
@@ -15,7 +12,6 @@ use super::footnote_definition::detect_footnote_definition;
use super::footnote_definition::footnote_definition;
use super::greater_block::greater_block;
use super::horizontal_rule::horizontal_rule;
use super::keyword::affiliated_keyword;
use super::keyword::keyword;
use super::latex_environment::latex_environment;
use super::lesser_block::comment_block;
@@ -28,11 +24,16 @@ use super::paragraph::paragraph;
use super::plain_list::detect_plain_list;
use super::plain_list::plain_list;
use super::table::detect_table;
use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
#[cfg(feature = "event_count")]
use crate::event_count::record_event;
#[cfg(feature = "event_count")]
use crate::event_count::EventType;
use crate::parser::affiliated_keyword::affiliated_keywords;
use crate::parser::bullshitium::bullshitium;
use crate::parser::bullshitium::detect_bullshitium;
use crate::parser::macros::ak_element;
use crate::parser::macros::element;
use crate::parser::table::org_mode_table;
@@ -56,7 +57,9 @@ fn _element<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
can_be_paragraph: bool,
) -> Res<OrgSource<'s>, Element<'s>> {
let (post_affiliated_keywords_input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
#[cfg(feature = "event_count")]
record_event(EventType::ElementStart, input);
let (post_affiliated_keywords_input, affiliated_keywords) = affiliated_keywords(input)?;
let mut affiliated_keywords = affiliated_keywords.into_iter();
@@ -240,6 +243,9 @@ fn _element<'b, 'g, 'r, 's>(
);
if can_be_paragraph {
// Fake paragraphs
element!(bullshitium, context, input, Element::Paragraph);
// Paragraph without affiliated keyword
ak_element!(
paragraph,
@@ -251,9 +257,7 @@ fn _element<'b, 'g, 'r, 's>(
);
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"No element.",
))))
Err(nom::Err::Error(CustomError::Static("No element.")))
}
pub(crate) const fn detect_element(
@@ -272,22 +276,60 @@ fn _detect_element<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> {
if alt((
parser_with_context!(detect_plain_list)(context),
let (post_affiliated_keywords_input, affiliated_keywords) = affiliated_keywords(input)?;
let mut affiliated_keywords = affiliated_keywords.into_iter();
ak_element!(
detect_plain_list,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input
);
ak_element!(
detect_footnote_definition,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input
);
ak_element!(
detect_diary_sexp,
detect_comment,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input
);
element!(detect_comment, input);
ak_element!(
detect_fixed_width_area,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input
);
ak_element!(
detect_table,
))(input)
.is_ok()
{
return Ok((input, ()));
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input
);
// Fake paragraphs
if !can_be_paragraph {
element!(detect_bullshitium, context, input);
}
if _element(context, input, can_be_paragraph).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No element detected.".into(),
))));
Err(nom::Err::Error(CustomError::Static("No element detected.")))
}

View File

@@ -1,11 +1,11 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::satisfy;
use nom::combinator::cond;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::sequence::tuple;
use super::org_source::OrgSource;
@@ -13,7 +13,6 @@ use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::EntityDefinition;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::types::Entity;
@@ -29,7 +28,7 @@ pub(crate) fn entity<'b, 'g, 'r, 's>(
let (remaining, _) = tag("\\")(input)?;
let (remaining, (entity_definition, entity_name, use_brackets)) = name(context, remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -44,6 +43,7 @@ pub(crate) fn entity<'b, 'g, 'r, 's>(
ascii: entity_definition.ascii,
utf8: entity_definition.utf8,
use_brackets,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -58,23 +58,21 @@ fn name<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, (&'g EntityDefinition<'s>, OrgSource<'s>, bool)> {
for entity in context.get_global_settings().entities {
let result = tuple((
tag::<_, _, CustomError<_>>(entity.name),
alt((
verify(map(tag("{}"), |_| true), |_| !entity.name.ends_with(" ")),
map(peek(recognize(entity_end)), |_| false),
)),
tag::<_, _, CustomError>(entity.name),
cond(
!entity.name.ends_with(' '),
alt((
map(tag("{}"), |_| true),
map(peek(recognize(entity_end)), |_| false),
)),
),
))(input);
match result {
Ok((remaining, (ent, use_brackets))) => {
return Ok((remaining, (entity, ent, use_brackets)));
}
Err(_) => {}
if let Ok((remaining, (ent, use_brackets))) = result {
return Ok((remaining, (entity, ent, use_brackets.unwrap_or(false))));
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoEntity".into(),
))))
Err(nom::Err::Error(CustomError::Static("NoEntity")))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@@ -39,7 +39,7 @@ pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
parser_with_context!(contents)(&parser_context),
)))(remaining)?;
let (remaining, _) = tag("@@")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -48,6 +48,7 @@ pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
source: source.into(),
backend: backend_name.into(),
contents: backend_contents.map(|(_colon, backend_contents)| backend_contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -2,15 +2,17 @@ use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use nom::InputTake;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
@@ -36,28 +38,25 @@ pub(crate) fn fixed_width_area<'b, 'g, 'r, 's, AK>(
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, first_line) = fixed_width_area_line_matcher(remaining)?;
let (remaining, mut remaining_lines) =
many0(preceded(not(exit_matcher), fixed_width_area_line_matcher))(remaining)?;
let (remaining, first_line) = fixed_width_area_line(remaining)?;
let (remaining, remaining_lines) = many0(preceded(
not(tuple((org_line_ending, exit_matcher))),
map(
tuple((org_line_ending, fixed_width_area_line)),
|(_line_ending, line_contents)| line_contents,
),
))(remaining)?;
let (remaining, _trailing_ws) =
let post_blank_begin = remaining;
let (remaining, _first_line_break) = org_line_ending(remaining)?;
let (remaining, _additional_post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let post_blank = get_consumed(post_blank_begin, remaining);
let source = get_consumed(input, remaining);
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
let last_line = remaining_lines.pop();
if let Some(last_line) = last_line {
value.push(Into::<&str>::into(first_line));
value.extend(remaining_lines.into_iter().map(Into::<&str>::into));
let last_line = Into::<&str>::into(last_line);
// Trim the line ending from the final line.
value.push(&last_line[..(last_line.len() - 1)])
} else {
// Trim the line ending from the only line.
let only_line = Into::<&str>::into(first_line);
value.push(&only_line[..(only_line.len() - 1)])
}
value.push(Into::<&str>::into(first_line));
value.extend(remaining_lines.into_iter().map(Into::<&str>::into));
Ok((
remaining,
FixedWidthArea {
@@ -67,36 +66,45 @@ where
affiliated_keywords,
),
value,
post_blank: if post_blank.len() > 0 {
Some(Into::<&str>::into(post_blank))
} else {
None
},
},
))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))
)]
fn fixed_width_area_line<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn fixed_width_area_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let (remaining, _) = tuple((space0, tag(":")))(input)?;
if let Ok((remaining, line_break)) = org_line_ending(remaining) {
return Ok((remaining, line_break));
if let Ok((_remain, _line_break)) = org_line_ending(remaining) {
return Ok((remaining, remaining.take(0)));
}
let (remaining, _) = tag(" ")(remaining)?;
let (remaining, value) = recognize(many_till(anychar, org_line_ending))(remaining)?;
let (remaining, value) = recognize(many_till(anychar, peek(org_line_ending)))(remaining)?;
Ok((remaining, value))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_fixed_width_area<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
)]
pub(crate) fn detect_fixed_width_area<'b, 'g, 'r, 's, AK>(
_affiliated_keywords: AK,
remaining: OrgSource<'s>,
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
tuple((
start_of_line,
space0,
tag(":"),
alt((tag(" "), org_line_ending)),
))(input)?;
))(remaining)?;
Ok((input, ()))
}

View File

@@ -12,7 +12,6 @@ use nom::multi::many_till;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::include_input;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
@@ -23,7 +22,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::element_parser::element;
use crate::parser::util::blank_line;
@@ -49,9 +47,9 @@ where
AK: IntoIterator<Item = Keyword<'s>>,
{
if immediate_in_section(context, "footnote definition") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element",
)));
}
start_of_line(remaining)?;
// Cannot be indented.
@@ -77,6 +75,7 @@ where
let parser_context = parser_context.with_additional_node(&contexts[2]);
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let before_contents = remaining;
let (mut remaining, (mut children, _exit_contents)) =
many_till(include_input(element_matcher), exit_matcher)(remaining)?;
@@ -92,13 +91,16 @@ where
}
}
let (remaining, _trailing_ws) =
let contents = get_consumed(before_contents, remaining);
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
FootnoteDefinition {
source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
@@ -125,7 +127,7 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
let (remaining, source) = alt((
recognize(tuple((
parser_with_context!(maybe_consume_trailing_whitespace)(context),
detect_footnote_definition,
|i| detect_footnote_definition(std::iter::empty(), i, context, i),
))),
recognize(tuple((
start_of_line,
@@ -138,20 +140,31 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
Ok((remaining, source))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
)]
pub(crate) fn detect_footnote_definition<'b, 'g, 'r, 's, AK>(
_affiliated_keywords: AK,
remaining: OrgSource<'s>,
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(remaining)?;
Ok((input, ()))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::bind_context;
use crate::context::Context;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::types::GetStandardProperties;
use crate::types::StandardProperties;
#[test]
fn two_paragraphs() {
@@ -165,24 +178,20 @@ line footnote.",
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
let footnote_definition_matcher = bind_context!(element(true), &initial_context);
let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition");
let (remaining, second_footnote_definition) =
footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(
first_footnote_definition
.get_standard_properties()
.get_source(),
first_footnote_definition.get_source(),
"[fn:1] A footnote.
"
);
assert_eq!(
second_footnote_definition
.get_standard_properties()
.get_source(),
second_footnote_definition.get_source(),
"[fn:2] A multi-
line footnote."
@@ -202,14 +211,12 @@ not in the footnote.",
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
let footnote_definition_matcher = bind_context!(element(true), &initial_context);
let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition");
assert_eq!(Into::<&str>::into(remaining), "not in the footnote.");
assert_eq!(
first_footnote_definition
.get_standard_properties()
.get_source(),
first_footnote_definition.get_source(),
"[fn:2] A multi-
line footnote.

View File

@@ -2,6 +2,7 @@ use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::combinator::all_consuming;
use nom::combinator::consumed;
use nom::combinator::map_parser;
use nom::combinator::verify;
use nom::multi::many1;
@@ -20,7 +21,6 @@ use crate::context::ExitMatcherNode;
use crate::context::List;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::footnote_definition::label;
use crate::parser::object_parser::standard_set_object;
@@ -60,7 +60,7 @@ fn anonymous_footnote<'b, 'g, 'r, 's>(
let initial_context = ContextElement::document_context();
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
let (remaining, children) = map_parser(
let (remaining, (contents, children)) = consumed(map_parser(
verify(
parser_with_context!(text_until_exit)(&parser_context),
|text| text.len() > 0,
@@ -70,17 +70,19 @@ fn anonymous_footnote<'b, 'g, 'r, 's>(
&initial_context,
)))(i)
}),
)(remaining)?;
))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
FootnoteReference {
source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
label: None,
definition: children,
},
@@ -107,7 +109,7 @@ fn inline_footnote<'b, 'g, 'r, 's>(
let initial_context = ContextElement::document_context();
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
let (remaining, children) = map_parser(
let (remaining, (contents, children)) = consumed(map_parser(
verify(
parser_with_context!(text_until_exit)(&parser_context),
|text| text.len() > 0,
@@ -117,17 +119,19 @@ fn inline_footnote<'b, 'g, 'r, 's>(
&initial_context,
)))(i)
}),
)(remaining)?;
))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
FootnoteReference {
source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
label: Some(label_contents.into()),
definition: children,
},
@@ -145,13 +149,15 @@ fn footnote_reference_only<'b, 'g, 'r, 's>(
let (remaining, _) = tag_no_case("[fn:")(input)?;
let (remaining, label_contents) = label(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
FootnoteReference {
source: source.into(),
contents: None,
post_blank: post_blank.map(Into::<&str>::into),
label: Some(label_contents.into()),
definition: Vec::with_capacity(0),
},
@@ -176,9 +182,9 @@ fn _footnote_definition_end<'b, 'g, 'r, 's>(
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 {
// Its impossible for the next character to end the footnote reference definition if we're any amount of brackets deep
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoFootnoteReferenceDefinitionEnd".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"NoFootnoteReferenceDefinitionEnd",
)));
}
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the footnote definition.

View File

@@ -5,22 +5,21 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource;
use super::paragraph::empty_paragraph;
use super::util::in_section;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::bind_context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
@@ -28,7 +27,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::element_parser::element;
use crate::parser::util::blank_line;
@@ -38,9 +36,7 @@ use crate::parser::util::start_of_line;
use crate::types::CenterBlock;
use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::QuoteBlock;
use crate::types::SetSource;
use crate::types::SpecialBlock;
#[cfg_attr(
@@ -61,10 +57,10 @@ where
let (remaining, (_begin, name)) = tuple((
tag_no_case("#+begin_"),
verify(name, |name: &OrgSource<'_>| {
match Into::<&str>::into(name).to_lowercase().as_str() {
"comment" | "example" | "export" | "src" | "verse" => false,
_ => true,
}
!matches!(
Into::<&str>::into(name).to_lowercase().as_str(),
"comment" | "example" | "export" | "src" | "verse",
)
}),
))(remaining)?;
let name = Into::<&str>::into(name);
@@ -104,7 +100,7 @@ fn center_block<'b, 'g, 'r, 's, AK>(
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, (source, children)) = greater_block_body(
let (remaining, body) = greater_block_body(
context,
input,
pre_affiliated_keywords_input,
@@ -114,12 +110,14 @@ where
Ok((
remaining,
Element::CenterBlock(CenterBlock {
source,
source: body.source,
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
children,
children: body.children,
contents: body.contents,
post_blank: body.post_blank,
}),
))
}
@@ -137,7 +135,7 @@ fn quote_block<'b, 'g, 'r, 's, AK>(
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, (source, children)) = greater_block_body(
let (remaining, body) = greater_block_body(
context,
input,
pre_affiliated_keywords_input,
@@ -147,12 +145,14 @@ where
Ok((
remaining,
Element::QuoteBlock(QuoteBlock {
source,
source: body.source,
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
children,
children: body.children,
contents: body.contents,
post_blank: body.post_blank,
}),
))
}
@@ -198,7 +198,7 @@ where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
let (remaining, (source, children)) = greater_block_body(
let (remaining, body) = greater_block_body(
context,
remaining,
pre_affiliated_keywords_input,
@@ -208,18 +208,28 @@ where
Ok((
remaining,
Element::SpecialBlock(SpecialBlock {
source,
source: body.source,
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
children,
children: body.children,
block_type: name,
parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)),
contents: body.contents,
post_blank: body.post_blank,
}),
))
}
#[derive(Debug)]
struct GreaterBlockBody<'s> {
source: &'s str,
children: Vec<Element<'s>>,
contents: Option<&'s str>,
post_blank: Option<&'s str>,
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
@@ -230,11 +240,11 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
pre_affiliated_keywords_input: OrgSource<'s>,
name: &'c str,
context_name: &'c str,
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> {
) -> Res<OrgSource<'s>, GreaterBlockBody<'s>> {
if in_section(context, context_name) {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element",
)));
}
let exit_with_name = greater_block_end(name);
let (remaining, _nl) = tuple((space0, line_ending))(input)?;
@@ -252,30 +262,43 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
not(exit_matcher)(remaining)?;
let (remaining, leading_blank_lines) = opt(consumed(tuple((
blank_line,
many0(preceded(not(exit_matcher), blank_line)),
))))(remaining)?;
let leading_blank_lines =
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
element.set_source(source.into());
element
});
let contents_begin = remaining;
let blank_line_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha,
exit_matcher: &leading_blank_lines_end,
});
let blank_line_context = parser_context.with_additional_node(&blank_line_context);
let (remaining, leading_blank_lines) =
opt(bind_context!(empty_paragraph, &blank_line_context))(remaining)?;
let (remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
if let Some(lines) = leading_blank_lines {
children.insert(0, lines);
children.insert(0, Element::Paragraph(lines));
}
let contents = get_consumed(contents_begin, remaining);
let (remaining, _end) = exit_with_name(&parser_context, remaining)?;
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(pre_affiliated_keywords_input, remaining);
Ok((remaining, (Into::<&str>::into(source), children)))
Ok((
remaining,
GreaterBlockBody {
source: Into::<&str>::into(source),
children,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -288,7 +311,7 @@ fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many_till(anychar, peek(tuple((space0, line_ending)))))(input)
}
fn greater_block_end<'c>(name: &'c str) -> impl ContextMatcher + 'c {
fn greater_block_end(name: &str) -> impl ContextMatcher + '_ {
move |context, input: OrgSource<'_>| _greater_block_end(context, input, name)
}
@@ -311,3 +334,14 @@ fn _greater_block_end<'b, 'g, 'r, 's, 'c>(
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))
)]
pub(crate) fn leading_blank_lines_end<'b, 'g, 'r, 's, 'c>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(not(blank_line))(input)
}

View File

@@ -18,18 +18,18 @@ use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::section::section;
use super::util::exit_matcher_parser;
use super::util::get_consumed;
use super::util::org_line_ending;
use super::util::org_space;
use super::util::org_space_or_line_ending;
use super::util::start_of_line;
use crate::context::parser_with_context;
use crate::context::bind_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::object_parser::standard_set_object;
use crate::parser::util::blank_line;
@@ -62,10 +62,12 @@ fn _heading<'b, 'g, 'r, 's>(
let mut scheduled = None;
let mut deadline = None;
let mut closed = None;
not(|i| context.check_exit_matcher(i))(input)?;
not(bind_context!(exit_matcher_parser, context))(input)?;
let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading(pre_headline.star_count))(context);
let section_matcher = bind_context!(section, context);
let heading_matcher = bind_context!(heading(pre_headline.star_count), context);
let (contents_begin, _) = opt(many0(blank_line))(remaining)?;
let maybe_post_blank = get_consumed(remaining, contents_begin);
let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
@@ -82,7 +84,8 @@ fn _heading<'b, 'g, 'r, 's>(
}
children.insert(0, section);
}
let remaining = if children.is_empty() {
let has_children = !children.is_empty();
let remaining = if !has_children {
// Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?;
remain
@@ -91,6 +94,7 @@ fn _heading<'b, 'g, 'r, 's>(
};
let is_archived = pre_headline.tags.contains(&"ARCHIVE");
let contents = get_consumed(contents_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -112,6 +116,16 @@ fn _heading<'b, 'g, 'r, 's>(
scheduled,
deadline,
closed,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
post_blank: if has_children {
None
} else {
Some(Into::<&str>::into(maybe_post_blank))
},
},
))
}
@@ -155,7 +169,7 @@ fn headline<'b, 'g, 'r, 's>(
let (remaining, (_, (headline_level, star_count, _), _)) = tuple((
start_of_line,
verify(
parser_with_context!(headline_level)(&parser_context),
bind_context!(headline_level, &parser_context),
|(_, count, _)| *count > parent_star_count,
),
peek(org_space),
@@ -163,7 +177,7 @@ fn headline<'b, 'g, 'r, 's>(
let (remaining, maybe_todo_keyword) = opt(tuple((
space1,
parser_with_context!(heading_keyword)(&parser_context),
bind_context!(heading_keyword, &parser_context),
peek(org_space_or_line_ending),
)))(remaining)?;
@@ -177,9 +191,7 @@ fn headline<'b, 'g, 'r, 's>(
let (remaining, maybe_title) = opt(tuple((
space1,
consumed(many1(parser_with_context!(standard_set_object)(
&parser_context,
))),
consumed(many1(bind_context!(standard_set_object, &parser_context))),
)))(remaining)?;
let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?;
@@ -206,11 +218,7 @@ fn headline<'b, 'g, 'r, 's>(
.map(|(_, (_, title))| title)
.unwrap_or(Vec::new()),
tags: maybe_tags
.map(|(_ws, tags)| {
tags.into_iter()
.map(|single_tag| Into::<&str>::into(single_tag))
.collect()
})
.map(|(_ws, tags)| tags.into_iter().map(Into::<&str>::into).collect())
.unwrap_or(Vec::new()),
is_footnote_section,
},
@@ -264,12 +272,9 @@ fn heading_keyword<'b, 'g, 'r, 's>(
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Todo, ent)));
}
Err(_) => {}
let result = tag::<_, _, CustomError>(todo_keyword)(input);
if let Ok((remaining, ent)) = result {
return Ok((remaining, (TodoKeywordType::Todo, ent)));
}
}
for todo_keyword in global_settings
@@ -277,30 +282,25 @@ fn heading_keyword<'b, 'g, 'r, 's>(
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Done, ent)));
}
Err(_) => {}
let result = tag::<_, _, CustomError>(todo_keyword)(input);
if let Ok((remaining, ent)) = result {
return Ok((remaining, (TodoKeywordType::Done, ent)));
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoTodoKeyword".into(),
))))
Err(nom::Err::Error(CustomError::Static("NoTodoKeyword")))
}
}
fn priority_cookie<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PriorityCookie> {
fn priority_cookie(input: OrgSource<'_>) -> Res<OrgSource<'_>, PriorityCookie> {
let (remaining, (_, priority_character, _)) = tuple((
tag("[#"),
verify(anychar, |c| c.is_alphanumeric()),
tag("]"),
))(input)?;
let cookie = PriorityCookie::try_from(priority_character).map_err(|_| {
nom::Err::Error(CustomError::MyError(MyError(
"Failed to cast priority cookie to number.".into(),
)))
nom::Err::Error(CustomError::Static(
"Failed to cast priority cookie to number.",
))
})?;
Ok((remaining, cookie))
}

View File

@@ -38,7 +38,7 @@ where
space0,
alt((line_ending, eof)),
)))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -49,6 +49,7 @@ where
context.get_global_settings(),
affiliated_keywords,
),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -35,7 +35,7 @@ pub(crate) fn scan_for_in_buffer_settings<'s>(
let mut remaining = input;
loop {
// Skip text until possible in_buffer_setting
let start_of_pound = take_until::<_, _, CustomError<_>>("#+")(remaining);
let start_of_pound = take_until::<_, _, CustomError>("#+")(remaining);
let start_of_pound = if let Ok((start_of_pound, _)) = start_of_pound {
start_of_pound
} else {
@@ -47,7 +47,7 @@ pub(crate) fn scan_for_in_buffer_settings<'s>(
let (remain, maybe_kw) = match filtered_keyword(in_buffer_settings_key)(start_of_line) {
Ok((remain, kw)) => (remain, Some(kw)),
Err(_) => {
let end_of_line = take_until::<_, _, CustomError<_>>("\n")(start_of_pound);
let end_of_line = take_until::<_, _, CustomError>("\n")(start_of_pound);
if let Ok((end_of_line, _)) = end_of_line {
(end_of_line, None)
} else {
@@ -84,11 +84,14 @@ fn in_buffer_settings_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSou
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug"))]
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "debug", skip(original_settings))
)]
pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
keywords: Vec<Keyword<'sf>>,
original_settings: &'g GlobalSettings<'g, 's>,
) -> Result<GlobalSettings<'g, 's>, String> {
) -> Result<GlobalSettings<'g, 's>, CustomError> {
let mut new_settings = original_settings.clone();
// Todo Keywords
@@ -98,7 +101,11 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|| kw.key.eq_ignore_ascii_case("typ_todo")
}) {
let (_, (in_progress_words, complete_words)) =
todo_keywords(kw.value).map_err(|err| err.to_string())?;
todo_keywords(kw.value).map_err(|err| match err {
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
nom::Err::Error(e) => e,
nom::Err::Failure(e) => e,
})?;
new_settings
.in_progress_todo_keywords
.extend(in_progress_words.into_iter().map(str::to_string));
@@ -112,9 +119,14 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
.iter()
.filter(|kw| kw.key.eq_ignore_ascii_case("startup"))
{
let (_remaining, settings) =
separated_list0(space1::<&str, nom::error::Error<_>>, is_not(" \t"))(kw.value)
.map_err(|err: nom::Err<_>| err.to_string())?;
let (_remaining, settings) = separated_list0(space1::<&str, CustomError>, is_not(" \t"))(
kw.value,
)
.map_err(|err: nom::Err<_>| match err {
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
nom::Err::Error(e) => e,
nom::Err::Failure(e) => e,
})?;
if settings.contains(&"odd") {
new_settings.odd_levels_only = HeadlineLevelFilter::Odd;
}
@@ -128,7 +140,11 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
.iter()
.filter(|kw| kw.key.eq_ignore_ascii_case("link"))
{
let (_, (link_key, link_value)) = link_template(kw.value).map_err(|e| e.to_string())?;
let (_, (link_key, link_value)) = link_template(kw.value).map_err(|err| match err {
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
nom::Err::Error(e) => e,
nom::Err::Failure(e) => e,
})?;
new_settings
.link_templates
.insert(link_key.to_owned(), link_value.to_owned());
@@ -139,11 +155,9 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
/// Apply in-buffer settings that do not impact parsing and therefore can be applied after parsing.
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(
document: &mut Document<'s>,
) -> Result<(), &'static str> {
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(document: &mut Document<'s>) {
document.category = Into::<AstNode>::into(&*document)
.into_iter()
.iter_all_ast_nodes()
.filter_map(|ast_node| {
if let AstNode::Keyword(ast_node) = ast_node {
if ast_node.key.eq_ignore_ascii_case("category") {
@@ -154,7 +168,6 @@ pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(
})
.last()
.map(|kw| kw.value.to_owned());
Ok(())
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@@ -19,7 +19,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
@@ -39,7 +38,7 @@ pub(crate) fn inline_babel_call<'b, 'g, 'r, 's>(
let (remaining, arguments) = argument(context, remaining)?;
let (remaining, end_header) = opt(parser_with_context!(header)(context))(remaining)?;
let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -55,6 +54,7 @@ pub(crate) fn inline_babel_call<'b, 'g, 'r, 's>(
None
},
end_header: end_header.map(Into::<&str>::into),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -131,9 +131,7 @@ fn _header_end<'b, 'g, 'r, 's>(
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 {
// Its impossible for the next character to end the header if we're any amount of bracket deep
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoHeaderEnd".into(),
))));
return Err(nom::Err::Error(CustomError::Static("NoHeaderEnd")));
}
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
@@ -183,9 +181,7 @@ fn _argument_end<'b, 'g, 'r, 's>(
let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth;
if current_depth > 0 {
// Its impossible for the next character to end the argument if we're any amount of parenthesis deep
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoArgumentEnd".into(),
))));
return Err(nom::Err::Error(CustomError::Static("NoArgumentEnd")));
}
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing parenthesis should end the argument.

View File

@@ -21,7 +21,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
@@ -39,7 +38,7 @@ pub(crate) fn inline_source_block<'b, 'g, 'r, 's>(
let (remaining, language) = lang(context, remaining)?;
let (remaining, parameters) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, value) = body(context, remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -49,6 +48,7 @@ pub(crate) fn inline_source_block<'b, 'g, 'r, 's>(
language: language.into(),
parameters: parameters.map(Into::<&str>::into),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -125,9 +125,7 @@ fn _header_end<'b, 'g, 'r, 's>(
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 {
// Its impossible for the next character to end the header if we're any amount of bracket deep
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoHeaderEnd".into(),
))));
return Err(nom::Err::Error(CustomError::Static("NoHeaderEnd")));
}
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
@@ -187,9 +185,7 @@ fn _body_end<'b, 'g, 'r, 's>(
let current_depth = input.get_brace_depth() - starting_brace_depth;
if current_depth > 0 {
// Its impossible for the next character to end the body if we're any amount of brace deep
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoBodyEnd".into(),
))));
return Err(nom::Err::Error(CustomError::Static("NoBodyEnd")));
}
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing brace should end the body.

View File

@@ -4,11 +4,10 @@ use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while1;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::peek;
use nom::combinator::recognize;
@@ -21,24 +20,20 @@ use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::Matcher;
use super::util::org_line_ending;
use crate::context::constants::ORG_ELEMENT_AFFILIATED_KEYWORDS;
use crate::context::constants::ORG_ELEMENT_DUAL_KEYWORDS;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::macros::element;
use crate::parser::util::start_of_line;
use crate::types::AffiliatedKeywords;
use crate::types::Keyword;
const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
"caption", "data", "headers", "header", "label", "name", "plot", "resname", "results",
"result", "source", "srcname", "tblname",
];
const ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["caption", "results"];
pub(crate) fn filtered_keyword<F: Matcher>(
pub(crate) fn filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>>(
key_parser: F,
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
) -> impl Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
move |input| _filtered_keyword(&key_parser, input)
}
@@ -46,7 +41,7 @@ pub(crate) fn filtered_keyword<F: Matcher>(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(key_parser))
)]
fn _filtered_keyword<'s, F: Matcher>(
fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>>(
key_parser: F,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
@@ -54,30 +49,22 @@ fn _filtered_keyword<'s, F: Matcher>(
// TODO: When key is a member of org-element-parsed-keywords, value can contain the standard set objects, excluding footnote references.
let (remaining, (consumed_input, (_, _, parsed_key, _))) =
consumed(tuple((space0, tag("#+"), key_parser, tag(":"))))(input)?;
match tuple((
space0::<OrgSource<'_>, CustomError<OrgSource<'_>>>,
alt((line_ending, eof)),
))(remaining)
{
Ok((remaining, _)) => {
return Ok((
remaining,
Keyword {
source: consumed_input.into(),
affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(),
value: "".into(),
},
));
}
Err(_) => {}
};
let (remaining, _ws) = space0(remaining)?;
let (remaining, parsed_value) = recognize(many_till(
anychar,
peek(tuple((space0, alt((line_ending, eof))))),
))(remaining)?;
let (remaining, _ws) = tuple((space0, alt((line_ending, eof))))(remaining)?;
if let Ok((remaining, _)) = org_line_ending(remaining) {
return Ok((
remaining,
Keyword {
source: consumed_input.into(),
affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(),
value: "",
post_blank: None,
},
));
}
let (remaining, parsed_value) =
recognize(many_till(anychar, peek(tuple((space0, org_line_ending)))))(remaining)?;
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
Ok((
remaining,
Keyword {
@@ -85,6 +72,7 @@ fn _filtered_keyword<'s, F: Matcher>(
affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(),
value: parsed_value.into(),
post_blank: None,
},
))
}
@@ -103,12 +91,13 @@ where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, mut kw) = filtered_keyword(regular_keyword_key)(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
kw.affiliated_keywords =
parse_affiliated_keywords(context.get_global_settings(), affiliated_keywords);
kw.source = Into::<&str>::into(source);
kw.post_blank = post_blank.map(Into::<&str>::into);
Ok((remaining, kw))
}
@@ -150,45 +139,43 @@ fn regular_keyword_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((
recognize(tuple((dual_affiliated_key, tag("["), optval, tag("]")))),
plain_affiliated_key,
export_keyword,
))(input)
element!(dual_affiliated_key, input);
element!(plain_affiliated_key, input);
element!(export_keyword, input);
Err(nom::Err::Error(CustomError::Static("No affiliated key.")))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn plain_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
for keyword in ORG_ELEMENT_AFFILIATED_KEYWORDS {
let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, ent));
}
Err(_) => {}
let result = map(
tuple((tag_no_case::<_, _, CustomError>(keyword), peek(tag(":")))),
|(key, _)| key,
)(input);
if let Ok((remaining, ent)) = result {
return Ok((remaining, ent));
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoKeywordKey".into(),
))))
Err(nom::Err::Error(CustomError::Static("NoKeywordKey")))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dual_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
for keyword in ORG_ELEMENT_DUAL_KEYWORDS {
let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, ent));
}
Err(_) => {}
let result = recognize(tuple((
tag_no_case::<_, _, CustomError>(keyword),
tag("["),
optval,
tag("]"),
peek(tag(":")),
)))(input);
if let Ok((remaining, ent)) = result {
return Ok((remaining, ent));
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoKeywordKey".into(),
))))
Err(nom::Err::Error(CustomError::Static("NoKeywordKey")))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -216,7 +203,7 @@ fn _optval_end<'s>(
unreachable!("Exceeded optval bracket depth.")
}
if current_depth == 0 {
let close_bracket = tag::<_, _, CustomError<_>>("]")(input);
let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() {
return close_bracket;
}
@@ -231,3 +218,18 @@ fn export_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>
take_while1(|c: char| c.is_alphanumeric() || "-_".contains(c)),
)))(input)
}
#[cfg(test)]
mod tests {
use test::Bencher;
use super::*;
use crate::parser::OrgSource;
#[bench]
fn bench_affiliated_keyword(b: &mut Bencher) {
let input = OrgSource::new("#+CAPTION[*foo*]: bar *baz*");
b.iter(|| assert!(affiliated_keyword(input).is_ok()));
}
}

View File

@@ -19,7 +19,7 @@ use crate::error::Res;
/// Parses the text in the value of a #+TODO keyword.
///
/// Example input: "foo bar baz | lorem ipsum"
pub(crate) fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, Vec<&'s str>)> {
pub(crate) fn todo_keywords(input: &str) -> Res<&str, (Vec<&str>, Vec<&str>)> {
let (remaining, mut before_pipe_words) = separated_list0(space1, todo_keyword_word)(input)?;
let (remaining, after_pipe_words) = opt(tuple((
tuple((space0, tag("|"), space0)),
@@ -30,12 +30,9 @@ pub(crate) fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, V
Ok((remaining, (before_pipe_words, after_pipe_words)))
} else if !before_pipe_words.is_empty() {
// If there was no pipe, then the last word becomes a completion state instead.
let mut after_pipe_words = Vec::with_capacity(1);
after_pipe_words.push(
before_pipe_words
.pop()
.expect("If-statement proves this is Some."),
);
let after_pipe_words = vec![before_pipe_words
.pop()
.expect("If-statement proves this is Some.")];
Ok((remaining, (before_pipe_words, after_pipe_words)))
} else {
// No words founds
@@ -43,7 +40,7 @@ pub(crate) fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, V
}
}
fn todo_keyword_word<'s>(input: &'s str) -> Res<&'s str, &'s str> {
fn todo_keyword_word(input: &str) -> Res<&str, &str> {
let (remaining, keyword) = verify(take_till(|c| "( \t\r\n|".contains(c)), |result: &str| {
!result.is_empty()
})(input)?;

View File

@@ -43,13 +43,8 @@ where
let value_start = remaining;
start_of_line(remaining)?;
let (remaining, _leading_whitespace) = space0(remaining)?;
let (remaining, (_opening, name, _open_close_brace, _ws, _line_ending)) = tuple((
tag_no_case(r#"\begin{"#),
name,
tag("}"),
space0,
line_ending,
))(remaining)?;
let (remaining, (_opening, name, _open_close_brace, _ws, _line_ending)) =
tuple((tag_no_case(r"\begin{"), name, tag("}"), space0, line_ending))(remaining)?;
let latex_environment_end_specialized = latex_environment_end(name.into());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
@@ -62,7 +57,7 @@ where
let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?;
let value_end = remaining;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let value = get_consumed(value_start, value_end);
@@ -75,6 +70,7 @@ where
affiliated_keywords,
),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -127,7 +123,7 @@ fn _latex_environment_end<'b, 'g, 'r, 's, 'c>(
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, _name, _close_brace, _ws, _line_ending)) = tuple((
tag_no_case(r#"\end{"#),
tag_no_case(r"\end{"),
tag_no_case(current_name_lower),
tag("}"),
space0,

View File

@@ -17,7 +17,6 @@ use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
@@ -40,7 +39,7 @@ pub(crate) fn latex_fragment<'b, 'g, 'r, 's>(
parser_with_context!(bordered_dollar_fragment)(context),
))(input)?;
let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -48,6 +47,7 @@ pub(crate) fn latex_fragment<'b, 'g, 'r, 's>(
LatexFragment {
source: source.into(),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -209,9 +209,9 @@ fn pre<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, ()> {
let preceding_character = input.get_preceding_character();
if let Some('$') = preceding_character {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for dollar char fragment.".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for dollar char fragment.",
)));
}
Ok((input, ()))
}
@@ -283,9 +283,9 @@ fn close_border<'b, 'g, 'r, 's>(
match preceding_character {
Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())),
_ => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for dollar char fragment.".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for dollar char fragment.",
)));
}
}
}

View File

@@ -27,7 +27,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::object_parser::standard_set_object;
use crate::parser::util::blank_line;
@@ -76,30 +75,33 @@ where
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters),
None => None,
};
let parameters = parameters.map(|(_ws, parameters)| parameters);
let object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
// Check for a completely empty block
let (remaining, children) = match consumed(many_till(blank_line, exit_matcher))(remaining) {
Ok((remaining, (whitespace, (_children, _exit_contents)))) => (
remaining,
vec![Object::PlainText(PlainText {
source: whitespace.into(),
})],
),
Err(_) => {
let (remaining, (children, _exit_contents)) =
many_till(object_matcher, exit_matcher)(remaining)?;
(remaining, children)
}
};
let (remaining, contents, children) =
match consumed(many_till(blank_line, exit_matcher))(remaining) {
Ok((remaining, (whitespace, (_children, _exit_contents)))) => (
remaining,
whitespace,
if whitespace.len() > 0 {
vec![Object::PlainText(PlainText {
source: whitespace.into(),
})]
} else {
Vec::new()
},
),
Err(_) => {
let (remaining, (contents, (children, _exit_contents))) =
consumed(many_till(object_matcher, exit_matcher))(remaining)?;
(remaining, contents, children)
}
};
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -110,8 +112,10 @@ where
context.get_global_settings(),
affiliated_keywords,
),
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
data: parameters.map(Into::<&str>::into),
children,
contents: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -148,7 +152,7 @@ where
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -160,6 +164,7 @@ where
affiliated_keywords,
),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -206,10 +211,10 @@ where
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let (remaining, contents) = content(&parser_context, remaining)?;
let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
@@ -240,7 +245,8 @@ where
retain_labels,
use_labels,
label_format,
contents,
value: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -280,10 +286,10 @@ where
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let (remaining, contents) = content(&parser_context, remaining)?;
let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -295,8 +301,9 @@ where
affiliated_keywords,
),
export_type: export_type.map(Into::<&str>::into),
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
contents,
data: parameters.map(Into::<&str>::into),
value: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -335,16 +342,16 @@ where
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let (remaining, contents) = content(&parser_context, remaining)?;
let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
if let Some(switches) = switches {
(
if switches.source.len() == 0 {
if switches.source.is_empty() {
None
} else {
Some(switches.source)
@@ -375,7 +382,8 @@ where
retain_labels,
use_labels,
label_format,
contents,
value: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -390,7 +398,7 @@ fn data<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not("\r\n")(input)
}
fn lesser_block_end<'c>(current_name: &'c str) -> impl ContextMatcher + 'c {
fn lesser_block_end(current_name: &str) -> impl ContextMatcher + '_ {
// Since the lesser block names are statically defined in code, we can simply assert that the name is lowercase instead of causing an allocation by converting to lowercase.
debug_assert!(current_name == current_name.to_lowercase());
move |context, input: OrgSource<'_>| _lesser_block_end(context, input, current_name)
@@ -420,7 +428,7 @@ fn _lesser_block_end<'b, 'g, 'r, 's, 'c>(
/// Parser for the beginning of a lesser block
///
/// current_name MUST be lowercase. We do not do the conversion ourselves because it is not allowed in a const fn.
const fn lesser_block_begin<'c>(current_name: &'c str) -> impl ContextMatcher + 'c {
const fn lesser_block_begin(current_name: &str) -> impl ContextMatcher + '_ {
// TODO: Since this is a const fn, is there ANY way to "generate" functions at compile time?
move |context, input: OrgSource<'_>| _lesser_block_begin(context, input, current_name)
}
@@ -531,11 +539,8 @@ fn _example_src_switches<'s>(
(SwitchState::Normal, "-r") => {
saw_r = true;
use_labels = false;
match retain_labels {
RetainLabels::Yes => {
retain_labels = RetainLabels::No;
}
_ => {}
if let RetainLabels::Yes = retain_labels {
retain_labels = RetainLabels::No;
}
}
(SwitchState::Normal, "-l") => {
@@ -610,7 +615,7 @@ fn _example_src_switches<'s>(
}
}
if !matched_a_word {
return Err(nom::Err::Error(CustomError::MyError(MyError("No words."))));
return Err(nom::Err::Error(CustomError::Static("No words.")));
}
let remaining = last_match_remaining;
@@ -657,49 +662,3 @@ fn switch_word<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not(" \t\r\n"),
))(input)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn content<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, String> {
let mut ret = String::new();
let mut remaining = input;
let exit_matcher_parser = parser_with_context!(exit_matcher_parser)(context);
loop {
if exit_matcher_parser(remaining).is_ok() {
break;
}
let (remain, (pre_escape_whitespace, line)) = content_line(remaining)?;
pre_escape_whitespace.map(|val| ret.push_str(Into::<&str>::into(val)));
ret.push_str(line.into());
remaining = remain;
}
Ok((remaining, ret))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn content_line<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (Option<OrgSource<'s>>, OrgSource<'s>)> {
let (remaining, pre_escape_whitespace) = opt(map(
tuple((
recognize(tuple((
space0,
many_till(
tag(","),
peek(tuple((tag(","), alt((tag("#+"), tag("*")))))),
),
))),
tag(","),
)),
|(pre_comma, _)| pre_comma,
))(input)?;
let (remaining, line_post_escape) = recognize(many_till(anychar, line_ending))(remaining)?;
Ok((remaining, (pre_escape_whitespace, line_post_escape)))
}

View File

@@ -7,7 +7,6 @@ use nom::multi::many0;
use super::org_source::OrgSource;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::types::LineBreak;
@@ -21,7 +20,7 @@ pub(crate) fn line_break<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, LineBreak<'s>> {
let (remaining, _) = pre(context, input)?;
let (remaining, _) = tag(r#"\\"#)(remaining)?;
let (remaining, _) = tag(r"\\")(remaining)?;
let (remaining, _) = recognize(many0(one_of(" \t")))(remaining)?;
let (remaining, _) = line_ending(remaining)?;
let source = get_consumed(input, remaining);
@@ -45,9 +44,9 @@ fn pre<'b, 'g, 'r, 's>(
match preceding_character {
// If None, we are at the start of the file
None | Some('\\') => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for line break.".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for line break.",
)));
}
_ => {}
};
@@ -55,9 +54,9 @@ fn pre<'b, 'g, 'r, 's>(
let current_line = input.text_since_line_break();
let is_non_empty_line = current_line.chars().any(|c| !c.is_whitespace());
if !is_non_empty_line {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre line for line break.".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre line for line break.",
)));
}
Ok((input, ()))

View File

@@ -1,6 +1,6 @@
/// Parse an element that has affiliated keywords.
macro_rules! ak_element {
($parser:ident, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr, $wrapper: expr) => {
($parser:expr, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr, $wrapper: expr) => {
if let Ok((remaining, ele)) = $parser(
$affiliated_keywords,
$post_affiliated_keywords_input,
@@ -10,7 +10,7 @@ macro_rules! ak_element {
return Ok((remaining, $wrapper(ele)));
}
};
($parser:ident, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr) => {
($parser:expr, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr) => {
if let Ok((remaining, ele)) = $parser(
$affiliated_keywords,
$post_affiliated_keywords_input,
@@ -25,16 +25,21 @@ macro_rules! ak_element {
pub(crate) use ak_element;
macro_rules! element {
($parser:ident, $context: expr, $input: expr, $wrapper: expr) => {
($parser:expr, $context: expr, $input: expr, $wrapper: expr) => {
if let Ok((remaining, ele)) = $parser($context, $input) {
return Ok((remaining, $wrapper(ele)));
}
};
($parser:ident, $context: expr, $input: expr) => {
($parser:expr, $context: expr, $input: expr) => {
if let Ok((remaining, ele)) = $parser($context, $input) {
return Ok((remaining, ele));
}
};
($parser:expr, $input: expr) => {
if let Ok((remaining, ele)) = $parser($input) {
return Ok((remaining, ele));
}
};
}
pub(crate) use element;

View File

@@ -1,6 +1,7 @@
mod affiliated_keyword;
mod angle_link;
mod babel_call;
mod bullshitium;
mod citation;
mod citation_reference;
mod clock;

View File

@@ -1,14 +1,9 @@
use nom::branch::alt;
use nom::combinator::map;
use super::org_source::OrgSource;
use super::plain_text::plain_text;
use super::regular_link::regular_link;
use super::subscript_and_superscript::detect_subscript_or_superscript;
use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::angle_link::angle_link;
use crate::parser::citation::citation;
@@ -19,6 +14,7 @@ use crate::parser::inline_babel_call::inline_babel_call;
use crate::parser::inline_source_block::inline_source_block;
use crate::parser::latex_fragment::latex_fragment;
use crate::parser::line_break::line_break;
use crate::parser::macros::element;
use crate::parser::org_macro::org_macro;
use crate::parser::plain_link::plain_link;
use crate::parser::radio_link::radio_link;
@@ -39,14 +35,14 @@ pub(crate) fn standard_set_object<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
parser_with_context!(standard_set_object_sans_plain_text)(context),
map(
parser_with_context!(plain_text(detect_standard_set_object_sans_plain_text))(context),
Object::PlainText,
),
))(input)?;
Ok((remaining, object))
element!(standard_set_object_sans_plain_text, context, input);
element!(
plain_text(detect_standard_set_object_sans_plain_text),
context,
input,
Object::PlainText
);
Err(nom::Err::Error(CustomError::Static("No object.")))
}
#[cfg_attr(
@@ -57,14 +53,14 @@ pub(crate) fn minimal_set_object<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
parser_with_context!(minimal_set_object_sans_plain_text)(context),
map(
parser_with_context!(plain_text(detect_minimal_set_object_sans_plain_text))(context),
Object::PlainText,
),
))(input)?;
Ok((remaining, object))
element!(minimal_set_object_sans_plain_text, context, input);
element!(
plain_text(detect_minimal_set_object_sans_plain_text),
context,
input,
Object::PlainText
);
Err(nom::Err::Error(CustomError::Static("No object.")))
}
#[cfg_attr(
@@ -75,56 +71,38 @@ fn standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
map(parser_with_context!(timestamp)(context), Object::Timestamp),
map(parser_with_context!(subscript)(context), Object::Subscript),
map(
parser_with_context!(superscript)(context),
Object::Superscript,
),
map(
parser_with_context!(statistics_cookie)(context),
Object::StatisticsCookie,
),
map(parser_with_context!(target)(context), Object::Target),
map(parser_with_context!(line_break)(context), Object::LineBreak),
map(
parser_with_context!(inline_source_block)(context),
Object::InlineSourceBlock,
),
map(
parser_with_context!(inline_babel_call)(context),
Object::InlineBabelCall,
),
map(parser_with_context!(citation)(context), Object::Citation),
map(
parser_with_context!(footnote_reference)(context),
Object::FootnoteReference,
),
map(
parser_with_context!(export_snippet)(context),
Object::ExportSnippet,
),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
map(parser_with_context!(radio_link)(context), Object::RadioLink),
map(
parser_with_context!(radio_target)(context),
Object::RadioTarget,
),
parser_with_context!(text_markup)(context),
map(
parser_with_context!(regular_link)(context),
Object::RegularLink,
),
map(parser_with_context!(plain_link)(context), Object::PlainLink),
map(parser_with_context!(angle_link)(context), Object::AngleLink),
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
))(input)?;
Ok((remaining, object))
element!(timestamp, context, input, Object::Timestamp);
element!(subscript, context, input, Object::Subscript);
element!(superscript, context, input, Object::Superscript);
element!(statistics_cookie, context, input, Object::StatisticsCookie);
element!(target, context, input, Object::Target);
element!(line_break, context, input, Object::LineBreak);
element!(
inline_source_block,
context,
input,
Object::InlineSourceBlock
);
element!(inline_babel_call, context, input, Object::InlineBabelCall);
element!(citation, context, input, Object::Citation);
element!(
footnote_reference,
context,
input,
Object::FootnoteReference
);
element!(export_snippet, context, input, Object::ExportSnippet);
element!(entity, context, input, Object::Entity);
element!(latex_fragment, context, input, Object::LatexFragment);
element!(radio_link, context, input, Object::RadioLink);
element!(radio_target, context, input, Object::RadioTarget);
element!(text_markup, context, input);
element!(regular_link, context, input, Object::RegularLink);
element!(plain_link, context, input, Object::PlainLink);
element!(angle_link, context, input, Object::AngleLink);
element!(org_macro, context, input, Object::OrgMacro);
Err(nom::Err::Error(CustomError::Static("No object.")))
}
#[cfg_attr(
@@ -135,20 +113,12 @@ fn minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
map(parser_with_context!(subscript)(context), Object::Subscript),
map(
parser_with_context!(superscript)(context),
Object::Superscript,
),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
parser_with_context!(text_markup)(context),
))(input)?;
Ok((remaining, object))
element!(subscript, context, input, Object::Subscript);
element!(superscript, context, input, Object::Superscript);
element!(entity, context, input, Object::Entity);
element!(latex_fragment, context, input, Object::LatexFragment);
element!(text_markup, context, input);
Err(nom::Err::Error(CustomError::Static("No object.")))
}
#[cfg_attr(
@@ -166,9 +136,7 @@ pub(crate) fn detect_standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
return Err(nom::Err::Error(CustomError::Static("No object detected.")));
}
#[cfg_attr(
@@ -186,9 +154,7 @@ fn detect_minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
return Err(nom::Err::Error(CustomError::Static("No object detected.")));
}
#[cfg_attr(
@@ -200,16 +166,18 @@ pub(crate) fn regular_link_description_set_object<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
let (remaining, object) = alt((
parser_with_context!(regular_link_description_set_object_sans_plain_text)(context),
map(
parser_with_context!(plain_text(
detect_regular_link_description_set_object_sans_plain_text
))(context),
Object::PlainText,
),
))(input)?;
Ok((remaining, object))
element!(
regular_link_description_set_object_sans_plain_text,
context,
input
);
element!(
plain_text(detect_regular_link_description_set_object_sans_plain_text),
context,
input,
Object::PlainText
);
Err(nom::Err::Error(CustomError::Static("No object.")))
}
#[cfg_attr(
@@ -221,27 +189,18 @@ fn regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
let (remaining, object) = alt((
map(
parser_with_context!(export_snippet)(context),
Object::ExportSnippet,
),
map(
parser_with_context!(statistics_cookie)(context),
Object::StatisticsCookie,
),
map(
parser_with_context!(inline_source_block)(context),
Object::InlineSourceBlock,
),
map(
parser_with_context!(inline_babel_call)(context),
Object::InlineBabelCall,
),
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
parser_with_context!(minimal_set_object_sans_plain_text)(context),
))(input)?;
Ok((remaining, object))
element!(export_snippet, context, input, Object::ExportSnippet);
element!(statistics_cookie, context, input, Object::StatisticsCookie);
element!(
inline_source_block,
context,
input,
Object::InlineSourceBlock
);
element!(inline_babel_call, context, input, Object::InlineBabelCall);
element!(org_macro, context, input, Object::OrgMacro);
element!(minimal_set_object_sans_plain_text, context, input);
Err(nom::Err::Error(CustomError::Static("No object.")))
}
#[cfg_attr(
@@ -259,9 +218,7 @@ fn detect_regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
Err(nom::Err::Error(CustomError::Static("No object detected.")))
}
#[cfg_attr(
@@ -272,14 +229,14 @@ pub(crate) fn table_cell_set_object<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
parser_with_context!(table_cell_set_object_sans_plain_text)(context),
map(
parser_with_context!(plain_text(detect_table_cell_set_object_sans_plain_text))(context),
Object::PlainText,
),
))(input)?;
Ok((remaining, object))
element!(table_cell_set_object_sans_plain_text, context, input);
element!(
plain_text(detect_table_cell_set_object_sans_plain_text),
context,
input,
Object::PlainText
);
Err(nom::Err::Error(CustomError::Static("No object.")))
}
#[cfg_attr(
@@ -290,33 +247,24 @@ fn table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
map(parser_with_context!(citation)(context), Object::Citation),
map(
parser_with_context!(export_snippet)(context),
Object::ExportSnippet,
),
map(
parser_with_context!(footnote_reference)(context),
Object::FootnoteReference,
),
map(parser_with_context!(radio_link)(context), Object::RadioLink),
map(
parser_with_context!(regular_link)(context),
Object::RegularLink,
),
map(parser_with_context!(plain_link)(context), Object::PlainLink),
map(parser_with_context!(angle_link)(context), Object::AngleLink),
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
map(
parser_with_context!(radio_target)(context),
Object::RadioTarget,
),
map(parser_with_context!(target)(context), Object::Target),
map(parser_with_context!(timestamp)(context), Object::Timestamp),
parser_with_context!(minimal_set_object_sans_plain_text)(context),
))(input)?;
Ok((remaining, object))
element!(citation, context, input, Object::Citation);
element!(export_snippet, context, input, Object::ExportSnippet);
element!(
footnote_reference,
context,
input,
Object::FootnoteReference
);
element!(radio_link, context, input, Object::RadioLink);
element!(regular_link, context, input, Object::RegularLink);
element!(plain_link, context, input, Object::PlainLink);
element!(angle_link, context, input, Object::AngleLink);
element!(org_macro, context, input, Object::OrgMacro);
element!(radio_target, context, input, Object::RadioTarget);
element!(target, context, input, Object::Target);
element!(timestamp, context, input, Object::Timestamp);
element!(minimal_set_object_sans_plain_text, context, input);
Err(nom::Err::Error(CustomError::Static("No object.")))
}
#[cfg_attr(
@@ -334,7 +282,5 @@ fn detect_table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
return Err(nom::Err::Error(CustomError::Static("No object detected.")));
}

View File

@@ -32,7 +32,7 @@ pub(crate) fn org_macro<'b, 'g, 'r, 's>(
let (remaining, macro_args) = opt(parser_with_context!(org_macro_args)(context))(remaining)?;
let (remaining, _) = tag("}}}")(remaining)?;
let macro_value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -47,6 +47,7 @@ pub(crate) fn org_macro<'b, 'g, 'r, 's>(
.map(|arg| arg.into())
.collect(),
value: Into::<&str>::into(macro_value),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -96,7 +97,7 @@ fn org_macro_arg<'b, 'g, 'r, 's>(
loop {
not(parser_with_context!(exit_matcher_parser)(context))(remaining)?;
not(peek(tag("}}}")))(remaining)?;
if peek(tuple((space0::<OrgSource<'_>, CustomError<_>>, tag(")"))))(remaining).is_ok() {
if peek(tuple((space0::<_, CustomError>, tag(")"))))(remaining).is_ok() {
break;
}
@@ -108,7 +109,7 @@ fn org_macro_arg<'b, 'g, 'r, 's>(
}
if next_char == '\\' {
escaping = true;
if peek(tag::<_, _, CustomError<_>>(")"))(new_remaining).is_ok() {
if peek(tag::<_, _, CustomError>(")"))(new_remaining).is_ok() {
// Special case for backslash at the end of a macro
remaining = new_remaining;
break;

View File

@@ -10,12 +10,9 @@ use nom::InputTakeAtPosition;
use nom::Offset;
use nom::Slice;
use crate::error::CustomError;
use crate::error::MyError;
pub(crate) type BracketDepth = i16;
#[derive(Copy, Clone, PartialEq)]
#[derive(Copy, Clone)]
pub(crate) struct OrgSource<'s> {
full_source: &'s str,
start: usize,
@@ -85,6 +82,15 @@ impl<'s> OrgSource<'s> {
self.slice(..(other.end - self.start))
}
pub(crate) fn get_until_end_of_str(&self, other: &'s str) -> OrgSource<'s> {
let full_source_start = self.full_source.as_ptr() as usize;
let other_start = other.as_ptr() as usize - full_source_start;
let other_end = other_start + other.len();
debug_assert!(other_start >= self.start);
debug_assert!(other_end <= self.end);
self.slice(..(other_end - self.start))
}
pub(crate) fn get_start_of_line(&self) -> OrgSource<'s> {
let skipped_text = self.text_since_line_break();
let mut bracket_depth = self.bracket_depth;
@@ -219,7 +225,7 @@ where
panic!("Attempted to extend past the end of the WrappedInput.")
}
if new_start == self.start && new_end == self.end {
return self.clone();
return *self;
}
let skipped_text = &self.full_source[self.start..new_start];
@@ -337,7 +343,7 @@ impl<'s> InputTakeAtPosition for OrgSource<'s> {
P: Fn(Self::Item) -> bool,
{
match Into::<&str>::into(self).position(predicate) {
Some(0) => Err(nom::Err::Error(E::from_error_kind(self.clone(), e))),
Some(0) => Err(nom::Err::Error(E::from_error_kind(*self, e))),
Some(idx) => Ok(self.take_split(idx)),
None => Err(nom::Err::Incomplete(nom::Needed::new(1))),
}
@@ -366,11 +372,11 @@ impl<'s> InputTakeAtPosition for OrgSource<'s> {
{
let window = Into::<&str>::into(self);
match window.position(predicate) {
Some(0) => Err(nom::Err::Error(E::from_error_kind(self.clone(), e))),
Some(0) => Err(nom::Err::Error(E::from_error_kind(*self, e))),
Some(n) => Ok(self.take_split(n)),
None => {
if window.input_len() == 0 {
Err(nom::Err::Error(E::from_error_kind(self.clone(), e)))
Err(nom::Err::Error(E::from_error_kind(*self, e)))
} else {
Ok(self.take_split(self.input_len()))
}
@@ -385,33 +391,6 @@ impl<'n, 's> FindSubstring<&'n str> for OrgSource<'s> {
}
}
pub(crate) fn convert_error<'a, I: Into<CustomError<&'a str>>>(
err: nom::Err<I>,
) -> nom::Err<CustomError<&'a str>> {
match err {
nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed),
nom::Err::Error(err) => nom::Err::Error(err.into()),
nom::Err::Failure(err) => nom::Err::Failure(err.into()),
}
}
impl<'s> From<CustomError<OrgSource<'s>>> for CustomError<&'s str> {
fn from(value: CustomError<OrgSource<'s>>) -> Self {
match value {
CustomError::MyError(err) => CustomError::MyError(err.into()),
CustomError::Nom(input, error_kind) => CustomError::Nom(input.into(), error_kind),
CustomError::IO(err) => CustomError::IO(err),
CustomError::BoxedError(err) => CustomError::BoxedError(err),
}
}
}
impl<'s> From<MyError<OrgSource<'s>>> for MyError<&'s str> {
fn from(value: MyError<OrgSource<'s>>) -> Self {
MyError(value.0.into())
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -1,5 +1,8 @@
use nom::branch::alt;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many1;
@@ -12,6 +15,7 @@ use super::org_source::OrgSource;
use super::util::blank_line;
use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
@@ -45,14 +49,14 @@ where
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) = verify(
let (remaining, (contents, (children, _exit_contents))) = consumed(verify(
many_till(standard_set_object_matcher, exit_matcher),
|(children, _exit_contents)| !children.is_empty(),
)(remaining)?;
))(remaining)?;
// Not checking parent exit matcher because if there are any children matched then we have a valid paragraph.
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -60,6 +64,8 @@ where
remaining,
Paragraph {
source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
@@ -69,6 +75,57 @@ where
))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn empty_paragraph<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
// If it is just a single newline then source, contents, and post-blank are "\n".
// If it has multiple newlines then contents is the first "\n" and post-blank is all the new lines.
// If there are any spaces on the first line then post-blank excludes the first line.
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, first_line_with_spaces) =
opt(recognize(tuple((space1, org_line_ending))))(input)?;
let post_blank_begin = remaining;
if let Some(first_line_with_spaces) = first_line_with_spaces {
let (remaining, _additional_lines) =
recognize(many_till(blank_line, exit_matcher))(remaining)?;
let post_blank = get_consumed(post_blank_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
Paragraph::of_text(
Into::<&str>::into(source),
Into::<&str>::into(first_line_with_spaces),
Some(Into::<&str>::into(first_line_with_spaces)),
Some(Into::<&str>::into(post_blank)),
),
))
} else {
let (remaining, first_line) = blank_line(remaining)?;
let (remaining, _additional_lines) =
recognize(many_till(blank_line, exit_matcher))(remaining)?;
let post_blank = get_consumed(post_blank_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
Paragraph::of_text(
Into::<&str>::into(source),
Into::<&str>::into(first_line),
Some(Into::<&str>::into(first_line)),
Some(Into::<&str>::into(post_blank)),
),
))
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
@@ -89,14 +146,15 @@ fn paragraph_end<'b, 'g, 'r, 's>(
#[cfg(test)]
mod tests {
use crate::context::parser_with_context;
use crate::context::bind_context;
use crate::context::Context;
use crate::context::ContextElement;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::parser::element_parser::element;
use crate::parser::org_source::OrgSource;
use crate::types::GetStandardProperties;
use crate::parser::paragraph::empty_paragraph;
use crate::types::StandardProperties;
#[test]
fn two_paragraphs() {
@@ -104,18 +162,25 @@ mod tests {
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
let paragraph_matcher = bind_context!(element(true), &initial_context);
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
let (remaining, second_paragraph) =
paragraph_matcher(remaining).expect("Parse second paragraph.");
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(
first_paragraph.get_standard_properties().get_source(),
"foo bar baz\n\n"
);
assert_eq!(
second_paragraph.get_standard_properties().get_source(),
"lorem ipsum"
);
assert_eq!(first_paragraph.get_source(), "foo bar baz\n\n");
assert_eq!(second_paragraph.get_source(), "lorem ipsum");
}
#[test]
fn paragraph_whitespace() {
let input = OrgSource::new("\n");
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let paragraph_matcher = bind_context!(empty_paragraph, &initial_context);
let (remaining, paragraph) = paragraph_matcher(input).expect("Parse paragraph");
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(paragraph.get_source(), "\n");
assert_eq!(paragraph.get_contents(), Some("\n"));
}
}

View File

@@ -36,7 +36,6 @@ use crate::context::ExitMatcherNode;
use crate::context::Matcher;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
@@ -55,7 +54,7 @@ pub(crate) fn plain_link<'b, 'g, 'r, 's>(
let (remaining, _) = pre(context, input)?;
let (remaining, path_plain) = parse_path_plain(context, remaining)?;
peek(parser_with_context!(post)(context))(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -67,6 +66,7 @@ pub(crate) fn plain_link<'b, 'g, 'r, 's>(
raw_link: path_plain.raw_link,
search_option: path_plain.search_option,
application: path_plain.application,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -95,9 +95,9 @@ fn pre<'b, 'g, 'r, 's>(
Some(x) if !WORD_CONSTITUENT_CHARACTERS.contains(x) => {}
Some(_) => {
// Not at start of line, cannot be a heading
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for plain link.".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for plain link.",
)));
}
};
Ok((input, ()))
@@ -262,18 +262,13 @@ pub(crate) fn protocol<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
for link_parameter in context.get_global_settings().link_parameters {
let result = tag_no_case::<_, _, CustomError<_>>(*link_parameter)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, ent));
}
Err(_) => {}
let result = tag_no_case::<_, _, CustomError>(*link_parameter)(input);
if let Ok((remaining, ent)) = result {
return Ok((remaining, ent));
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoLinkProtocol".into(),
))))
Err(nom::Err::Error(CustomError::Static("NoLinkProtocol")))
}
#[cfg_attr(
@@ -330,8 +325,7 @@ fn impl_path_plain_end<'b, 'g, 'r, 's>(
}
if current_depth == 0 {
let close_parenthesis =
tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(remaining);
let close_parenthesis = tag::<_, _, CustomError>(")")(remaining);
if close_parenthesis.is_ok() {
return close_parenthesis;
}
@@ -345,9 +339,7 @@ fn impl_path_plain_end<'b, 'g, 'r, 's>(
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"No path plain end".into(),
))))
Err(nom::Err::Error(CustomError::Static("No path plain end")))
}
#[cfg_attr(
@@ -423,18 +415,18 @@ fn _path_plain_parenthesis_end<'s>(
unreachable!("Exceeded plain link parenthesis depth.")
}
if current_depth == 0 {
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input);
let close_parenthesis = tag::<_, _, CustomError>(")")(input);
if close_parenthesis.is_ok() {
return close_parenthesis;
}
}
if current_depth == 1 {
let open_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("(")(input);
let open_parenthesis = tag::<_, _, CustomError>("(")(input);
if open_parenthesis.is_ok() {
return open_parenthesis;
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"No closing parenthesis".into(),
))))
Err(nom::Err::Error(CustomError::Static(
"No closing parenthesis",
)))
}

View File

@@ -3,9 +3,11 @@ use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::digit1;
use nom::character::complete::line_ending;
use nom::character::complete::multispace1;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::not;
@@ -17,15 +19,16 @@ use nom::multi::many0;
use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
use nom::InputTake;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::element_parser::element;
use super::keyword::affiliated_keyword;
use super::object_parser::standard_set_object;
use super::org_source::OrgSource;
use super::util::include_input;
use super::util::indentation_level;
use super::util::non_whitespace_character;
use crate::context::bind_context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
@@ -33,7 +36,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
@@ -53,13 +55,17 @@ use crate::types::PlainListType;
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
tracing::instrument(ret, level = "debug", skip(context, _affiliated_keywords))
)]
pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
pub(crate) fn detect_plain_list<'b, 'g, 'r, 's, AK>(
_affiliated_keywords: AK,
remaining: OrgSource<'s>,
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, ()>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
if verify(
tuple((
start_of_line,
@@ -68,16 +74,54 @@ pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
alt((space1, line_ending, eof)),
)),
|(_start, (indent_level, _), (_bullet_type, bull), _after_whitespace)| {
!Into::<&str>::into(bull).starts_with("*") || *indent_level > 0
!Into::<&str>::into(bull).starts_with('*') || *indent_level > 0
},
)(input)
)(remaining)
.is_ok()
{
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No element detected.".into(),
))));
return Err(nom::Err::Error(CustomError::Static("No element detected.")));
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn detect_not_plain_list_item_indent<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (u16, OrgSource<'s>)> {
if let Ok((_remaining, (_, indent, _))) = tuple((
start_of_line,
parser_with_context!(indentation_level)(context),
not(tuple((
parser_with_context!(bullet)(context),
alt((space1, line_ending, eof)),
))),
))(input)
{
return Ok((input, indent));
}
// Headlines are not plain list items.
if let Ok((_remaining, (_, indent, _))) = verify(
tuple((
start_of_line,
parser_with_context!(indentation_level)(context),
tuple((
parser_with_context!(bullet)(context),
alt((space1, line_ending, eof)),
)),
)),
|(_, (depth, _), ((_, bullet), _))| {
*depth == 0 && Into::<&str>::into(bullet).starts_with('*')
},
)(input)
{
return Ok((input, indent));
}
return Err(nom::Err::Error(CustomError::Static("No element detected.")));
}
#[cfg_attr(
@@ -109,6 +153,7 @@ where
let mut children = Vec::new();
let mut first_item_indentation: Option<IndentationLevel> = None;
let mut first_item_list_type: Option<PlainListType> = None;
let contents_begin = remaining;
let mut remaining = remaining;
// The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here:
@@ -120,7 +165,7 @@ where
// While #3 is the most slow, it also seems to cleanest and involves the least manual mutation of already-parsed objects so I am going with #3 for now, but we should revisit #1 or #2 when the parser is more developed.
loop {
let list_item = parser_with_context!(plain_list_item)(&parser_context)(remaining);
let list_item = plain_list_item(&parser_context, remaining);
match (&first_item_list_type, &list_item) {
(None, Ok((_remain, (list_type, _item)))) => {
let _ = first_item_list_type.insert(*list_type);
@@ -140,27 +185,20 @@ where
}
};
let maybe_exit = parser_with_context!(exit_matcher_parser)(&parser_context)(remaining);
let maybe_exit = exit_matcher_parser(&parser_context, remaining);
if maybe_exit.is_ok() {
break;
}
}
let (final_child_start, _final_item_first_parse) = match children.pop() {
Some(final_child) => final_child,
None => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Plain lists require at least one element.".into(),
))));
}
};
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
let final_item_context = parser_context.with_additional_node(&final_item_context);
let (remaining, (_, reparsed_final_item)) =
parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?;
children.push((final_child_start, reparsed_final_item));
if children.is_empty() {
return Err(nom::Err::Error(CustomError::Static(
"Plain lists require at least one element.",
)));
}
let (remaining, _trailing_ws) =
let contents = get_consumed(contents_begin, remaining);
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -173,6 +211,8 @@ where
),
list_type: first_item_list_type.expect("Plain lists require at least one element."),
children: children.into_iter().map(|(_start, item)| item).collect(),
contents: Some(Into::<&str>::into(contents)),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -187,10 +227,10 @@ fn plain_list_item<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, (PlainListType, PlainListItem<'s>)> {
start_of_line(input)?;
let (remaining, (indent_level, _leading_whitespace)) = indentation_level(context, input)?;
let (remaining, (bullet_type, bull)) = verify(
parser_with_context!(bullet)(context),
|(_bullet_type, bull)| !Into::<&str>::into(bull).starts_with("*") || indent_level > 0,
)(remaining)?;
let (remaining, (bullet_type, bull)) =
verify(bind_context!(bullet, context), |(_bullet_type, bull)| {
!Into::<&str>::into(bull).starts_with('*') || indent_level > 0
})(remaining)?;
let (remaining, maybe_counter_set) =
opt(tuple((space1, tag("[@"), counter_set_value, tag("]"))))(remaining)?;
@@ -199,7 +239,7 @@ fn plain_list_item<'b, 'g, 'r, 's>(
let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?;
let (remaining, maybe_tag) = if let BulletType::Unordered = bullet_type {
opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)?
opt(tuple((space1, bind_context!(item_tag, context))))(remaining)?
} else {
(remaining, None)
};
@@ -211,6 +251,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
};
let exit_matcher = plain_list_item_end(indent_level);
let final_item_whitespace_cutoff = final_item_whitespace_cutoff(indent_level);
let final_whitespace_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &final_item_whitespace_cutoff,
});
let final_whitespace_context = context.with_additional_node(&final_whitespace_context);
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::ExitMatcherNode(ExitMatcherNode {
@@ -218,67 +264,62 @@ fn plain_list_item<'b, 'g, 'r, 's>(
exit_matcher: &exit_matcher,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = final_whitespace_context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let maybe_contentless_item: Res<OrgSource<'_>, ()> = peek(parser_with_context!(
detect_contentless_item_contents
)(&parser_context))(remaining);
match maybe_contentless_item {
Ok((_rem, _ws)) => {
let (remaining, _trailing_ws) = if context.should_consume_trailing_whitespace() {
recognize(alt((recognize(many1(blank_line)), eof)))(remaining)?
} else {
recognize(alt((blank_line, eof)))(remaining)?
};
let source = get_consumed(input, remaining);
return Ok((
remaining,
(
list_type,
PlainListItem {
source: source.into(),
indentation: indent_level,
bullet: bull.into(),
counter: maybe_counter_set,
checkbox: None,
tag: maybe_tag
.map(|(_ws, item_tag)| item_tag)
.unwrap_or(Vec::new()),
pre_blank: 0,
children: Vec::new(),
let maybe_contentless_item: Res<OrgSource<'_>, ()> =
detect_contentless_item_contents(&parser_context, remaining);
if let Ok((_rem, _ws)) = maybe_contentless_item {
let (remaining, post_blank) = if tuple((
blank_line,
bind_context!(final_item_whitespace_cutoff, context),
))(remaining)
.is_ok()
{
recognize(alt((blank_line, eof)))(remaining)?
} else {
recognize(alt((recognize(many1(blank_line)), eof)))(remaining)?
};
let source = get_consumed(input, remaining);
return Ok((
remaining,
(
list_type,
PlainListItem {
source: source.into(),
indentation: indent_level,
bullet: bull.into(),
counter: maybe_counter_set,
checkbox: None,
tag: maybe_tag
.map(|(_ws, item_tag)| item_tag)
.unwrap_or(Vec::new()),
pre_blank: 0,
children: Vec::new(),
contents: None,
post_blank: if post_blank.len() > 0 {
Some(Into::<&str>::into(post_blank))
} else {
None
},
),
));
}
Err(_) => {}
};
},
),
));
}
let (remaining, pre_blank) = item_tag_post_gap(&parser_context, remaining)?;
let pre_blank = Into::<&str>::into(pre_blank)
.bytes()
.filter(|b| *b == b'\n')
.count();
let (mut remaining, (mut children, _exit_contents)) = many_till(
include_input(parser_with_context!(element(true))(&parser_context)),
parser_with_context!(exit_matcher_parser)(&parser_context),
)(remaining)?;
let (remaining, (contents, (children, _exit_contents))) = consumed(many_till(
include_input(bind_context!(element(true), &parser_context)),
bind_context!(exit_matcher_parser, &parser_context),
))(remaining)?;
if !children.is_empty() && !context.should_consume_trailing_whitespace() {
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
let final_item_context = parser_context.with_additional_node(&final_item_context);
let (final_child_start, _original_final_child) = children
.pop()
.expect("if-statement already checked that children was non-empty.");
let (remain, reparsed_final_element) = include_input(parser_with_context!(element(true))(
&final_item_context,
))(final_child_start)?;
remaining = remain;
children.push(reparsed_final_element);
}
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
// We have to use the parser_context here to include the whitespace cut-off
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(&final_whitespace_context, remaining)?;
let source = get_consumed(input, remaining);
return Ok((
@@ -299,6 +340,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
pre_blank: PlainListItemPreBlank::try_from(pre_blank)
.expect("pre-blank cannot be larger than 2."),
children: children.into_iter().map(|(_start, item)| item).collect(),
contents: if contents.len() > 0 {
Some(contents.into())
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
},
),
));
@@ -325,7 +372,7 @@ fn bullet<'b, 'g, 'r, 's>(
map(tag("+"), |bull| (BulletType::Unordered, bull)),
map(
recognize(tuple((
parser_with_context!(counter)(context),
bind_context!(counter, context),
alt((tag("."), tag(")"))),
))),
|bull| (BulletType::Ordered, bull),
@@ -380,6 +427,52 @@ fn counter_set_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PlainListIt
))(input)
}
const fn final_item_whitespace_cutoff(indent_level: IndentationLevel) -> impl ContextMatcher {
move |context, input: OrgSource<'_>| {
impl_final_item_whitespace_cutoff(context, input, indent_level)
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
fn impl_final_item_whitespace_cutoff<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
indent_level: IndentationLevel,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
// element!(plain_list_end, context, input);
if let Ok((_remaining, _)) = verify(
tuple((
opt(blank_line),
bind_context!(indentation_level, context),
not(multispace1),
)),
|(_, (depth, _stars), _not_whitespace)| *depth < indent_level,
)(input)
{
return Ok((input, input.take(0)));
}
if let Ok((_remaining, _)) = tuple((
opt(blank_line),
verify(
bind_context!(detect_not_plain_list_item_indent, context),
|(depth, _)| *depth == indent_level,
),
))(input)
{
return Ok((input, input.take(0)));
}
Err(nom::Err::Error(CustomError::Static(
"No whitespace cut-off.",
)))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))
@@ -415,7 +508,7 @@ fn _plain_list_item_end<'b, 'g, 'r, 's>(
start_of_line(input)?;
recognize(tuple((
opt(blank_line),
parser_with_context!(line_indented_lte_matcher)(context),
bind_context!(line_indented_lte_matcher, context),
)))(input)
}
@@ -434,7 +527,7 @@ fn _line_indented_lte<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let matched = recognize(verify(
tuple((
parser_with_context!(indentation_level)(context),
bind_context!(indentation_level, context),
non_whitespace_character,
)),
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
@@ -460,8 +553,8 @@ fn item_tag<'b, 'g, 'r, 's>(
let (remaining, (children, _exit_contents)) = verify(
many_till(
// TODO: Should this be using a different set like the minimal set?
parser_with_context!(standard_set_object)(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context),
bind_context!(standard_set_object, &parser_context),
bind_context!(exit_matcher_parser, &parser_context),
),
|(children, _exit_contents)| !children.is_empty(),
)(input)?;
@@ -511,7 +604,7 @@ fn item_tag_post_gap<'b, 'g, 'r, 's>(
alt((
peek(recognize(not(blank_line))),
peek(recognize(tuple((many0(blank_line), eof)))),
parser_with_context!(exit_matcher_parser)(context),
bind_context!(exit_matcher_parser, context),
)),
),
))),
@@ -541,7 +634,7 @@ fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, ()> {
let (remaining, _) = recognize(many_till(
blank_line,
parser_with_context!(exit_matcher_parser)(context),
bind_context!(exit_matcher_parser, context),
))(input)?;
Ok((remaining, ()))
}
@@ -549,10 +642,11 @@ fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
#[cfg(test)]
mod tests {
use super::*;
use crate::context::bind_context;
use crate::context::Context;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::types::GetStandardProperties;
use crate::types::StandardProperties;
#[test]
fn plain_list_item_empty() {
@@ -560,10 +654,10 @@ mod tests {
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context);
let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context);
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1.");
assert_eq!(result.get_source(), "1.");
}
#[test]
@@ -572,10 +666,10 @@ mod tests {
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context);
let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context);
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1. foo");
assert_eq!(result.get_source(), "1. foo");
}
#[test]
@@ -587,7 +681,7 @@ mod tests {
let (remaining, result) =
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1.");
assert_eq!(result.get_source(), "1.");
}
#[test]
@@ -599,7 +693,7 @@ mod tests {
let (remaining, result) =
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1. foo");
assert_eq!(result.get_source(), "1. foo");
}
#[test]
@@ -639,12 +733,12 @@ mod tests {
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let plain_list_matcher = parser_with_context!(element(true))(&initial_context);
let plain_list_matcher = bind_context!(element(true), &initial_context);
let (remaining, result) =
plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(Into::<&str>::into(remaining), " ipsum\n");
assert_eq!(
result.get_standard_properties().get_source(),
result.get_source(),
r#"1. foo
2. bar
baz
@@ -667,12 +761,12 @@ baz"#,
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let plain_list_matcher = parser_with_context!(element(true))(&initial_context);
let plain_list_matcher = bind_context!(element(true), &initial_context);
let (remaining, result) =
plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(Into::<&str>::into(remaining), "baz");
assert_eq!(
result.get_standard_properties().get_source(),
result.get_source(),
r#"1. foo
1. bar
@@ -700,12 +794,12 @@ dolar"#,
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let plain_list_matcher = parser_with_context!(element(true))(&initial_context);
let plain_list_matcher = bind_context!(element(true), &initial_context);
let (remaining, result) =
plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(Into::<&str>::into(remaining), "dolar");
assert_eq!(
result.get_standard_properties().get_source(),
result.get_source(),
r#"1. foo
bar
@@ -730,7 +824,7 @@ dolar"#,
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let result = detect_plain_list(&initial_context, input);
let result = detect_plain_list(std::iter::empty(), input, &initial_context, input);
assert!(result.is_ok());
}
@@ -740,7 +834,7 @@ dolar"#,
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let result = detect_plain_list(&initial_context, input);
let result = detect_plain_list(std::iter::empty(), input, &initial_context, input);
assert!(result.is_ok());
}
@@ -750,7 +844,7 @@ dolar"#,
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let result = detect_plain_list(&initial_context, input);
let result = detect_plain_list(std::iter::empty(), input, &initial_context, input);
// Since there is no whitespace after the '+' this is a paragraph, not a plain list.
assert!(result.is_err());
}
@@ -761,7 +855,7 @@ dolar"#,
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let result = detect_plain_list(&initial_context, input);
let result = detect_plain_list(std::iter::empty(), input, &initial_context, input);
assert!(result.is_ok());
}
}

View File

@@ -20,7 +20,6 @@ use super::util::org_space_or_line_ending;
use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::types::Object;
use crate::types::PlainText;
@@ -92,41 +91,35 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
break;
}
let is_not_whitespace = is_not::<&str, &str, CustomError<_>>(" \t\r\n")(goal);
match is_not_whitespace {
Ok((new_goal, payload)) => {
let (new_remaining, _) = tuple((
tag_no_case(payload),
// TODO: Test to see what the REAL condition is. Checking for not-alphabetic works fine for now, but the real criteria might be something like the plain text exit matcher.
peek(alt((
recognize(verify(anychar, |c| !c.is_alphanumeric())),
eof,
))),
))(remaining)?;
remaining = new_remaining;
goal = new_goal;
continue;
}
Err(_) => {}
};
let is_not_whitespace = is_not::<_, _, CustomError>(" \t\r\n")(goal);
if let Ok((new_goal, payload)) = is_not_whitespace {
let (new_remaining, _) = tuple((
tag_no_case(payload),
// TODO: Test to see what the REAL condition is. Checking for not-alphabetic works fine for now, but the real criteria might be something like the plain text exit matcher.
peek(alt((
recognize(verify(anychar, |c| !c.is_alphanumeric())),
eof,
))),
))(remaining)?;
remaining = new_remaining;
goal = new_goal;
continue;
}
let is_whitespace = recognize(many1(alt((
recognize(one_of::<&str, &str, CustomError<_>>(" \t")),
recognize(one_of::<_, _, CustomError>(" \t")),
line_ending,
))))(goal);
match is_whitespace {
Ok((new_goal, _)) => {
let (new_remaining, _) = many1(org_space_or_line_ending)(remaining)?;
remaining = new_remaining;
goal = new_goal;
continue;
}
Err(_) => {}
};
if let Ok((new_goal, _)) = is_whitespace {
let (new_remaining, _) = many1(org_space_or_line_ending)(remaining)?;
remaining = new_remaining;
goal = new_goal;
continue;
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Target does not match.".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Target does not match.",
)));
}
let source = get_consumed(input, remaining);
@@ -144,12 +137,13 @@ mod tests {
use nom::combinator::map;
use super::*;
use crate::context::bind_context;
use crate::context::Context;
use crate::context::ContextElement;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::parser::object_parser::detect_standard_set_object_sans_plain_text;
use crate::types::GetStandardProperties;
use crate::types::StandardProperties;
#[test]
fn plain_text_simple() {
@@ -157,14 +151,15 @@ mod tests {
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let plain_text_matcher = parser_with_context!(plain_text(
detect_standard_set_object_sans_plain_text
))(&initial_context);
let (remaining, result) = map(plain_text_matcher, Object::PlainText)(input).unwrap();
let (remaining, result) = map(
bind_context!(
plain_text(detect_standard_set_object_sans_plain_text),
&initial_context
),
Object::PlainText,
)(input)
.unwrap();
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(
result.get_standard_properties().get_source(),
Into::<&str>::into(input)
);
assert_eq!(result.get_source(), Into::<&str>::into(input));
}
}

View File

@@ -33,7 +33,7 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
many1(parser_with_context!(planning_parameter)(context))(remaining)?;
let (remaining, _trailing_ws) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -62,6 +62,7 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
scheduled,
deadline,
closed,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -6,6 +6,7 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::opt;
use nom::combinator::recognize;
@@ -21,7 +22,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
@@ -39,9 +39,9 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PropertyDrawer<'s>> {
if immediate_in_section(context, "property-drawer") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element",
)));
}
let (
remaining,
@@ -65,14 +65,11 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let node_property_matcher = parser_with_context!(node_property)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) =
many_till(node_property_matcher, exit_matcher)(remaining)?;
let (remaining, (contents, children)) =
consumed(parser_with_context!(children)(&parser_context))(remaining)?;
let (remaining, _end) = property_drawer_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -81,10 +78,31 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
PropertyDrawer {
source: source.into(),
children,
contents: if contents.len() > 0 {
Some(contents.into())
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
fn children<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<NodeProperty<'s>>> {
let node_property_matcher = parser_with_context!(node_property)(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, (children, _exit_contents)) =
many_till(node_property_matcher, exit_matcher)(input)?;
Ok((remaining, children))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))

View File

@@ -21,7 +21,6 @@ use crate::context::ExitMatcherNode;
use crate::context::List;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::types::Object;
@@ -40,7 +39,7 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>(
let rematched_target = rematch_target(context, radio_target, input);
if let Ok((remaining, rematched_target)) = rematched_target {
let path = get_consumed(input, remaining);
let (remaining, _) = space0(remaining)?;
let (remaining, post_blank) = space0(remaining)?;
let source = get_consumed(input, remaining);
return Ok((
remaining,
@@ -48,13 +47,16 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>(
source: source.into(),
children: rematched_target,
path: path.into(),
post_blank: if post_blank.len() > 0 {
Some(Into::<&str>::into(post_blank))
} else {
None
},
},
));
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoRadioLink".into(),
))))
Err(nom::Err::Error(CustomError::Static("NoRadioLink")))
}
#[cfg_attr(
@@ -98,9 +100,9 @@ pub(crate) fn rematch_target<'x, 'b, 'g, 'r, 's>(
new_matches.push(new_match);
}
_ => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"OnlyMinimalSetObjectsAllowed".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"OnlyMinimalSetObjectsAllowed",
)));
}
};
}
@@ -137,7 +139,7 @@ pub(crate) fn radio_target<'b, 'g, 'r, 's>(
))(remaining)?;
let (remaining, _closing) = tag(">>>")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -145,6 +147,7 @@ pub(crate) fn radio_target<'b, 'g, 'r, 's>(
RadioTarget {
source: source.into(),
value: raw_value.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))
@@ -178,85 +181,96 @@ mod tests {
use crate::parser::element_parser::element;
use crate::types::Bold;
use crate::types::Element;
use crate::types::GetStandardProperties;
use crate::types::PlainText;
use crate::types::StandardProperties;
#[test]
fn plain_text_radio_target() {
fn plain_text_radio_target() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("foo bar baz");
let radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })];
let global_settings = {
let mut global_settings = GlobalSettings::default();
global_settings.radio_targets = vec![&radio_target_match];
global_settings
let global_settings = GlobalSettings {
radio_targets: vec![&radio_target_match],
..Default::default()
};
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
let (remaining, first_paragraph) =
element(true)(&initial_context, input).expect("Parse first paragraph");
let first_paragraph = match first_paragraph {
Element::Paragraph(paragraph) => paragraph,
_ => panic!("Should be a paragraph!"),
};
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(
first_paragraph.get_standard_properties().get_source(),
"foo bar baz"
);
assert_eq!(first_paragraph.get_source(), "foo bar baz");
assert_eq!(first_paragraph.children.len(), 3);
assert_eq!(
first_paragraph
.children
.get(1)
.expect("Len already asserted to be 3"),
&Object::RadioLink(RadioLink {
source: "bar ",
children: vec![Object::PlainText(PlainText { source: "bar" })],
path: "bar".into()
})
);
match first_paragraph
.children
.get(1)
.expect("Len already asserted to be 3.")
{
Object::RadioLink(inner) => {
assert_eq!(inner.get_source(), "bar ");
assert_eq!(inner.path, "bar");
assert_eq!(inner.children.len(), 1);
let child = inner
.children
.first()
.expect("Length already asserted to be 1.");
assert!(matches!(child, Object::PlainText(_)));
assert_eq!(child.get_source(), "bar");
}
_ => {
return Err("Child should be a radio link.".into());
}
};
Ok(())
}
#[test]
fn bold_radio_target() {
fn bold_radio_target() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("foo *bar* baz");
let radio_target_match = vec![Object::Bold(Bold {
source: "*bar*",
contents: "bar",
post_blank: Some(" "),
children: vec![Object::PlainText(PlainText { source: "bar" })],
})];
let global_settings = {
let mut global_settings = GlobalSettings::default();
global_settings.radio_targets = vec![&radio_target_match];
global_settings
let global_settings = GlobalSettings {
radio_targets: vec![&radio_target_match],
..Default::default()
};
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_paragraph) =
paragraph_matcher(input.into()).expect("Parse first paragraph");
element(true)(&initial_context, input).expect("Parse first paragraph");
let first_paragraph = match first_paragraph {
Element::Paragraph(paragraph) => paragraph,
_ => panic!("Should be a paragraph!"),
};
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(
first_paragraph.get_standard_properties().get_source(),
"foo *bar* baz"
);
assert_eq!(first_paragraph.get_source(), "foo *bar* baz");
assert_eq!(first_paragraph.children.len(), 3);
assert_eq!(
first_paragraph
.children
.get(1)
.expect("Len already asserted to be 3"),
&Object::RadioLink(RadioLink {
source: "*bar* ",
children: vec![Object::Bold(Bold {
source: "*bar* ",
children: vec![Object::PlainText(PlainText { source: "bar" })]
})],
path: "*bar* ".into()
})
);
match first_paragraph
.children
.get(1)
.expect("Len already asserted to be 3.")
{
Object::RadioLink(inner) => {
assert_eq!(inner.get_source(), "*bar* ");
assert_eq!(inner.path, "*bar* ");
assert_eq!(inner.children.len(), 1);
let child = inner
.children
.first()
.expect("Length already asserted to be 1.");
assert!(matches!(child, Object::Bold(_)));
assert_eq!(child.get_source(), "*bar* ");
}
_ => {
return Err("Child should be a radio link.".into());
}
};
Ok(())
}
}

View File

@@ -43,7 +43,6 @@ use crate::context::ExitMatcherNode;
use crate::context::List;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::types::LinkType;
use crate::types::Object;
@@ -74,7 +73,7 @@ fn regular_link_without_description<'b, 'g, 'r, 's>(
let (remaining, _opening_bracket) = tag("[[")(input)?;
let (remaining, path) = pathreg(context, remaining)?;
let (remaining, _closing_bracket) = tag("]]")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -85,6 +84,8 @@ fn regular_link_without_description<'b, 'g, 'r, 's>(
path: path.path,
raw_link: path.raw_link,
search_option: path.search_option,
contents: None,
post_blank: post_blank.map(Into::<&str>::into),
children: Vec::new(),
application: path.application,
},
@@ -102,9 +103,10 @@ fn regular_link_with_description<'b, 'g, 'r, 's>(
let (remaining, _opening_bracket) = tag("[[")(input)?;
let (remaining, path) = pathreg(context, remaining)?;
let (remaining, _closing_bracket) = tag("][")(remaining)?;
let (remaining, description) = description(context, remaining)?;
let (remaining, (contents, description)) =
consumed(parser_with_context!(description)(context))(remaining)?;
let (remaining, _closing_bracket) = tag("]]")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -115,6 +117,8 @@ fn regular_link_with_description<'b, 'g, 'r, 's>(
path: path.path,
raw_link: path.raw_link,
search_option: path.search_option,
contents: Some(Into::<&str>::into(contents)),
post_blank: post_blank.map(Into::<&str>::into),
children: description,
application: path.application,
},
@@ -139,14 +143,7 @@ fn pathreg<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PathReg<'s>> {
let (remaining, path) = map_parser(
escaped(
take_till1(|c| match c {
'\\' | '[' | ']' => true,
_ => false,
}),
'\\',
anychar,
),
escaped(take_till1(|c| matches!(c, '\\' | '[' | ']')), '\\', anychar),
parser_with_context!(parse_path_reg)(context),
)(input)?;
Ok((remaining, path))
@@ -170,11 +167,7 @@ fn parse_path_reg<'b, 'g, 'r, 's>(
parser_with_context!(protocol_path_reg)(context),
fuzzy_path_reg,
))(replaced_input)
.map_err(|_| {
nom::Err::Error(CustomError::MyError(MyError(
"No pathreg match after replacement.",
)))
})?;
.map_err(|_| nom::Err::Error(CustomError::Static("No pathreg match after replacement.")))?;
let remaining = input.take(input.len());
let link_type = match link.link_type {
LinkType::Protocol(protocol) => LinkType::Protocol(protocol.into_owned().into()),
@@ -262,11 +255,8 @@ fn apply_link_templates<'b, 'g, 'r, 's>(
};
}
// Handle lingering state
match state {
ParserState::Percent => {
ret.push('%');
}
_ => {}
if let ParserState::Percent = state {
ret.push('%');
}
if !injected_value {
ret.push_str(inject_value);
@@ -493,7 +483,5 @@ fn impl_path_reg_end<'b, 'g, 'r, 's>(
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"No path reg end".into(),
))))
Err(nom::Err::Error(CustomError::Static("No path reg end")))
}

View File

@@ -65,14 +65,14 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
},
)(remaining)?;
comment_and_property_drawer_element.map(|(comment, property_drawer, _ws)| {
if let Some((comment, property_drawer, _ws)) = comment_and_property_drawer_element {
children.insert(0, Element::PropertyDrawer(property_drawer));
comment
.map(Element::Comment)
.map(|ele| children.insert(0, ele));
});
if let Some(ele) = comment.map(Element::Comment) {
children.insert(0, ele);
}
}
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -80,6 +80,7 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
remaining,
Section {
source: source.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))
@@ -121,14 +122,14 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
!children.is_empty() || property_drawer_element.is_some() || planning_element.is_some()
},
)(remaining)?;
property_drawer_element
.map(Element::PropertyDrawer)
.map(|ele| children.insert(0, ele));
planning_element
.map(Element::Planning)
.map(|ele| children.insert(0, ele));
if let Some(ele) = property_drawer_element.map(Element::PropertyDrawer) {
children.insert(0, ele);
}
if let Some(ele) = planning_element.map(Element::Planning) {
children.insert(0, ele)
}
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -136,6 +137,7 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
remaining,
Section {
source: source.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))

View File

@@ -40,7 +40,7 @@ fn percent_statistics_cookie<'b, 'g, 'r, 's>(
tag("%]"),
)))(input)?;
let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -48,6 +48,7 @@ fn percent_statistics_cookie<'b, 'g, 'r, 's>(
StatisticsCookie {
source: source.into(),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -68,7 +69,7 @@ fn fraction_statistics_cookie<'b, 'g, 'r, 's>(
tag("]"),
)))(input)?;
let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -76,6 +77,7 @@ fn fraction_statistics_cookie<'b, 'g, 'r, 's>(
StatisticsCookie {
source: source.into(),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -3,6 +3,7 @@ use nom::bytes::complete::tag;
use nom::bytes::complete::take_while;
use nom::character::complete::anychar;
use nom::character::complete::one_of;
use nom::combinator::consumed;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::opt;
@@ -26,7 +27,6 @@ use crate::context::ExitMatcherNode;
use crate::context::Matcher;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::types::Object;
@@ -39,7 +39,7 @@ pub(crate) fn detect_subscript_or_superscript<'s>(input: OrgSource<'s>) -> Res<O
// This does not have to detect all valid subscript/superscript but all that it detects must be valid.
let (remaining, _) = one_of("_^")(input)?;
pre(input)?;
if tag::<_, _, CustomError<_>>("*")(remaining).is_ok() {
if tag::<_, _, CustomError>("*")(remaining).is_ok() {
return Ok((input, ()));
}
let (remaining, _) = opt(one_of("+-"))(remaining)?;
@@ -55,17 +55,20 @@ pub(crate) fn subscript<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Subscript<'s>> {
// We check for the underscore first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
let (remaining, _) = tag("_")(input)?;
pre(input)?;
let (remaining, body) = script_body(context, remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let (use_brackets, body) = match body {
ScriptBody::Braceless(text) => (false, vec![Object::PlainText(PlainText { source: text })]),
ScriptBody::WithBraces(body) => (true, body),
let (use_brackets, contents, body) = match body {
ScriptBody::Braceless(text) => (
false,
text,
vec![Object::PlainText(PlainText { source: text })],
),
ScriptBody::WithBraces(contents, body) => (true, contents, body),
};
Ok((
@@ -73,6 +76,8 @@ pub(crate) fn subscript<'b, 'g, 'r, 's>(
Subscript {
source: source.into(),
use_brackets,
contents,
post_blank: post_blank.map(Into::<&str>::into),
children: body,
},
))
@@ -90,13 +95,17 @@ pub(crate) fn superscript<'b, 'g, 'r, 's>(
let (remaining, _) = tag("^")(input)?;
pre(input)?;
let (remaining, body) = script_body(context, remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let (use_brackets, body) = match body {
ScriptBody::Braceless(text) => (false, vec![Object::PlainText(PlainText { source: text })]),
ScriptBody::WithBraces(body) => (true, body),
let (use_brackets, contents, body) = match body {
ScriptBody::Braceless(text) => (
false,
text,
vec![Object::PlainText(PlainText { source: text })],
),
ScriptBody::WithBraces(contents, body) => (true, contents, body),
};
Ok((
@@ -104,6 +113,8 @@ pub(crate) fn superscript<'b, 'g, 'r, 's>(
Superscript {
source: source.into(),
use_brackets,
contents,
post_blank: post_blank.map(Into::<&str>::into),
children: body,
},
))
@@ -118,7 +129,7 @@ fn pre<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
#[derive(Debug)]
enum ScriptBody<'s> {
Braceless(&'s str),
WithBraces(Vec<Object<'s>>),
WithBraces(&'s str, Vec<Object<'s>>),
}
#[cfg_attr(
@@ -136,9 +147,10 @@ fn script_body<'b, 'g, 'r, 's>(
map(parser_with_context!(script_alphanum)(context), |body| {
ScriptBody::Braceless(body.into())
}),
map(parser_with_context!(script_with_braces)(context), |body| {
ScriptBody::WithBraces(body.into())
}),
map(
parser_with_context!(script_with_braces)(context),
|(contents, body)| ScriptBody::WithBraces(Into::<&str>::into(contents), body),
),
map(
parser_with_context!(script_with_parenthesis)(context),
|body| ScriptBody::Braceless(body.into()),
@@ -175,7 +187,7 @@ fn script_alphanum<'b, 'g, 'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(verify(anychar, |c| {
c.is_alphanumeric() || r#",.\"#.contains(*c)
c.is_alphanumeric() || r",.\".contains(*c)
}))(input)
}
@@ -183,7 +195,7 @@ fn script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Org
fn end_script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?;
peek(tuple((
take_while(|c| r#",.\"#.contains(c)),
take_while(|c| r",.\".contains(c)),
not(script_alphanum_character),
)))(remaining)?;
Ok((remaining, final_char))
@@ -196,7 +208,7 @@ fn end_script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>,
fn script_with_braces<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
) -> Res<OrgSource<'s>, (OrgSource<'s>, Vec<Object<'s>>)> {
let (remaining, _) = tag("{")(input)?;
let exit_with_depth = script_with_braces_end(remaining.get_brace_depth());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
@@ -205,13 +217,13 @@ fn script_with_braces<'b, 'g, 'r, 's>(
});
let parser_context = context.with_additional_node(&parser_context);
let (remaining, (children, _exit_contents)) = many_till(
let (remaining, (contents, (children, _exit_contents))) = consumed(many_till(
parser_with_context!(standard_set_object)(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context),
)(remaining)?;
))(remaining)?;
let (remaining, _) = tag("}")(remaining)?;
Ok((remaining, children))
Ok((remaining, (contents, children)))
}
fn script_with_braces_end(starting_brace_depth: BracketDepth) -> impl ContextMatcher {
@@ -232,9 +244,9 @@ fn _script_with_braces_end<'b, 'g, 'r, 's>(
let current_depth = input.get_brace_depth() - starting_brace_depth;
if current_depth > 0 {
// Its impossible for the next character to end the subscript or superscript if we're any amount of braces deep
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid end for subscript or superscript.".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Not a valid end for subscript or superscript.",
)));
}
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing brace should end the subscript or superscript.
@@ -282,12 +294,12 @@ fn _script_with_parenthesis_end<'s>(
unreachable!("Exceeded citation key suffix bracket depth.")
}
if current_depth == 0 {
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input);
let close_parenthesis = tag::<_, _, CustomError>(")")(input);
if close_parenthesis.is_ok() {
return close_parenthesis;
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"No script parenthesis end.".into(),
))))
Err(nom::Err::Error(CustomError::Static(
"No script parenthesis end.",
)))
}

View File

@@ -3,6 +3,7 @@ use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
@@ -14,7 +15,6 @@ use nom::multi::many_till;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword;
use super::keyword::table_formula_keyword;
use super::object_parser::table_cell_set_object;
use super::org_source::OrgSource;
@@ -68,13 +68,13 @@ where
let org_mode_table_row_matcher = parser_with_context!(org_mode_table_row)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) =
many_till(org_mode_table_row_matcher, exit_matcher)(remaining)?;
let (remaining, (contents, (children, _exit_contents))) =
consumed(many_till(org_mode_table_row_matcher, exit_matcher))(remaining)?;
let (remaining, formulas) =
many0(parser_with_context!(table_formula_keyword)(context))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -88,14 +88,26 @@ where
),
formulas,
children,
contents: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_table<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
tuple((start_of_line, space0, tag("|")))(input)?;
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
)]
pub(crate) fn detect_table<'b, 'g, 'r, 's, AK>(
_affiliated_keywords: AK,
remaining: OrgSource<'s>,
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
tuple((start_of_line, space0, tag("|")))(remaining)?;
Ok((input, ()))
}
@@ -141,6 +153,7 @@ fn org_mode_table_row_rule<'b, 'g, 'r, 's>(
TableRow {
source: source.into(),
children: Vec::new(),
contents: None,
},
))
}
@@ -155,8 +168,8 @@ fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, TableRow<'s>> {
start_of_line(input)?;
let (remaining, _) = tuple((space0, tag("|")))(input)?;
let (remaining, children) =
many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?;
let (remaining, (contents, children)) =
consumed(many1(parser_with_context!(org_mode_table_cell)(context)))(remaining)?;
let (remaining, _tail) = recognize(tuple((space0, org_line_ending)))(remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -164,6 +177,11 @@ fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
TableRow {
source: source.into(),
children,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
},
))
}
@@ -185,12 +203,12 @@ fn org_mode_table_cell<'b, 'g, 'r, 's>(
parser_with_context!(table_cell_set_object)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, _) = space0(input)?;
let (remaining, (children, _exit_contents)) = verify(
let (remaining, (contents, (children, _exit_contents))) = consumed(verify(
many_till(table_cell_set_object_matcher, exit_matcher),
|(children, exit_contents)| {
!children.is_empty() || Into::<&str>::into(exit_contents).ends_with("|")
!children.is_empty() || Into::<&str>::into(exit_contents).ends_with('|')
},
)(remaining)?;
))(remaining)?;
let (remaining, _tail) = org_mode_table_cell_end(&parser_context, remaining)?;
@@ -201,6 +219,7 @@ fn org_mode_table_cell<'b, 'g, 'r, 's>(
TableCell {
source: source.into(),
children,
contents: Into::<&str>::into(contents),
},
))
}

View File

@@ -13,7 +13,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::types::Target;
@@ -42,12 +41,12 @@ pub(crate) fn target<'b, 'g, 'r, 's>(
.get_preceding_character()
.expect("We cannot be at the start of the file because we are inside a target.");
if preceding_character.is_whitespace() {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Targets cannot end with whitespace.".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Targets cannot end with whitespace.",
)));
}
let (remaining, _) = tag(">>")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -56,6 +55,7 @@ pub(crate) fn target<'b, 'g, 'r, 's>(
Target {
source: source.into(),
value: body.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -3,11 +3,13 @@ use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::multispace1;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::all_consuming;
use nom::combinator::consumed;
use nom::combinator::map;
use nom::combinator::map_parser;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
@@ -34,7 +36,6 @@ use crate::context::ExitMatcherNode;
use crate::context::List;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::radio_link::rematch_target;
use crate::parser::util::exit_matcher_parser;
@@ -77,12 +78,14 @@ fn bold<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Bold<'s>> {
let (remaining, children) = text_markup_object("*")(context, input)?;
let (remaining, (contents, children, post_blank)) = text_markup_object("*")(context, input)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Bold {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))
@@ -96,12 +99,14 @@ fn italic<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Italic<'s>> {
let (remaining, children) = text_markup_object("/")(context, input)?;
let (remaining, (contents, children, post_blank)) = text_markup_object("/")(context, input)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Italic {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))
@@ -115,12 +120,14 @@ fn underline<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Underline<'s>> {
let (remaining, children) = text_markup_object("_")(context, input)?;
let (remaining, (contents, children, post_blank)) = text_markup_object("_")(context, input)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Underline {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))
@@ -134,12 +141,14 @@ fn strike_through<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, StrikeThrough<'s>> {
let (remaining, children) = text_markup_object("+")(context, input)?;
let (remaining, (contents, children, post_blank)) = text_markup_object("+")(context, input)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
StrikeThrough {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))
@@ -153,13 +162,14 @@ fn verbatim<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Verbatim<'s>> {
let (remaining, contents) = text_markup_string("=")(context, input)?;
let (remaining, (contents, post_blank)) = text_markup_string("=")(context, input)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Verbatim {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -172,24 +182,27 @@ fn code<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Code<'s>> {
let (remaining, contents) = text_markup_string("~")(context, input)?;
let (remaining, (contents, post_blank)) = text_markup_string("~")(context, input)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Code {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
fn text_markup_object<'c>(
marker_symbol: &'c str,
fn text_markup_object(
marker_symbol: &str,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>>
+ 'c {
) -> Res<
OrgSource<'s>,
(OrgSource<'s>, Vec<Object<'s>>, Option<OrgSource<'s>>),
> + '_ {
move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol)
}
@@ -201,7 +214,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
marker_symbol: &'c str,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
) -> Res<OrgSource<'s>, (OrgSource<'s>, Vec<Object<'s>>, Option<OrgSource<'s>>)> {
let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) =
@@ -216,7 +229,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
let initial_context = ContextElement::document_context();
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
let (remaining, children) = map_parser(
let (remaining, (contents, children)) = consumed(map_parser(
verify(
parser_with_context!(text_until_exit)(&parser_context),
|text| text.len() > 0,
@@ -226,7 +239,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
&initial_context,
)))(i)
}),
)(remaining)?;
))(remaining)?;
{
#[cfg(feature = "tracing")]
@@ -234,25 +247,25 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
#[cfg(feature = "tracing")]
let _enter = span.enter();
if exit_matcher_parser(context, remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Parent exit matcher is triggering.".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Parent exit matcher is triggering.",
)));
}
}
let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
Ok((remaining, children))
Ok((remaining, (contents, children, post_blank)))
}
fn text_markup_string<'c>(
marker_symbol: &'c str,
fn text_markup_string(
marker_symbol: &str,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>>
+ 'c {
) -> Res<OrgSource<'s>, (OrgSource<'s>, Option<OrgSource<'s>>)>
+ '_ {
move |context, input: OrgSource<'_>| _text_markup_string(context, input, marker_symbol)
}
@@ -264,7 +277,7 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
marker_symbol: &'c str,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
) -> Res<OrgSource<'s>, (OrgSource<'s>, Option<OrgSource<'s>>)> {
let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) =
@@ -290,16 +303,16 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
#[cfg(feature = "tracing")]
let _enter = span.enter();
if exit_matcher_parser(context, remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Parent exit matcher is triggering.".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Parent exit matcher is triggering.",
)));
}
}
let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
Ok((remaining, contents))
Ok((remaining, (contents, post_blank)))
}
#[cfg_attr(
@@ -321,9 +334,9 @@ fn pre<'b, 'g, 'r, 's>(
// If None, we are at the start of the file which is technically the beginning of a line.
Some('-') | Some('(') | Some('{') | Some('\'') | Some('"') => {}
Some(_) => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for text markup.".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for text markup.",
)));
}
None => unreachable!(), // None is for start of file, which should already be handled by the start_of_line matcher above.
};
@@ -343,10 +356,7 @@ fn post<'b, 'g, 'r, 's>(
Ok((remaining, ()))
}
fn text_markup_end<'c>(
marker_symbol: &'c str,
contents_start_offset: usize,
) -> impl ContextMatcher + 'c {
fn text_markup_end(marker_symbol: &str, contents_start_offset: usize) -> impl ContextMatcher + '_ {
move |context, input: OrgSource<'_>| {
_text_markup_end(context, input, marker_symbol, contents_start_offset)
}
@@ -363,9 +373,9 @@ fn _text_markup_end<'b, 'g, 'r, 's, 'c>(
contents_start_offset: usize,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
if input.get_byte_offset() == contents_start_offset {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Text markup cannot be empty".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Text markup cannot be empty",
)));
}
not(preceded_by_whitespace(false))(input)?;
let (remaining, _marker) = terminated(
@@ -386,13 +396,15 @@ impl<'x> RematchObject<'x> for Bold<'x> {
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) =
let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "*", &self.children)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Object::Bold(Bold {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
}),
))
@@ -409,13 +421,15 @@ impl<'x> RematchObject<'x> for Italic<'x> {
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) =
let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "/", &self.children)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Object::Italic(Italic {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
}),
))
@@ -432,13 +446,15 @@ impl<'x> RematchObject<'x> for Underline<'x> {
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) =
let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "_", &self.children)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Object::Underline(Underline {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
}),
))
@@ -455,13 +471,15 @@ impl<'x> RematchObject<'x> for StrikeThrough<'x> {
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) =
let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "+", &self.children)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Object::StrikeThrough(StrikeThrough {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
}),
))
@@ -477,7 +495,7 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
input: OrgSource<'s>,
marker_symbol: &'static str,
original_match_children: &'x Vec<Object<'x>>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
) -> Res<OrgSource<'s>, (OrgSource<'s>, Vec<Object<'s>>, Option<OrgSource<'s>>)> {
let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
@@ -488,6 +506,7 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
});
let parser_context = context.with_additional_node(&parser_context);
let contents_begin = remaining;
let (remaining, children) =
// TODO: This doesn't really check the exit matcher between each object. I think it may be possible to construct an org document that parses incorrectly with the current code.
rematch_target(&parser_context, original_match_children, remaining)?;
@@ -498,13 +517,15 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
#[cfg(feature = "tracing")]
let _enter = span.enter();
if exit_matcher_parser(context, remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Parent exit matcher is triggering.".into(),
))));
return Err(nom::Err::Error(CustomError::Static(
"Parent exit matcher is triggering.",
)));
}
}
let contents_end = remaining;
let contents = contents_begin.get_until(contents_end);
let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?;
Ok((remaining, children))
let (remaining, post_blank) = opt(space1)(remaining)?;
Ok((remaining, (contents, children, post_blank)))
}

Some files were not shown because too many files have changed in this diff Show More