116 Commits

Author SHA1 Message Date
Tom Alexander
db3086743c Publish version 0.1.4.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 23:28:17 -04:00
Tom Alexander
207a0546b0 Run full test suite despite default feature selection.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 23:23:31 -04:00
Tom Alexander
e9480fd156 Disable the compare tests when the compare feature is disabled.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 23:17:46 -04:00
Tom Alexander
28aca041f7 Publish version 0.1.3.
Some checks failed
rustfmt Build rustfmt has failed
rust-test Build rust-test has failed
rust-build Build rust-build has succeeded
2023-08-29 23:05:36 -04:00
Tom Alexander
d82def2a70 Merge branch 'compare_improvements'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 22:58:42 -04:00
Tom Alexander
d471f7178b Wrap the components of headlines in diff layers. 2023-08-29 22:57:08 -04:00
Tom Alexander
2c5c26c55f Allow diff layers that are not associated with tokens. 2023-08-29 22:47:40 -04:00
Tom Alexander
7944659802 Compare headline level. 2023-08-29 22:11:56 -04:00
Tom Alexander
58aca53144 Move get_property into util. 2023-08-29 22:07:23 -04:00
Tom Alexander
6f2d90162b Do not use the plain text parser for property drawers.
This additional exit condition was causing property drawers to parse incorrectly.
2023-08-29 22:03:20 -04:00
Tom Alexander
f170a557ed Use character offsets in diff. 2023-08-29 21:49:16 -04:00
Tom Alexander
eaa38ce772 Include an error message for failed bounds checking.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 21:27:54 -04:00
Tom Alexander
a6d742a536 Fix handling line breaks after divider in description lists. 2023-08-29 21:27:54 -04:00
Tom Alexander
45be9e7bde Merge branch 'description_list'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 19:36:45 -04:00
Tom Alexander
f6c895319f Add support for diffing description lists. 2023-08-29 19:35:54 -04:00
Tom Alexander
2682779534 Add support for parsing description lists. 2023-08-29 18:13:55 -04:00
Tom Alexander
b48d472546 Fix build.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 18:07:35 -04:00
Tom Alexander
ea6faf728c Add test for description list. 2023-08-29 18:07:35 -04:00
Tom Alexander
f4ea1b7303 Merge branch 'accuracy_fixes_from_feeding_large_documents'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has failed
2023-08-29 17:49:19 -04:00
Tom Alexander
80b55fdd45 Consume trailing whitespace for macro. 2023-08-29 17:42:58 -04:00
Tom Alexander
f426e32798 Do not include trailing punctuation or whitespace in plain links. 2023-08-29 17:35:04 -04:00
Tom Alexander
66037356c5 Add test showing plain links ending in punctuation currently do not parse correctly. 2023-08-29 17:24:44 -04:00
Tom Alexander
1bcd1895c0 Allow affiliating keywords with regular keywords. 2023-08-29 17:19:13 -04:00
Tom Alexander
e3d38cfbe2 Add a test for floating affiliated keywords. 2023-08-29 17:05:19 -04:00
Tom Alexander
2ba0dc49be Support export affiliated keywords. 2023-08-29 17:01:35 -04:00
Tom Alexander
9df40fb13f Only allow specific keywords for affiliated keywords. 2023-08-29 16:56:07 -04:00
Tom Alexander
cc671925db Support empty sections under headings. 2023-08-29 16:07:43 -04:00
Tom Alexander
950baa9d5d Only allow a single section under a heading. 2023-08-29 16:03:13 -04:00
Tom Alexander
56865c68fc Do not allow plain links without a path. 2023-08-29 15:44:04 -04:00
Tom Alexander
f592b73ae7 Merge branch 'reduce_context_usage_in_exit_matchers'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 15:17:16 -04:00
Tom Alexander
3206027b96 Add all entities. 2023-08-29 15:16:22 -04:00
Tom Alexander
3e6df7ba78 Print character offset from rust's parse perspective during compare. 2023-08-29 14:40:58 -04:00
Tom Alexander
ac313d093e Improve error handling in compare. 2023-08-29 14:20:53 -04:00
Tom Alexander
f376f1cf8e Add a test for empty sections. 2023-08-29 14:10:26 -04:00
Tom Alexander
f21385a901 Add a helper function for logging during debugging.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 14:03:14 -04:00
Tom Alexander
1d06d95bb1 Add a minimum stars figure to heading parser to reduce re-parses. 2023-08-29 14:01:28 -04:00
Tom Alexander
bfc88c1d1b Use a detect_headline function instead of the full headline parse for the section_end exit matcher.
This shaved 2 seconds off the first 800 lines of org-mode/doc/org-guide.org.
2023-08-29 11:35:54 -04:00
Tom Alexander
f29720e5b9 Switch to using a type for bracket depth.
This is to make changing the type easier in the future.
2023-08-29 11:18:15 -04:00
Tom Alexander
27a9b5aeb1 Switch to i16 for backet depth count.
This is having a measurable performance increase. 32k bracket depth should be enough for any non-malicious document.
2023-08-29 11:14:50 -04:00
Tom Alexander
8051c3d2b7 Remove line number tracking.
The documentation was incorrect, none of the org-mode elements have a line number restriction for their contents.
2023-08-29 11:09:28 -04:00
Tom Alexander
bd97d2f69d Switch to i32 for tracking bracket depth. 2023-08-29 11:07:00 -04:00
Tom Alexander
14b1d0526c Manually implement Debug and make convert_error more generic.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 10:58:05 -04:00
Tom Alexander
288350daef Iterate over the bytes instead of characters when counting brackets.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-28 03:52:21 -04:00
Tom Alexander
c683516620 Switch inline source blocks to using bracket depth from OrgSource instead of from the context.
This is for the same reasons as footnote references.
2023-08-28 03:04:33 -04:00
Tom Alexander
e731e8ff6b Switch inline babel call to using bracket depth from OrgSource instead of from the context.
This is for the same reasons as footnote references.
2023-08-28 03:04:33 -04:00
Tom Alexander
4c2037ec44 Switch subscript and superscript to using bracket depth from OrgSource instead of from the context.
This is for the same reasons as footnote references.
2023-08-28 03:04:33 -04:00
Tom Alexander
a46b358549 Switch citations to using bracket depth from OrgSource instead of from the context.
This is for the same reasons as footnote references.
2023-08-28 03:04:32 -04:00
Tom Alexander
ec813e3b3f Switch to using bracket depth from OrgSource instead of from the context for footnote references.
It is currently unknown if this will produce a performance increase, but unless it has a significant performance penalty we are going to go forward with this change because it makes it more explicit which values need to be read deeply from other elements (therefore needing to be in the context) vs values that can be bound to the exit matcher since they are only used within the confines of the current element.

I suspect we will get a performance boost since it will be reducing the nodes that need to be walked in the context but maintaining bracket depth count over the entire document instead of only inside elements that need balanced brackets could cost us.
2023-08-28 03:04:32 -04:00
Tom Alexander
f11f7bcc73 Keep track of bracket, brace, and parenthesis depth when iterating over the OrgSource. 2023-08-28 01:18:46 -04:00
Tom Alexander
9e0e5f6f0a Remove line number limit for LaTeX fragments. 2023-08-28 01:18:46 -04:00
Tom Alexander
16e788c36c Add tests for LaTeX fragments and text markup that span more than three lines.
The documentation currently states that the body for these cannot span more than three lines but that is not the behavior I am seeing from emacs in practice. Waiting on a mailing list response to tell me if this is a documentation error or a parser error.
2023-08-28 01:18:46 -04:00
Tom Alexander
b35d785e73 Fix tracing in the run_docker_compare.bash script.
Some checks failed
rust-build Build rust-build has failed
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-08-28 01:18:45 -04:00
Tom Alexander
1952d175c0 Record line number in OrgSource.
This will be used for elements who have limits on the number of lines inside of them. This includes LaTeX fragment bodies and text markup.
2023-08-28 01:18:45 -04:00
Tom Alexander
20c17c40be Switch greater blocks to using name provided when building exit matcher instead of from context. 2023-08-28 01:18:45 -04:00
Tom Alexander
b6b869df25 Minor improvement to error message in diff.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-28 01:05:09 -04:00
Tom Alexander
18a396b7cb Remove deprecated tests.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-27 23:25:50 -04:00
Tom Alexander
085490476e Fix make dockertest.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-27 22:41:55 -04:00
Tom Alexander
9c9964c66f Add lt and gt entities.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-27 22:15:23 -04:00
Tom Alexander
1a3e26c148 Update plain list greater block exit matcher priority test to match blog post.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
It is still testing the same thing, but I changed the contents a bit to match what is being used in my blog post.
2023-08-27 21:03:16 -04:00
Tom Alexander
e9e6a8ff64 Merge branch 'clean_up_docker'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-27 18:03:51 -04:00
Tom Alexander
b124317f30 Fix up scripts to handle the changes to the docker containers. 2023-08-27 18:03:37 -04:00
Tom Alexander
ad389f0776 Remove volumes in the clean step.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-27 17:53:51 -04:00
Tom Alexander
75dfc7f812 Depend on build when using the docker images. 2023-08-27 17:51:57 -04:00
Tom Alexander
c17de8ef5e Set up the same mounts when running make shell. 2023-08-27 17:50:33 -04:00
Tom Alexander
378b6bb391 Update the run targets for the Makefiles for the docker containers.
This was previously using the standard docker makefile I use as a starting point for all of my docker makefiles. Now it will properly mount the source directory.
2023-08-27 17:46:36 -04:00
Tom Alexander
cc86591a6c Support the debug/dev profile in the perf script.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-27 17:02:12 -04:00
Tom Alexander
f25dbc1d7c Add a script for testing organic parse times.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
This is not meant to produce publishable or comparable benchmarks. Such a script would have to run many iterations with the input already loaded into memory, proper prioritization via nice/ionice, and have a warm-up phase. This is just automating a basic test I am frequently running to compare parse times when investigating performance issues.
2023-08-27 16:56:32 -04:00
Tom Alexander
daee50c160 Merge branch 'dynamic_block_test'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-27 16:23:28 -04:00
Tom Alexander
3e143796f7 Compare heading todo keywords.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
This only handles the default case where the only valid TODO keywords are TODO and DONE.
2023-08-27 15:56:08 -04:00
Tom Alexander
9cc5e63c1b Compare heading tags.
Some checks failed
rust-test Build rust-test has failed
rust-build Build rust-build has succeeded
2023-08-25 07:05:59 -04:00
Tom Alexander
be6197e4c7 Store the tags in the heading. 2023-08-25 06:20:06 -04:00
Tom Alexander
2d4e54845b Add support for parsing tags in headlines. 2023-08-25 06:13:29 -04:00
Tom Alexander
d5ea650b96 Add a test for dynamic blocks. 2023-08-25 05:36:57 -04:00
Tom Alexander
60363579b5 Merge branch 'plain_list_content_on_next_line'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-25 05:28:14 -04:00
Tom Alexander
1b678fe81f Add tests for detect_plain_list. 2023-08-25 05:27:09 -04:00
Tom Alexander
bfea828e62 Update detect_plain_list to support line breaks. 2023-08-25 05:27:08 -04:00
Tom Alexander
bc5745a95f Add support for list items with a line break before their contents. 2023-08-25 05:18:26 -04:00
Tom Alexander
efa372a9e9 Add a test case that breaks the current parser. 2023-08-25 04:39:58 -04:00
Tom Alexander
2fb57daaec Move the table cell object parser into the object parser file.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-25 04:35:54 -04:00
Tom Alexander
3a38f4cd35 Add support for the ast entity.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-25 04:28:24 -04:00
Tom Alexander
45e16fea2d Honor the NO_COLOR environment variable.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-25 04:25:39 -04:00
Tom Alexander
5134cece7b Add color to compare output.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-25 04:09:52 -04:00
Tom Alexander
19432d91ab Get the emacs and org-mode versions when launching the compare script.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-25 03:11:19 -04:00
Tom Alexander
16a107eebb Update org-mode version. 2023-08-25 02:56:28 -04:00
Tom Alexander
77348b560c Parameterize the emacs and org-mode versions in the dockerfiles.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-08-25 02:03:35 -04:00
Tom Alexander
fc79507ef3 Merge branch 'plain_list_perf_investigation'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-25 01:10:04 -04:00
Tom Alexander
9c1e6ccc97 Add a detect_element function.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
This is an optimization. When you have something like plain text which ends when it hits the next element, we only need to parse enough to detect that an element is about to occur. For elements like plain lists, this is as simple as parsing a line starting with optional whitespace and then a bullet, which avoids parsing the entire plain list tree. The benefit is most noticeable in deeply nested plain lists.
2023-08-25 01:07:53 -04:00
Tom Alexander
0dbc8f0925 Remove redundant exit matcher checks.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-25 00:01:56 -04:00
Tom Alexander
02fe10fba3 Move objects to a lower exit class.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
Paragraph's exit matcher which detects elements was causing the plain list parser to exit after the first item was parsed which was causing significant amounts of re-parsing.
2023-08-24 23:34:23 -04:00
Tom Alexander
33d7ae03d1 Add a TODO.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-24 21:35:34 -04:00
Tom Alexander
03faa7257f Move the indent level for plain list's exit matcher to const fn instead of grabbing from the context.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
This made a slight improvement to performance.
2023-08-24 20:50:24 -04:00
Tom Alexander
ae3510abd5 Do not cast lesser block name to lowercase at runtime.
This reduced the runtime of my problematic test case from 6.9 seconds to 6 seconds.
2023-08-24 20:10:43 -04:00
fluxcdbot
ad3f47864a CI: autofix rust code.
Some checks failed
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has failed
2023-08-24 23:43:41 +00:00
Tom Alexander
533ef2a9a8 Merge branch 'wrapped_input'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-24 19:40:55 -04:00
Tom Alexander
cf37bc4111 Remove unnecessary context from some util functions.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-24 19:29:00 -04:00
Tom Alexander
e5224cda63 Removing dead code.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-24 18:40:25 -04:00
Tom Alexander
64e3481660 Update get_consumed to use the new wrapped input type. 2023-08-24 18:33:40 -04:00
Tom Alexander
32071ce74d Fix handling of start of line in OrgSource.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-24 18:08:16 -04:00
Tom Alexander
e84e2b5147 Update tests to compile again. 2023-08-24 17:15:24 -04:00
Tom Alexander
3348807a05 Eliminate the document root context element. 2023-08-24 17:01:12 -04:00
Tom Alexander
720afa5d32 Update getting the previous character and previous line.
This can be done a lot more efficiently now that we are keeping track of this information in the wrapped input type instead of having to fetch to the original document out of the context tree.
2023-08-24 16:56:07 -04:00
Tom Alexander
dab598e5e7 Convert all functions to using the wrapped input type.
Some checks failed
rust-test Build rust-test has failed
rust-build Build rust-build has failed
2023-08-24 16:06:29 -04:00
Tom Alexander
b7a5dd48ea Impl missing traits. 2023-08-22 23:32:27 -04:00
Tom Alexander
c475dce6da Fix lifetime issue. 2023-08-22 23:14:23 -04:00
Tom Alexander
6d1675fa00 Lifetime issue. 2023-08-22 22:57:44 -04:00
Tom Alexander
cda49c628c Move the wrapped input into the parser. 2023-08-22 22:33:50 -04:00
Tom Alexander
65b87bd65d Merge remote-tracking branch 'input/main' into wrapped_input 2023-08-22 22:26:55 -04:00
Tom Alexander
5a7f34b63e Prepare for merging into Organic. 2023-08-22 22:24:35 -04:00
Tom Alexander
edff1e089d Implement text since line break. 2023-08-22 22:18:44 -04:00
Tom Alexander
bc29f1dfc0 Add slicing tests. 2023-08-22 21:38:50 -04:00
Tom Alexander
e4656cddf6 Implement slice, take, and compare. 2023-08-22 21:25:13 -04:00
Tom Alexander
1e3dadd458 Wrap the input. 2023-08-22 17:24:26 -04:00
Tom Alexander
2ec055af5a Very simple setup. 2023-08-22 17:22:13 -04:00
Tom Alexander
6823db5c60 Initial commit. 2023-08-22 17:11:45 -04:00
Tom Alexander
21e1ceb8e0 Merge branch 'add_performance_check_scripts'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-22 14:21:44 -04:00
Tom Alexander
655af88cdf Add scripts for running perf and callgrind. 2023-08-22 14:21:27 -04:00
98 changed files with 4650 additions and 2094 deletions

View File

@@ -14,10 +14,6 @@ spec:
- name: path-to-dockerfile - name: path-to-dockerfile
description: The path to the Dockerfile description: The path to the Dockerfile
type: string type: string
- name: command
type: array
description: Command to run.
default: []
tasks: tasks:
- name: report-pending - name: report-pending
taskRef: taskRef:
@@ -92,8 +88,6 @@ spec:
runAfter: runAfter:
- build-image - build-image
params: params:
- name: command
value: ["$(params.command[*])"]
- name: args - name: args
value: ["--no-default-features"] value: ["--no-default-features"]
- name: docker-image - name: docker-image
@@ -109,8 +103,6 @@ spec:
runAfter: runAfter:
- run-image-none - run-image-none
params: params:
- name: command
value: ["$(params.command[*])"]
- name: args - name: args
value: ["--no-default-features", "--features", "tracing"] value: ["--no-default-features", "--features", "tracing"]
- name: docker-image - name: docker-image
@@ -126,8 +118,6 @@ spec:
runAfter: runAfter:
- run-image-tracing - run-image-tracing
params: params:
- name: command
value: ["$(params.command[*])"]
- name: args - name: args
value: ["--no-default-features", "--features", "compare"] value: ["--no-default-features", "--features", "compare"]
- name: docker-image - name: docker-image
@@ -143,8 +133,6 @@ spec:
runAfter: runAfter:
- run-image-compare - run-image-compare
params: params:
- name: command
value: ["$(params.command[*])"]
- name: args - name: args
value: [] value: []
- name: docker-image - name: docker-image
@@ -160,8 +148,6 @@ spec:
runAfter: runAfter:
- run-image-default - run-image-default
params: params:
- name: command
value: ["$(params.command[*])"]
- name: args - name: args
value: ["--no-default-features", "--features", "tracing,compare"] value: ["--no-default-features", "--features", "tracing,compare"]
- name: docker-image - name: docker-image
@@ -256,5 +242,3 @@ spec:
value: docker/organic_build/ value: docker/organic_build/
- name: path-to-dockerfile - name: path-to-dockerfile
value: docker/organic_build/Dockerfile value: docker/organic_build/Dockerfile
- name: command
value: [cargo, build]

View File

@@ -18,14 +18,6 @@ spec:
- name: path-to-dockerfile - name: path-to-dockerfile
description: The path to the Dockerfile description: The path to the Dockerfile
type: string type: string
- name: command
type: array
description: Command to run.
default: []
- name: args
type: array
description: Arguments passed to command.
default: []
tasks: tasks:
- name: do-stuff - name: do-stuff
taskSpec: taskSpec:
@@ -117,10 +109,17 @@ spec:
runAfter: runAfter:
- build-image - build-image
params: params:
- name: command
value: ["$(params.command[*])"]
- name: args - name: args
value: ["$(params.args[*])"] value:
[
--no-default-features,
--features,
compare,
--no-fail-fast,
--lib,
--test,
test_loader,
]
- name: docker-image - name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)" value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
finally: finally:
@@ -212,7 +211,3 @@ spec:
value: docker/organic_test/ value: docker/organic_test/
- name: path-to-dockerfile - name: path-to-dockerfile
value: docker/organic_test/Dockerfile value: docker/organic_test/Dockerfile
- name: command
value: [cargo, test]
- name: args
value: [--lib, --test, test_loader]

View File

@@ -14,14 +14,6 @@ spec:
- name: path-to-dockerfile - name: path-to-dockerfile
description: The path to the Dockerfile description: The path to the Dockerfile
type: string type: string
- name: rustfmt-command
type: array
description: Command to run rustfmt.
default: []
- name: rustfmt-args
type: array
description: Arguments passed to rustfmt.
default: []
- name: GIT_USER_NAME - name: GIT_USER_NAME
description: The username for git description: The username for git
type: string type: string
@@ -119,10 +111,6 @@ spec:
runAfter: runAfter:
- build-image - build-image
params: params:
- name: command
value: ["$(params.rustfmt-command[*])"]
- name: args
value: ["$(params.rustfmt-args[*])"]
- name: docker-image - name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)" value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: cargo-fix - name: cargo-fix
@@ -240,7 +228,3 @@ spec:
value: docker/cargo_fmt/ value: docker/cargo_fmt/
- name: path-to-dockerfile - name: path-to-dockerfile
value: docker/cargo_fmt/Dockerfile value: docker/cargo_fmt/Dockerfile
- name: command
value: [cargo, fmt]
- name: args
value: []

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "organic" name = "organic"
version = "0.1.2" version = "0.1.4"
authors = ["Tom Alexander <tom@fizz.buzz>"] authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser." description = "An org-mode parser."
edition = "2021" edition = "2021"
@@ -13,8 +13,7 @@ resolver = "2"
include = [ include = [
"LICENSE", "LICENSE",
"**/*.rs", "**/*.rs",
"Cargo.toml", "Cargo.toml"
"tests/*"
] ]
[lib] [lib]
@@ -40,10 +39,18 @@ tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-fil
walkdir = "2.3.3" walkdir = "2.3.3"
[features] [features]
default = ["compare", "tracing"] default = []
compare = [] compare = []
tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"] tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"]
[profile.release] # Optimized build for any sort of release.
[profile.release-lto]
inherits = "release"
lto = true lto = true
strip = "symbols" strip = "symbols"
# 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"
lto = true
debug = true

View File

@@ -35,12 +35,12 @@ clean:
.PHONY: test .PHONY: test
test: test:
> cargo test --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS) > cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: dockertest .PHONY: dockertest
dockertest: dockertest:
> $(MAKE) -C docker/organic_test > $(MAKE) -C docker/organic_test
> docker run --init --rm -i -t -v "$$(readlink -f ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test cargo test --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS) > docker run --init --rm -i -t -v "$$(readlink -f ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: dockerclean .PHONY: dockerclean
dockerclean: dockerclean:
@@ -49,11 +49,11 @@ dockerclean:
.PHONY: integrationtest .PHONY: integrationtest
integrationtest: integrationtest:
> cargo test --no-fail-fast --test test_loader -- --test-threads $(TESTJOBS) > cargo test --no-default-features --features compare --no-fail-fast --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: unittest .PHONY: unittest
unittest: unittest:
> cargo test --lib -- --test-threads $(TESTJOBS) > cargo test --no-default-features --lib -- --test-threads $(TESTJOBS)
.PHONY: jaeger .PHONY: jaeger
jaeger: jaeger:

View File

@@ -1,10 +1,16 @@
#[cfg(feature = "compare")]
use std::env; use std::env;
#[cfg(feature = "compare")]
use std::fs::File; use std::fs::File;
#[cfg(feature = "compare")]
use std::io::Write; use std::io::Write;
#[cfg(feature = "compare")]
use std::path::Path; use std::path::Path;
#[cfg(feature = "compare")]
use walkdir::WalkDir; use walkdir::WalkDir;
#[cfg(feature = "compare")]
fn main() { fn main() {
let out_dir = env::var("OUT_DIR").unwrap(); let out_dir = env::var("OUT_DIR").unwrap();
let destination = Path::new(&out_dir).join("tests.rs"); let destination = Path::new(&out_dir).join("tests.rs");
@@ -31,6 +37,10 @@ fn main() {
} }
} }
#[cfg(not(feature = "compare"))]
fn main() {}
#[cfg(feature = "compare")]
fn write_test(test_file: &mut File, test: &walkdir::DirEntry) { fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
let test_name = test let test_name = test
.path() .path()
@@ -55,6 +65,7 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
.unwrap(); .unwrap();
} }
#[cfg(feature = "compare")]
fn write_header(test_file: &mut File) { fn write_header(test_file: &mut File) {
write!( write!(
test_file, test_file,
@@ -70,14 +81,13 @@ use organic::parser::sexp::sexp_with_padding;
.unwrap(); .unwrap();
} }
#[cfg(feature = "compare")]
fn is_expect_fail(name: &str) -> Option<&str> { fn is_expect_fail(name: &str) -> Option<&str> {
match name { match name {
"autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."), "autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
"autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."), "autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
"autogen_lesser_element_paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."), "autogen_lesser_element_paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."),
"autogen_greater_element_plain_list_trailing_whitespace_ownership_test_case_1" => Some("Seeing odd behavior about whitespace ownership."), // https://list.orgmode.org/9372527e-3852-419e-936a-7b4dd38cc847@app.fastmail.com/ "autogen_unicode_hearts" => Some("Unicode is coming out of emacs strange."),
"autogen_greater_element_plain_list_trailing_whitespace_ownership_test_case_3" => Some("Seeing odd behavior about whitespace ownership."), // https://list.orgmode.org/9372527e-3852-419e-936a-7b4dd38cc847@app.fastmail.com/
"autogen_greater_element_plain_list_trailing_whitespace_ownership_test_case_4" => Some("Seeing odd behavior about whitespace ownership."), // https://list.orgmode.org/9372527e-3852-419e-936a-7b4dd38cc847@app.fastmail.com/
_ => None, _ => None,
} }
} }

View File

@@ -26,10 +26,11 @@ else
@echo "REMOTE_REPO not defined, not removing from remote repo." @echo "REMOTE_REPO not defined, not removing from remote repo."
endif endif
# NOTE: This target will write to folders underneath the git-root
.PHONY: run .PHONY: run
run: run: build
docker run --rm -i -t $(IMAGE_NAME) docker run --rm --init -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME)
.PHONY: shell .PHONY: shell
shell: shell: build
docker run --rm -i -t --entrypoint /bin/bash $(IMAGE_NAME) docker run --rm -i -t --entrypoint /bin/sh -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME)

View File

@@ -2,3 +2,5 @@ FROM rustlang/rust:nightly-alpine3.17
RUN apk add --no-cache musl-dev RUN apk add --no-cache musl-dev
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
ENTRYPOINT ["cargo", "build"]

View File

@@ -25,11 +25,13 @@ ifdef REMOTE_REPO
else else
@echo "REMOTE_REPO not defined, not removing from remote repo." @echo "REMOTE_REPO not defined, not removing from remote repo."
endif endif
docker volume rm cargo-cache
# NOTE: This target will write to folders underneath the git-root
.PHONY: run .PHONY: run
run: run: build
docker run --rm -i -t $(IMAGE_NAME) docker run --rm --init -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME)
.PHONY: shell .PHONY: shell
shell: shell: build
docker run --rm -i -t --entrypoint /bin/bash $(IMAGE_NAME) docker run --rm -i -t --entrypoint /bin/sh -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME)

View File

@@ -1,11 +1,10 @@
FROM alpine:3.17 AS build FROM alpine:3.17 AS build
RUN apk add --no-cache build-base musl-dev git autoconf make texinfo gnutls-dev ncurses-dev gawk RUN apk add --no-cache build-base musl-dev git autoconf make texinfo gnutls-dev ncurses-dev gawk
FROM build AS build-emacs FROM build AS build-emacs
ARG EMACS_VERSION=emacs-29.1
RUN git clone --depth 1 --branch emacs-29.1 https://git.savannah.gnu.org/git/emacs.git /root/emacs RUN git clone --depth 1 --branch $EMACS_VERSION https://git.savannah.gnu.org/git/emacs.git /root/emacs
WORKDIR /root/emacs WORKDIR /root/emacs
RUN mkdir /root/dist RUN mkdir /root/dist
RUN ./autogen.sh RUN ./autogen.sh
@@ -15,9 +14,12 @@ RUN make DESTDIR="/root/dist" install
FROM build AS build-org-mode FROM build AS build-org-mode
ARG ORG_VERSION=7bdec435ff5d86220d13c431e799c5ed44a57da1
COPY --from=build-emacs /root/dist/ / COPY --from=build-emacs /root/dist/ /
RUN mkdir /root/dist RUN mkdir /root/dist
RUN mkdir /root/org-mode && git -C /root/org-mode init --initial-branch=main && git -C /root/org-mode remote add origin https://git.savannah.gnu.org/git/emacs/org-mode.git && git -C /root/org-mode fetch origin 299193bf091a63474fc8036bd31de51800a2555a && git -C /root/org-mode checkout FETCH_HEAD # Savannah does not allow fetching specific revisions, so we're going to have to put unnecessary load on their server by cloning main and then checking out the revision we want.
RUN git clone https://git.savannah.gnu.org/git/emacs/org-mode.git /root/org-mode && git -C /root/org-mode checkout $ORG_VERSION
# RUN mkdir /root/org-mode && git -C /root/org-mode init --initial-branch=main && git -C /root/org-mode remote add origin https://git.savannah.gnu.org/git/emacs/org-mode.git && git -C /root/org-mode fetch origin $ORG_VERSION && git -C /root/org-mode checkout FETCH_HEAD
WORKDIR /root/org-mode WORKDIR /root/org-mode
RUN make compile RUN make compile
RUN make DESTDIR="/root/dist" install RUN make DESTDIR="/root/dist" install
@@ -28,3 +30,5 @@ RUN apk add --no-cache musl-dev ncurses gnutls
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
COPY --from=build-emacs /root/dist/ / COPY --from=build-emacs /root/dist/ /
COPY --from=build-org-mode /root/dist/ / COPY --from=build-org-mode /root/dist/ /
ENTRYPOINT ["cargo", "test"]

View File

@@ -25,11 +25,12 @@ ifdef REMOTE_REPO
else else
@echo "REMOTE_REPO not defined, not removing from remote repo." @echo "REMOTE_REPO not defined, not removing from remote repo."
endif endif
docker volume rm rust-cache cargo-cache
.PHONY: run .PHONY: run
run: run: build
docker run --rm -i -t $(IMAGE_NAME) docker run --rm --init -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) --no-default-features --features compare --no-fail-fast --lib --test test_loader
.PHONY: shell .PHONY: shell
shell: shell: build
docker run --rm -i -t --entrypoint /bin/bash $(IMAGE_NAME) docker run --rm -i -t --entrypoint /bin/sh -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)

1
elisp_snippets/README.md Normal file
View File

@@ -0,0 +1 @@
This folder is for snippets of elisp that are useful for development.

View File

@@ -0,0 +1,3 @@
(dolist (var org-element-affiliated-keywords)
(message "\"%s\"," (downcase var))
)

View File

@@ -0,0 +1,5 @@
(dolist (var org-entities)
(when (listp var)
(message "\"%s\"," (nth 0 var))
)
)

View File

@@ -1,6 +1,7 @@
1. foo 1. plain-list
#+begin_center #+begin_center
#+end_center #+end_center
2. bar
Is this still in the plain list?

View File

@@ -1 +0,0 @@
This folder is an investigation into whether or not my exit matchers should operate from the top down or bottom up.

View File

@@ -1 +0,0 @@
foo *bar baz * lorem* ipsum

View File

@@ -1,3 +0,0 @@
Looks like 2 blank lines always exits the top-level plain list.
Plain lists do not seem to go inside paragraphs but rather exist beside them.

View File

@@ -1,12 +0,0 @@
1. foo
bar
1. baz
lorem
ipsum
dolar

View File

@@ -1 +0,0 @@
Looks like table cells cannot contain lists but can contain bolds

View File

@@ -1,5 +0,0 @@
ip *su* m
| foo | bar |
|----------+-----|
| 1. lo *re* m | |

View File

@@ -0,0 +1,25 @@
#+BEGIN: clocktable :scope file :maxlevel 2
#+CAPTION: Clock summary at [2023-08-25 Fri 05:34]
| Headline | Time |
|--------------+--------|
| *Total time* | *0:00* |
#+END:
#+BEGIN: columnview :hlines 1 :id global
| ITEM | TODO | PRIORITY | TAGS |
|-------+------+----------+------------------------------|
| Foo | | B | |
|-------+------+----------+------------------------------|
| Bar | TODO | B | |
|-------+------+----------+------------------------------|
| Baz | | B | :thisisatag: |
| Lorem | | B | :thisshouldinheritfromabove: |
| Ipsum | | B | :multiple:tags: |
#+END:
* Foo
* TODO Bar
* Baz :thisisatag:
** Lorem :thisshouldinheritfromabove:
*** Ipsum :multiple:tags:
* Dolar ::
* cat :dog: bat

View File

@@ -0,0 +1,7 @@
1. foo
2.
bar
1.
#+begin_center
Still in the list
#+end_center

View File

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

View File

@@ -0,0 +1,11 @@
- foo :: bar
- cat ::
dog
- lorem
:: ipsum
-
lorem :: ipsum
- dolar *bold* foo :: ipsum
- big gap ::
stuff

View File

@@ -0,0 +1,7 @@
** foo
:PROPERTIES:
:DESCRIPTION: lorem
:ALT_TITLE: ipsum
:END:
bar

View File

@@ -0,0 +1,15 @@
#+name: foo
#+caption: bar
#+caption: baz
[[file:lorem/ipsum.png]]
#+name: cat
#+foo: dog
[[file:lorem/ipsum.png]]
#+name: cat
#+foo: dog
foo

View File

@@ -0,0 +1,22 @@
# Extra open
[cite/a/b-_/foo:unbalancedglobal[prefix;keyprefix @foo keysuffix;globalsuffix]
[cite/a/b-_/foo:globalprefix;unbalancedkey[prefix @foo keysuffix;globalsuffix]
[cite/a/b-_/foo:globalprefix;keyprefix @foo unbalancedkey[suffix;globalsuffix]
[cite/a/b-_/foo:globalprefix;keyprefix @foo keysuffix;unbalancedglobal[suffix]
# Extra close
[cite/a/b-_/foo:unbalancedglobal]prefix;keyprefix @foo keysuffix;globalsuffix]
[cite/a/b-_/foo:globalprefix;unbalancedkey]prefix @foo keysuffix;globalsuffix]
[cite/a/b-_/foo:globalprefix;keyprefix @foo unbalancedkey]suffix;globalsuffix]
[cite/a/b-_/foo:globalprefix;keyprefix @foo keysuffix;unbalancedglobal]suffix]
# balanced:
[cite/a/b-_/foo:gl[obalpref]ix;ke[ypref]ix @foo ke[ysuff]ix;gl[obalsuff]ix]

View File

@@ -0,0 +1,2 @@
[fn:2:This footnote [ has balanced ] brackets inside it]
[fn::This footnote does not have balanced [ brackets inside it]

View File

@@ -0,0 +1,6 @@
$foo
bar
baz
lorem
ipsum
dolar$

View File

@@ -0,0 +1,52 @@
non-link text
eww://
rmail://
mhe://
irc://
info://
gnus://
docview://
bibtex://
bbdb://
w3m://
doi://
file+sys://
file+emacs://
shell://
news://
mailto://
https://
http://
ftp://
help://
file://
elisp://
randomfakeprotocl://
non-link text
non-link text
eww:
rmail:
mhe:
irc:
info:
gnus:
docview:
bibtex:
bbdb:
w3m:
doi:
file+sys:
file+emacs:
shell:
news:
mailto:
https:
http:
ftp:
help:
file:
elisp:
randomfakeprotocl:
non-link text

View File

@@ -0,0 +1,3 @@
mailto:foo@bar.baz.
mailto:foo@bar.baz....

View File

@@ -0,0 +1,17 @@
foo *bar
baz* lorem
text *markup
can
span* more
than *three
lines.
foo
bar* baz
foo *bar \\
baz \\
lorem \\
ipsum \\
dolar* cat

View File

@@ -0,0 +1,9 @@
* Foo
* Bar
* Baz

View File

@@ -0,0 +1 @@
🧡💛💚💙💜

13
scripts/callgrind.bash Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
#
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR/../"
RUSTFLAGS="-C opt-level=0" cargo build --no-default-features
valgrind --tool=callgrind --callgrind-out-file=callgrind.out target/debug/compare
echo "You probably want to run:"
echo "callgrind_annotate --auto=yes callgrind.out"

29
scripts/perf.bash Executable file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
#
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${PROFILE:="perf"}
cd "$DIR/../"
function main {
local additional_flags=()
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
PROFILE="debug"
else
additional_flags+=(--profile "$PROFILE")
fi
cargo build --no-default-features "${additional_flags[@]}"
perf record --freq=2000 --call-graph dwarf --output=perf.data target/${PROFILE}/compare
# Convert to a format firefox will read
# flags to consider --show-info
perf script -F +pid --input perf.data > perf.firefox
echo "You probably want to go to https://profiler.firefox.com/"
echo "Either that or run hotspot"
}
main "${@}"

View File

@@ -7,6 +7,8 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${SHELL:="NO"} # or YES to launch a shell instead of running the test : ${SHELL:="NO"} # or YES to launch a shell instead of running the test
: ${TRACE:="NO"} # or YES to send traces to jaeger : ${TRACE:="NO"} # or YES to send traces to jaeger
: ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking : ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking
: ${NO_COLOR:=""} # Set to anything to disable color output
cd "$DIR/../" cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath) REALPATH=$(command -v uu-realpath || command -v realpath)
@@ -24,23 +26,31 @@ function build_container {
function launch_container { function launch_container {
local additional_flags=() local additional_flags=()
local additional_args=() local additional_args=()
local features=(compare)
if [ "$SHELL" != "YES" ]; then if [ "$NO_COLOR" != "" ]; then
additional_args+=(cargo run) additional_flags+=(--env "NO_COLOR=$NO_COLOR")
else
additional_flags+=(-t)
fi fi
if [ "$TRACE" = "YES" ]; then if [ "$TRACE" = "YES" ]; then
# We use the host network so it can talk to jaeger hosted at 127.0.0.1 # We use the host network so it can talk to jaeger hosted at 127.0.0.1
additional_flags+=(--network=host --env RUST_LOG=debug) additional_flags+=(--network=host --env RUST_LOG=debug)
features+=(tracing)
fi
if [ "$SHELL" != "YES" ]; then
local features_joined=$(IFS=","; echo "${features[*]}")
additional_args+=(cargo run --no-default-features --features "$features_joined")
else
additional_args+=(/bin/sh)
additional_flags+=(-t)
fi fi
if [ "$BACKTRACE" = "YES" ]; then if [ "$BACKTRACE" = "YES" ]; then
additional_flags+=(--env RUST_BACKTRACE=full) additional_flags+=(--env RUST_BACKTRACE=full)
fi fi
docker run "${additional_flags[@]}" --init --rm -i -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test "${additional_args[@]}" docker run "${additional_flags[@]}" --init --rm -i -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}"
} }
main "${@}" main "${@}"

View File

@@ -4,6 +4,8 @@ set -euo pipefail
IFS=$'\n\t' IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${NO_COLOR:=""} # Set to anything to disable color output
cd "$DIR/../" cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath) REALPATH=$(command -v uu-realpath || command -v realpath)
MAKE=$(command -v gmake || command -v make) MAKE=$(command -v gmake || command -v make)
@@ -40,17 +42,21 @@ function get_test_names {
function launch_container { function launch_container {
local test="$1" local test="$1"
local additional_args=() local additional_flags=()
if [ "$NO_COLOR" != "" ]; then
additional_flags+=(--env "NO_COLOR=$NO_COLOR")
fi
local init_script=$(cat <<EOF local init_script=$(cat <<EOF
set -euo pipefail set -euo pipefail
IFS=\$'\n\t' IFS=\$'\n\t'
cargo test --no-fail-fast --lib --test test_loader "$test" -- --show-output cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader "$test" -- --show-output
EOF EOF
) )
docker run --init --rm -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test sh -c "$init_script" docker run "${additional_flags[@]}" --init --rm -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script"
} }

View File

@@ -12,7 +12,7 @@ function main {
local test local test
while read test; do while read test; do
cargo test --no-fail-fast --test test_loader "$test" -- --show-output cargo test --no-default-features --features compare --no-fail-fast --test test_loader "$test" -- --show-output
done<<<"$test_names" done<<<"$test_names"
} }

23
scripts/time_parse.bash Executable file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
#
# Time running a single parse without invoking a compare with emacs.
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${PROFILE:="release-lto"}
cd "$DIR/../"
function main {
local additional_flags=()
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
PROFILE="debug"
else
additional_flags+=(--profile "$PROFILE")
fi
cargo build --no-default-features "${additional_flags[@]}"
time ./target/${PROFILE}/compare
}
main "${@}"

File diff suppressed because it is too large Load Diff

View File

@@ -3,3 +3,5 @@ mod parse;
mod util; mod util;
pub use diff::compare_document; pub use diff::compare_document;
pub use parse::emacs_parse_org_document; pub use parse::emacs_parse_org_document;
pub use parse::get_emacs_version;
pub use parse::get_org_mode_version;

View File

@@ -49,3 +49,40 @@ where
} }
output output
} }
pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
let elisp_script = r#"(progn
(message "%s" (version))
)"#;
let mut cmd = Command::new("emacs");
let proc = cmd
.arg("-q")
.arg("--no-site-file")
.arg("--no-splash")
.arg("--batch")
.arg("--eval")
.arg(elisp_script);
let out = proc.output()?;
out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?)
}
pub fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
let elisp_script = r#"(progn
(org-mode)
(message "%s" (org-version nil t nil))
)"#;
let mut cmd = Command::new("emacs");
let proc = cmd
.arg("-q")
.arg("--no-site-file")
.arg("--no-splash")
.arg("--batch")
.arg("--eval")
.arg(elisp_script);
let out = proc.output()?;
out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?)
}

View File

@@ -42,6 +42,37 @@ pub fn assert_bounds<'s, S: Source<'s>>(
emacs: &'s Token<'s>, emacs: &'s Token<'s>,
rust: &'s S, rust: &'s S,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
let standard_properties = get_standard_properties(emacs)?;
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_offsets(source, rust);
if (rust_begin + 1) != begin || (rust_end + 1) != end {
Err(format!("Rust bounds (in bytes) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin + 1, rust_end = rust_end + 1, emacs_begin=begin, emacs_end=end))?;
}
Ok(())
}
struct StandardProperties {
begin: Option<usize>,
#[allow(dead_code)]
post_affiliated: Option<usize>,
#[allow(dead_code)]
contents_begin: Option<usize>,
#[allow(dead_code)]
contents_end: Option<usize>,
end: Option<usize>,
#[allow(dead_code)]
post_blank: Option<usize>,
}
fn get_standard_properties<'s>(
emacs: &'s Token<'s>,
) -> Result<StandardProperties, Box<dyn std::error::Error>> {
let children = emacs.as_list()?; let children = emacs.as_list()?;
let attributes_child = children let attributes_child = children
.iter() .iter()
@@ -49,34 +80,80 @@ pub fn assert_bounds<'s, S: Source<'s>>(
.ok_or("Should have an attributes child.")?; .ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?; let attributes_map = attributes_child.as_map()?;
let standard_properties = attributes_map.get(":standard-properties"); let standard_properties = attributes_map.get(":standard-properties");
let (begin, end) = if standard_properties.is_some() { Ok(if standard_properties.is_some() {
let std_props = standard_properties let mut std_props = standard_properties
.expect("if statement proves its Some") .expect("if statement proves its Some")
.as_vector()?; .as_vector()?
let begin = std_props .into_iter();
.get(0) let begin = maybe_token_to_usize(std_props.next())?;
.ok_or("Missing first element in standard properties")? let post_affiliated = maybe_token_to_usize(std_props.next())?;
.as_atom()?; let contents_begin = maybe_token_to_usize(std_props.next())?;
let end = std_props let contents_end = maybe_token_to_usize(std_props.next())?;
.get(1) let end = maybe_token_to_usize(std_props.next())?;
.ok_or("Missing first element in standard properties")? let post_blank = maybe_token_to_usize(std_props.next())?;
.as_atom()?; StandardProperties {
(begin, end) begin,
post_affiliated,
contents_begin,
contents_end,
end,
post_blank,
}
} else { } else {
let begin = attributes_map let begin = maybe_token_to_usize(attributes_map.get(":begin").map(|token| *token))?;
.get(":begin") let end = maybe_token_to_usize(attributes_map.get(":end").map(|token| *token))?;
.ok_or("Missing :begin attribute.")? let contents_begin =
.as_atom()?; maybe_token_to_usize(attributes_map.get(":contents-begin").map(|token| *token))?;
let end = attributes_map let contents_end =
.get(":end") maybe_token_to_usize(attributes_map.get(":contents-end").map(|token| *token))?;
.ok_or("Missing :end attribute.")? let post_blank =
.as_atom()?; maybe_token_to_usize(attributes_map.get(":post-blank").map(|token| *token))?;
(begin, end) let post_affiliated =
}; maybe_token_to_usize(attributes_map.get(":post-affiliated").map(|token| *token))?;
let (rust_begin, rust_end) = get_offsets(source, rust); StandardProperties {
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end { begin,
Err(format!("Rust bounds ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin + 1, rust_end = rust_end + 1, emacs_begin=begin, emacs_end=end))?; post_affiliated,
} contents_begin,
contents_end,
Ok(()) end,
post_blank,
}
})
}
fn maybe_token_to_usize(
token: Option<&Token<'_>>,
) -> Result<Option<usize>, Box<dyn std::error::Error>> {
Ok(token
.map(|token| token.as_atom())
.map_or(Ok(None), |r| r.map(Some))?
.map(|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))?)
}
pub fn get_property<'s, 'x>(
emacs: &'s Token<'s>,
key: &'x str,
) -> Result<Option<&'s 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_map = attributes_child.as_map()?;
let prop = attributes_map
.get(key)
.ok_or(format!("Missing {} attribute.", key))?;
match prop.as_atom() {
Ok("nil") => return Ok(None),
_ => {}
};
Ok(Some(*prop))
} }

View File

@@ -4,6 +4,7 @@ use nom::IResult;
pub type Res<T, U> = IResult<T, U, CustomError<T>>; pub type Res<T, U> = IResult<T, U, CustomError<T>>;
// TODO: MyError probably shouldn't be based on the same type as the input type since it's used exclusively with static strings right now.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum CustomError<I> { pub enum CustomError<I> {
MyError(MyError<I>), MyError(MyError<I>),

View File

@@ -7,6 +7,10 @@ mod compare;
pub use compare::compare_document; pub use compare::compare_document;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
pub use compare::emacs_parse_org_document; pub use compare::emacs_parse_org_document;
#[cfg(feature = "compare")]
pub use compare::get_emacs_version;
#[cfg(feature = "compare")]
pub use compare::get_org_mode_version;
mod error; mod error;
pub mod parser; pub mod parser;

View File

@@ -1,14 +1,16 @@
#![feature(round_char_boundary)] #![feature(round_char_boundary)]
#[cfg(feature = "compare")]
use std::io::Read; use std::io::Read;
#[cfg(feature = "compare")]
use ::organic::parser::document; use ::organic::parser::document;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
use organic::compare_document; use organic::compare_document;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
use organic::emacs_parse_org_document; use organic::emacs_parse_org_document;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
use organic::get_emacs_version;
#[cfg(feature = "compare")]
use organic::get_org_mode_version;
#[cfg(feature = "compare")]
use organic::parser::sexp::sexp_with_padding; use organic::parser::sexp::sexp_with_padding;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
@@ -37,14 +39,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn main_body() -> Result<(), Box<dyn std::error::Error>> { fn main_body() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(not(feature = "compare"))]
let org_contents = "";
#[cfg(feature = "compare")]
let org_contents = read_stdin_to_string()?; let org_contents = read_stdin_to_string()?;
run_compare(org_contents) run_compare(org_contents)
} }
#[cfg(feature = "compare")]
fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> { fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
let mut stdin_contents = String::new(); let mut stdin_contents = String::new();
std::io::stdin() std::io::stdin()
@@ -55,18 +53,23 @@ fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> { fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
let (remaining, rust_parsed) = document(org_contents.as_ref()).expect("Org Parse failure"); let emacs_version = get_emacs_version()?;
let org_sexp = emacs_parse_org_document(org_contents.as_ref())?; let org_mode_version = get_org_mode_version()?;
let org_contents = org_contents.as_ref();
eprintln!("Using emacs version: {}", emacs_version.trim());
eprintln!("Using org-mode version: {}", org_mode_version.trim());
let (remaining, rust_parsed) = document(org_contents).map_err(|e| e.to_string())?;
let org_sexp = emacs_parse_org_document(org_contents)?;
let (_remaining, parsed_sexp) = let (_remaining, parsed_sexp) =
sexp_with_padding(org_sexp.as_str()).expect("Sexp Parse failure"); sexp_with_padding(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents.as_ref()); println!("{}\n\n\n", org_contents);
println!("{}", org_sexp); println!("{}", org_sexp);
println!("{:#?}", rust_parsed); println!("{:#?}", rust_parsed);
// We do the diffing after printing out both parsed forms in case the diffing panics // We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?; let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print()?; diff_result.print(org_contents)?;
if diff_result.is_bad() { if diff_result.is_bad() {
Err("Diff results do not match.")?; Err("Diff results do not match.")?;
@@ -79,7 +82,11 @@ fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error:
} }
#[cfg(not(feature = "compare"))] #[cfg(not(feature = "compare"))]
fn run_compare<P: AsRef<str>>(_org_contents: P) -> Result<(), Box<dyn std::error::Error>> { fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
println!("This program was built with compare disabled. Doing nothing."); eprintln!(
"This program was built with compare disabled. Only parsing with organic, not comparing."
);
let (_remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?;
println!("{:#?}", rust_parsed);
Ok(()) Ok(())
} }

View File

@@ -4,6 +4,7 @@ use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
@@ -16,7 +17,10 @@ use crate::parser::util::get_consumed;
use crate::parser::AngleLink; use crate::parser::AngleLink;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn angle_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, AngleLink<'s>> { pub fn angle_link<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, AngleLink<'s>> {
let (remaining, _) = tag("<")(input)?; let (remaining, _) = tag("<")(input)?;
let (remaining, proto) = protocol(context, remaining)?; let (remaining, proto) = protocol(context, remaining)?;
let (remaining, _separator) = tag(":")(remaining)?; let (remaining, _separator) = tag(":")(remaining)?;
@@ -26,18 +30,21 @@ pub fn angle_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
Ok(( Ok((
remaining, remaining,
AngleLink { AngleLink {
source, source: source.into(),
link_type: proto, link_type: proto.into(),
path, path: path.into(),
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_angle<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn path_angle<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &path_angle_end, exit_matcher: &path_angle_end,
})); }));
@@ -48,6 +55,9 @@ fn path_angle<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_angle_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn path_angle_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag(">")(input) tag(">")(input)
} }

View File

@@ -11,16 +11,17 @@ use nom::multi::many_till;
use nom::multi::separated_list1; use nom::multi::separated_list1;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::citation_reference::must_balance_bracket;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::Res; use crate::error::Res;
use crate::parser::citation_reference::citation_reference; use crate::parser::citation_reference::citation_reference;
use crate::parser::citation_reference::citation_reference_key; use crate::parser::citation_reference::citation_reference_key;
use crate::parser::citation_reference::get_bracket_depth;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
use crate::parser::object::Citation; use crate::parser::object::Citation;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::CitationBracket;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
@@ -29,26 +30,36 @@ use crate::parser::util::get_consumed;
use crate::parser::Object; use crate::parser::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Citation<'s>> { pub fn citation<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Citation<'s>> {
// TODO: Despite being a standard object, citations cannot exist inside the global prefix/suffix for other citations because citations must contain something that matches @key which is forbidden inside the global prefix/suffix. This TODO is to evaluate if its worth putting in an explicit check for this (which can be easily accomplished by checking the output of `get_bracket_depth()`). I suspect its not worth it because I expect, outside of intentionally crafted inputs, this parser will exit immediately inside a citation since it is unlikely to find the "[cite" substring inside a citation global prefix/suffix. // TODO: Despite being a standard object, citations cannot exist inside the global prefix/suffix for other citations because citations must contain something that matches @key which is forbidden inside the global prefix/suffix. This TODO is to evaluate if its worth putting in an explicit check for this (which can be easily accomplished by checking the output of `get_bracket_depth()`). I suspect its not worth it because I expect, outside of intentionally crafted inputs, this parser will exit immediately inside a citation since it is unlikely to find the "[cite" substring inside a citation global prefix/suffix.
let (remaining, _) = tag_no_case("[cite")(input)?; let (remaining, _) = tag_no_case("[cite")(input)?;
let (remaining, _) = opt(citestyle)(remaining)?; let (remaining, _) = opt(citestyle)(remaining)?;
let (remaining, _) = tag(":")(remaining)?; let (remaining, _) = tag(":")(remaining)?;
let (remaining, _prefix) = opt(parser_with_context!(global_prefix)(context))(remaining)?; let (remaining, _prefix) =
must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?;
let (remaining, _references) = let (remaining, _references) =
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?; separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
let (remaining, _suffix) = opt(tuple(( let (remaining, _suffix) = must_balance_bracket(opt(tuple((
tag(";"), tag(";"),
parser_with_context!(global_suffix)(context), parser_with_context!(global_suffix)(context),
)))(remaining)?; ))))(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Citation { source })) Ok((
remaining,
Citation {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn citestyle<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> { fn citestyle<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tuple((tag("/"), style))(input)?; let (remaining, _) = tuple((tag("/"), style))(input)?;
let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?; let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -56,14 +67,14 @@ fn citestyle<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn style<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> { fn style<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| { recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_-".contains(*c) c.is_alphanumeric() || "_-".contains(*c)
})))(input) })))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn variant<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> { fn variant<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| { recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_-/".contains(*c) c.is_alphanumeric() || "_-/".contains(*c)
})))(input) })))(input)
@@ -72,17 +83,13 @@ fn variant<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_prefix<'r, 's>( fn global_prefix<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient. let exit_with_depth = global_prefix_end(input.get_bracket_depth());
let parser_context = context let parser_context =
.with_additional_node(ContextElement::CitationBracket(CitationBracket { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
position: input, class: ExitClass::Gamma,
depth: 0, exit_matcher: &exit_with_depth,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &global_prefix_end,
})); }));
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
@@ -95,28 +102,27 @@ fn global_prefix<'r, 's>(
Ok((remaining, children)) Ok((remaining, children))
} }
fn global_prefix_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_global_prefix_end(context, input, starting_bracket_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn _global_prefix_end<'r, 's>(
let context_depth = get_bracket_depth(context) context: Context<'r, 's>,
.expect("This function should only be called from inside a citation."); input: OrgSource<'s>,
let text_since_context_entry = get_consumed(context_depth.position, input); starting_bracket_depth: BracketDepth,
let mut current_depth = context_depth.depth; ) -> Res<OrgSource<'s>, OrgSource<'s>> {
for c in text_since_context_entry.chars() { let current_depth = input.get_bracket_depth() - starting_bracket_depth;
match c { if current_depth < 0 {
'[' => { // This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
current_depth += 1; unreachable!("Exceeded citation global prefix bracket depth.")
}
']' if current_depth == 0 => {
panic!("Exceeded citation global prefix bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input); let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
@@ -130,17 +136,13 @@ fn global_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_suffix<'r, 's>( fn global_suffix<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient. let exit_with_depth = global_suffix_end(input.get_bracket_depth());
let parser_context = context let parser_context =
.with_additional_node(ContextElement::CitationBracket(CitationBracket { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
position: input, class: ExitClass::Gamma,
depth: 0, exit_matcher: &exit_with_depth,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &global_suffix_end,
})); }));
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
@@ -152,28 +154,27 @@ fn global_suffix<'r, 's>(
Ok((remaining, children)) Ok((remaining, children))
} }
fn global_suffix_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_global_suffix_end(context, input, starting_bracket_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn _global_suffix_end<'r, 's>(
let context_depth = get_bracket_depth(context) context: Context<'r, 's>,
.expect("This function should only be called from inside a citation."); input: OrgSource<'s>,
let text_since_context_entry = get_consumed(context_depth.position, input); starting_bracket_depth: BracketDepth,
let mut current_depth = context_depth.depth; ) -> Res<OrgSource<'s>, OrgSource<'s>> {
for c in text_since_context_entry.chars() { let current_depth = input.get_bracket_depth() - starting_bracket_depth;
match c { if current_depth < 0 {
'[' => { // This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
current_depth += 1; unreachable!("Exceeded citation global suffix bracket depth.")
}
']' if current_depth == 0 => {
panic!("Exceeded citation global suffix bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input); let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
@@ -188,24 +189,21 @@ fn global_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'
mod tests { mod tests {
use super::*; use super::*;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree; use crate::parser::parser_context::ContextTree;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::Source; use crate::parser::source::Source;
#[test] #[test]
fn citation_simple() { fn citation_simple() {
let input = "[cite:@foo]"; let input = OrgSource::new("[cite:@foo]");
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let paragraph_matcher = parser_with_context!(element(true))(&document_context);
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph"); let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
let first_paragraph = match first_paragraph { let first_paragraph = match first_paragraph {
crate::parser::Element::Paragraph(paragraph) => paragraph, crate::parser::Element::Paragraph(paragraph) => paragraph,
_ => panic!("Should be a paragraph!"), _ => panic!("Should be a paragraph!"),
}; };
assert_eq!(remaining, ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(first_paragraph.get_source(), "[cite:@foo]"); assert_eq!(first_paragraph.get_source(), "[cite:@foo]");
assert_eq!(first_paragraph.children.len(), 1); assert_eq!(first_paragraph.children.len(), 1);
assert_eq!( assert_eq!(

View File

@@ -10,13 +10,15 @@ use nom::multi::many_till;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
use crate::parser::object::CitationReference; use crate::parser::object::CitationReference;
use crate::parser::object_parser::minimal_set_object; use crate::parser::object_parser::minimal_set_object;
use crate::parser::parser_context::CitationBracket;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
@@ -28,21 +30,28 @@ use crate::parser::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation_reference<'r, 's>( pub fn citation_reference<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, CitationReference<'s>> { ) -> Res<OrgSource<'s>, CitationReference<'s>> {
let (remaining, _prefix) = opt(parser_with_context!(key_prefix)(context))(input)?; let (remaining, _prefix) =
must_balance_bracket(opt(parser_with_context!(key_prefix)(context)))(input)?;
let (remaining, _key) = parser_with_context!(citation_reference_key)(context)(remaining)?; let (remaining, _key) = parser_with_context!(citation_reference_key)(context)(remaining)?;
let (remaining, _suffix) = opt(parser_with_context!(key_suffix)(context))(remaining)?; let (remaining, _suffix) =
must_balance_bracket(opt(parser_with_context!(key_suffix)(context)))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, CitationReference { source })) Ok((
remaining,
CitationReference {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation_reference_key<'r, 's>( pub fn citation_reference_key<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(tuple(( let (remaining, source) = recognize(tuple((
tag("@"), tag("@"),
many1(verify( many1(verify(
@@ -59,16 +68,15 @@ pub fn citation_reference_key<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_prefix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Vec<Object<'s>>> { fn key_prefix<'r, 's>(
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient. context: Context<'r, 's>,
let parser_context = context input: OrgSource<'s>,
.with_additional_node(ContextElement::CitationBracket(CitationBracket { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
position: input, let exit_with_depth = key_prefix_end(input.get_bracket_depth());
depth: 0, let parser_context =
})) context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma,
class: ExitClass::Beta, exit_matcher: &exit_with_depth,
exit_matcher: &key_prefix_end,
})); }));
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
@@ -81,16 +89,15 @@ fn key_prefix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_suffix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Vec<Object<'s>>> { fn key_suffix<'r, 's>(
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient. context: Context<'r, 's>,
let parser_context = context input: OrgSource<'s>,
.with_additional_node(ContextElement::CitationBracket(CitationBracket { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
position: input, let exit_with_depth = key_suffix_end(input.get_bracket_depth());
depth: 0, let parser_context =
})) context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma,
class: ExitClass::Beta, exit_matcher: &exit_with_depth,
exit_matcher: &key_suffix_end,
})); }));
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
@@ -102,39 +109,27 @@ fn key_suffix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
Ok((remaining, children)) Ok((remaining, children))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn key_prefix_end(
pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r CitationBracket<'s>> { starting_bracket_depth: BracketDepth,
for node in context.iter() { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
match node.get_data() { move |context: Context, input: OrgSource<'_>| {
ContextElement::CitationBracket(depth) => return Some(depth), _key_prefix_end(context, input, starting_bracket_depth)
_ => {}
}
} }
None
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn _key_prefix_end<'r, 's>(
let context_depth = get_bracket_depth(context) context: Context<'r, 's>,
.expect("This function should only be called from inside a citation reference."); input: OrgSource<'s>,
let text_since_context_entry = get_consumed(context_depth.position, input); starting_bracket_depth: BracketDepth,
let mut current_depth = context_depth.depth; ) -> Res<OrgSource<'s>, OrgSource<'s>> {
for c in text_since_context_entry.chars() { let current_depth = input.get_bracket_depth() - starting_bracket_depth;
match c { if current_depth < 0 {
'[' => { // This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
current_depth += 1; unreachable!("Exceeded citation key prefix bracket depth.")
}
']' if current_depth == 0 => {
panic!("Exceeded citation reference key prefix bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input); let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
@@ -145,31 +140,48 @@ fn key_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
))(input) ))(input)
} }
fn key_suffix_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_key_suffix_end(context, input, starting_bracket_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn _key_suffix_end<'r, 's>(
let context_depth = get_bracket_depth(context) _context: Context<'r, 's>,
.expect("This function should only be called from inside a citation reference."); input: OrgSource<'s>,
let text_since_context_entry = get_consumed(context_depth.position, input); starting_bracket_depth: BracketDepth,
let mut current_depth = context_depth.depth; ) -> Res<OrgSource<'s>, OrgSource<'s>> {
for c in text_since_context_entry.chars() { let current_depth = input.get_bracket_depth() - starting_bracket_depth;
match c { if current_depth < 0 {
'[' => { // This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
current_depth += 1; unreachable!("Exceeded citation key suffix bracket depth.")
}
']' if current_depth == 0 => {
panic!("Exceeded citation reference key prefix bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input); let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
} }
tag(";")(input) tag(";")(input)
} }
pub fn must_balance_bracket<'s, F, O>(
mut inner: F,
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O>
where
F: FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O>,
{
move |input: OrgSource<'_>| {
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(),
))));
}
Ok((remaining, output))
}
}

View File

@@ -11,6 +11,7 @@ use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
@@ -19,8 +20,11 @@ use crate::parser::util::start_of_line;
use crate::parser::Clock; use crate::parser::Clock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn clock<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Clock<'s>> { pub fn clock<'r, 's>(
start_of_line(context, input)?; context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Clock<'s>> {
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, _clock) = tag_no_case("clock:")(remaining)?; let (remaining, _clock) = tag_no_case("clock:")(remaining)?;
let (remaining, _gap_whitespace) = space1(remaining)?; let (remaining, _gap_whitespace) = space1(remaining)?;
@@ -31,14 +35,19 @@ pub fn clock<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, C
))(remaining)?; ))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Clock { source })) Ok((
remaining,
Clock {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp_range_duration<'r, 's>( fn inactive_timestamp_range_duration<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple(( recognize(tuple((
tag("["), tag("["),
is_not("\r\n]"), is_not("\r\n]"),
@@ -50,14 +59,17 @@ fn inactive_timestamp_range_duration<'r, 's>(
space1, space1,
digit1, digit1,
tag(":"), tag(":"),
verify(digit1, |mm: &str| mm.len() == 2), verify(digit1, |mm: &OrgSource<'_>| mm.len() == 2),
space0, space0,
alt((line_ending, eof)), alt((line_ending, eof)),
)))(input) )))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn inactive_timestamp<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple(( recognize(tuple((
tag("["), tag("["),
is_not("\r\n]"), is_not("\r\n]"),

View File

@@ -11,6 +11,7 @@ use nom::multi::many0;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::get_consumed; use super::util::get_consumed;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
@@ -24,10 +25,13 @@ use crate::parser::util::start_of_line;
use crate::parser::Comment; use crate::parser::Comment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn comment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Comment<'s>> { pub fn comment<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Comment<'s>> {
if immediate_in_section(context, "comment") { if immediate_in_section(context, "comment") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element", "Cannot nest objects of the same element".into(),
)))); ))));
} }
let parser_context = context.with_additional_node(ContextElement::Context("comment")); let parser_context = context.with_additional_node(ContextElement::Context("comment"));
@@ -38,12 +42,20 @@ pub fn comment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?; many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Comment { source })) Ok((
remaining,
Comment {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn comment_line<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn comment_line<'r, 's>(
start_of_line(context, input)?; _context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let (remaining, _indent) = space0(input)?; let (remaining, _indent) = space0(input)?;
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple(( let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple((
tag("#"), tag("#"),
@@ -57,22 +69,21 @@ fn comment_line<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree; use crate::parser::parser_context::ContextTree;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
#[test] #[test]
fn require_space_after_hash() { fn require_space_after_hash() {
let input = "# Comment line let input = OrgSource::new(
"# Comment line
#not a comment #not a comment
# Comment again"; # Comment again",
);
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let comment_matcher = parser_with_context!(comment)(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let comment_matcher = parser_with_context!(comment)(&document_context);
let (remaining, first_comment) = comment_matcher(input).expect("Parse first comment"); let (remaining, first_comment) = comment_matcher(input).expect("Parse first comment");
assert_eq!( assert_eq!(
remaining, Into::<&str>::into(remaining),
r#"#not a comment r#"#not a comment
# Comment again"# # Comment again"#
); );

View File

@@ -6,6 +6,7 @@ use nom::combinator::eof;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::sexp::sexp; use super::sexp::sexp;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
@@ -14,8 +15,11 @@ use crate::parser::util::start_of_line;
use crate::parser::DiarySexp; use crate::parser::DiarySexp;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn diary_sexp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, DiarySexp<'s>> { pub fn diary_sexp<'r, 's>(
start_of_line(context, input)?; _context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, DiarySexp<'s>> {
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, _clock) = tag("%%")(remaining)?; let (remaining, _clock) = tag("%%")(remaining)?;
let (remaining, _gap_whitespace) = space0(remaining)?; let (remaining, _gap_whitespace) = space0(remaining)?;
@@ -24,5 +28,10 @@ pub fn diary_sexp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?; recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, DiarySexp { source })) Ok((
remaining,
DiarySexp {
source: source.into(),
},
))
} }

View File

@@ -1,6 +1,8 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::map; use nom::combinator::map;
@@ -12,10 +14,13 @@ use nom::multi::many0;
use nom::multi::many1; use nom::multi::many1;
use nom::multi::many1_count; use nom::multi::many1_count;
use nom::multi::many_till; use nom::multi::many_till;
use nom::multi::separated_list1;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::element::Element; use super::element::Element;
use super::object::Object; use super::object::Object;
use super::org_source::convert_error;
use super::org_source::OrgSource;
use super::parser_with_context::parser_with_context; use super::parser_with_context::parser_with_context;
use super::source::Source; use super::source::Source;
use super::token::AllTokensIterator; use super::token::AllTokensIterator;
@@ -48,7 +53,10 @@ pub struct Document<'s> {
pub struct Heading<'s> { pub struct Heading<'s> {
pub source: &'s str, pub source: &'s str,
pub stars: usize, pub stars: usize,
pub todo_keyword: Option<&'s str>,
// TODO: add todo-type enum
pub title: Vec<Object<'s>>, pub title: Vec<Object<'s>>,
pub tags: Vec<&'s str>,
pub children: Vec<DocumentElement<'s>>, pub children: Vec<DocumentElement<'s>>,
} }
@@ -95,9 +103,10 @@ impl<'s> Source<'s> for Heading<'s> {
#[allow(dead_code)] #[allow(dead_code)]
pub fn document(input: &str) -> Res<&str, Document> { pub fn document(input: &str) -> Res<&str, Document> {
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let wrapped_input = OrgSource::new(input);
initial_context.with_additional_node(ContextElement::DocumentRoot(input)); let (remaining, document) = _document(&initial_context, wrapped_input)
let (remaining, document) = _document(&document_context, input)?; .map(|(rem, out)| (Into::<&str>::into(rem), out))
.map_err(convert_error)?;
{ {
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets. // 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<'_>>> = document let all_radio_targets: Vec<&Vec<Object<'_>>> = document
@@ -113,19 +122,24 @@ pub fn document(input: &str) -> Res<&str, Document> {
.map(|rt| &rt.children) .map(|rt| &rt.children)
.collect(); .collect();
if !all_radio_targets.is_empty() { if !all_radio_targets.is_empty() {
let document_context = document_context let initial_context = initial_context
.with_additional_node(ContextElement::RadioTarget(all_radio_targets)); .with_additional_node(ContextElement::RadioTarget(all_radio_targets));
let (remaining, document) = _document(&document_context, input)?; let (remaining, document) = _document(&initial_context, wrapped_input)
return Ok((remaining, document)); .map(|(rem, out)| (Into::<&str>::into(rem), out))
.map_err(convert_error)?;
return Ok((remaining.into(), document));
} }
} }
Ok((remaining, document)) Ok((remaining.into(), document))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn _document<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Document<'s>> { fn _document<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Document<'s>> {
let zeroth_section_matcher = parser_with_context!(zeroth_section)(context); let zeroth_section_matcher = parser_with_context!(zeroth_section)(context);
let heading_matcher = parser_with_context!(heading)(context); let heading_matcher = parser_with_context!(heading(0))(context);
let (remaining, _blank_lines) = many0(blank_line)(input)?; let (remaining, _blank_lines) = many0(blank_line)(input)?;
let (remaining, zeroth_section) = opt(zeroth_section_matcher)(remaining)?; let (remaining, zeroth_section) = opt(zeroth_section_matcher)(remaining)?;
let (remaining, children) = many0(heading_matcher)(remaining)?; let (remaining, children) = many0(heading_matcher)(remaining)?;
@@ -133,7 +147,7 @@ pub fn _document<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
Ok(( Ok((
remaining, remaining,
Document { Document {
source, source: source.into(),
zeroth_section, zeroth_section,
children, children,
}, },
@@ -141,7 +155,10 @@ pub fn _document<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn zeroth_section<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Section<'s>> { fn zeroth_section<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser // TODO: The zeroth section is specialized so it probably needs its own parser
let parser_context = context let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
@@ -182,11 +199,20 @@ fn zeroth_section<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Section { source, children })) Ok((
remaining,
Section {
source: source.into(),
children,
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section<'r, 's>(context: Context<'r, 's>, mut input: &'s str) -> Res<&'s str, Section<'s>> { fn section<'r, 's>(
context: Context<'r, 's>,
mut input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser // TODO: The zeroth section is specialized so it probably needs its own parser
let parser_context = context let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
@@ -222,67 +248,161 @@ fn section<'r, 's>(context: Context<'r, 's>, mut input: &'s str) -> Res<&'s str,
let (remaining, _trailing_ws) = let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Section { source, children }))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
let headline_matcher = parser_with_context!(headline)(context);
recognize(headline_matcher)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn heading<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?;
let (remaining, (star_count, _ws, title)) = headline(context, input)?;
let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading)(context);
let (remaining, children) = many0(alt((
map(
verify(heading_matcher, |h| h.stars > star_count),
DocumentElement::Heading,
),
map(section_matcher, DocumentElement::Section),
)))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Heading { Section {
source, source: source.into(),
stars: star_count,
title,
children, children,
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'r, 's>( fn section_end<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, (usize, &'s str, Vec<Object<'s>>)> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = recognize(detect_headline)(input)
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { }
class: ExitClass::Document,
exit_matcher: &headline_end,
}));
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let start_of_line_matcher = parser_with_context!(start_of_line)(&parser_context);
let (remaining, (_sol, star_count, ws, title, _line_ending)) = tuple(( const fn heading(
start_of_line_matcher, parent_stars: usize,
many1_count(tag("*")), ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Heading<'s>> {
space1, move |context: Context, input: OrgSource<'_>| _heading(context, input, parent_stars)
many1(standard_set_object_matcher),
alt((line_ending, eof)),
))(input)?;
Ok((remaining, (star_count, ws, title)))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline_end<'r, 's>(_context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn _heading<'r, 's>(
line_ending(input) context: Context<'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<OrgSource<'s>, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?;
let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) =
headline(context, input, parent_stars)?;
let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading(star_count))(context);
let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, mut children) =
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
if let Some(section) = maybe_section {
children.insert(0, section);
}
let remaining = if children.is_empty() {
// Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?;
remain
} else {
remaining
};
let source = get_consumed(input, remaining);
Ok((
remaining,
Heading {
source: source.into(),
stars: star_count,
todo_keyword: maybe_todo_keyword
.map(|(todo_keyword, _ws)| Into::<&str>::into(todo_keyword)),
title,
tags: heading_tags,
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, many1(tag("*")), space1))(input)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<
OrgSource<'s>,
(
usize,
OrgSource<'s>,
Option<(OrgSource<'s>, OrgSource<'s>)>,
Vec<Object<'s>>,
Vec<&'s str>,
),
> {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &headline_title_end,
}));
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let (
remaining,
(_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending),
) = tuple((
start_of_line,
verify(many1_count(tag("*")), |star_count| {
*star_count > parent_stars
}),
space1,
opt(tuple((heading_keyword, space1))),
many1(standard_set_object_matcher),
opt(tuple((space0, tags))),
space0,
alt((line_ending, eof)),
))(input)?;
Ok((
remaining,
(
star_count,
ws,
maybe_todo_keyword,
title,
maybe_tags
.map(|(_ws, tags)| {
tags.into_iter()
.map(|single_tag| Into::<&str>::into(single_tag))
.collect()
})
.unwrap_or(Vec::new()),
),
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline_title_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
opt(tuple((space0, tags, space0))),
alt((line_ending, eof)),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn tags<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
let (remaining, (_open, tags, _close)) =
tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?;
Ok((remaining, tags))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_@#%".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn heading_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should take into account the value of "#+TODO:" ref https://orgmode.org/manual/Per_002dfile-keywords.html and possibly the configurable variable org-todo-keywords ref https://orgmode.org/manual/Workflow-states.html. Case is significant.
alt((tag("TODO"), tag("DONE")))(input)
} }
impl<'s> Document<'s> { impl<'s> Document<'s> {

View File

@@ -10,6 +10,7 @@ use nom::combinator::recognize;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -31,13 +32,16 @@ use crate::parser::Element;
use crate::parser::Paragraph; use crate::parser::Paragraph;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn drawer<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Drawer<'s>> { pub fn drawer<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Drawer<'s>> {
if immediate_in_section(context, "drawer") { if immediate_in_section(context, "drawer") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element", "Cannot nest objects of the same element".into(),
)))); ))));
} }
start_of_line(context, input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_open_colon, drawer_name, _close_colon, _new_line)) = tuple(( let (remaining, (_open_colon, drawer_name, _close_colon, _new_line)) = tuple((
tag(":"), tag(":"),
@@ -63,9 +67,9 @@ pub fn drawer<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
))(remaining) ))(remaining)
{ {
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => { Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
let mut element = Element::Paragraph(Paragraph::of_text(first_line)); let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
let source = get_consumed(remaining, remain); let source = get_consumed(remaining, remain);
element.set_source(source); element.set_source(source.into());
(remain, vec![element]) (remain, vec![element])
} }
Err(_) => { Err(_) => {
@@ -81,21 +85,24 @@ pub fn drawer<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
Ok(( Ok((
remaining, remaining,
Drawer { Drawer {
source, source: source.into(),
name: drawer_name, name: drawer_name.into(),
children, children,
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> { fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input) take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn drawer_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn drawer_end<'r, 's>(
start_of_line(context, input)?; _context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
recognize(tuple(( recognize(tuple((
space0, space0,
tag_no_case(":end:"), tag_no_case(":end:"),

View File

@@ -11,6 +11,7 @@ use nom::combinator::recognize;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -33,15 +34,15 @@ use crate::parser::Element;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn dynamic_block<'r, 's>( pub fn dynamic_block<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, DynamicBlock<'s>> { ) -> Res<OrgSource<'s>, DynamicBlock<'s>> {
// TODO: Do I need to differentiate between different dynamic block types. // TODO: Do I need to differentiate between different dynamic block types.
if immediate_in_section(context, "dynamic block") { if immediate_in_section(context, "dynamic block") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element", "Cannot nest objects of the same element".into(),
)))); ))));
} }
start_of_line(context, input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, name, parameters, _ws)) = tuple(( let (remaining, (_begin, name, parameters, _ws)) = tuple((
recognize(tuple((tag_no_case("#+begin:"), space1))), recognize(tuple((tag_no_case("#+begin:"), space1))),
@@ -69,9 +70,9 @@ pub fn dynamic_block<'r, 's>(
))(remaining) ))(remaining)
{ {
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => { Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
let mut element = Element::Paragraph(Paragraph::of_text(first_line)); let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
let source = get_consumed(remaining, remain); let source = get_consumed(remaining, remain);
element.set_source(source); element.set_source(source.into());
(remain, vec![element]) (remain, vec![element])
} }
Err(_) => { Err(_) => {
@@ -86,27 +87,30 @@ pub fn dynamic_block<'r, 's>(
Ok(( Ok((
remaining, remaining,
DynamicBlock { DynamicBlock {
source, source: source.into(),
name, name: name.into(),
parameters, parameters: parameters.map(|val| val.into()),
children, children,
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> { fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not(" \t\r\n")(input) is_not(" \t\r\n")(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn parameters<'s>(input: &'s str) -> Res<&'s str, &'s str> { fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not("\r\n")(input) is_not("\r\n")(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dynamic_block_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn dynamic_block_end<'r, 's>(
start_of_line(context, input)?; _context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let (remaining, source) = recognize(tuple(( let (remaining, source) = recognize(tuple((
space0, space0,
tag_no_case("#+end:"), tag_no_case("#+end:"),

View File

@@ -12,6 +12,7 @@ use super::fixed_width_area::fixed_width_area;
use super::footnote_definition::footnote_definition; use super::footnote_definition::footnote_definition;
use super::greater_block::greater_block; use super::greater_block::greater_block;
use super::horizontal_rule::horizontal_rule; use super::horizontal_rule::horizontal_rule;
use super::keyword::affiliated_keyword;
use super::keyword::keyword; use super::keyword::keyword;
use super::latex_environment::latex_environment; use super::latex_environment::latex_environment;
use super::lesser_block::comment_block; use super::lesser_block::comment_block;
@@ -19,28 +20,32 @@ use super::lesser_block::example_block;
use super::lesser_block::export_block; use super::lesser_block::export_block;
use super::lesser_block::src_block; use super::lesser_block::src_block;
use super::lesser_block::verse_block; use super::lesser_block::verse_block;
use super::org_source::OrgSource;
use super::paragraph::paragraph; use super::paragraph::paragraph;
use super::plain_list::detect_plain_list;
use super::plain_list::plain_list; use super::plain_list::plain_list;
use super::source::SetSource; use super::source::SetSource;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::table::org_mode_table; use crate::parser::table::org_mode_table;
pub fn element( pub const fn element(
can_be_paragraph: bool, can_be_paragraph: bool,
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, Element<'s>> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Element<'s>> {
move |context: Context, input: &str| _element(context, input, can_be_paragraph) move |context: Context, input: OrgSource<'_>| _element(context, input, can_be_paragraph)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _element<'r, 's>( fn _element<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<&'s str, Element<'s>> { ) -> Res<OrgSource<'s>, Element<'s>> {
let plain_list_matcher = parser_with_context!(plain_list)(context); let plain_list_matcher = parser_with_context!(plain_list)(context);
let greater_block_matcher = parser_with_context!(greater_block)(context); let greater_block_matcher = parser_with_context!(greater_block)(context);
let dynamic_block_matcher = parser_with_context!(dynamic_block)(context); let dynamic_block_matcher = parser_with_context!(dynamic_block)(context);
@@ -58,10 +63,12 @@ fn _element<'r, 's>(
let fixed_width_area_matcher = parser_with_context!(fixed_width_area)(context); let fixed_width_area_matcher = parser_with_context!(fixed_width_area)(context);
let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context); let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context);
let keyword_matcher = parser_with_context!(keyword)(context); let keyword_matcher = parser_with_context!(keyword)(context);
let affiliated_keyword_matcher = parser_with_context!(affiliated_keyword)(context);
let paragraph_matcher = parser_with_context!(paragraph)(context); let paragraph_matcher = parser_with_context!(paragraph)(context);
let latex_environment_matcher = parser_with_context!(latex_environment)(context); let latex_environment_matcher = parser_with_context!(latex_environment)(context);
let (remaining, mut affiliated_keywords) = many0(keyword_matcher)(input)?; // TODO: Affiliated keywords cannot be on comments, clocks, headings, inlinetasks, items, node properties, planning, property drawers, sections, and table rows
let (remaining, mut affiliated_keywords) = many0(affiliated_keyword_matcher)(input)?;
let (remaining, mut element) = match alt(( let (remaining, mut element) = match alt((
map(plain_list_matcher, Element::PlainList), map(plain_list_matcher, Element::PlainList),
map(greater_block_matcher, Element::GreaterBlock), map(greater_block_matcher, Element::GreaterBlock),
@@ -80,6 +87,7 @@ fn _element<'r, 's>(
map(fixed_width_area_matcher, Element::FixedWidthArea), map(fixed_width_area_matcher, Element::FixedWidthArea),
map(horizontal_rule_matcher, Element::HorizontalRule), map(horizontal_rule_matcher, Element::HorizontalRule),
map(latex_environment_matcher, Element::LatexEnvironment), map(latex_environment_matcher, Element::LatexEnvironment),
map(keyword_matcher, Element::Keyword),
))(remaining) ))(remaining)
{ {
the_ok @ Ok(_) => the_ok, the_ok @ Ok(_) => the_ok,
@@ -89,12 +97,12 @@ fn _element<'r, 's>(
the_ok @ Ok(_) => the_ok, the_ok @ Ok(_) => the_ok,
Err(_) => { Err(_) => {
affiliated_keywords.clear(); affiliated_keywords.clear();
map(keyword_matcher, Element::Keyword)(input) map(affiliated_keyword_matcher, Element::Keyword)(input)
} }
} }
} else { } else {
affiliated_keywords.clear(); affiliated_keywords.clear();
map(keyword_matcher, Element::Keyword)(input) map(affiliated_keyword_matcher, Element::Keyword)(input)
} }
} }
}?; }?;
@@ -103,7 +111,30 @@ fn _element<'r, 's>(
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
element.set_source(source); element.set_source(source.into());
Ok((remaining, element)) Ok((remaining, element))
} }
pub const fn detect_element(
can_be_paragraph: bool,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
move |context: Context, input: OrgSource<'_>| _detect_element(context, input, can_be_paragraph)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _detect_element<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> {
if detect_plain_list(context, input).is_ok() {
return Ok((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(),
))));
}

View File

@@ -7,43 +7,474 @@ use nom::combinator::eof;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::object::Entity; use crate::parser::object::Entity;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
// TODO: Make this a user-provided variable corresponding to elisp's org-entities
const ORG_ENTITIES: [&'static str; 413] = [
"Agrave",
"agrave",
"Aacute",
"aacute",
"Acirc",
"acirc",
"Amacr",
"amacr",
"Atilde",
"atilde",
"Auml",
"auml",
"Aring",
"AA",
"aring",
"AElig",
"aelig",
"Ccedil",
"ccedil",
"Egrave",
"egrave",
"Eacute",
"eacute",
"Ecirc",
"ecirc",
"Euml",
"euml",
"Igrave",
"igrave",
"Iacute",
"iacute",
"Idot",
"inodot",
"Icirc",
"icirc",
"Iuml",
"iuml",
"Ntilde",
"ntilde",
"Ograve",
"ograve",
"Oacute",
"oacute",
"Ocirc",
"ocirc",
"Otilde",
"otilde",
"Ouml",
"ouml",
"Oslash",
"oslash",
"OElig",
"oelig",
"Scaron",
"scaron",
"szlig",
"Ugrave",
"ugrave",
"Uacute",
"uacute",
"Ucirc",
"ucirc",
"Uuml",
"uuml",
"Yacute",
"yacute",
"Yuml",
"yuml",
"fnof",
"real",
"image",
"weierp",
"ell",
"imath",
"jmath",
"Alpha",
"alpha",
"Beta",
"beta",
"Gamma",
"gamma",
"Delta",
"delta",
"Epsilon",
"epsilon",
"varepsilon",
"Zeta",
"zeta",
"Eta",
"eta",
"Theta",
"theta",
"thetasym",
"vartheta",
"Iota",
"iota",
"Kappa",
"kappa",
"Lambda",
"lambda",
"Mu",
"mu",
"nu",
"Nu",
"Xi",
"xi",
"Omicron",
"omicron",
"Pi",
"pi",
"Rho",
"rho",
"Sigma",
"sigma",
"sigmaf",
"varsigma",
"Tau",
"Upsilon",
"upsih",
"upsilon",
"Phi",
"phi",
"varphi",
"Chi",
"chi",
"acutex",
"Psi",
"psi",
"tau",
"Omega",
"omega",
"piv",
"varpi",
"partial",
"alefsym",
"aleph",
"gimel",
"beth",
"dalet",
"ETH",
"eth",
"THORN",
"thorn",
"dots",
"cdots",
"hellip",
"middot",
"iexcl",
"iquest",
"shy",
"ndash",
"mdash",
"quot",
"acute",
"ldquo",
"rdquo",
"bdquo",
"lsquo",
"rsquo",
"sbquo",
"laquo",
"raquo",
"lsaquo",
"rsaquo",
"circ",
"vert",
"vbar",
"brvbar",
"S",
"sect",
"amp",
"lt",
"gt",
"tilde",
"slash",
"plus",
"under",
"equal",
"asciicirc",
"dagger",
"dag",
"Dagger",
"ddag",
"nbsp",
"ensp",
"emsp",
"thinsp",
"curren",
"cent",
"pound",
"yen",
"euro",
"EUR",
"dollar",
"USD",
"copy",
"reg",
"trade",
"minus",
"pm",
"plusmn",
"times",
"frasl",
"colon",
"div",
"frac12",
"frac14",
"frac34",
"permil",
"sup1",
"sup2",
"sup3",
"radic",
"sum",
"prod",
"micro",
"macr",
"deg",
"prime",
"Prime",
"infin",
"infty",
"prop",
"propto",
"not",
"neg",
"land",
"wedge",
"lor",
"vee",
"cap",
"cup",
"smile",
"frown",
"int",
"therefore",
"there4",
"because",
"sim",
"cong",
"simeq",
"asymp",
"approx",
"ne",
"neq",
"equiv",
"triangleq",
"le",
"leq",
"ge",
"geq",
"lessgtr",
"lesseqgtr",
"ll",
"Ll",
"lll",
"gg",
"Gg",
"ggg",
"prec",
"preceq",
"preccurlyeq",
"succ",
"succeq",
"succcurlyeq",
"sub",
"subset",
"sup",
"supset",
"nsub",
"sube",
"nsup",
"supe",
"setminus",
"forall",
"exist",
"exists",
"nexist",
"nexists",
"empty",
"emptyset",
"isin",
"in",
"notin",
"ni",
"nabla",
"ang",
"angle",
"perp",
"parallel",
"sdot",
"cdot",
"lceil",
"rceil",
"lfloor",
"rfloor",
"lang",
"rang",
"langle",
"rangle",
"hbar",
"mho",
"larr",
"leftarrow",
"gets",
"lArr",
"Leftarrow",
"uarr",
"uparrow",
"uArr",
"Uparrow",
"rarr",
"to",
"rightarrow",
"rArr",
"Rightarrow",
"darr",
"downarrow",
"dArr",
"Downarrow",
"harr",
"leftrightarrow",
"hArr",
"Leftrightarrow",
"crarr",
"hookleftarrow",
"arccos",
"arcsin",
"arctan",
"arg",
"cos",
"cosh",
"cot",
"coth",
"csc",
"deg",
"det",
"dim",
"exp",
"gcd",
"hom",
"inf",
"ker",
"lg",
"lim",
"liminf",
"limsup",
"ln",
"log",
"max",
"min",
"Pr",
"sec",
"sin",
"sinh",
"sup",
"tan",
"tanh",
"bull",
"bullet",
"star",
"lowast",
"ast",
"odot",
"oplus",
"otimes",
"check",
"checkmark",
"para",
"ordf",
"ordm",
"cedil",
"oline",
"uml",
"zwnj",
"zwj",
"lrm",
"rlm",
"smiley",
"blacksmile",
"sad",
"frowny",
"clubs",
"clubsuit",
"spades",
"spadesuit",
"hearts",
"heartsuit",
"diams",
"diamondsuit",
"diamond",
"Diamond",
"loz",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn entity<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Entity<'s>> { pub fn entity<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Entity<'s>> {
let (remaining, _) = tag("\\")(input)?; let (remaining, _) = tag("\\")(input)?;
let (remaining, entity_name) = name(context, remaining)?; let (remaining, entity_name) = name(context, remaining)?;
let (remaining, _) = alt(( let (remaining, _) = alt((tag("{}"), peek(recognize(entity_end))))(remaining)?;
tag("{}"),
peek(recognize(parser_with_context!(entity_end)(context))),
))(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Entity { Entity {
source, source: source.into(),
entity_name, entity_name: entity_name.into(),
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn name<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should be defined by org-entities and optionally org-entities-user // TODO: This should be defined by org-entities and optionally org-entities-user
for entity in ORG_ENTITIES {
let result = tag_no_case::<_, _, CustomError<_>>(entity)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, ent));
}
Err(_) => {}
}
}
// TODO: Add the rest of the entities, this is a very incomplete list Err(nom::Err::Error(CustomError::MyError(MyError(
let (remaining, proto) = alt((alt((tag_no_case("delta"), tag_no_case("pi"))),))(input)?; "NoEntity".into(),
Ok((remaining, proto)) ))))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn entity_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> { fn entity_end<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (remaining, _) = alt((eof, recognize(satisfy(|c| !c.is_alphabetic()))))(input)?; let (remaining, _) = alt((eof, recognize(satisfy(|c| !c.is_alphabetic()))))(input)?;
Ok((remaining, ())) Ok((remaining, ()))

View File

@@ -8,6 +8,9 @@ pub enum ExitClass {
/// Elements who cede priority to alpha elements when matching. /// Elements who cede priority to alpha elements when matching.
Beta = 300, Beta = 300,
/// Elements who cede priority to alpha and beta elements when matching.
Gamma = 4000,
} }
impl std::fmt::Display for ExitClass { impl std::fmt::Display for ExitClass {

View File

@@ -7,6 +7,7 @@ use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
@@ -20,13 +21,13 @@ use crate::parser::ExportSnippet;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn export_snippet<'r, 's>( pub fn export_snippet<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, ExportSnippet<'s>> { ) -> Res<OrgSource<'s>, ExportSnippet<'s>> {
let (remaining, _) = tag("@@")(input)?; let (remaining, _) = tag("@@")(input)?;
let (remaining, backend_name) = backend(context, remaining)?; let (remaining, backend_name) = backend(context, remaining)?;
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &export_snippet_end, exit_matcher: &export_snippet_end,
})); }));
let (remaining, backend_contents) = opt(tuple(( let (remaining, backend_contents) = opt(tuple((
@@ -38,15 +39,18 @@ pub fn export_snippet<'r, 's>(
Ok(( Ok((
remaining, remaining,
ExportSnippet { ExportSnippet {
source, source: source.into(),
backend: backend_name, backend: backend_name.into(),
contents: backend_contents.map(|(_colon, backend_contents)| backend_contents), contents: backend_contents.map(|(_colon, backend_contents)| backend_contents.into()),
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn backend<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn backend<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, backend_name) = let (remaining, backend_name) =
recognize(many1(verify(anychar, |c| c.is_alphanumeric() || *c == '-')))(input)?; recognize(many1(verify(anychar, |c| c.is_alphanumeric() || *c == '-')))(input)?;
@@ -54,7 +58,10 @@ fn backend<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn contents<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn contents<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(verify( let (remaining, source) = recognize(verify(
many_till(anychar, parser_with_context!(exit_matcher_parser)(context)), many_till(anychar, parser_with_context!(exit_matcher_parser)(context)),
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),
@@ -63,6 +70,9 @@ fn contents<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn export_snippet_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn export_snippet_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag("@@")(input) tag("@@")(input)
} }

View File

@@ -11,6 +11,7 @@ use nom::multi::many0;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
@@ -22,8 +23,8 @@ use crate::parser::FixedWidthArea;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn fixed_width_area<'r, 's>( pub fn fixed_width_area<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, FixedWidthArea<'s>> { ) -> Res<OrgSource<'s>, FixedWidthArea<'s>> {
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context); 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 exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, _first_line) = fixed_width_area_line_matcher(input)?; let (remaining, _first_line) = fixed_width_area_line_matcher(input)?;
@@ -31,15 +32,20 @@ pub fn fixed_width_area<'r, 's>(
many0(preceded(not(exit_matcher), fixed_width_area_line_matcher))(remaining)?; many0(preceded(not(exit_matcher), fixed_width_area_line_matcher))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, FixedWidthArea { source })) Ok((
remaining,
FixedWidthArea {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn fixed_width_area_line<'r, 's>( fn fixed_width_area_line<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(context, input)?; start_of_line(input)?;
let (remaining, _indent) = space0(input)?; let (remaining, _indent) = space0(input)?;
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple(( let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple((
tag(":"), tag(":"),

View File

@@ -10,6 +10,7 @@ use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::WORD_CONSTITUENT_CHARACTERS; use super::util::WORD_CONSTITUENT_CHARACTERS;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
@@ -31,14 +32,14 @@ use crate::parser::util::start_of_line;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn footnote_definition<'r, 's>( pub fn footnote_definition<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, FootnoteDefinition<'s>> { ) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> {
if immediate_in_section(context, "footnote definition") { if immediate_in_section(context, "footnote definition") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element", "Cannot nest objects of the same element".into(),
)))); ))));
} }
start_of_line(context, input)?; start_of_line(input)?;
// Cannot be indented. // Cannot be indented.
let (remaining, (_lead_in, lbl, _lead_out, _ws)) = let (remaining, (_lead_in, lbl, _lead_out, _ws)) =
tuple((tag_no_case("[fn:"), label, tag("]"), space0))(input)?; tuple((tag_no_case("[fn:"), label, tag("]"), space0))(input)?;
@@ -59,15 +60,15 @@ pub fn footnote_definition<'r, 's>(
Ok(( Ok((
remaining, remaining,
FootnoteDefinition { FootnoteDefinition {
source, source: source.into(),
label: lbl, label: lbl.into(),
children, children,
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn label<'s>(input: &'s str) -> Res<&'s str, &'s str> { pub fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt(( alt((
digit1, digit1,
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)), take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)),
@@ -77,9 +78,8 @@ pub fn label<'s>(input: &'s str) -> Res<&'s str, &'s str> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_definition_end<'r, 's>( fn footnote_definition_end<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let start_of_line_matcher = parser_with_context!(start_of_line)(context);
let allow_nesting_context = let allow_nesting_context =
context.with_additional_node(ContextElement::Context("allow nesting footnotes")); context.with_additional_node(ContextElement::Context("allow nesting footnotes"));
let footnote_definition_matcher = parser_with_context!(footnote_definition)( let footnote_definition_matcher = parser_with_context!(footnote_definition)(
@@ -97,8 +97,10 @@ fn footnote_definition_end<'r, 's>(
footnote_definition_matcher, footnote_definition_matcher,
))), ))),
recognize(tuple(( recognize(tuple((
start_of_line_matcher, start_of_line,
verify(many1(blank_line), |lines: &Vec<&str>| lines.len() >= 2), verify(many1(blank_line), |lines: &Vec<OrgSource<'_>>| {
lines.len() >= 2
}),
))), ))),
))(input)?; ))(input)?;
@@ -108,27 +110,26 @@ fn footnote_definition_end<'r, 's>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree; use crate::parser::parser_context::ContextTree;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::Source; use crate::parser::Source;
#[test] #[test]
fn two_paragraphs() { fn two_paragraphs() {
let input = "[fn:1] A footnote. let input = OrgSource::new(
"[fn:1] A footnote.
[fn:2] A multi- [fn:2] A multi-
line footnote."; line footnote.",
);
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let footnote_definition_matcher = parser_with_context!(element(true))(&document_context);
let (remaining, first_footnote_definition) = let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition"); footnote_definition_matcher(input).expect("Parse first footnote_definition");
let (remaining, second_footnote_definition) = let (remaining, second_footnote_definition) =
footnote_definition_matcher(remaining).expect("Parse second footnote_definition."); footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
assert_eq!(remaining, ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!( assert_eq!(
first_footnote_definition.get_source(), first_footnote_definition.get_source(),
"[fn:1] A footnote. "[fn:1] A footnote.
@@ -145,19 +146,19 @@ line footnote."
#[test] #[test]
fn multiline_break() { fn multiline_break() {
let input = "[fn:2] A multi- let input = OrgSource::new(
"[fn:2] A multi-
line footnote. line footnote.
not in the footnote."; not in the footnote.",
);
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let footnote_definition_matcher = parser_with_context!(element(true))(&document_context);
let (remaining, first_footnote_definition) = let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition"); footnote_definition_matcher(input).expect("Parse first footnote_definition");
assert_eq!(remaining, "not in the footnote."); assert_eq!(Into::<&str>::into(remaining), "not in the footnote.");
assert_eq!( assert_eq!(
first_footnote_definition.get_source(), first_footnote_definition.get_source(),
"[fn:2] A multi- "[fn:2] A multi-

View File

@@ -5,6 +5,8 @@ use nom::character::complete::space0;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::parser_context::ContextElement; use super::parser_context::ContextElement;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
@@ -14,18 +16,16 @@ use crate::parser::exiting::ExitClass;
use crate::parser::footnote_definition::label; use crate::parser::footnote_definition::label;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_context::FootnoteReferenceDefinition;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::FootnoteReference; use crate::parser::FootnoteReference;
use crate::parser::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn footnote_reference<'r, 's>( pub fn footnote_reference<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, FootnoteReference<'s>> { ) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
alt(( alt((
parser_with_context!(anonymous_footnote)(context), parser_with_context!(anonymous_footnote)(context),
parser_with_context!(footnote_reference_only)(context), parser_with_context!(footnote_reference_only)(context),
@@ -36,21 +36,15 @@ pub fn footnote_reference<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn anonymous_footnote<'r, 's>( fn anonymous_footnote<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, FootnoteReference<'s>> { ) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn::")(input)?; let (remaining, _) = tag_no_case("[fn::")(input)?;
let parser_context = context let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
.with_additional_node(ContextElement::FootnoteReferenceDefinition( let parser_context =
FootnoteReferenceDefinition { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
position: remaining,
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
exit_matcher: &footnote_definition_end, exit_matcher: &exit_with_depth,
})); }));
// TODO: I could insert FootnoteReferenceDefinition entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
parser_with_context!(standard_set_object)(&parser_context), parser_with_context!(standard_set_object)(&parser_context),
@@ -65,7 +59,7 @@ fn anonymous_footnote<'r, 's>(
Ok(( Ok((
remaining, remaining,
FootnoteReference { FootnoteReference {
source, source: source.into(),
label: None, label: None,
definition: children, definition: children,
}, },
@@ -75,23 +69,17 @@ fn anonymous_footnote<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inline_footnote<'r, 's>( fn inline_footnote<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, FootnoteReference<'s>> { ) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn:")(input)?; let (remaining, _) = tag_no_case("[fn:")(input)?;
let (remaining, label_contents) = label(remaining)?; let (remaining, label_contents) = label(remaining)?;
let (remaining, _) = tag(":")(remaining)?; let (remaining, _) = tag(":")(remaining)?;
let parser_context = context let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
.with_additional_node(ContextElement::FootnoteReferenceDefinition( let parser_context =
FootnoteReferenceDefinition { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
position: remaining,
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
exit_matcher: &footnote_definition_end, exit_matcher: &exit_with_depth,
})); }));
// TODO: I could insert FootnoteReferenceDefinition entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
parser_with_context!(standard_set_object)(&parser_context), parser_with_context!(standard_set_object)(&parser_context),
@@ -106,8 +94,8 @@ fn inline_footnote<'r, 's>(
Ok(( Ok((
remaining, remaining,
FootnoteReference { FootnoteReference {
source, source: source.into(),
label: Some(label_contents), label: Some(label_contents.into()),
definition: children, definition: children,
}, },
)) ))
@@ -115,9 +103,9 @@ fn inline_footnote<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_reference_only<'r, 's>( fn footnote_reference_only<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, FootnoteReference<'s>> { ) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn:")(input)?; let (remaining, _) = tag_no_case("[fn:")(input)?;
let (remaining, label_contents) = label(remaining)?; let (remaining, label_contents) = label(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
@@ -126,59 +114,37 @@ fn footnote_reference_only<'r, 's>(
Ok(( Ok((
remaining, remaining,
FootnoteReference { FootnoteReference {
source, source: source.into(),
label: Some(label_contents), label: Some(label_contents.into()),
definition: Vec::with_capacity(0), definition: Vec::with_capacity(0),
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn footnote_definition_end(
fn definition<'s>(input: &'s str) -> Res<&'s str, Vec<Object<'s>>> { starting_bracket_depth: BracketDepth,
Ok((input, vec![])) ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_footnote_definition_end(context, input, starting_bracket_depth)
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_definition_end<'r, 's>( fn _footnote_definition_end<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { starting_bracket_depth: BracketDepth,
let context_depth = get_bracket_depth(context) ) -> Res<OrgSource<'s>, OrgSource<'s>> {
.expect("This function should only be called from inside a footnote definition."); let current_depth = input.get_bracket_depth() - starting_bracket_depth;
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in text_since_context_entry.chars() {
match c {
'[' => {
current_depth += 1;
}
']' if current_depth == 0 => {
panic!("Exceeded footnote reference definition bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
}
if current_depth > 0 { if current_depth > 0 {
// Its impossible for the next character to end the footnote reference definition if we're any amount of brackets deep // 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( return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoFootnoteReferenceDefinitionEnd", "NoFootnoteReferenceDefinitionEnd".into(),
)))); ))));
} }
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the footnote definition.
unreachable!("Exceeded footnote reference definition bracket depth.")
}
tag("]")(input) tag("]")(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn get_bracket_depth<'r, 's>(
context: Context<'r, 's>,
) -> Option<&'r FootnoteReferenceDefinition<'s>> {
for node in context.iter() {
match node.get_data() {
ContextElement::FootnoteReferenceDefinition(depth) => return Some(depth),
_ => {}
}
}
None
}

View File

@@ -11,6 +11,7 @@ use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -33,37 +34,39 @@ use crate::parser::Paragraph;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn greater_block<'r, 's>( pub fn greater_block<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, GreaterBlock<'s>> { ) -> Res<OrgSource<'s>, GreaterBlock<'s>> {
// TODO: Do I need to differentiate between different greater block types. // TODO: Do I need to differentiate between different greater block types.
start_of_line(context, input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, name)) = tuple(( let (remaining, (_begin, name)) = tuple((
tag_no_case("#+begin_"), tag_no_case("#+begin_"),
verify(name, |name: &str| match name.to_lowercase().as_str() { verify(name, |name: &OrgSource<'_>| {
"comment" | "example" | "export" | "src" | "verse" => false, match Into::<&str>::into(name).to_lowercase().as_str() {
_ => true, "comment" | "example" | "export" | "src" | "verse" => false,
_ => true,
}
}), }),
))(remaining)?; ))(remaining)?;
let context_name = match name.to_lowercase().as_str() { let context_name = match Into::<&str>::into(name).to_lowercase().as_str() {
"center" => "center block", "center" => "center block",
"quote" => "quote block", "quote" => "quote block",
_ => "greater block", _ => "greater block",
}; };
if immediate_in_section(context, context_name) { if immediate_in_section(context, context_name) {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element", "Cannot nest objects of the same element".into(),
)))); ))));
} }
let exit_with_name = greater_block_end(name.into());
let (remaining, parameters) = opt(tuple((space1, parameters)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, parameters)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = line_ending(remaining)?;
let parser_context = context let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::Context(context_name)) .with_additional_node(ContextElement::Context(context_name))
.with_additional_node(ContextElement::GreaterBlock(name))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Alpha,
exit_matcher: &greater_block_end, exit_matcher: &exit_with_name,
})); }));
let parameters = match parameters { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
@@ -80,9 +83,9 @@ pub fn greater_block<'r, 's>(
))(remaining) ))(remaining)
{ {
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => { Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
let mut element = Element::Paragraph(Paragraph::of_text(first_line)); let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
let source = get_consumed(remaining, remain); let source = get_consumed(remaining, remain);
element.set_source(source); element.set_source(source.into());
(remain, vec![element]) (remain, vec![element])
} }
Err(_) => { Err(_) => {
@@ -91,7 +94,7 @@ pub fn greater_block<'r, 's>(
(remaining, children) (remaining, children)
} }
}; };
let (remaining, _end) = greater_block_end(&parser_context, 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 // Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
@@ -99,46 +102,45 @@ pub fn greater_block<'r, 's>(
Ok(( Ok((
remaining, remaining,
GreaterBlock { GreaterBlock {
source, source: source.into(),
name, name: name.into(),
parameters, parameters: parameters.map(|val| Into::<&str>::into(val)),
children, children,
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> { fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not(" \t\r\n")(input) is_not(" \t\r\n")(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn parameters<'s>(input: &'s str) -> Res<&'s str, &'s str> { fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not("\r\n")(input) is_not("\r\n")(input)
} }
fn greater_block_end<'x>(
name: &'x str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: Can this be done without making an owned copy?
let name = name.to_owned();
move |context: Context, input: OrgSource<'_>| _greater_block_end(context, input, name.as_str())
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn greater_block_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn _greater_block_end<'r, 's, 'x>(
start_of_line(context, input)?; _context: Context<'r, 's>,
let current_name: &str = get_context_greater_block_name(context).ok_or(nom::Err::Error( input: OrgSource<'s>,
CustomError::MyError(MyError("Not inside a greater block")), name: &'x str,
))?; ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, _name, _ws)) = tuple(( let (remaining, (_begin, _name, _ws)) = tuple((
tag_no_case("#+end_"), tag_no_case("#+end_"),
tag_no_case(current_name), tag_no_case(name),
alt((eof, line_ending)), alt((eof, line_ending)),
))(remaining)?; ))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, source)) Ok((remaining, source))
} }
fn get_context_greater_block_name<'r, 's>(context: Context<'r, 's>) -> Option<&'s str> {
for thing in context.iter() {
match thing.get_data() {
ContextElement::GreaterBlock(name) => return Some(name),
_ => {}
};
}
None
}

View File

@@ -1,6 +1,7 @@
use super::element::Element; use super::element::Element;
use super::lesser_element::TableCell; use super::lesser_element::TableCell;
use super::source::Source; use super::source::Source;
use super::Object;
#[derive(Debug)] #[derive(Debug)]
pub struct PlainList<'s> { pub struct PlainList<'s> {
@@ -13,6 +14,7 @@ pub struct PlainListItem<'s> {
pub source: &'s str, pub source: &'s str,
pub indentation: usize, pub indentation: usize,
pub bullet: &'s str, pub bullet: &'s str,
pub tag: Vec<Object<'s>>,
pub children: Vec<Element<'s>>, pub children: Vec<Element<'s>>,
} }

View File

@@ -8,6 +8,7 @@ use nom::combinator::verify;
use nom::multi::many1_count; use nom::multi::many1_count;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
@@ -15,15 +16,20 @@ use crate::parser::HorizontalRule;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn horizontal_rule<'r, 's>( pub fn horizontal_rule<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, HorizontalRule<'s>> { ) -> Res<OrgSource<'s>, HorizontalRule<'s>> {
start_of_line(context, input)?; start_of_line(input)?;
let (remaining, rule) = recognize(tuple(( let (remaining, rule) = recognize(tuple((
space0, space0,
verify(many1_count(tag("-")), |dashes| *dashes >= 5), verify(many1_count(tag("-")), |dashes| *dashes >= 5),
space0, space0,
alt((line_ending, eof)), alt((line_ending, eof)),
)))(input)?; )))(input)?;
Ok((remaining, HorizontalRule { source: rule })) Ok((
remaining,
HorizontalRule {
source: rule.into(),
},
))
} }

View File

@@ -10,10 +10,13 @@ use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::BabelHeaderBracket;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
@@ -24,8 +27,8 @@ use crate::parser::InlineBabelCall;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn inline_babel_call<'r, 's>( pub fn inline_babel_call<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, InlineBabelCall<'s>> { ) -> Res<OrgSource<'s>, InlineBabelCall<'s>> {
let (remaining, _) = tag_no_case("call_")(input)?; let (remaining, _) = tag_no_case("call_")(input)?;
let (remaining, _name) = name(context, remaining)?; let (remaining, _name) = name(context, remaining)?;
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
@@ -33,14 +36,22 @@ pub fn inline_babel_call<'r, 's>(
let (remaining, _header2) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, _header2) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, InlineBabelCall { source })) Ok((
remaining,
InlineBabelCall {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn name<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &name_end, exit_matcher: &name_end,
})); }));
let (remaining, name) = recognize(many_till( let (remaining, name) = recognize(many_till(
@@ -51,22 +62,25 @@ fn name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s st
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn name_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(one_of("[("))(input) recognize(one_of("[("))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn header<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn header<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("[")(input)?; let (remaining, _) = tag("[")(input)?;
let parser_context = context let exit_with_depth = header_end(remaining.get_bracket_depth());
.with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket { let parser_context =
position: remaining, context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
depth: 0, class: ExitClass::Gamma,
})) exit_matcher: &exit_with_depth,
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &header_end,
})); }));
let (remaining, name) = recognize(many_till( let (remaining, name) = recognize(many_till(
@@ -77,41 +91,46 @@ fn header<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s
Ok((remaining, name)) Ok((remaining, name))
} }
fn header_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_header_end(context, input, starting_bracket_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn header_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn _header_end<'r, 's>(
let context_depth = get_bracket_depth(context) _context: Context<'r, 's>,
.expect("This function should only be called from inside an inline babel call header."); input: OrgSource<'s>,
let text_since_context_entry = get_consumed(context_depth.position, input); starting_bracket_depth: BracketDepth,
let mut current_depth = context_depth.depth; ) -> Res<OrgSource<'s>, OrgSource<'s>> {
for c in text_since_context_entry.chars() { let current_depth = input.get_bracket_depth() - starting_bracket_depth;
match c { if current_depth > 0 {
'(' => { // Its impossible for the next character to end the header if we're any amount of bracket deep
current_depth += 1; return Err(nom::Err::Error(CustomError::MyError(MyError(
} "NoHeaderEnd".into(),
')' if current_depth == 0 => { ))));
panic!("Exceeded inline babel call header bracket depth.") }
} if current_depth < 0 {
')' if current_depth > 0 => { // This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
current_depth -= 1; unreachable!("Exceeded header bracket depth.")
}
_ => {}
}
} }
alt((tag("]"), line_ending))(input) alt((tag("]"), line_ending))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn argument<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn argument<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("(")(input)?; let (remaining, _) = tag("(")(input)?;
let parser_context = context let exit_with_depth = argument_end(remaining.get_parenthesis_depth());
.with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket { let parser_context =
position: remaining, context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
depth: 0, class: ExitClass::Gamma,
})) exit_matcher: &exit_with_depth,
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &argument_end,
})); }));
let (remaining, name) = recognize(many_till( let (remaining, name) = recognize(many_till(
@@ -122,36 +141,30 @@ fn argument<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'
Ok((remaining, name)) Ok((remaining, name))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn argument_end(
fn argument_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { starting_parenthesis_depth: BracketDepth,
let context_depth = get_bracket_depth(context) ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
.expect("This function should only be called from inside an inline babel call argument."); move |context: Context, input: OrgSource<'_>| {
let text_since_context_entry = get_consumed(context_depth.position, input); _argument_end(context, input, starting_parenthesis_depth)
let mut current_depth = context_depth.depth;
for c in text_since_context_entry.chars() {
match c {
'[' => {
current_depth += 1;
}
']' if current_depth == 0 => {
panic!("Exceeded inline babel call argument bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
} }
alt((tag(")"), line_ending))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r BabelHeaderBracket<'s>> { fn _argument_end<'r, 's>(
for node in context.iter() { _context: Context<'r, 's>,
match node.get_data() { input: OrgSource<'s>,
ContextElement::BabelHeaderBracket(depth) => return Some(depth), starting_parenthesis_depth: BracketDepth,
_ => {} ) -> Res<OrgSource<'s>, OrgSource<'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(),
))));
} }
None if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing parenthesis should end the argument.
unreachable!("Exceeded argument parenthesis depth.")
}
alt((tag(")"), line_ending))(input)
} }

View File

@@ -1,3 +1,4 @@
use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar; use nom::character::complete::anychar;
@@ -11,13 +12,15 @@ use nom::multi::many_till;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
use tracing::span; use tracing::span;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_context::InlineSourceBlockBracket;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
@@ -26,19 +29,27 @@ use crate::parser::InlineSourceBlock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn inline_source_block<'r, 's>( pub fn inline_source_block<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, InlineSourceBlock<'s>> { ) -> Res<OrgSource<'s>, InlineSourceBlock<'s>> {
let (remaining, _) = tag_no_case("src_")(input)?; let (remaining, _) = tag_no_case("src_")(input)?;
let (remaining, _) = lang(context, remaining)?; let (remaining, _) = lang(context, remaining)?;
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _body) = body(context, remaining)?; let (remaining, _body) = body(context, remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, InlineSourceBlock { source })) Ok((
remaining,
InlineSourceBlock {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn lang<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn lang<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
@@ -52,24 +63,25 @@ fn lang<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s st
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn lang_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn lang_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(one_of("[{"))(input) recognize(one_of("[{"))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn header<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn header<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("[")(input)?; let (remaining, _) = tag("[")(input)?;
let parser_context = context let exit_with_depth = header_end(remaining.get_bracket_depth());
.with_additional_node(ContextElement::InlineSourceBlockBracket( let parser_context =
InlineSourceBlockBracket { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
position: remaining,
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
exit_matcher: &header_end, exit_matcher: &exit_with_depth,
})); }));
let (remaining, header_contents) = recognize(many_till( let (remaining, header_contents) = recognize(many_till(
@@ -80,50 +92,46 @@ fn header<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s
Ok((remaining, header_contents)) Ok((remaining, header_contents))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn header_end(
fn header_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { starting_bracket_depth: BracketDepth,
let context_depth = get_bracket_depth(context) ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
.expect("This function should only be called from inside an inline source block header."); move |context: Context, input: OrgSource<'_>| {
let text_since_context_entry = get_consumed(context_depth.position, input); _header_end(context, input, starting_bracket_depth)
let mut current_depth = context_depth.depth;
for c in text_since_context_entry.chars() {
match c {
'[' => {
current_depth += 1;
}
']' if current_depth == 0 => {
panic!("Exceeded inline source block header bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
} }
if current_depth == 0 {
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
if close_bracket.is_ok() {
return close_bracket;
}
}
line_ending(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn body<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn _header_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'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(),
))));
}
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
unreachable!("Exceeded header bracket depth.")
}
alt((tag("]"), line_ending))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn body<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("{")(input)?; let (remaining, _) = tag("{")(input)?;
let parser_context = context let exit_with_depth = body_end(remaining.get_brace_depth());
.with_additional_node(ContextElement::InlineSourceBlockBracket( let parser_context =
InlineSourceBlockBracket { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
position: remaining,
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
exit_matcher: &body_end, exit_matcher: &exit_with_depth,
})); }));
let (remaining, body_contents) = recognize(many_till( let (remaining, body_contents) = recognize(many_till(
@@ -135,7 +143,7 @@ fn body<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s st
let span = span!( let span = span!(
tracing::Level::DEBUG, tracing::Level::DEBUG,
"outside end body", "outside end body",
remaining = remaining remaining = Into::<&str>::into(remaining)
); );
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
let _enter = span.enter(); let _enter = span.enter();
@@ -144,57 +152,28 @@ fn body<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s st
Ok((remaining, body_contents)) Ok((remaining, body_contents))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn body_end(
fn body_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { starting_brace_depth: BracketDepth,
let context_depth = get_bracket_depth(context) ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
.expect("This function should only be called from inside an inline source block body."); move |context: Context, input: OrgSource<'_>| _body_end(context, input, starting_brace_depth)
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in text_since_context_entry.chars() {
match c {
'{' => {
current_depth += 1;
}
'}' if current_depth == 0 => {
panic!("Exceeded inline source block body bracket depth.")
}
'}' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
}
{
#[cfg(feature = "tracing")]
let span = span!(
tracing::Level::DEBUG,
"inside end body",
remaining = input,
current_depth = current_depth
);
#[cfg(feature = "tracing")]
let _enter = span.enter();
if current_depth == 0 {
let close_bracket = tag::<&str, &str, CustomError<&str>>("}")(input);
if close_bracket.is_ok() {
return close_bracket;
}
}
}
line_ending(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn get_bracket_depth<'r, 's>( fn _body_end<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
) -> Option<&'r InlineSourceBlockBracket<'s>> { input: OrgSource<'s>,
for node in context.iter() { starting_brace_depth: BracketDepth,
match node.get_data() { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
ContextElement::InlineSourceBlockBracket(depth) => return Some(depth), 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(),
))));
} }
None if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing brace should end the body.
unreachable!("Exceeded body brace depth.")
}
alt((tag("}"), line_ending))(input)
} }

View File

@@ -2,6 +2,8 @@ use nom::branch::alt;
use nom::bytes::complete::is_not; use nom::bytes::complete::is_not;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; 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::line_ending;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
@@ -9,16 +11,30 @@ use nom::combinator::eof;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::Keyword; use crate::parser::Keyword;
const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
"caption", "data", "header", "headers", "label", "name", "plot", "resname", "result",
"results", "source", "srcname", "tblname",
];
const ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["caption", "results"];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn keyword<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Keyword<'s>> { pub fn keyword<'r, 's>(
start_of_line(context, input)?; _context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
start_of_line(input)?;
// TODO: When key is a member of org-element-parsed-keywords, value can contain the standard set objects, excluding footnote references. // TODO: When key is a member of org-element-parsed-keywords, value can contain the standard set objects, excluding footnote references.
let (remaining, rule) = recognize(tuple(( let (remaining, rule) = recognize(tuple((
space0, space0,
@@ -30,5 +46,118 @@ pub fn keyword<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
alt((recognize(tuple((space1, is_not("\r\n")))), space0)), alt((recognize(tuple((space1, is_not("\r\n")))), space0)),
alt((line_ending, eof)), alt((line_ending, eof)),
)))(input)?; )))(input)?;
Ok((remaining, Keyword { source: rule })) Ok((
remaining,
Keyword {
source: rule.into(),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn affiliated_keyword<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
start_of_line(input)?;
// TODO: When key is a member of org-element-parsed-keywords, value can contain the standard set objects, excluding footnote references.
let (remaining, rule) = recognize(tuple((
space0,
tag("#+"),
affiliated_key,
tag(":"),
alt((recognize(tuple((space1, is_not("\r\n")))), space0)),
alt((line_ending, eof)),
)))(input)?;
Ok((
remaining,
Keyword {
source: rule.into(),
},
))
}
#[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)
}
#[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(_) => {}
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoKeywordKey".into(),
))))
}
#[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(_) => {}
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoKeywordKey".into(),
))))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn optval<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many_till(
anychar,
peek(optval_end(input.get_bracket_depth())),
))(input)
}
const fn optval_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |input: OrgSource<'_>| _optval_end(input, starting_bracket_depth)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _optval_end<'s>(
input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the opval.
unreachable!("Exceeded optval bracket depth.")
}
if current_depth == 0 {
let close_bracket = tag::<_, _, CustomError<_>>("]")(input);
if close_bracket.is_ok() {
return close_bracket;
}
}
tag("\n")(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn export_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
tag_no_case("attr_"),
take_while1(|c: char| c.is_alphanumeric() || "-_".contains(c)),
)))(input)
} }

View File

@@ -11,6 +11,7 @@ use nom::combinator::recognize;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::get_consumed; use super::util::get_consumed;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
@@ -25,9 +26,9 @@ use crate::parser::LatexEnvironment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn latex_environment<'r, 's>( pub fn latex_environment<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, LatexEnvironment<'s>> { ) -> Res<OrgSource<'s>, LatexEnvironment<'s>> {
start_of_line(context, input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_opening, name, _open_close_brace, _ws, _line_ending)) = tuple(( let (remaining, (_opening, name, _open_close_brace, _ws, _line_ending)) = tuple((
tag_no_case(r#"\begin{"#), tag_no_case(r#"\begin{"#),
@@ -37,7 +38,7 @@ pub fn latex_environment<'r, 's>(
line_ending, line_ending,
))(remaining)?; ))(remaining)?;
let latex_environment_end_specialized = latex_environment_end(name); let latex_environment_end_specialized = latex_environment_end(name.into());
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
@@ -48,11 +49,16 @@ pub fn latex_environment<'r, 's>(
let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?; let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, LatexEnvironment { source })) Ok((
remaining,
LatexEnvironment {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> { fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
take_while1(|c: char| c.is_alphanumeric() || c == '*')(input) take_while1(|c: char| c.is_alphanumeric() || c == '*')(input)
} }
@@ -60,11 +66,15 @@ fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> {
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(end_matcher)) tracing::instrument(ret, level = "debug", skip(end_matcher))
)] )]
pub fn contents<'r, 's, F: Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str>>( pub fn contents<
'r,
's,
F: Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>,
>(
end_matcher: F, end_matcher: F,
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(many_till( let (remaining, source) = recognize(many_till(
anychar, anychar,
peek(alt(( peek(alt((
@@ -78,20 +88,20 @@ pub fn contents<'r, 's, F: Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str>
fn latex_environment_end( fn latex_environment_end(
current_name: &str, current_name: &str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_name_lower = current_name.to_lowercase(); let current_name_lower = current_name.to_lowercase();
move |context: Context, input: &str| { move |context: Context, input: OrgSource<'_>| {
_latex_environment_end(context, input, current_name_lower.as_str()) _latex_environment_end(context, input, current_name_lower.as_str())
} }
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _latex_environment_end<'r, 's, 'x>( fn _latex_environment_end<'r, 's, 'x>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
current_name_lower: &'x str, current_name_lower: &'x str,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(context, input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, _name, _close_brace, _ws, _line_ending)) = tuple(( let (remaining, (_begin, _name, _close_brace, _ws, _line_ending)) = tuple((
tag_no_case(r#"\end{"#), tag_no_case(r#"\end{"#),

View File

@@ -13,6 +13,7 @@ use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -20,14 +21,13 @@ use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::get_one_before;
use crate::parser::LatexFragment; use crate::parser::LatexFragment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn latex_fragment<'r, 's>( pub fn latex_fragment<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, LatexFragment<'s>> { ) -> Res<OrgSource<'s>, LatexFragment<'s>> {
let (remaining, _) = alt(( let (remaining, _) = alt((
parser_with_context!(raw_latex_fragment)(context), parser_with_context!(raw_latex_fragment)(context),
parser_with_context!(escaped_parenthesis_fragment)(context), parser_with_context!(escaped_parenthesis_fragment)(context),
@@ -38,11 +38,19 @@ pub fn latex_fragment<'r, 's>(
))(input)?; ))(input)?;
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, LatexFragment { source })) Ok((
remaining,
LatexFragment {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn raw_latex_fragment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn raw_latex_fragment<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("\\")(input)?; let (remaining, _) = tag("\\")(input)?;
let (remaining, _) = name(context, remaining)?; let (remaining, _) = name(context, remaining)?;
let (remaining, _) = many0(parser_with_context!(brackets)(context))(remaining)?; let (remaining, _) = many0(parser_with_context!(brackets)(context))(remaining)?;
@@ -52,12 +60,18 @@ fn raw_latex_fragment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn name<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
alpha1(input) alpha1(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn brackets<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn brackets<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, body) = alt(( let (remaining, body) = alt((
recognize(tuple(( recognize(tuple((
tag("["), tag("["),
@@ -88,8 +102,8 @@ fn brackets<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn escaped_parenthesis_fragment<'r, 's>( fn escaped_parenthesis_fragment<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("\\(")(input)?; let (remaining, _) = tag("\\(")(input)?;
let (remaining, _) = recognize(many_till( let (remaining, _) = recognize(many_till(
anychar, anychar,
@@ -107,8 +121,8 @@ fn escaped_parenthesis_fragment<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn escaped_bracket_fragment<'r, 's>( fn escaped_bracket_fragment<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("\\[")(input)?; let (remaining, _) = tag("\\[")(input)?;
let (remaining, _) = recognize(many_till( let (remaining, _) = recognize(many_till(
anychar, anychar,
@@ -126,8 +140,8 @@ fn escaped_bracket_fragment<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn double_dollar_fragment<'r, 's>( fn double_dollar_fragment<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: The documentation on the dollar sign versions is incomplete. Test to figure out what the real requirements are. For example, can this span more than 3 lines and can this contain a single $ since its terminated by $$? // TODO: The documentation on the dollar sign versions is incomplete. Test to figure out what the real requirements are. For example, can this span more than 3 lines and can this contain a single $ since its terminated by $$?
let (remaining, _) = tag("$$")(input)?; let (remaining, _) = tag("$$")(input)?;
let (remaining, _) = recognize(many_till( let (remaining, _) = recognize(many_till(
@@ -144,7 +158,10 @@ fn double_dollar_fragment<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dollar_char_fragment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn dollar_char_fragment<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (_, _) = pre(context, input)?; let (_, _) = pre(context, input)?;
let (remaining, _) = tag("$")(input)?; let (remaining, _) = tag("$")(input)?;
let (remaining, _) = verify(none_of(".,?;\""), |c| !c.is_whitespace())(remaining)?; let (remaining, _) = verify(none_of(".,?;\""), |c| !c.is_whitespace())(remaining)?;
@@ -156,21 +173,18 @@ fn dollar_char_fragment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> { pub fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let document_root = context.get_document_root().unwrap(); let preceding_character = input.get_preceding_character();
let preceding_character = get_one_before(document_root, input)
.map(|slice| slice.chars().next())
.flatten();
if let Some('$') = preceding_character { if let Some('$') = preceding_character {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for dollar char fragment.", "Not a valid pre character for dollar char fragment.".into(),
)))); ))));
} }
Ok((input, ())) Ok((input, ()))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn post<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> { pub fn post<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
// TODO: What about eof? Test to find out. // TODO: What about eof? Test to find out.
// TODO: Figure out which punctuation characters should be included. // TODO: Figure out which punctuation characters should be included.
@@ -181,24 +195,20 @@ pub fn post<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn bordered_dollar_fragment<'r, 's>( fn bordered_dollar_fragment<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (_, _) = pre(context, input)?; let (_, _) = pre(context, input)?;
let (remaining, _) = tag("$")(input)?; let (remaining, _) = tag("$")(input)?;
// TODO: I'm assuming I should be peeking at the borders but the documentation is not clear. Test to figure out. // TODO: I'm assuming I should be peeking at the borders but the documentation is not clear. Test to figure out.
let (_, _) = peek(parser_with_context!(open_border)(context))(remaining)?; let (_, _) = peek(parser_with_context!(open_border)(context))(remaining)?;
// TODO: As an optimization it would be nice to exit early upon hitting the 3rd line break let (remaining, _) = recognize(many_till(
let (remaining, _) = verify( anychar,
recognize(many_till( peek(alt((
anychar, parser_with_context!(exit_matcher_parser)(context),
peek(alt(( tag("$"),
parser_with_context!(exit_matcher_parser)(context), ))),
tag("$"), ))(remaining)?;
))),
)),
|body: &str| body.lines().take(4).count() <= 3,
)(remaining)?;
let (_, _) = peek(parser_with_context!(close_border)(context))(remaining)?; let (_, _) = peek(parser_with_context!(close_border)(context))(remaining)?;
let (remaining, _) = tag("$")(remaining)?; let (remaining, _) = tag("$")(remaining)?;
@@ -209,21 +219,24 @@ fn bordered_dollar_fragment<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn open_border<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { pub fn open_border<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(verify(none_of(".,;$"), |c| !c.is_whitespace()))(input) recognize(verify(none_of(".,;$"), |c| !c.is_whitespace()))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn close_border<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> { pub fn close_border<'r, 's>(
let document_root = context.get_document_root().unwrap(); _context: Context<'r, 's>,
let preceding_character = get_one_before(document_root, input) input: OrgSource<'s>,
.map(|slice| slice.chars().next()) ) -> Res<OrgSource<'s>, ()> {
.flatten(); let preceding_character = input.get_preceding_character();
match preceding_character { match preceding_character {
Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())), Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())),
_ => { _ => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for dollar char fragment.", "Not a valid pre character for dollar char fragment.".into(),
)))); ))));
} }
} }

View File

@@ -11,6 +11,7 @@ use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
@@ -34,8 +35,8 @@ use crate::parser::util::text_until_exit;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn verse_block<'r, 's>( pub fn verse_block<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, VerseBlock<'s>> { ) -> Res<OrgSource<'s>, VerseBlock<'s>> {
let (remaining, name) = lesser_block_begin("verse")(context, input)?; let (remaining, name) = lesser_block_begin("verse")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = line_ending(remaining)?;
@@ -58,7 +59,9 @@ pub fn verse_block<'r, 's>(
let (remaining, children) = match consumed(many_till(blank_line, exit_matcher))(remaining) { let (remaining, children) = match consumed(many_till(blank_line, exit_matcher))(remaining) {
Ok((remaining, (whitespace, (_children, _exit_contents)))) => ( Ok((remaining, (whitespace, (_children, _exit_contents)))) => (
remaining, remaining,
vec![Object::PlainText(PlainText { source: whitespace })], vec![Object::PlainText(PlainText {
source: whitespace.into(),
})],
), ),
Err(_) => { Err(_) => {
let (remaining, (children, _exit_contents)) = let (remaining, (children, _exit_contents)) =
@@ -72,9 +75,9 @@ pub fn verse_block<'r, 's>(
Ok(( Ok((
remaining, remaining,
VerseBlock { VerseBlock {
source, source: source.into(),
name, name: name.into(),
data: parameters, data: parameters.map(|parameters| Into::<&str>::into(parameters)),
children, children,
}, },
)) ))
@@ -83,8 +86,8 @@ pub fn verse_block<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn comment_block<'r, 's>( pub fn comment_block<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, CommentBlock<'s>> { ) -> Res<OrgSource<'s>, CommentBlock<'s>> {
let (remaining, name) = lesser_block_begin("comment")(context, input)?; let (remaining, name) = lesser_block_begin("comment")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = line_ending(remaining)?;
@@ -108,10 +111,10 @@ pub fn comment_block<'r, 's>(
Ok(( Ok((
remaining, remaining,
CommentBlock { CommentBlock {
source, source: source.into(),
name, name: name.into(),
data: parameters, data: parameters.map(|parameters| Into::<&str>::into(parameters)),
contents, contents: contents.into(),
}, },
)) ))
} }
@@ -119,9 +122,9 @@ pub fn comment_block<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn example_block<'r, 's>( pub fn example_block<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, ExampleBlock<'s>> { ) -> Res<OrgSource<'s>, ExampleBlock<'s>> {
let (remaining, name) = lesser_block_begin("example")(context, input)?; let (remaining, _name) = lesser_block_begin("example")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = line_ending(remaining)?;
let lesser_block_end_specialized = lesser_block_end("example"); let lesser_block_end_specialized = lesser_block_end("example");
@@ -144,10 +147,10 @@ pub fn example_block<'r, 's>(
Ok(( Ok((
remaining, remaining,
ExampleBlock { ExampleBlock {
source, source: source.into(),
name, name: source.into(),
data: parameters, data: parameters.map(|parameters| Into::<&str>::into(parameters)),
contents, contents: contents.into(),
}, },
)) ))
} }
@@ -155,8 +158,8 @@ pub fn example_block<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn export_block<'r, 's>( pub fn export_block<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, ExportBlock<'s>> { ) -> Res<OrgSource<'s>, ExportBlock<'s>> {
let (remaining, name) = lesser_block_begin("export")(context, input)?; let (remaining, name) = lesser_block_begin("export")(context, input)?;
// https://orgmode.org/worg/org-syntax.html#Blocks claims that export blocks must have a single word for data but testing shows no data and multi-word data still parses as an export block. // https://orgmode.org/worg/org-syntax.html#Blocks claims that export blocks must have a single word for data but testing shows no data and multi-word data still parses as an export block.
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
@@ -181,16 +184,19 @@ pub fn export_block<'r, 's>(
Ok(( Ok((
remaining, remaining,
ExportBlock { ExportBlock {
source, source: source.into(),
name, name: name.into(),
data: parameters, data: parameters.map(|parameters| Into::<&str>::into(parameters)),
contents, contents: contents.into(),
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn src_block<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, SrcBlock<'s>> { pub fn src_block<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, SrcBlock<'s>> {
let (remaining, name) = lesser_block_begin("src")(context, input)?; let (remaining, name) = lesser_block_begin("src")(context, input)?;
// https://orgmode.org/worg/org-syntax.html#Blocks claims that data is mandatory and must follow the LANGUAGE SWITCHES ARGUMENTS pattern but testing has shown that no data and incorrect data here will still parse to a src block. // https://orgmode.org/worg/org-syntax.html#Blocks claims that data is mandatory and must follow the LANGUAGE SWITCHES ARGUMENTS pattern but testing has shown that no data and incorrect data here will still parse to a src block.
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
@@ -215,40 +221,40 @@ pub fn src_block<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
Ok(( Ok((
remaining, remaining,
SrcBlock { SrcBlock {
source, source: source.into(),
name, name: name.into(),
data: parameters, data: parameters.map(|parameters| Into::<&str>::into(parameters)),
contents, contents: contents.into(),
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> { fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not(" \t\r\n")(input) is_not(" \t\r\n")(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn data<'s>(input: &'s str) -> Res<&'s str, &'s str> { fn data<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not("\r\n")(input) is_not("\r\n")(input)
} }
fn lesser_block_end( fn lesser_block_end(
current_name: &str, current_name: &str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_name_lower = current_name.to_lowercase(); let current_name_lower = current_name.to_lowercase();
move |context: Context, input: &str| { move |context: Context, input: OrgSource<'_>| {
_lesser_block_end(context, input, current_name_lower.as_str()) _lesser_block_end(context, input, current_name_lower.as_str())
} }
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _lesser_block_end<'r, 's, 'x>( fn _lesser_block_end<'r, 's, 'x>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
current_name_lower: &'x str, current_name_lower: &'x str,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(context, input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, _name, _ws)) = tuple(( let (remaining, (_begin, _name, _ws)) = tuple((
tag_no_case("#+end_"), tag_no_case("#+end_"),
@@ -259,27 +265,28 @@ fn _lesser_block_end<'r, 's, 'x>(
Ok((remaining, source)) Ok((remaining, source))
} }
fn lesser_block_begin( /// Parser for the beginning of a lesser block
current_name: &str, ///
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str> { /// current_name MUST be lowercase. We do not do the conversion ourselves because it is not allowed in a const fn.
let current_name_lower = current_name.to_lowercase(); const fn lesser_block_begin(
move |context: Context, input: &str| { current_name: &'static str,
_lesser_block_begin(context, input, current_name_lower.as_str()) ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
} // TODO: Since this is a const fn, is there ANY way to "generate" functions at compile time?
move |context: Context, input: OrgSource<'_>| _lesser_block_begin(context, input, current_name)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _lesser_block_begin<'r, 's, 'x>( fn _lesser_block_begin<'r, 's, 'x>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
current_name_lower: &'x str, current_name_lower: &'x str,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(context, input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, name)) = tuple(( let (remaining, (_begin, name)) = tuple((
tag_no_case("#+begin_"), tag_no_case("#+begin_"),
verify(name, |name: &str| { verify(name, |name: &OrgSource<'_>| {
name.to_lowercase().as_str() == current_name_lower Into::<&str>::into(name).to_lowercase().as_str() == current_name_lower
}), }),
))(remaining)?; ))(remaining)?;
Ok((remaining, name)) Ok((remaining, name))

View File

@@ -4,55 +4,51 @@ use nom::character::complete::one_of;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::multi::many0; use nom::multi::many0;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::get_current_line_before_position;
use crate::parser::util::get_one_before;
use crate::parser::LineBreak; use crate::parser::LineBreak;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn line_break<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, LineBreak<'s>> { pub fn line_break<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, LineBreak<'s>> {
let (remaining, _) = pre(context, input)?; 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, _) = recognize(many0(one_of(" \t")))(remaining)?;
let (remaining, _) = line_ending(remaining)?; let (remaining, _) = line_ending(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, LineBreak { source })) Ok((
remaining,
LineBreak {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> { fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let document_root = context.get_document_root().unwrap(); let preceding_character = input.get_preceding_character();
let preceding_character = get_one_before(document_root, input)
.map(|slice| slice.chars().next())
.flatten();
match preceding_character { match preceding_character {
// If None, we are at the start of the file // If None, we are at the start of the file
None | Some('\\') => { None | Some('\\') => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for line break.", "Not a valid pre character for line break.".into(),
)))); ))));
} }
_ => {} _ => {}
}; };
let current_line = get_current_line_before_position(document_root, input);
match current_line { let current_line = input.text_since_line_break();
Some(line) => { let is_non_empty_line = current_line.chars().any(|c| !c.is_whitespace());
let is_non_empty_line = line.chars().any(|c| !c.is_whitespace()); if !is_non_empty_line {
if !is_non_empty_line { return Err(nom::Err::Error(CustomError::MyError(MyError(
return Err(nom::Err::Error(CustomError::MyError(MyError( "Not a valid pre line for line break.".into(),
"Not a valid pre line for line break.", ))));
))));
}
}
None => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No preceding line for line break.",
))));
}
} }
Ok((input, ())) Ok((input, ()))

View File

@@ -30,6 +30,7 @@ mod list;
mod object; mod object;
mod object_parser; mod object_parser;
mod org_macro; mod org_macro;
mod org_source;
mod paragraph; mod paragraph;
mod parser_context; mod parser_context;
mod parser_with_context; mod parser_with_context;

View File

@@ -374,3 +374,9 @@ impl<'s> Source<'s> for Timestamp<'s> {
self.source self.source
} }
} }
impl<'s> Source<'s> for PlainText<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}

View File

@@ -1,7 +1,7 @@
use nom::branch::alt; use nom::branch::alt;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::not;
use super::org_source::OrgSource;
use super::parser_with_context::parser_with_context; use super::parser_with_context::parser_with_context;
use super::plain_text::plain_text; use super::plain_text::plain_text;
use super::regular_link::regular_link; use super::regular_link::regular_link;
@@ -31,11 +31,9 @@ use crate::parser::timestamp::timestamp;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn standard_set_object<'r, 's>( pub fn standard_set_object<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
not(|i| context.check_exit_matcher(i))(input)?; let (remaining, object) = alt((
alt((
map(parser_with_context!(timestamp)(context), Object::Timestamp), map(parser_with_context!(timestamp)(context), Object::Timestamp),
map(parser_with_context!(subscript)(context), Object::Subscript), map(parser_with_context!(subscript)(context), Object::Subscript),
map( map(
@@ -84,17 +82,16 @@ pub fn standard_set_object<'r, 's>(
map(parser_with_context!(angle_link)(context), Object::AngleLink), map(parser_with_context!(angle_link)(context), Object::AngleLink),
map(parser_with_context!(org_macro)(context), Object::OrgMacro), map(parser_with_context!(org_macro)(context), Object::OrgMacro),
map(parser_with_context!(plain_text)(context), Object::PlainText), map(parser_with_context!(plain_text)(context), Object::PlainText),
))(input) ))(input)?;
Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn minimal_set_object<'r, 's>( pub fn minimal_set_object<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
not(|i| context.check_exit_matcher(i))(input)?; let (remaining, object) = alt((
alt((
map(parser_with_context!(subscript)(context), Object::Subscript), map(parser_with_context!(subscript)(context), Object::Subscript),
map( map(
parser_with_context!(superscript)(context), parser_with_context!(superscript)(context),
@@ -107,16 +104,16 @@ pub fn minimal_set_object<'r, 's>(
), ),
parser_with_context!(text_markup)(context), parser_with_context!(text_markup)(context),
map(parser_with_context!(plain_text)(context), Object::PlainText), map(parser_with_context!(plain_text)(context), Object::PlainText),
))(input) ))(input)?;
Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn any_object_except_plain_text<'r, 's>( pub fn any_object_except_plain_text<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
// Used for exit matchers so this does not check exit matcher condition. let (remaining, object) = alt((
alt((
map(parser_with_context!(timestamp)(context), Object::Timestamp), map(parser_with_context!(timestamp)(context), Object::Timestamp),
map(parser_with_context!(subscript)(context), Object::Subscript), map(parser_with_context!(subscript)(context), Object::Subscript),
map( map(
@@ -164,16 +161,17 @@ pub fn any_object_except_plain_text<'r, 's>(
map(parser_with_context!(plain_link)(context), Object::PlainLink), map(parser_with_context!(plain_link)(context), Object::PlainLink),
map(parser_with_context!(angle_link)(context), Object::AngleLink), map(parser_with_context!(angle_link)(context), Object::AngleLink),
map(parser_with_context!(org_macro)(context), Object::OrgMacro), map(parser_with_context!(org_macro)(context), Object::OrgMacro),
))(input) ))(input)?;
Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link_description_object_set<'r, 's>( pub fn regular_link_description_object_set<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Object<'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 ]] // TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
alt(( let (remaining, object) = alt((
map( map(
parser_with_context!(export_snippet)(context), parser_with_context!(export_snippet)(context),
Object::ExportSnippet, Object::ExportSnippet,
@@ -192,5 +190,40 @@ pub fn regular_link_description_object_set<'r, 's>(
), ),
map(parser_with_context!(org_macro)(context), Object::OrgMacro), map(parser_with_context!(org_macro)(context), Object::OrgMacro),
parser_with_context!(minimal_set_object)(context), parser_with_context!(minimal_set_object)(context),
))(input) ))(input)?;
Ok((remaining, object))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_cell_set_object<'r, 's>(
context: Context<'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)(context),
))(input)?;
Ok((remaining, object))
} }

View File

@@ -1,5 +1,6 @@
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
@@ -7,6 +8,7 @@ use nom::combinator::verify;
use nom::multi::many0; use nom::multi::many0;
use nom::multi::separated_list0; use nom::multi::separated_list0;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::object::OrgMacro; use crate::parser::object::OrgMacro;
@@ -15,25 +17,36 @@ use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_macro<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, OrgMacro<'s>> { pub fn org_macro<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgMacro<'s>> {
let (remaining, _) = tag("{{{")(input)?; let (remaining, _) = tag("{{{")(input)?;
let (remaining, macro_name) = org_macro_name(context, remaining)?; let (remaining, macro_name) = org_macro_name(context, remaining)?;
let (remaining, macro_args) = opt(parser_with_context!(org_macro_args)(context))(remaining)?; let (remaining, macro_args) = opt(parser_with_context!(org_macro_args)(context))(remaining)?;
let (remaining, _) = tag("}}}")(remaining)?; let (remaining, _) = tag("}}}")(remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
OrgMacro { OrgMacro {
source, source: source.into(),
macro_name, macro_name: macro_name.into(),
macro_args: macro_args.unwrap_or_else(|| Vec::with_capacity(0)), macro_args: macro_args
.unwrap_or_else(|| Vec::with_capacity(0))
.into_iter()
.map(|arg| arg.into())
.collect(),
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn org_macro_name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn org_macro_name<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = verify(anychar, |c| c.is_alphabetic())(input)?; let (remaining, _) = verify(anychar, |c| c.is_alphabetic())(input)?;
let (remaining, _) = many0(verify(anychar, |c| { let (remaining, _) = many0(verify(anychar, |c| {
c.is_alphanumeric() || *c == '-' || *c == '_' c.is_alphanumeric() || *c == '-' || *c == '_'
@@ -43,7 +56,10 @@ fn org_macro_name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn org_macro_args<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Vec<&'s str>> { fn org_macro_args<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
let (remaining, _) = tag("(")(input)?; let (remaining, _) = tag("(")(input)?;
let (remaining, args) = let (remaining, args) =
separated_list0(tag(","), parser_with_context!(org_macro_arg)(context))(remaining)?; separated_list0(tag(","), parser_with_context!(org_macro_arg)(context))(remaining)?;
@@ -53,7 +69,10 @@ fn org_macro_args<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn org_macro_arg<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn org_macro_arg<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let mut remaining = input; let mut remaining = input;
let mut escaping: bool = false; let mut escaping: bool = false;
loop { loop {

446
src/parser/org_source.rs Normal file
View File

@@ -0,0 +1,446 @@
use std::ops::RangeBounds;
use nom::Compare;
use nom::InputIter;
use nom::InputLength;
use nom::InputTake;
use nom::InputTakeAtPosition;
use nom::Offset;
use nom::Slice;
use crate::error::CustomError;
use crate::error::MyError;
pub type BracketDepth = i16;
#[derive(Copy, Clone)]
pub struct OrgSource<'s> {
full_source: &'s str,
start: usize,
end: usize, // exclusive
start_of_line: usize,
bracket_depth: BracketDepth, // []
brace_depth: BracketDepth, // {}
parenthesis_depth: BracketDepth, // ()
preceding_character: Option<char>,
}
impl<'s> std::fmt::Debug for OrgSource<'s> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("OrgSource")
.field(&Into::<&str>::into(self))
.finish()
}
}
impl<'s> OrgSource<'s> {
/// Returns a wrapped string that keeps track of values we need for parsing org-mode.
///
/// Only call this on the full original string. Calling this on a substring can result in invalid values.
pub fn new(input: &'s str) -> Self {
OrgSource {
full_source: input,
start: 0,
end: input.len(),
start_of_line: 0,
preceding_character: None,
bracket_depth: 0,
brace_depth: 0,
parenthesis_depth: 0,
}
}
/// Get the text since the line break preceding the start of this WrappedInput.
pub fn text_since_line_break(&self) -> &'s str {
&self.full_source[self.start_of_line..self.start]
}
pub fn len(&self) -> usize {
self.end - self.start
}
pub fn get_preceding_character(&self) -> Option<char> {
self.preceding_character
}
pub fn is_at_start_of_line(&self) -> bool {
self.start == self.start_of_line
}
pub fn get_until(&self, other: OrgSource<'s>) -> OrgSource<'s> {
assert!(other.start >= self.start);
assert!(other.end <= self.end);
self.slice(..(other.start - self.start))
}
pub fn get_bracket_depth(&self) -> BracketDepth {
self.bracket_depth
}
pub fn get_brace_depth(&self) -> BracketDepth {
self.brace_depth
}
pub fn get_parenthesis_depth(&self) -> BracketDepth {
self.parenthesis_depth
}
}
impl<'s> InputTake for OrgSource<'s> {
fn take(&self, count: usize) -> Self {
self.slice(..count)
}
fn take_split(&self, count: usize) -> (Self, Self) {
(self.slice(count..), self.slice(..count))
}
}
impl<'s, 'o, O: Into<&'o str>> Compare<O> for OrgSource<'s> {
fn compare(&self, t: O) -> nom::CompareResult {
(&self.full_source[self.start..self.end]).compare(t.into())
}
fn compare_no_case(&self, t: O) -> nom::CompareResult {
(&self.full_source[self.start..self.end]).compare_no_case(t.into())
}
}
impl<'s> From<&'s str> for OrgSource<'s> {
fn from(value: &'s str) -> Self {
OrgSource::new(value)
}
}
impl<'s> From<&OrgSource<'s>> for &'s str {
fn from(value: &OrgSource<'s>) -> Self {
&value.full_source[value.start..value.end]
}
}
impl<'s> From<OrgSource<'s>> for &'s str {
fn from(value: OrgSource<'s>) -> Self {
&value.full_source[value.start..value.end]
}
}
impl<'s, R> Slice<R> for OrgSource<'s>
where
R: RangeBounds<usize>,
{
fn slice(&self, range: R) -> Self {
let new_start = match range.start_bound() {
std::ops::Bound::Included(idx) => self.start + idx,
std::ops::Bound::Excluded(idx) => self.start + idx - 1,
std::ops::Bound::Unbounded => self.start,
};
let new_end = match range.end_bound() {
std::ops::Bound::Included(idx) => self.start + idx + 1,
std::ops::Bound::Excluded(idx) => self.start + idx,
std::ops::Bound::Unbounded => self.end,
};
if new_start < self.start {
panic!("Attempted to extend before the start of the WrappedInput.")
}
if new_end > self.end {
panic!("Attempted to extend past the end of the WrappedInput.")
}
let skipped_text = &self.full_source[self.start..new_start];
let mut start_of_line = self.start_of_line;
let mut bracket_depth = self.bracket_depth;
let mut brace_depth = self.brace_depth;
let mut parenthesis_depth = self.parenthesis_depth;
for (offset, byte) in skipped_text.bytes().enumerate() {
match byte {
b'\n' => {
start_of_line = self.start + offset + 1;
}
b'[' => {
bracket_depth += 1;
}
b']' => {
bracket_depth -= 1;
}
b'{' => {
brace_depth += 1;
}
b'}' => {
brace_depth -= 1;
}
b'(' => {
parenthesis_depth += 1;
}
b')' => {
parenthesis_depth -= 1;
}
_ => {}
};
}
OrgSource {
full_source: self.full_source,
start: new_start,
end: new_end,
start_of_line,
preceding_character: skipped_text.chars().last(),
bracket_depth,
brace_depth,
parenthesis_depth,
}
}
}
impl<'s> std::fmt::Display for OrgSource<'s> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
Into::<&str>::into(self).fmt(f)
}
}
impl<'s> InputLength for OrgSource<'s> {
fn input_len(&self) -> usize {
self.end - self.start
}
}
impl<'s> InputIter for OrgSource<'s> {
type Item = <&'s str as InputIter>::Item;
type Iter = <&'s str as InputIter>::Iter;
type IterElem = <&'s str as InputIter>::IterElem;
fn iter_indices(&self) -> Self::Iter {
Into::<&str>::into(self).char_indices()
}
fn iter_elements(&self) -> Self::IterElem {
Into::<&str>::into(self).iter_elements()
}
fn position<P>(&self, predicate: P) -> Option<usize>
where
P: Fn(Self::Item) -> bool,
{
Into::<&str>::into(self).position(predicate)
}
fn slice_index(&self, count: usize) -> Result<usize, nom::Needed> {
Into::<&str>::into(self).slice_index(count)
}
}
impl<'s> Offset for OrgSource<'s> {
fn offset(&self, second: &Self) -> usize {
second.start - self.start
}
}
impl<'s> InputTakeAtPosition for OrgSource<'s> {
type Item = <&'s str as InputTakeAtPosition>::Item;
fn split_at_position<P, E: nom::error::ParseError<Self>>(
&self,
predicate: P,
) -> nom::IResult<Self, Self, E>
where
P: Fn(Self::Item) -> bool,
{
match Into::<&str>::into(self).position(predicate) {
Some(idx) => Ok(self.take_split(idx)),
None => Err(nom::Err::Incomplete(nom::Needed::new(1))),
}
}
fn split_at_position1<P, E: nom::error::ParseError<Self>>(
&self,
predicate: P,
e: nom::error::ErrorKind,
) -> nom::IResult<Self, Self, E>
where
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(idx) => Ok(self.take_split(idx)),
None => Err(nom::Err::Incomplete(nom::Needed::new(1))),
}
}
fn split_at_position_complete<P, E: nom::error::ParseError<Self>>(
&self,
predicate: P,
) -> nom::IResult<Self, Self, E>
where
P: Fn(Self::Item) -> bool,
{
match self.split_at_position(predicate) {
Err(nom::Err::Incomplete(_)) => Ok(self.take_split(self.input_len())),
res => res,
}
}
fn split_at_position1_complete<P, E: nom::error::ParseError<Self>>(
&self,
predicate: P,
e: nom::error::ErrorKind,
) -> nom::IResult<Self, Self, E>
where
P: Fn(Self::Item) -> bool,
{
let window = Into::<&str>::into(self);
match window.position(predicate) {
Some(0) => Err(nom::Err::Error(E::from_error_kind(self.clone(), 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)))
} else {
Ok(self.take_split(self.input_len()))
}
}
}
}
}
pub 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),
}
}
}
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::*;
#[test]
fn range() {
let input = OrgSource::new("foo bar baz");
let output = input.slice(4..7);
assert_eq!(output.to_string(), "bar");
}
#[test]
fn range_to() {
let input = OrgSource::new("foo bar baz");
let output = input.slice(..7);
assert_eq!(output.to_string(), "foo bar");
}
#[test]
fn range_from() {
let input = OrgSource::new("foo bar baz");
let output = input.slice(4..);
assert_eq!(output.to_string(), "bar baz");
}
#[test]
fn full_range() {
let input = OrgSource::new("foo bar baz");
let output = input.slice(..);
assert_eq!(output.to_string(), "foo bar baz");
}
#[test]
fn nested_range() {
let input = OrgSource::new("lorem foo bar baz ipsum");
let first_cut = input.slice(6..17);
let output = first_cut.slice(4..7);
assert_eq!(first_cut.to_string(), "foo bar baz");
assert_eq!(output.to_string(), "bar");
}
#[test]
#[should_panic]
fn out_of_bounds() {
let input = OrgSource::new("lorem foo bar baz ipsum");
input.slice(6..30);
}
#[test]
#[should_panic]
fn out_of_nested_bounds() {
let input = OrgSource::new("lorem foo bar baz ipsum");
let first_cut = input.slice(6..17);
first_cut.slice(4..14);
}
#[test]
fn line_break() {
let input = OrgSource::new("lorem\nfoo\nbar\nbaz\nipsum");
assert_eq!(input.slice(5..).start_of_line, 0);
assert_eq!(input.slice(6..).start_of_line, 6);
assert_eq!(input.slice(6..).slice(10..).start_of_line, 14);
}
#[test]
fn text_since_line_break() {
let input = OrgSource::new("lorem\nfoo\nbar\nbaz\nipsum");
assert_eq!(input.text_since_line_break(), "");
assert_eq!(input.slice(5..).text_since_line_break(), "lorem");
assert_eq!(input.slice(6..).text_since_line_break(), "");
assert_eq!(input.slice(6..).slice(10..).text_since_line_break(), "ba");
}
#[test]
fn preceding_character() {
let input = OrgSource::new("lorem\nfoo\nbar\nbaz\nipsum");
assert_eq!(input.get_preceding_character(), None);
assert_eq!(input.slice(5..).get_preceding_character(), Some('m'));
assert_eq!(input.slice(6..).get_preceding_character(), Some('\n'));
assert_eq!(
input.slice(6..).slice(10..).get_preceding_character(),
Some('a')
);
}
#[test]
fn is_at_start_of_line() {
let input = OrgSource::new("lorem\nfoo\nbar\nbaz\nipsum");
assert_eq!(input.is_at_start_of_line(), true);
assert_eq!(input.slice(5..).is_at_start_of_line(), false);
assert_eq!(input.slice(6..).is_at_start_of_line(), true);
assert_eq!(input.slice(6..).slice(10..).is_at_start_of_line(), false);
}
#[test]
fn preceding_character_unicode() {
let input = OrgSource::new("🧡💛💚💙💜");
assert_eq!(input.get_preceding_character(), None);
assert_eq!(input.slice(8..).get_preceding_character(), Some('💛'));
}
#[test]
fn depth() {
let input = OrgSource::new("[][()][({)]}}}}");
assert_eq!(input.get_bracket_depth(), 0);
assert_eq!(input.get_brace_depth(), 0);
assert_eq!(input.get_parenthesis_depth(), 0);
assert_eq!(input.slice(4..).get_bracket_depth(), 1);
assert_eq!(input.slice(4..).get_brace_depth(), 0);
assert_eq!(input.slice(4..).get_parenthesis_depth(), 1);
assert_eq!(input.slice(4..).slice(6..).get_bracket_depth(), 1);
assert_eq!(input.slice(4..).slice(6..).get_brace_depth(), 1);
assert_eq!(input.slice(4..).slice(6..).get_parenthesis_depth(), 0);
assert_eq!(input.slice(14..).get_brace_depth(), -2);
}
}

View File

@@ -6,12 +6,13 @@ use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::element_parser::detect_element;
use super::lesser_element::Paragraph; use super::lesser_element::Paragraph;
use super::org_source::OrgSource;
use super::util::blank_line; use super::util::blank_line;
use super::util::get_consumed; use super::util::get_consumed;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
@@ -21,10 +22,13 @@ use crate::parser::util::exit_matcher_parser;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn paragraph<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Paragraph<'s>> { pub fn paragraph<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &paragraph_end, exit_matcher: &paragraph_end,
})); }));
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context); let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
@@ -39,15 +43,23 @@ pub fn paragraph<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Paragraph { source, children })) Ok((
remaining,
Paragraph {
source: source.into(),
children,
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn paragraph_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn paragraph_end<'r, 's>(
let non_paragraph_element_matcher = parser_with_context!(element(false))(context); context: Context<'r, 's>,
let start_of_line_matcher = parser_with_context!(start_of_line)(&context); input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let non_paragraph_element_matcher = parser_with_context!(detect_element(false))(context);
alt(( alt((
recognize(tuple((start_of_line_matcher, many1(blank_line)))), recognize(tuple((start_of_line, many1(blank_line)))),
recognize(non_paragraph_element_matcher), recognize(non_paragraph_element_matcher),
eof, eof,
))(input) ))(input)
@@ -56,22 +68,20 @@ fn paragraph_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::parser_context::ContextElement; use crate::parser::org_source::OrgSource;
use crate::parser::parser_context::ContextTree; use crate::parser::parser_context::ContextTree;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::Source; use crate::parser::source::Source;
#[test] #[test]
fn two_paragraphs() { fn two_paragraphs() {
let input = "foo bar baz\n\nlorem ipsum"; let input = OrgSource::new("foo bar baz\n\nlorem ipsum");
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let paragraph_matcher = parser_with_context!(element(true))(&document_context);
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph"); let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
let (remaining, second_paragraph) = let (remaining, second_paragraph) =
paragraph_matcher(remaining).expect("Parse second paragraph."); paragraph_matcher(remaining).expect("Parse second paragraph.");
assert_eq!(remaining, ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(first_paragraph.get_source(), "foo bar baz\n\n"); assert_eq!(first_paragraph.get_source(), "foo bar baz\n\n");
assert_eq!(second_paragraph.get_source(), "lorem ipsum"); assert_eq!(second_paragraph.get_source(), "lorem ipsum");
} }

View File

@@ -5,6 +5,7 @@ use nom::IResult;
use super::list::List; use super::list::List;
use super::list::Node; use super::list::Node;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use super::Object; use super::Object;
use crate::error::CustomError; use crate::error::CustomError;
@@ -12,7 +13,8 @@ use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
type Matcher = dyn for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str>; type Matcher =
dyn for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ContextTree<'r, 's> { pub struct ContextTree<'r, 's> {
@@ -47,8 +49,8 @@ impl<'r, 's> ContextTree<'r, 's> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn check_exit_matcher( pub fn check_exit_matcher(
&'r self, &'r self,
i: &'s str, i: OrgSource<'s>,
) -> IResult<&'s str, &'s str, CustomError<&'s str>> { ) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> {
// Special check for EOF. We don't just make this a document-level exit matcher since the IgnoreParent ChainBehavior could cause early exit matchers to not run. // Special check for EOF. We don't just make this a document-level exit matcher since the IgnoreParent ChainBehavior could cause early exit matchers to not run.
let at_end_of_file = eof(i); let at_end_of_file = eof(i);
if at_end_of_file.is_ok() { if at_end_of_file.is_ok() {
@@ -60,7 +62,7 @@ impl<'r, 's> ContextTree<'r, 's> {
// exit_matcher: ChainBehavior::IgnoreParent(Some(&always_fail)), // exit_matcher: ChainBehavior::IgnoreParent(Some(&always_fail)),
// })); // }));
let mut current_class_filter = ExitClass::Beta; let mut current_class_filter = ExitClass::Gamma;
for current_node in self.iter() { for current_node in self.iter() {
let context_element = current_node.get_data(); let context_element = current_node.get_data();
match context_element { match context_element {
@@ -78,20 +80,9 @@ impl<'r, 's> ContextTree<'r, 's> {
}; };
} }
// TODO: Make this a specific error instead of just a generic MyError // TODO: Make this a specific error instead of just a generic MyError
return Err(nom::Err::Error(CustomError::MyError(MyError("NoExit")))); return Err(nom::Err::Error(CustomError::MyError(MyError(
} "NoExit".into(),
))));
pub fn get_document_root(&self) -> Option<&'s str> {
for current_node in self.iter() {
let context_element = current_node.get_data();
match context_element {
ContextElement::DocumentRoot(body) => {
return Some(body);
}
_ => {}
}
}
None
} }
/// Indicates if elements should consume the whitespace after them. /// Indicates if elements should consume the whitespace after them.
@@ -117,21 +108,12 @@ impl<'r, 's> ContextTree<'r, 's> {
#[derive(Debug)] #[derive(Debug)]
pub enum ContextElement<'r, 's> { pub enum ContextElement<'r, 's> {
/// Stores a reference to the entire org-mode document being parsed.
///
/// This is used for look-behind.
DocumentRoot(&'s str),
/// Stores a parser that indicates that children should exit upon matching an exit matcher. /// Stores a parser that indicates that children should exit upon matching an exit matcher.
ExitMatcherNode(ExitMatcherNode<'r>), ExitMatcherNode(ExitMatcherNode<'r>),
/// Stores the name of the current element to prevent directly nesting elements of the same type.
Context(&'r str), Context(&'r str),
/// Stores the indentation level of the current list item.
ListItem(usize),
/// Stores the name of the greater block.
GreaterBlock(&'s str),
/// Indicates if elements should consume the whitespace after them. /// Indicates if elements should consume the whitespace after them.
ConsumeTrailingWhitespace(bool), ConsumeTrailingWhitespace(bool),
@@ -141,71 +123,6 @@ pub enum ContextElement<'r, 's> {
/// org-mode document since text needs to be re-parsed to look for /// org-mode document since text needs to be re-parsed to look for
/// radio links matching the contents of radio targets. /// radio links matching the contents of radio targets.
RadioTarget(Vec<&'r Vec<Object<'s>>>), RadioTarget(Vec<&'r Vec<Object<'s>>>),
/// Stores the current bracket depth inside a footnote reference's definition.
///
/// The definition inside a footnote reference must have balanced
/// brackets [] inside the definition, so this stores the amount
/// of opening brackets subtracted by the amount of closing
/// brackets within the definition must equal zero.
///
/// A reference to the position in the string is also included so
/// unbalanced brackets can be detected in the middle of an
/// object.
FootnoteReferenceDefinition(FootnoteReferenceDefinition<'s>),
/// Stores the current bracket depth inside a citation.
///
/// The global prefix, global suffix, key prefix, and key suffix
/// inside a footnote reference must have balanced brackets []
/// inside the definition, so this stores the amount of opening
/// brackets subtracted by the amount of closing brackets within
/// the definition must equal zero. None of the prefixes or
/// suffixes can be nested inside each other so we can use a
/// single type for this without conflict.
///
/// A reference to the position in the string is also included so
/// unbalanced brackets can be detected in the middle of an
/// object.
CitationBracket(CitationBracket<'s>),
/// Stores the current bracket or parenthesis depth inside an inline babel call.
///
/// Inside an inline babel call the headers must have balanced
/// parentheses () and the arguments must have balanced brackets
/// [], so this stores the amount of opening brackets subtracted
/// by the amount of closing brackets within the definition must
/// equal zero.
///
/// A reference to the position in the string is also included so
/// unbalanced brackets can be detected in the middle of an
/// object.
BabelHeaderBracket(BabelHeaderBracket<'s>),
/// Stores the current bracket or parenthesis depth inside an inline babel call.
///
/// Inside an inline babel call the headers must have balanced
/// parentheses () and the arguments must have balanced brackets
/// [], so this stores the amount of opening brackets subtracted
/// by the amount of closing brackets within the definition must
/// equal zero.
///
/// A reference to the position in the string is also included so
/// unbalanced brackets can be detected in the middle of an
/// object.
InlineSourceBlockBracket(InlineSourceBlockBracket<'s>),
/// Stores the current bracket or parenthesis depth inside a
/// superscript or superscript.
///
/// Inside the braces of a subscript or superscript there must be
/// balanced braces {}, so this stores the amount of opening
/// braces subtracted by the amount of closing braces within the
/// definition must equal zero.
///
/// A reference to the position in the string is also included so
/// unbalanced braces can be detected in the middle of an object.
SubscriptSuperscriptBrace(SubscriptSuperscriptBrace<'s>),
} }
pub struct ExitMatcherNode<'r> { pub struct ExitMatcherNode<'r> {
@@ -213,36 +130,6 @@ pub struct ExitMatcherNode<'r> {
pub class: ExitClass, pub class: ExitClass,
} }
#[derive(Debug)]
pub struct FootnoteReferenceDefinition<'s> {
pub position: &'s str,
pub depth: usize,
}
#[derive(Debug)]
pub struct CitationBracket<'s> {
pub position: &'s str,
pub depth: usize,
}
#[derive(Debug)]
pub struct BabelHeaderBracket<'s> {
pub position: &'s str,
pub depth: usize,
}
#[derive(Debug)]
pub struct InlineSourceBlockBracket<'s> {
pub position: &'s str,
pub depth: usize,
}
#[derive(Debug)]
pub struct SubscriptSuperscriptBrace<'s> {
pub position: &'s str,
pub depth: usize,
}
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> { impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut formatter = f.debug_struct("ExitMatcherNode"); let mut formatter = f.debug_struct("ExitMatcherNode");

View File

@@ -7,8 +7,10 @@ use nom::character::complete::one_of;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -20,11 +22,40 @@ use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::get_one_before;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS; use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
// TODO: Make this a user-provided variable corresponding to elisp's org-link-parameters
const ORG_LINK_PARAMETERS: [&'static str; 23] = [
"id",
"eww",
"rmail",
"mhe",
"irc",
"info",
"gnus",
"docview",
"bibtex",
"bbdb",
"w3m",
"doi",
"file+sys",
"file+emacs",
"shell",
"news",
"mailto",
"https",
"http",
"ftp",
"help",
"file",
"elisp",
];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn plain_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, PlainLink<'s>> { pub fn plain_link<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainLink<'s>> {
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, proto) = protocol(context, remaining)?; let (remaining, proto) = protocol(context, remaining)?;
let (remaining, _separator) = tag(":")(remaining)?; let (remaining, _separator) = tag(":")(remaining)?;
@@ -34,19 +65,16 @@ pub fn plain_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
Ok(( Ok((
remaining, remaining,
PlainLink { PlainLink {
source, source: source.into(),
link_type: proto, link_type: proto.into(),
path, path: path.into(),
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> { fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let document_root = context.get_document_root().unwrap(); let preceding_character = input.get_preceding_character();
let preceding_character = get_one_before(document_root, input)
.map(|slice| slice.chars().next())
.flatten();
match preceding_character { match preceding_character {
// If None, we are at the start of the file which is fine // If None, we are at the start of the file which is fine
None => {} None => {}
@@ -54,7 +82,7 @@ fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
Some(_) => { Some(_) => {
// Not at start of line, cannot be a heading // Not at start of line, cannot be a heading
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for plain link.", "Not a valid pre character for plain link.".into(),
)))); ))));
} }
}; };
@@ -62,62 +90,63 @@ fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn post<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> { fn post<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (remaining, _) = alt((eof, recognize(none_of(WORD_CONSTITUENT_CHARACTERS))))(input)?; let (remaining, _) = alt((eof, recognize(none_of(WORD_CONSTITUENT_CHARACTERS))))(input)?;
Ok((remaining, ())) Ok((remaining, ()))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn protocol<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { pub fn protocol<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should be defined by org-link-parameters // TODO: This should be defined by org-link-parameters
let (remaining, proto) = alt(( for link_parameter in ORG_LINK_PARAMETERS {
alt(( let result = tag_no_case::<_, _, CustomError<_>>(link_parameter)(input);
tag_no_case("id"), match result {
tag_no_case("eww"), Ok((remaining, ent)) => {
tag_no_case("rmail"), return Ok((remaining, ent));
tag_no_case("mhe"), }
tag_no_case("irc"), Err(_) => {}
tag_no_case("info"), }
tag_no_case("gnus"), }
tag_no_case("docview"),
tag_no_case("bibtex"), Err(nom::Err::Error(CustomError::MyError(MyError(
tag_no_case("bbdb"), "NoLinkProtocol".into(),
tag_no_case("w3m"), ))))
)),
alt((
tag_no_case("doi"),
tag_no_case("file+sys"),
tag_no_case("file+emacs"),
tag_no_case("shell"),
tag_no_case("news"),
tag_no_case("mailto"),
tag_no_case("https"),
tag_no_case("http"),
tag_no_case("ftp"),
tag_no_case("help"),
tag_no_case("file"),
tag_no_case("elisp"),
)),
))(input)?;
Ok((remaining, proto))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_plain<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn path_plain<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: "optionally containing parenthesis-wrapped non-whitespace non-bracket substrings up to a depth of two. The string must end with either a non-punctation non-whitespace character, a forwards slash, or a parenthesis-wrapped substring" // TODO: "optionally containing parenthesis-wrapped non-whitespace non-bracket substrings up to a depth of two. The string must end with either a non-punctation non-whitespace character, a forwards slash, or a parenthesis-wrapped substring"
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &path_plain_end, exit_matcher: &path_plain_end,
})); }));
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, path) = recognize(many_till(anychar, peek(exit_matcher)))(input)?; let (remaining, path) = recognize(verify(
many_till(anychar, peek(exit_matcher)),
|(children, _exit_contents)| !children.is_empty(),
))(input)?;
Ok((remaining, path)) Ok((remaining, path))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_plain_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn path_plain_end<'r, 's>(
recognize(one_of(" \t\r\n()[]<>"))(input) _context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many_till(
verify(anychar, |c| {
*c != '/' && (c.is_ascii_punctuation() || c.is_whitespace())
}),
one_of(" \t\r\n()[]<>"),
))(input)
} }

View File

@@ -6,18 +6,24 @@ use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1; use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::greater_element::PlainList; use super::greater_element::PlainList;
use super::greater_element::PlainListItem; use super::greater_element::PlainListItem;
use super::object_parser::standard_set_object;
use super::org_source::OrgSource;
use super::parser_with_context::parser_with_context; use super::parser_with_context::parser_with_context;
use super::util::non_whitespace_character; use super::util::non_whitespace_character;
use super::Context; use super::Context;
use super::Object;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
@@ -32,7 +38,35 @@ use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, PlainList<'s>> { pub fn detect_plain_list<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if verify(
tuple((
start_of_line,
space0,
bullet,
alt((space1, line_ending, eof)),
)),
|(_start, indent, bull, _after_whitespace)| {
Into::<&str>::into(bull) != "*" || indent.len() > 0
},
)(input)
.is_ok()
{
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No element detected.".into(),
))));
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn plain_list<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainList<'s>> {
let parser_context = context let parser_context = context
.with_additional_node(ContextElement::Context("plain list")) .with_additional_node(ContextElement::Context("plain list"))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
@@ -76,7 +110,7 @@ pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
Some(final_child) => final_child, Some(final_child) => final_child,
None => { None => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Plain lists require at least one element.", "Plain lists require at least one element.".into(),
)))); ))));
} }
}; };
@@ -86,14 +120,11 @@ pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?; parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?;
children.push((final_child_start, reparsed_final_item)); children.push((final_child_start, reparsed_final_item));
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
PlainList { PlainList {
source, source: source.into(),
children: children.into_iter().map(|(_start, item)| item).collect(), children: children.into_iter().map(|(_start, item)| item).collect(),
}, },
)) ))
@@ -102,25 +133,27 @@ pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn plain_list_item<'r, 's>( pub fn plain_list_item<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, PlainListItem<'s>> { ) -> Res<OrgSource<'s>, PlainListItem<'s>> {
start_of_line(context, input)?; start_of_line(input)?;
let (remaining, leading_whitespace) = space0(input)?; let (remaining, leading_whitespace) = space0(input)?;
// 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) // 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)
let indent_level = leading_whitespace.len(); let indent_level = leading_whitespace.len();
let (remaining, bull) = let (remaining, bull) = verify(bullet, |bull: &OrgSource<'_>| {
verify(bullet, |bull: &str| bull != "*" || indent_level > 0)(remaining)?; Into::<&str>::into(bull) != "*" || indent_level > 0
})(remaining)?;
let maybe_contentless_item: Res<&str, &str> = alt((eof, line_ending))(remaining); let maybe_contentless_item: Res<OrgSource<'_>, OrgSource<'_>> = eof(remaining);
match maybe_contentless_item { match maybe_contentless_item {
Ok((rem, _ws)) => { Ok((rem, _ws)) => {
let source = get_consumed(input, rem); let source = get_consumed(input, rem);
return Ok(( return Ok((
rem, rem,
PlainListItem { PlainListItem {
source, source: source.into(),
indentation: indent_level, indentation: indent_level,
bullet: bull, bullet: bull.into(),
tag: Vec::new(),
children: Vec::new(), children: Vec::new(),
}, },
)); ));
@@ -128,21 +161,26 @@ pub fn plain_list_item<'r, 's>(
Err(_) => {} Err(_) => {}
}; };
let (remaining, _ws) = space1(remaining)?; let (remaining, maybe_tag) = opt(tuple((
space1,
parser_with_context!(item_tag)(context),
tag(" ::"),
)))(remaining)?;
let (remaining, _ws) = item_tag_post_gap(context, remaining)?;
let exit_matcher = plain_list_item_end(indent_level);
let parser_context = context let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::ListItem(indent_level))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
exit_matcher: &plain_list_item_end, exit_matcher: &exit_matcher,
})); }));
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = many_till(
many_till( parser_with_context!(element(true))(&parser_context),
parser_with_context!(element(true))(&parser_context), alt((
peek(recognize(tuple((start_of_line, many0(blank_line), eof)))),
parser_with_context!(exit_matcher_parser)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context),
), )),
|(children, _exit_contents)| !children.is_empty(),
)(remaining)?; )(remaining)?;
let (remaining, _trailing_ws) = let (remaining, _trailing_ws) =
@@ -152,16 +190,19 @@ pub fn plain_list_item<'r, 's>(
return Ok(( return Ok((
remaining, remaining,
PlainListItem { PlainListItem {
source, source: source.into(),
indentation: indent_level, indentation: indent_level,
bullet: bull, bullet: bull.into(),
tag: maybe_tag
.map(|(_ws, item_tag, _divider)| item_tag)
.unwrap_or(Vec::new()),
children, children,
}, },
)); ));
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn bullet<'s>(i: &'s str) -> Res<&'s str, &'s str> { fn bullet<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt(( alt((
tag("*"), tag("*"),
tag("-"), tag("-"),
@@ -171,118 +212,179 @@ fn bullet<'s>(i: &'s str) -> Res<&'s str, &'s str> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn counter<'s>(i: &'s str) -> Res<&'s str, &'s str> { fn counter<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((recognize(one_of("abcdefghijklmnopqrstuvwxyz")), digit1))(i) alt((recognize(one_of("abcdefghijklmnopqrstuvwxyz")), digit1))(i)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn plain_list_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn plain_list_end<'r, 's>(
let start_of_line_matcher = parser_with_context!(start_of_line)(context); _context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple(( recognize(tuple((
start_of_line_matcher, start_of_line,
verify(many1(blank_line), |lines: &Vec<&str>| lines.len() >= 2), verify(many1(blank_line), |lines: &Vec<OrgSource<'_>>| {
lines.len() >= 2
}),
)))(input) )))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] const fn plain_list_item_end(
fn plain_list_item_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { indent_level: usize,
start_of_line(context, input)?; ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let line_indented_lte_matcher = line_indented_lte(indent_level);
move |context: Context, input: OrgSource<'_>| {
_plain_list_item_end(context, input, &line_indented_lte_matcher)
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(line_indented_lte_matcher))
)]
fn _plain_list_item_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
line_indented_lte_matcher: impl for<'rr, 'ss> Fn(
Context<'rr, 'ss>,
OrgSource<'ss>,
) -> Res<OrgSource<'ss>, OrgSource<'ss>>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
recognize(tuple(( recognize(tuple((
opt(blank_line), opt(blank_line),
parser_with_context!(line_indented_lte)(context), parser_with_context!(line_indented_lte_matcher)(context),
)))(input) )))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] const fn line_indented_lte(
fn line_indented_lte<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { indent_level: usize,
let current_item_indent_level: &usize = ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
get_context_item_indent(context).ok_or(nom::Err::Error(CustomError::MyError(MyError( move |context: Context, input: OrgSource<'_>| _line_indented_lte(context, input, indent_level)
"Not inside a plain list item", }
))))?;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _line_indented_lte<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
indent_level: usize,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let matched = recognize(verify( let matched = recognize(verify(
tuple((space0::<&str, _>, non_whitespace_character)), tuple((space0::<OrgSource<'_>, _>, 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) // 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)
|(_space0, _anychar)| _space0.len() <= *current_item_indent_level, |(_space0, _anychar)| _space0.len() <= indent_level,
))(input)?; ))(input)?;
Ok(matched) Ok(matched)
} }
fn get_context_item_indent<'r, 's>(context: Context<'r, 's>) -> Option<&'r usize> { #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
for thing in context.iter() { fn item_tag<'r, 's>(
match thing.get_data() { context: Context<'r, 's>,
ContextElement::ListItem(depth) => return Some(depth), input: OrgSource<'s>,
_ => {} ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
}; let parser_context =
} context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
None class: ExitClass::Gamma,
exit_matcher: &item_tag_end,
}));
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),
),
|(children, _exit_contents)| !children.is_empty(),
)(input)?;
Ok((remaining, children))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(alt((
line_ending,
tag(" :: "),
recognize(tuple((tag(" ::"), alt((line_ending, eof))))),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag_post_gap<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
verify(
recognize(tuple((
alt((blank_line, space0)),
many_till(
blank_line,
alt((
peek(recognize(not(blank_line))),
peek(recognize(tuple((many0(blank_line), eof)))),
parser_with_context!(exit_matcher_parser)(context),
)),
),
))),
|gap| gap.len() > 0,
)(input)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree; use crate::parser::parser_context::ContextTree;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::Source; use crate::parser::Source;
#[test] #[test]
fn plain_list_item_empty() { fn plain_list_item_empty() {
let input = "1."; let input = OrgSource::new("1.");
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&document_context);
let (remaining, result) = plain_list_item_matcher(input).unwrap(); let (remaining, result) = plain_list_item_matcher(input).unwrap();
assert_eq!(remaining, ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.source, "1."); assert_eq!(result.source, "1.");
} }
#[test] #[test]
fn plain_list_item_simple() { fn plain_list_item_simple() {
let input = "1. foo"; let input = OrgSource::new("1. foo");
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&document_context);
let (remaining, result) = plain_list_item_matcher(input).unwrap(); let (remaining, result) = plain_list_item_matcher(input).unwrap();
assert_eq!(remaining, ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.source, "1. foo"); assert_eq!(result.source, "1. foo");
} }
#[test] #[test]
fn plain_list_empty() { fn plain_list_empty() {
let input = "1."; let input = OrgSource::new("1.");
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let plain_list_matcher = parser_with_context!(plain_list)(&document_context);
let (remaining, result) = plain_list_matcher(input).unwrap(); let (remaining, result) = plain_list_matcher(input).unwrap();
assert_eq!(remaining, ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.source, "1."); assert_eq!(result.source, "1.");
} }
#[test] #[test]
fn plain_list_simple() { fn plain_list_simple() {
let input = "1. foo"; let input = OrgSource::new("1. foo");
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let plain_list_matcher = parser_with_context!(plain_list)(&document_context);
let (remaining, result) = plain_list_matcher(input).unwrap(); let (remaining, result) = plain_list_matcher(input).unwrap();
assert_eq!(remaining, ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.source, "1. foo"); assert_eq!(result.source, "1. foo");
} }
#[test] #[test]
fn plain_list_cant_start_line_with_asterisk() { fn plain_list_cant_start_line_with_asterisk() {
// Plain lists with an asterisk bullet must be indented or else they would be a headline // Plain lists with an asterisk bullet must be indented or else they would be a headline
let input = "* foo"; let input = OrgSource::new("* foo");
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let plain_list_matcher = parser_with_context!(plain_list)(&document_context);
let result = plain_list_matcher(input); let result = plain_list_matcher(input);
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -290,32 +392,30 @@ mod tests {
#[test] #[test]
fn indented_can_start_line_with_asterisk() { fn indented_can_start_line_with_asterisk() {
// Plain lists with an asterisk bullet must be indented or else they would be a headline // Plain lists with an asterisk bullet must be indented or else they would be a headline
let input = " * foo"; let input = OrgSource::new(" * foo");
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let plain_list_matcher = parser_with_context!(plain_list)(&document_context);
let result = plain_list_matcher(input); let result = plain_list_matcher(input);
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[test] #[test]
fn two_blank_lines_ends_list() { fn two_blank_lines_ends_list() {
let input = r#"1. foo let input = OrgSource::new(
r#"1. foo
2. bar 2. bar
baz baz
3. lorem 3. lorem
ipsum ipsum
"#; "#,
);
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let plain_list_matcher = parser_with_context!(element(true))(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let plain_list_matcher = parser_with_context!(element(true))(&document_context);
let (remaining, result) = let (remaining, result) =
plain_list_matcher(input).expect("Should parse the plain list successfully."); plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(remaining, " ipsum\n"); assert_eq!(Into::<&str>::into(remaining), " ipsum\n");
assert_eq!( assert_eq!(
result.get_source(), result.get_source(),
r#"1. foo r#"1. foo
@@ -330,18 +430,18 @@ mod tests {
#[test] #[test]
fn two_blank_lines_ends_nested_list() { fn two_blank_lines_ends_nested_list() {
let input = r#"1. foo let input = OrgSource::new(
r#"1. foo
1. bar 1. bar
baz"#; baz"#,
);
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let plain_list_matcher = parser_with_context!(element(true))(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let plain_list_matcher = parser_with_context!(element(true))(&document_context);
let (remaining, result) = let (remaining, result) =
plain_list_matcher(input).expect("Should parse the plain list successfully."); plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(remaining, "baz"); assert_eq!(Into::<&str>::into(remaining), "baz");
assert_eq!( assert_eq!(
result.get_source(), result.get_source(),
r#"1. foo r#"1. foo
@@ -354,7 +454,8 @@ baz"#;
#[test] #[test]
fn interior_trailing_whitespace() { fn interior_trailing_whitespace() {
let input = r#"1. foo let input = OrgSource::new(
r#"1. foo
bar bar
@@ -365,14 +466,13 @@ baz"#;
ipsum ipsum
dolar"#; dolar"#,
);
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let plain_list_matcher = parser_with_context!(element(true))(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let plain_list_matcher = parser_with_context!(element(true))(&document_context);
let (remaining, result) = let (remaining, result) =
plain_list_matcher(input).expect("Should parse the plain list successfully."); plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(remaining, "dolar"); assert_eq!(Into::<&str>::into(remaining), "dolar");
assert_eq!( assert_eq!(
result.get_source(), result.get_source(),
r#"1. foo r#"1. foo
@@ -389,4 +489,40 @@ dolar"#;
"# "#
); );
} }
#[test]
fn detect_line_break() {
let input = OrgSource::new(
r#"+
"#,
);
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let result = detect_plain_list(&initial_context, input);
assert!(result.is_ok());
}
#[test]
fn detect_eof() {
let input = OrgSource::new(r#"+"#);
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let result = detect_plain_list(&initial_context, input);
assert!(result.is_ok());
}
#[test]
fn detect_no_gap() {
let input = OrgSource::new(r#"+foo"#);
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let result = detect_plain_list(&initial_context, input);
// Since there is no whitespace after the '+' this is a paragraph, not a plain list.
assert!(result.is_err());
}
#[test]
fn detect_with_gap() {
let input = OrgSource::new(r#"+ foo"#);
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let result = detect_plain_list(&initial_context, input);
assert!(result.is_ok());
}
} }

View File

@@ -8,6 +8,7 @@ use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::object::PlainText; use super::object::PlainText;
use super::org_source::OrgSource;
use super::radio_link::RematchObject; use super::radio_link::RematchObject;
use super::Context; use super::Context;
use super::Object; use super::Object;
@@ -17,7 +18,10 @@ use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn plain_text<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, PlainText<'s>> { pub fn plain_text<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainText<'s>> {
let (remaining, source) = recognize(verify( let (remaining, source) = recognize(verify(
many_till( many_till(
anychar, anychar,
@@ -29,11 +33,19 @@ pub fn plain_text<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),
))(input)?; ))(input)?;
Ok((remaining, PlainText { source })) Ok((
remaining,
PlainText {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn plain_text_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn plain_text_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(parser_with_context!(any_object_except_plain_text)(context))(input) recognize(parser_with_context!(any_object_except_plain_text)(context))(input)
} }
@@ -42,10 +54,12 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
fn rematch_object<'r, 's>( fn rematch_object<'r, 's>(
&'x self, &'x self,
_context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
map(tag(self.source), |s| { map(tag(self.source), |s| {
Object::PlainText(PlainText { source: s }) Object::PlainText(PlainText {
source: Into::<&str>::into(s),
})
})(input) })(input)
} }
} }
@@ -55,20 +69,17 @@ mod tests {
use nom::combinator::map; use nom::combinator::map;
use super::*; use super::*;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree; use crate::parser::parser_context::ContextTree;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::Source; use crate::parser::source::Source;
#[test] #[test]
fn plain_text_simple() { fn plain_text_simple() {
let input = "foobarbaz"; let input = OrgSource::new("foobarbaz");
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = let plain_text_matcher = parser_with_context!(plain_text)(&initial_context);
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let plain_text_matcher = parser_with_context!(plain_text)(&document_context);
let (remaining, result) = map(plain_text_matcher, Object::PlainText)(input).unwrap(); let (remaining, result) = map(plain_text_matcher, Object::PlainText)(input).unwrap();
assert_eq!(remaining, ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_source(), input); assert_eq!(result.get_source(), Into::<&str>::into(input));
} }
} }

View File

@@ -9,6 +9,7 @@ use nom::combinator::eof;
use nom::multi::separated_list1; use nom::multi::separated_list1;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::lesser_element::Planning; use crate::parser::lesser_element::Planning;
@@ -16,19 +17,27 @@ use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn planning<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Planning<'s>> { pub fn planning<'r, 's>(
start_of_line(context, input)?; _context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Planning<'s>> {
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, _planning_parameters) = separated_list1(space1, planning_parameter)(remaining)?; let (remaining, _planning_parameters) = separated_list1(space1, planning_parameter)(remaining)?;
let (remaining, _trailing_ws) = tuple((space0, alt((line_ending, eof))))(remaining)?; let (remaining, _trailing_ws) = tuple((space0, alt((line_ending, eof))))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Planning { source })) Ok((
remaining,
Planning {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn planning_parameter<'s>(input: &'s str) -> Res<&'s str, &'s str> { fn planning_parameter<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _planning_type) = alt(( let (remaining, _planning_type) = alt((
tag_no_case("DEADLINE"), tag_no_case("DEADLINE"),
tag_no_case("SCHEDULED"), tag_no_case("SCHEDULED"),

View File

@@ -2,16 +2,18 @@ use nom::branch::alt;
use nom::bytes::complete::is_not; use nom::bytes::complete::is_not;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -22,7 +24,6 @@ use crate::parser::greater_element::PropertyDrawer;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::plain_text::plain_text;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section; use crate::parser::util::immediate_in_section;
@@ -32,18 +33,18 @@ use crate::parser::util::start_of_line;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn property_drawer<'r, 's>( pub fn property_drawer<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, PropertyDrawer<'s>> { ) -> Res<OrgSource<'s>, PropertyDrawer<'s>> {
if immediate_in_section(context, "property-drawer") { if immediate_in_section(context, "property-drawer") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element", "Cannot nest objects of the same element".into(),
)))); ))));
} }
let ( let (
remaining, remaining,
(_start_of_line, _leading_whitespace, _open_tag, _trailing_whitespace, _line_ending), (_start_of_line, _leading_whitespace, _open_tag, _trailing_whitespace, _line_ending),
) = tuple(( ) = tuple((
parser_with_context!(start_of_line)(context), start_of_line,
space0, space0,
tag_no_case(":PROPERTIES:"), tag_no_case(":PROPERTIES:"),
space0, space0,
@@ -68,13 +69,22 @@ pub fn property_drawer<'r, 's>(
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, PropertyDrawer { source, children })) Ok((
remaining,
PropertyDrawer {
source: source.into(),
children,
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn property_drawer_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn property_drawer_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple(( recognize(tuple((
parser_with_context!(start_of_line)(context), start_of_line,
space0, space0,
tag_no_case(":end:"), tag_no_case(":end:"),
space0, space0,
@@ -85,23 +95,27 @@ fn property_drawer_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn node_property<'r, 's>( fn node_property<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, NodeProperty<'s>> { ) -> Res<OrgSource<'s>, NodeProperty<'s>> {
let (remaining, (_start_of_line, _leading_whitespace, _open_colon, _name, _close_colon)) = let (remaining, (_start_of_line, _leading_whitespace, _open_colon, _name, _close_colon)) =
tuple(( tuple((
parser_with_context!(start_of_line)(context), start_of_line,
space0, space0,
tag(":"), tag(":"),
parser_with_context!(node_property_name)(context), parser_with_context!(node_property_name)(context),
tag(":"), tag(":"),
))(input)?; ))(input)?;
match tuple((space0::<&str, nom::error::Error<&str>>, line_ending))(remaining) { match tuple((
space0::<OrgSource<'_>, nom::error::Error<OrgSource<'_>>>,
line_ending,
))(remaining)
{
Ok((remaining, _ws)) => { Ok((remaining, _ws)) => {
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
NodeProperty { NodeProperty {
source, source: source.into(),
value: None, value: None,
}, },
)) ))
@@ -113,8 +127,8 @@ fn node_property<'r, 's>(
Ok(( Ok((
remaining, remaining,
NodeProperty { NodeProperty {
source, source: source.into(),
value: Some(value), value: Some(value.into()),
}, },
)) ))
} }
@@ -122,7 +136,10 @@ fn node_property<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn node_property_name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn node_property_name<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
@@ -130,18 +147,23 @@ fn node_property_name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&
})); }));
let (remaining, name) = recognize(tuple(( let (remaining, name) = recognize(tuple((
map(parser_with_context!(plain_text)(&parser_context), |pt| { verify(
pt.source many_till(
}), anychar,
parser_with_context!(exit_matcher_parser)(&parser_context),
),
|(children, _exit_contents)| !children.is_empty(),
),
opt(tag("+")), opt(tag("+")),
)))(input)?; )))(input)?;
Ok((remaining, name)) Ok((remaining, name))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn node_property_name_end<'r, 's>( fn node_property_name_end<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((tag("+:"), tag(":")))(input) alt((tag("+:"), tag(":")))(input)
} }

View File

@@ -5,6 +5,7 @@ use nom::character::complete::space0;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use super::Object; use super::Object;
use crate::error::CustomError; use crate::error::CustomError;
@@ -21,7 +22,10 @@ use crate::parser::RadioLink;
use crate::parser::RadioTarget; use crate::parser::RadioTarget;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn radio_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, RadioLink<'s>> { pub fn radio_link<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, RadioLink<'s>> {
let radio_targets = context let radio_targets = context
.iter() .iter()
.filter_map(|context_element| match context_element.get_data() { .filter_map(|context_element| match context_element.get_data() {
@@ -37,14 +41,14 @@ pub fn radio_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
return Ok(( return Ok((
remaining, remaining,
RadioLink { RadioLink {
source, source: source.into(),
children: rematched_target, children: rematched_target,
}, },
)); ));
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::MyError(MyError(
"NoRadioLink", "NoRadioLink".into(),
)))) ))))
} }
@@ -52,8 +56,8 @@ pub fn radio_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
pub fn rematch_target<'x, 'r, 's>( pub fn rematch_target<'x, 'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
target: &'x Vec<Object<'x>>, target: &'x Vec<Object<'x>>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let mut remaining = input; let mut remaining = input;
let mut new_matches = Vec::with_capacity(target.len()); let mut new_matches = Vec::with_capacity(target.len());
for original_object in target { for original_object in target {
@@ -71,7 +75,7 @@ pub fn rematch_target<'x, 'r, 's>(
} }
_ => { _ => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"OnlyMinimalSetObjectsAllowed", "OnlyMinimalSetObjectsAllowed".into(),
)))); ))));
} }
}; };
@@ -82,12 +86,12 @@ pub fn rematch_target<'x, 'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn radio_target<'r, 's>( pub fn radio_target<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, RadioTarget<'s>> { ) -> Res<OrgSource<'s>, RadioTarget<'s>> {
let (remaining, _opening) = tag("<<<")(input)?; let (remaining, _opening) = tag("<<<")(input)?;
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &radio_target_end, exit_matcher: &radio_target_end,
})); }));
@@ -102,11 +106,20 @@ pub fn radio_target<'r, 's>(
let (remaining, _closing) = tag(">>>")(remaining)?; let (remaining, _closing) = tag(">>>")(remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?; let (remaining, _trailing_whitespace) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, RadioTarget { source, children })) Ok((
remaining,
RadioTarget {
source: source.into(),
children,
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn radio_target_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn radio_target_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((tag("<"), tag(">"), line_ending))(input) alt((tag("<"), tag(">"), line_ending))(input)
} }
@@ -114,8 +127,8 @@ pub trait RematchObject<'x> {
fn rematch_object<'r, 's>( fn rematch_object<'r, 's>(
&'x self, &'x self,
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Object<'s>>; ) -> Res<OrgSource<'s>, Object<'s>>;
} }
#[cfg(test)] #[cfg(test)]
@@ -131,11 +144,10 @@ mod tests {
#[test] #[test]
fn plain_text_radio_target() { fn plain_text_radio_target() {
let input = "foo bar baz"; let input = OrgSource::new("foo bar baz");
let radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })]; let radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })];
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = initial_context let document_context = initial_context
.with_additional_node(ContextElement::DocumentRoot(input))
.with_additional_node(ContextElement::RadioTarget(vec![&radio_target_match])); .with_additional_node(ContextElement::RadioTarget(vec![&radio_target_match]));
let paragraph_matcher = parser_with_context!(element(true))(&document_context); let paragraph_matcher = parser_with_context!(element(true))(&document_context);
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph"); let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
@@ -143,7 +155,7 @@ mod tests {
crate::parser::Element::Paragraph(paragraph) => paragraph, crate::parser::Element::Paragraph(paragraph) => paragraph,
_ => panic!("Should be a paragraph!"), _ => panic!("Should be a paragraph!"),
}; };
assert_eq!(remaining, ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(first_paragraph.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.len(), 3);
assert_eq!( assert_eq!(
@@ -160,22 +172,22 @@ mod tests {
#[test] #[test]
fn bold_radio_target() { fn bold_radio_target() {
let input = "foo *bar* baz"; let input = OrgSource::new("foo *bar* baz");
let radio_target_match = vec![Object::Bold(Bold { let radio_target_match = vec![Object::Bold(Bold {
source: "*bar*", source: "*bar*",
children: vec![Object::PlainText(PlainText { source: "bar" })], children: vec![Object::PlainText(PlainText { source: "bar" })],
})]; })];
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context = initial_context let document_context = initial_context
.with_additional_node(ContextElement::DocumentRoot(input))
.with_additional_node(ContextElement::RadioTarget(vec![&radio_target_match])); .with_additional_node(ContextElement::RadioTarget(vec![&radio_target_match]));
let paragraph_matcher = parser_with_context!(element(true))(&document_context); let paragraph_matcher = parser_with_context!(element(true))(&document_context);
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph"); let (remaining, first_paragraph) =
paragraph_matcher(input.into()).expect("Parse first paragraph");
let first_paragraph = match first_paragraph { let first_paragraph = match first_paragraph {
crate::parser::Element::Paragraph(paragraph) => paragraph, crate::parser::Element::Paragraph(paragraph) => paragraph,
_ => panic!("Should be a paragraph!"), _ => panic!("Should be a paragraph!"),
}; };
assert_eq!(remaining, ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(first_paragraph.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.len(), 3);
assert_eq!( assert_eq!(

View File

@@ -7,6 +7,7 @@ use nom::character::complete::space0;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::OrgSource;
use super::parser_with_context::parser_with_context; use super::parser_with_context::parser_with_context;
use super::util::get_consumed; use super::util::get_consumed;
use super::Context; use super::Context;
@@ -22,8 +23,8 @@ use crate::parser::util::exit_matcher_parser;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link<'r, 's>( pub fn regular_link<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, RegularLink<'s>> { ) -> Res<OrgSource<'s>, RegularLink<'s>> {
alt(( alt((
parser_with_context!(regular_link_without_description)(context), parser_with_context!(regular_link_without_description)(context),
parser_with_context!(regular_link_with_description)(context), parser_with_context!(regular_link_with_description)(context),
@@ -33,21 +34,26 @@ pub fn regular_link<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link_without_description<'r, 's>( pub fn regular_link_without_description<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, RegularLink<'s>> { ) -> Res<OrgSource<'s>, RegularLink<'s>> {
let (remaining, _opening_bracket) = tag("[[")(input)?; let (remaining, _opening_bracket) = tag("[[")(input)?;
let (remaining, _path) = pathreg(context, remaining)?; let (remaining, _path) = pathreg(context, remaining)?;
let (remaining, _closing_bracket) = tag("]]")(remaining)?; let (remaining, _closing_bracket) = tag("]]")(remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?; let (remaining, _trailing_whitespace) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, RegularLink { source })) Ok((
remaining,
RegularLink {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link_with_description<'r, 's>( pub fn regular_link_with_description<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, RegularLink<'s>> { ) -> Res<OrgSource<'s>, RegularLink<'s>> {
let (remaining, _opening_bracket) = tag("[[")(input)?; let (remaining, _opening_bracket) = tag("[[")(input)?;
let (remaining, _path) = pathreg(context, remaining)?; let (remaining, _path) = pathreg(context, remaining)?;
let (remaining, _closing_bracket) = tag("][")(remaining)?; let (remaining, _closing_bracket) = tag("][")(remaining)?;
@@ -55,11 +61,19 @@ pub fn regular_link_with_description<'r, 's>(
let (remaining, _closing_bracket) = tag("]]")(remaining)?; let (remaining, _closing_bracket) = tag("]]")(remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?; let (remaining, _trailing_whitespace) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, RegularLink { source })) Ok((
remaining,
RegularLink {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn pathreg<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { pub fn pathreg<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, path) = escaped( let (remaining, path) = escaped(
take_till1(|c| match c { take_till1(|c| match c {
'\\' | ']' => true, '\\' | ']' => true,
@@ -74,8 +88,8 @@ pub fn pathreg<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn description<'r, 's>( pub fn description<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
@@ -93,6 +107,9 @@ pub fn description<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn description_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn description_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag("]]")(input) tag("]]")(input)
} }

View File

@@ -16,6 +16,9 @@ use nom::sequence::delimited;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::convert_error;
use super::org_source::OrgSource;
use super::util::get_consumed;
use crate::error::Res; use crate::error::Res;
#[derive(Debug)] #[derive(Debug)]
@@ -28,50 +31,10 @@ pub enum Token<'s> {
#[derive(Debug)] #[derive(Debug)]
pub struct TextWithProperties<'s> { pub struct TextWithProperties<'s> {
#[allow(dead_code)]
pub text: &'s str, pub text: &'s str,
#[allow(dead_code)]
pub properties: Vec<Token<'s>>, pub properties: Vec<Token<'s>>,
} }
impl<'s> TextWithProperties<'s> {
pub fn unquote(&self) -> Result<String, Box<dyn std::error::Error>> {
let mut out = String::with_capacity(self.text.len());
if !self.text.starts_with(r#"""#) {
return Err("Quoted text does not start with quote.".into());
}
if !self.text.ends_with(r#"""#) {
return Err("Quoted text does not end with quote.".into());
}
let interior_text = &self.text[1..(self.text.len() - 1)];
let mut state = ParseState::Normal;
for current_char in interior_text.chars().into_iter() {
state = match (state, current_char) {
(ParseState::Normal, '\\') => ParseState::Escape,
(ParseState::Normal, _) => {
out.push(current_char);
ParseState::Normal
}
(ParseState::Escape, 'n') => {
out.push('\n');
ParseState::Normal
}
(ParseState::Escape, '\\') => {
out.push('\\');
ParseState::Normal
}
(ParseState::Escape, '"') => {
out.push('"');
ParseState::Normal
}
_ => todo!(),
};
}
Ok(out)
}
}
enum ParseState { enum ParseState {
Normal, Normal,
Escape, Escape,
@@ -81,28 +44,28 @@ impl<'s> Token<'s> {
pub fn as_vector<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> { pub fn as_vector<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
Ok(match self { Ok(match self {
Token::Vector(children) => Ok(children), Token::Vector(children) => Ok(children),
_ => Err(format!("wrong token type {:?}", self)), _ => Err(format!("wrong token type, expected vector: {:?}", self)),
}?) }?)
} }
pub fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> { pub fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
Ok(match self { Ok(match self {
Token::List(children) => Ok(children), Token::List(children) => Ok(children),
_ => Err(format!("wrong token type {:?}", self)), _ => Err(format!("wrong token type, expected list: {:?}", self)),
}?) }?)
} }
pub fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> { pub fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> {
Ok(match self { Ok(match self {
Token::Atom(body) => Ok(*body), Token::Atom(body) => Ok(*body),
_ => Err(format!("wrong token type {:?}", self)), _ => Err(format!("wrong token type, expected atom: {:?}", self)),
}?) }?)
} }
pub fn as_text<'p>(&'p self) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> { pub fn as_text<'p>(&'p self) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> {
Ok(match self { Ok(match self {
Token::TextWithProperties(body) => Ok(body), Token::TextWithProperties(body) => Ok(body),
_ => Err(format!("wrong token type {:?}", self)), _ => Err(format!("wrong token type, expected text: {:?}", self)),
}?) }?)
} }
@@ -132,27 +95,66 @@ impl<'s> Token<'s> {
} }
} }
pub fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
let mut out = String::with_capacity(text.len());
if !text.starts_with(r#"""#) {
return Err("Quoted text does not start with quote.".into());
}
if !text.ends_with(r#"""#) {
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.chars().into_iter() {
state = match (state, current_char) {
(ParseState::Normal, '\\') => ParseState::Escape,
(ParseState::Normal, _) => {
out.push(current_char);
ParseState::Normal
}
(ParseState::Escape, 'n') => {
out.push('\n');
ParseState::Normal
}
(ParseState::Escape, '\\') => {
out.push('\\');
ParseState::Normal
}
(ParseState::Escape, '"') => {
out.push('"');
ParseState::Normal
}
_ => todo!(),
};
}
Ok(out)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = multispace0(input)?; let (remaining, _) = multispace0(input)?;
let (remaining, tkn) = token(remaining)?; let remaining = OrgSource::new(remaining);
let (remaining, tkn) = token(remaining)
.map(|(rem, out)| (Into::<&str>::into(rem), out))
.map_err(convert_error)?;
let (remaining, _) = multispace0(remaining)?; let (remaining, _) = multispace0(remaining)?;
Ok((remaining, tkn)) Ok((remaining, tkn))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn sexp<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { pub fn sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
let (remaining, tkn) = token(input)?; let (remaining, tkn) = token(input)?;
Ok((remaining, tkn)) Ok((remaining, tkn))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn token<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { fn token<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
alt((list, vector, atom))(input) alt((list, vector, atom))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn list<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { fn list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
let (remaining, _) = tag("(")(input)?; let (remaining, _) = tag("(")(input)?;
let (remaining, children) = delimited( let (remaining, children) = delimited(
multispace0, multispace0,
@@ -164,7 +166,7 @@ fn list<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn vector<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { fn vector<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
let (remaining, _) = tag("[")(input)?; let (remaining, _) = tag("[")(input)?;
let (remaining, children) = delimited( let (remaining, children) = delimited(
multispace0, multispace0,
@@ -176,7 +178,7 @@ fn vector<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { fn atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
not(peek(one_of(")]")))(input)?; not(peek(one_of(")]")))(input)?;
alt(( alt((
text_with_properties, text_with_properties,
@@ -187,16 +189,16 @@ fn atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { fn unquoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
let (remaining, body) = take_till1(|c| match c { let (remaining, body) = take_till1(|c| match c {
' ' | '\t' | '\r' | '\n' | ')' | ']' => true, ' ' | '\t' | '\r' | '\n' | ')' | ']' => true,
_ => false, _ => false,
})(input)?; })(input)?;
Ok((remaining, Token::Atom(body))) Ok((remaining, Token::Atom(body.into())))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { fn quoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
let (remaining, _) = tag(r#"""#)(input)?; let (remaining, _) = tag(r#"""#)(input)?;
let (remaining, _) = escaped( let (remaining, _) = escaped(
take_till1(|c| match c { take_till1(|c| match c {
@@ -208,11 +210,11 @@ fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
)(remaining)?; )(remaining)?;
let (remaining, _) = tag(r#"""#)(remaining)?; let (remaining, _) = tag(r#"""#)(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Token::Atom(source))) Ok((remaining, Token::Atom(source.into())))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn hash_notation<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { fn hash_notation<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
let (remaining, _) = tag("#<")(input)?; let (remaining, _) = tag("#<")(input)?;
let (remaining, _body) = take_till1(|c| match c { let (remaining, _body) = take_till1(|c| match c {
'>' => true, '>' => true,
@@ -220,10 +222,10 @@ fn hash_notation<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
})(remaining)?; })(remaining)?;
let (remaining, _) = tag(">")(remaining)?; let (remaining, _) = tag(">")(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Token::Atom(source))) Ok((remaining, Token::Atom(source.into())))
} }
fn text_with_properties<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { fn text_with_properties<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
let (remaining, _) = tag("#(")(input)?; let (remaining, _) = tag("#(")(input)?;
let (remaining, (text, props)) = delimited( let (remaining, (text, props)) = delimited(
multispace0, multispace0,
@@ -246,25 +248,6 @@ fn text_with_properties<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
)) ))
} }
/// 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 {
assert!(is_slice_of(input, remaining));
let source = {
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
&input[..offset]
};
source
}
/// Check if the child string slice is a slice of the parent string slice.
fn is_slice_of(parent: &str, child: &str) -> bool {
let parent_start = parent.as_ptr() as usize;
let parent_end = parent_start + parent.len();
let child_start = child.as_ptr() as usize;
let child_end = child_start + child.len();
child_start >= parent_start && child_end <= parent_end
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -4,6 +4,7 @@ use nom::character::complete::space0;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
@@ -12,8 +13,8 @@ use crate::parser::StatisticsCookie;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn statistics_cookie<'r, 's>( pub fn statistics_cookie<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, StatisticsCookie<'s>> { ) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
alt(( alt((
parser_with_context!(percent_statistics_cookie)(context), parser_with_context!(percent_statistics_cookie)(context),
parser_with_context!(fraction_statistics_cookie)(context), parser_with_context!(fraction_statistics_cookie)(context),
@@ -22,20 +23,25 @@ pub fn statistics_cookie<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn percent_statistics_cookie<'r, 's>( pub fn percent_statistics_cookie<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, StatisticsCookie<'s>> { ) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
let (remaining, source) = let (remaining, source) =
recognize(tuple((tag("["), nom::character::complete::u64, tag("%]"))))(input)?; recognize(tuple((tag("["), nom::character::complete::u64, tag("%]"))))(input)?;
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
Ok((remaining, StatisticsCookie { source })) Ok((
remaining,
StatisticsCookie {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn fraction_statistics_cookie<'r, 's>( pub fn fraction_statistics_cookie<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, StatisticsCookie<'s>> { ) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
let (remaining, source) = recognize(tuple(( let (remaining, source) = recognize(tuple((
tag("["), tag("["),
nom::character::complete::u64, nom::character::complete::u64,
@@ -44,5 +50,10 @@ pub fn fraction_statistics_cookie<'r, 's>(
tag("]"), tag("]"),
)))(input)?; )))(input)?;
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
Ok((remaining, StatisticsCookie { source })) Ok((
remaining,
StatisticsCookie {
source: source.into(),
},
))
} }

View File

@@ -11,6 +11,8 @@ use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use super::Object; use super::Object;
use crate::error::CustomError; use crate::error::CustomError;
@@ -20,50 +22,58 @@ use crate::parser::exiting::ExitClass;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_context::SubscriptSuperscriptBrace;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::get_one_before;
use crate::parser::Subscript; use crate::parser::Subscript;
use crate::parser::Superscript; use crate::parser::Superscript;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn subscript<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Subscript<'s>> { pub fn subscript<'r, 's>(
context: Context<'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. // 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)?; let (remaining, _) = tag("_")(input)?;
pre(context, input)?; pre(context, input)?;
let (remaining, _body) = script_body(context, remaining)?; let (remaining, _body) = script_body(context, remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Subscript { source })) Ok((
remaining,
Subscript {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn superscript<'r, 's>( pub fn superscript<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Superscript<'s>> { ) -> Res<OrgSource<'s>, Superscript<'s>> {
// We check for the circumflex first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily. // We check for the circumflex 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)?; let (remaining, _) = tag("^")(input)?;
pre(context, input)?; pre(context, input)?;
let (remaining, _body) = script_body(context, remaining)?; let (remaining, _body) = script_body(context, remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Superscript { source })) Ok((
remaining,
Superscript {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> { fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let document_root = context.get_document_root().unwrap(); let preceding_character = input.get_preceding_character();
let preceding_character = get_one_before(document_root, input)
.map(|slice| slice.chars().next())
.flatten();
match preceding_character { match preceding_character {
Some(c) if !c.is_whitespace() => {} Some(c) if !c.is_whitespace() => {}
_ => { _ => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Must be preceded by a non-whitespace character.", "Must be preceded by a non-whitespace character.".into(),
)))); ))));
} }
}; };
@@ -77,27 +87,36 @@ enum ScriptBody<'s> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn script_body<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ScriptBody<'s>> { fn script_body<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ScriptBody<'s>> {
alt(( alt((
map(parser_with_context!(script_asterisk)(context), |body| { map(parser_with_context!(script_asterisk)(context), |body| {
ScriptBody::Braceless(body) ScriptBody::Braceless(body.into())
}), }),
map(parser_with_context!(script_alphanum)(context), |body| { map(parser_with_context!(script_alphanum)(context), |body| {
ScriptBody::Braceless(body) ScriptBody::Braceless(body.into())
}), }),
map(parser_with_context!(script_with_braces)(context), |body| { map(parser_with_context!(script_with_braces)(context), |body| {
ScriptBody::WithBraces(body) ScriptBody::WithBraces(body.into())
}), }),
))(input) ))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn script_asterisk<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn script_asterisk<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag("*")(input) tag("*")(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn script_alphanum<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn script_alphanum<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _sign) = opt(recognize(one_of("+-")))(input)?; let (remaining, _sign) = opt(recognize(one_of("+-")))(input)?;
let (remaining, _script) = many_till( let (remaining, _script) = many_till(
parser_with_context!(script_alphanum_character)(context), parser_with_context!(script_alphanum_character)(context),
@@ -109,9 +128,9 @@ fn script_alphanum<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn script_alphanum_character<'r, 's>( fn script_alphanum_character<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(verify(anychar, |c| { recognize(verify(anychar, |c| {
c.is_alphanumeric() || r#",.\"#.contains(*c) c.is_alphanumeric() || r#",.\"#.contains(*c)
}))(input) }))(input)
@@ -120,8 +139,8 @@ fn script_alphanum_character<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn end_script_alphanum_character<'r, 's>( fn end_script_alphanum_character<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?; let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?;
peek(not(parser_with_context!(script_alphanum_character)( peek(not(parser_with_context!(script_alphanum_character)(
context, context,
@@ -132,19 +151,14 @@ fn end_script_alphanum_character<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn script_with_braces<'r, 's>( fn script_with_braces<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let (remaining, _) = tag("{")(input)?; let (remaining, _) = tag("{")(input)?;
let parser_context = context let exit_with_depth = script_with_braces_end(remaining.get_brace_depth());
.with_additional_node(ContextElement::SubscriptSuperscriptBrace( let parser_context =
SubscriptSuperscriptBrace { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
position: remaining, class: ExitClass::Gamma,
depth: 0, exit_matcher: &exit_with_depth,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &script_with_braces_end,
})); }));
let (remaining, (children, _exit_contents)) = many_till( let (remaining, (children, _exit_contents)) = many_till(
@@ -156,49 +170,30 @@ fn script_with_braces<'r, 's>(
Ok((remaining, children)) Ok((remaining, children))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn script_with_braces_end(
fn script_with_braces_end<'r, 's>( starting_brace_depth: BracketDepth,
context: Context<'r, 's>, ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
input: &'s str, move |context: Context, input: OrgSource<'_>| {
) -> Res<&'s str, &'s str> { _script_with_braces_end(context, input, starting_brace_depth)
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a subscript or superscript.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in text_since_context_entry.chars() {
match c {
'{' => {
current_depth += 1;
}
'}' if current_depth == 0 => {
panic!("Exceeded subscript or superscript brace depth.")
}
'}' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
} }
if current_depth == 0 {
let close_bracket = tag::<&str, &str, CustomError<&str>>("}")(input);
if close_bracket.is_ok() {
return close_bracket;
}
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid end for subscript or superscript.",
))));
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn get_bracket_depth<'r, 's>( fn _script_with_braces_end<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
) -> Option<&'r SubscriptSuperscriptBrace<'s>> { input: OrgSource<'s>,
for node in context.iter() { starting_brace_depth: BracketDepth,
match node.get_data() { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
ContextElement::SubscriptSuperscriptBrace(depth) => return Some(depth), 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(),
))));
} }
None if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing brace should end the subscript or superscript.
unreachable!("Exceeded subscript or superscript brace depth.")
}
tag("}")(input)
} }

View File

@@ -12,13 +12,13 @@ use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::object_parser::table_cell_set_object;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
use crate::parser::greater_element::TableRow; use crate::parser::greater_element::TableRow;
use crate::parser::lesser_element::TableCell; use crate::parser::lesser_element::TableCell;
use crate::parser::object::Object;
use crate::parser::object_parser::minimal_set_object;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
@@ -31,8 +31,11 @@ use crate::parser::Table;
/// ///
/// This is not the table.el style. /// This is not the table.el style.
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Table<'s>> { pub fn org_mode_table<'r, 's>(
start_of_line(context, input)?; context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Table<'s>> {
start_of_line(input)?;
peek(tuple((space0, tag("|"))))(input)?; peek(tuple((space0, tag("|"))))(input)?;
let parser_context = context let parser_context = context
@@ -52,20 +55,29 @@ pub fn org_mode_table<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&
// TODO: Consume trailing formulas // TODO: Consume trailing formulas
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Table { source, children })) Ok((
remaining,
Table {
source: source.into(),
children,
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn table_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn table_end<'r, 's>(
start_of_line(context, input)?; _context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
recognize(tuple((space0, not(tag("|")))))(input) recognize(tuple((space0, not(tag("|")))))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table_row<'r, 's>( pub fn org_mode_table_row<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, TableRow<'s>> { ) -> Res<OrgSource<'s>, TableRow<'s>> {
alt(( alt((
parser_with_context!(org_mode_table_row_rule)(context), parser_with_context!(org_mode_table_row_rule)(context),
parser_with_context!(org_mode_table_row_regular)(context), parser_with_context!(org_mode_table_row_regular)(context),
@@ -74,16 +86,16 @@ pub fn org_mode_table_row<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table_row_rule<'r, 's>( pub fn org_mode_table_row_rule<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, TableRow<'s>> { ) -> Res<OrgSource<'s>, TableRow<'s>> {
start_of_line(context, input)?; start_of_line(input)?;
let (remaining, _) = tuple((space0, tag("|-"), is_not("\r\n"), line_ending))(input)?; let (remaining, _) = tuple((space0, tag("|-"), is_not("\r\n"), line_ending))(input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
TableRow { TableRow {
source, source: source.into(),
children: Vec::new(), children: Vec::new(),
}, },
)) ))
@@ -92,22 +104,28 @@ pub fn org_mode_table_row_rule<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table_row_regular<'r, 's>( pub fn org_mode_table_row_regular<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, TableRow<'s>> { ) -> Res<OrgSource<'s>, TableRow<'s>> {
start_of_line(context, input)?; start_of_line(input)?;
let (remaining, _) = tuple((space0, tag("|")))(input)?; let (remaining, _) = tuple((space0, tag("|")))(input)?;
let (remaining, children) = let (remaining, children) =
many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?; many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?;
let (remaining, _tail) = recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?; let (remaining, _tail) = recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, TableRow { source, children })) Ok((
remaining,
TableRow {
source: source.into(),
children,
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table_cell<'r, 's>( pub fn org_mode_table_cell<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, TableCell<'s>> { ) -> Res<OrgSource<'s>, TableCell<'s>> {
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
@@ -118,31 +136,28 @@ pub fn org_mode_table_cell<'r, 's>(
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till(table_cell_set_object_matcher, exit_matcher), many_till(table_cell_set_object_matcher, exit_matcher),
|(children, exit_contents)| !children.is_empty() || exit_contents.ends_with("|"), |(children, exit_contents)| {
!children.is_empty() || Into::<&str>::into(exit_contents).ends_with("|")
},
)(input)?; )(input)?;
let (remaining, _tail) = org_mode_table_cell_end(&parser_context, remaining)?; let (remaining, _tail) = org_mode_table_cell_end(&parser_context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, TableCell { source, children })) Ok((
remaining,
TableCell {
source: source.into(),
children,
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn org_mode_table_cell_end<'r, 's>( fn org_mode_table_cell_end<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((space0, alt((tag("|"), peek(line_ending))))))(input) recognize(tuple((space0, alt((tag("|"), peek(line_ending))))))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_cell_set_object<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, Object<'s>> {
not(|i| context.check_exit_matcher(i))(input)?;
parser_with_context!(minimal_set_object)(context)(input)
// TODO: add citations, export snippets, footnote references, links, macros, radio targets, targets, and timestamps.
}

View File

@@ -7,6 +7,7 @@ use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -17,11 +18,13 @@ use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::get_one_before;
use crate::parser::Target; use crate::parser::Target;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn target<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Target<'s>> { pub fn target<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Target<'s>> {
let (remaining, _) = tag("<<")(input)?; let (remaining, _) = tag("<<")(input)?;
let (remaining, _) = peek(verify(anychar, |c| { let (remaining, _) = peek(verify(anychar, |c| {
!c.is_whitespace() && !"<>\n".contains(*c) !c.is_whitespace() && !"<>\n".contains(*c)
@@ -37,24 +40,30 @@ pub fn target<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
parser_with_context!(exit_matcher_parser)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context),
))(remaining)?; ))(remaining)?;
let document_root = context.get_document_root().unwrap(); let preceding_character = remaining
let preceding_character = get_one_before(document_root, remaining) .get_preceding_character()
.map(|slice| slice.chars().next())
.flatten()
.expect("We cannot be at the start of the file because we are inside a target."); .expect("We cannot be at the start of the file because we are inside a target.");
if preceding_character.is_whitespace() { if preceding_character.is_whitespace() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Targets cannot end with whitespace.", "Targets cannot end with whitespace.".into(),
)))); ))));
} }
let (remaining, _) = tag(">>")(remaining)?; let (remaining, _) = tag(">>")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Target { source })) Ok((
remaining,
Target {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn target_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn target_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(one_of("<>\n"))(input) recognize(one_of("<>\n"))(input)
} }

View File

@@ -15,6 +15,7 @@ use nom::sequence::terminated;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
use tracing::span; use tracing::span;
use super::org_source::OrgSource;
use super::radio_link::RematchObject; use super::radio_link::RematchObject;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
@@ -28,7 +29,6 @@ use crate::parser::parser_with_context::parser_with_context;
use crate::parser::radio_link::rematch_target; use crate::parser::radio_link::rematch_target;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::get_one_before;
use crate::parser::util::preceded_by_whitespace; use crate::parser::util::preceded_by_whitespace;
use crate::parser::Bold; use crate::parser::Bold;
use crate::parser::Code; use crate::parser::Code;
@@ -39,7 +39,10 @@ use crate::parser::Underline;
use crate::parser::Verbatim; use crate::parser::Verbatim;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn text_markup<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Object<'s>> { pub fn text_markup<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
alt(( alt((
map(parser_with_context!(bold)(context), Object::Bold), map(parser_with_context!(bold)(context), Object::Bold),
map(parser_with_context!(italic)(context), Object::Italic), map(parser_with_context!(italic)(context), Object::Italic),
@@ -54,76 +57,129 @@ pub fn text_markup<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn bold<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Bold<'s>> { pub fn bold<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Bold<'s>> {
let text_markup_object_specialized = text_markup_object("*"); let text_markup_object_specialized = text_markup_object("*");
let (remaining, children) = text_markup_object_specialized(context, input)?; let (remaining, children) = text_markup_object_specialized(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Bold { source, children })) Ok((
remaining,
Bold {
source: source.into(),
children,
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn italic<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Italic<'s>> { pub fn italic<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Italic<'s>> {
let text_markup_object_specialized = text_markup_object("/"); let text_markup_object_specialized = text_markup_object("/");
let (remaining, children) = text_markup_object_specialized(context, input)?; let (remaining, children) = text_markup_object_specialized(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Italic { source, children })) Ok((
remaining,
Italic {
source: source.into(),
children,
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn underline<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Underline<'s>> { pub fn underline<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Underline<'s>> {
let text_markup_object_specialized = text_markup_object("_"); let text_markup_object_specialized = text_markup_object("_");
let (remaining, children) = text_markup_object_specialized(context, input)?; let (remaining, children) = text_markup_object_specialized(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Underline { source, children })) Ok((
remaining,
Underline {
source: source.into(),
children,
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn strike_through<'r, 's>( pub fn strike_through<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, StrikeThrough<'s>> { ) -> Res<OrgSource<'s>, StrikeThrough<'s>> {
let text_markup_object_specialized = text_markup_object("+"); let text_markup_object_specialized = text_markup_object("+");
let (remaining, children) = text_markup_object_specialized(context, input)?; let (remaining, children) = text_markup_object_specialized(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, StrikeThrough { source, children })) Ok((
remaining,
StrikeThrough {
source: source.into(),
children,
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn verbatim<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Verbatim<'s>> { pub fn verbatim<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Verbatim<'s>> {
let text_markup_string_specialized = text_markup_string("="); let text_markup_string_specialized = text_markup_string("=");
let (remaining, contents) = text_markup_string_specialized(context, input)?; let (remaining, contents) = text_markup_string_specialized(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Verbatim { source, contents })) Ok((
remaining,
Verbatim {
source: source.into(),
contents: contents.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn code<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Code<'s>> { pub fn code<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Code<'s>> {
let text_markup_string_specialized = text_markup_string("~"); let text_markup_string_specialized = text_markup_string("~");
let (remaining, contents) = text_markup_string_specialized(context, input)?; let (remaining, contents) = text_markup_string_specialized(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Code { source, contents })) Ok((
remaining,
Code {
source: source.into(),
contents: contents.into(),
},
))
} }
fn text_markup_object( fn text_markup_object(
marker_symbol: &str, marker_symbol: &str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, Vec<Object<'s>>> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let marker_symbol = marker_symbol.to_owned(); let marker_symbol = marker_symbol.to_owned();
move |context: Context, input: &str| _text_markup_object(context, input, marker_symbol.as_str()) move |context: Context, input: OrgSource<'_>| {
_text_markup_object(context, input, marker_symbol.as_str())
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _text_markup_object<'r, 's, 'x>( fn _text_markup_object<'r, 's, 'x>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
marker_symbol: &'x str, marker_symbol: &'x str,
) -> Res<&'s str, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?; let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
let text_markup_end_specialized = text_markup_end(open); let text_markup_end_specialized = text_markup_end(open.into());
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &text_markup_end_specialized, exit_matcher: &text_markup_end_specialized,
})); }));
@@ -142,7 +198,7 @@ fn _text_markup_object<'r, 's, 'x>(
let _enter = span.enter(); let _enter = span.enter();
if exit_matcher_parser(context, remaining).is_ok() { if exit_matcher_parser(context, remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Parent exit matcher is triggering.", "Parent exit matcher is triggering.".into(),
)))); ))));
} }
} }
@@ -154,24 +210,26 @@ fn _text_markup_object<'r, 's, 'x>(
fn text_markup_string( fn text_markup_string(
marker_symbol: &str, marker_symbol: &str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let marker_symbol = marker_symbol.to_owned(); let marker_symbol = marker_symbol.to_owned();
move |context: Context, input: &str| _text_markup_string(context, input, marker_symbol.as_str()) move |context: Context, input: OrgSource<'_>| {
_text_markup_string(context, input, marker_symbol.as_str())
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _text_markup_string<'r, 's, 'x>( fn _text_markup_string<'r, 's, 'x>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
marker_symbol: &'x str, marker_symbol: &'x str,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?; let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
let text_markup_end_specialized = text_markup_end(open); let text_markup_end_specialized = text_markup_end(open.into());
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &text_markup_end_specialized, exit_matcher: &text_markup_end_specialized,
})); }));
@@ -190,7 +248,7 @@ fn _text_markup_string<'r, 's, 'x>(
let _enter = span.enter(); let _enter = span.enter();
if exit_matcher_parser(context, remaining).is_ok() { if exit_matcher_parser(context, remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Parent exit matcher is triggering.", "Parent exit matcher is triggering.".into(),
)))); ))));
} }
} }
@@ -201,11 +259,8 @@ fn _text_markup_string<'r, 's, 'x>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> { pub fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let document_root = context.get_document_root().unwrap(); let preceding_character = input.get_preceding_character();
let preceding_character = get_one_before(document_root, input)
.map(|slice| slice.chars().next())
.flatten();
match preceding_character { match preceding_character {
// If None, we are at the start of the file which is technically the beginning of a line. // If None, we are at the start of the file which is technically the beginning of a line.
None | Some('\r') | Some('\n') | Some(' ') | Some('\t') | Some('-') | Some('(') None | Some('\r') | Some('\n') | Some(' ') | Some('\t') | Some('-') | Some('(')
@@ -213,7 +268,7 @@ pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()>
Some(_) => { Some(_) => {
// Not at start of line, cannot be a heading // Not at start of line, cannot be a heading
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for text markup.", "Not a valid pre character for text markup.".into(),
)))); ))));
} }
}; };
@@ -221,25 +276,27 @@ pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()>
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn post<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> { pub fn post<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (remaining, _) = alt((recognize(one_of(" \r\n\t-.,;:!?')}[\">")), line_ending))(input)?; let (remaining, _) = alt((recognize(one_of(" \r\n\t-.,;:!?')}[\">")), line_ending))(input)?;
Ok((remaining, ())) Ok((remaining, ()))
} }
fn text_markup_end( fn text_markup_end(
marker_symbol: &str, marker_symbol: &str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let marker_symbol = marker_symbol.to_owned(); let marker_symbol = marker_symbol.to_owned();
move |context: Context, input: &str| _text_markup_end(context, input, marker_symbol.as_str()) move |context: Context, input: OrgSource<'_>| {
_text_markup_end(context, input, marker_symbol.as_str())
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _text_markup_end<'r, 's, 'x>( fn _text_markup_end<'r, 's, 'x>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
marker_symbol: &'x str, marker_symbol: &'x str,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
not(parser_with_context!(preceded_by_whitespace)(context))(input)?; not(preceded_by_whitespace)(input)?;
let (remaining, _marker) = terminated( let (remaining, _marker) = terminated(
tag(marker_symbol), tag(marker_symbol),
peek(parser_with_context!(post)(context)), peek(parser_with_context!(post)(context)),
@@ -253,29 +310,35 @@ impl<'x> RematchObject<'x> for Bold<'x> {
fn rematch_object<'r, 's>( fn rematch_object<'r, 's>(
&'x self, &'x self,
_context: Context<'r, 's>, _context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) = let (remaining, children) =
_rematch_text_markup_object(_context, input, "*", &self.children)?; _rematch_text_markup_object(_context, input, "*", &self.children)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Object::Bold(Bold { source, children }))) Ok((
remaining,
Object::Bold(Bold {
source: source.into(),
children,
}),
))
} }
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _rematch_text_markup_object<'r, 's, 'x>( fn _rematch_text_markup_object<'r, 's, 'x>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
marker_symbol: &'static str, marker_symbol: &'static str,
original_match_children: &'x Vec<Object<'x>>, original_match_children: &'x Vec<Object<'x>>,
) -> Res<&'s str, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?; let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
let text_markup_end_specialized = text_markup_end(open); let text_markup_end_specialized = text_markup_end(open.into());
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &text_markup_end_specialized, exit_matcher: &text_markup_end_specialized,
})); }));
@@ -290,7 +353,7 @@ fn _rematch_text_markup_object<'r, 's, 'x>(
let _enter = span.enter(); let _enter = span.enter();
if exit_matcher_parser(context, remaining).is_ok() { if exit_matcher_parser(context, remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Parent exit matcher is triggering.", "Parent exit matcher is triggering.".into(),
)))); ))));
} }
} }

View File

@@ -11,6 +11,7 @@ use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
@@ -23,7 +24,10 @@ use crate::parser::util::get_consumed;
use crate::parser::Timestamp; use crate::parser::Timestamp;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn timestamp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Timestamp<'s>> { pub fn timestamp<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
// TODO: This would be more efficient if we didn't throw away the parse result of the first half of an active/inactive date range timestamp if the parse fails (as in, the first thing active_date_range_timestamp parses is a active_timestamp but then we throw that away if it doesn't turn out to be a full active_date_range_timestamp despite the active_timestamp parse being completely valid). I am going with the simplest/cleanest approach for the first implementation. // TODO: This would be more efficient if we didn't throw away the parse result of the first half of an active/inactive date range timestamp if the parse fails (as in, the first thing active_date_range_timestamp parses is a active_timestamp but then we throw that away if it doesn't turn out to be a full active_date_range_timestamp despite the active_timestamp parse being completely valid). I am going with the simplest/cleanest approach for the first implementation.
alt(( alt((
// Order matters here. If its a date range, we need to parse the entire date range instead of just the first timestamp. If its a time range, we need to make sure thats parsed as a time range instead of as the "rest" portion of a single timestamp. // Order matters here. If its a date range, we need to parse the entire date range instead of just the first timestamp. If its a time range, we need to make sure thats parsed as a time range instead of as the "rest" portion of a single timestamp.
@@ -40,22 +44,30 @@ pub fn timestamp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn diary_timestamp<'r, 's>( fn diary_timestamp<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Timestamp<'s>> { ) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("<%%(")(input)?; let (remaining, _) = tag("<%%(")(input)?;
let (remaining, _body) = sexp(context, remaining)?; let (remaining, _body) = sexp(context, remaining)?;
let (remaining, _) = tag(")>")(remaining)?; let (remaining, _) = tag(")>")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source })) Ok((
remaining,
Timestamp {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn sexp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn sexp<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &sexp_end, exit_matcher: &sexp_end,
})); }));
@@ -71,20 +83,23 @@ fn sexp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s st
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn sexp_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn sexp_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((tag(")>"), recognize(one_of(">\n"))))(input) alt((tag(")>"), recognize(one_of(">\n"))))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn active_timestamp<'r, 's>( fn active_timestamp<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Timestamp<'s>> { ) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("<")(input)?; let (remaining, _) = tag("<")(input)?;
let (remaining, _date) = date(context, remaining)?; let (remaining, _date) = date(context, remaining)?;
let time_context = let time_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &active_time_rest_end, exit_matcher: &active_time_rest_end,
})); }));
let (remaining, _time) = let (remaining, _time) =
@@ -100,19 +115,24 @@ fn active_timestamp<'r, 's>(
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source })) Ok((
remaining,
Timestamp {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp<'r, 's>( fn inactive_timestamp<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Timestamp<'s>> { ) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("[")(input)?; let (remaining, _) = tag("[")(input)?;
let (remaining, _date) = date(context, remaining)?; let (remaining, _date) = date(context, remaining)?;
let time_context = let time_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &inactive_time_rest_end, exit_matcher: &inactive_time_rest_end,
})); }));
let (remaining, _time) = let (remaining, _time) =
@@ -128,14 +148,19 @@ fn inactive_timestamp<'r, 's>(
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source })) Ok((
remaining,
Timestamp {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn active_date_range_timestamp<'r, 's>( fn active_date_range_timestamp<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Timestamp<'s>> { ) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _first_timestamp) = active_timestamp(context, input)?; let (remaining, _first_timestamp) = active_timestamp(context, input)?;
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace // TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
let (remaining, _separator) = tag("--")(remaining)?; let (remaining, _separator) = tag("--")(remaining)?;
@@ -144,24 +169,29 @@ fn active_date_range_timestamp<'r, 's>(
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source })) Ok((
remaining,
Timestamp {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn active_time_range_timestamp<'r, 's>( fn active_time_range_timestamp<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Timestamp<'s>> { ) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("<")(input)?; let (remaining, _) = tag("<")(input)?;
let (remaining, _date) = date(context, remaining)?; let (remaining, _date) = date(context, remaining)?;
let time_context = let time_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &active_time_rest_end, exit_matcher: &active_time_rest_end,
})); }));
let first_time_context = let first_time_context =
time_context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { time_context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &time_range_rest_end, exit_matcher: &time_range_rest_end,
})); }));
let (remaining, _first_time) = let (remaining, _first_time) =
@@ -179,14 +209,19 @@ fn active_time_range_timestamp<'r, 's>(
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source })) Ok((
remaining,
Timestamp {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_date_range_timestamp<'r, 's>( fn inactive_date_range_timestamp<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Timestamp<'s>> { ) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _first_timestamp) = inactive_timestamp(context, input)?; let (remaining, _first_timestamp) = inactive_timestamp(context, input)?;
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace // TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
let (remaining, _separator) = tag("--")(remaining)?; let (remaining, _separator) = tag("--")(remaining)?;
@@ -195,24 +230,29 @@ fn inactive_date_range_timestamp<'r, 's>(
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source })) Ok((
remaining,
Timestamp {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_time_range_timestamp<'r, 's>( fn inactive_time_range_timestamp<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Timestamp<'s>> { ) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("[")(input)?; let (remaining, _) = tag("[")(input)?;
let (remaining, _date) = date(context, remaining)?; let (remaining, _date) = date(context, remaining)?;
let time_context = let time_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &inactive_time_rest_end, exit_matcher: &inactive_time_rest_end,
})); }));
let first_time_context = let first_time_context =
time_context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { time_context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &time_range_rest_end, exit_matcher: &time_range_rest_end,
})); }));
let (remaining, _first_time) = let (remaining, _first_time) =
@@ -230,17 +270,26 @@ fn inactive_time_range_timestamp<'r, 's>(
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source })) Ok((
remaining,
Timestamp {
source: source.into(),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn date<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn date<'r, 's>(
let (remaining, _year) = verify(digit1, |year: &str| year.len() == 4)(input)?; context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _year) = verify(digit1, |year: &OrgSource<'_>| year.len() == 4)(input)?;
let (remaining, _) = tag("-")(remaining)?; let (remaining, _) = tag("-")(remaining)?;
let (remaining, _month) = verify(digit1, |month: &str| month.len() == 2)(remaining)?; let (remaining, _month) = verify(digit1, |month: &OrgSource<'_>| month.len() == 2)(remaining)?;
let (remaining, _) = tag("-")(remaining)?; let (remaining, _) = tag("-")(remaining)?;
let (remaining, _day_of_month) = let (remaining, _day_of_month) = verify(digit1, |day_of_month: &OrgSource<'_>| {
verify(digit1, |day_of_month: &str| day_of_month.len() == 2)(remaining)?; day_of_month.len() == 2
})(remaining)?;
let (remaining, _dayname) = let (remaining, _dayname) =
opt(tuple((space1, parser_with_context!(dayname)(context))))(remaining)?; opt(tuple((space1, parser_with_context!(dayname)(context))))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -248,10 +297,13 @@ fn date<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s st
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dayname<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn dayname<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &dayname_end, exit_matcher: &dayname_end,
})); }));
@@ -267,25 +319,36 @@ fn dayname<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dayname_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn dayname_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(verify(anychar, |c| { recognize(verify(anychar, |c| {
c.is_whitespace() || "+-]>0123456789\n".contains(*c) c.is_whitespace() || "+-]>0123456789\n".contains(*c)
}))(input) }))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn time<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn time<'r, 's>(
let (remaining, _hour) = context: Context<'r, 's>,
verify(digit1, |hour: &str| hour.len() >= 1 && hour.len() <= 2)(input)?; input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _hour) = verify(digit1, |hour: &OrgSource<'_>| {
hour.len() >= 1 && hour.len() <= 2
})(input)?;
let (remaining, _) = tag(":")(remaining)?; let (remaining, _) = tag(":")(remaining)?;
let (remaining, _minute) = verify(digit1, |minute: &str| minute.len() == 2)(remaining)?; let (remaining, _minute) =
verify(digit1, |minute: &OrgSource<'_>| minute.len() == 2)(remaining)?;
let (remaining, _time_rest) = opt(parser_with_context!(time_rest)(context))(remaining)?; let (remaining, _time_rest) = opt(parser_with_context!(time_rest)(context))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, source)) Ok((remaining, source))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn time_rest<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn time_rest<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, body) = recognize(verify( let (remaining, body) = recognize(verify(
many_till(anychar, parser_with_context!(exit_matcher_parser)(context)), many_till(anychar, parser_with_context!(exit_matcher_parser)(context)),
|(body, _end_contents)| !body.is_empty(), |(body, _end_contents)| !body.is_empty(),
@@ -295,7 +358,10 @@ fn time_rest<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn active_time_rest_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn active_time_rest_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt(( alt((
recognize(verify(anychar, |c| ">\n".contains(*c))), recognize(verify(anychar, |c| ">\n".contains(*c))),
recognize(tuple((space1, parser_with_context!(repeater)(context)))), recognize(tuple((space1, parser_with_context!(repeater)(context)))),
@@ -309,8 +375,8 @@ fn active_time_rest_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_time_rest_end<'r, 's>( fn inactive_time_rest_end<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt(( alt((
recognize(verify(anychar, |c| "]\n".contains(*c))), recognize(verify(anychar, |c| "]\n".contains(*c))),
recognize(tuple((space1, parser_with_context!(repeater)(context)))), recognize(tuple((space1, parser_with_context!(repeater)(context)))),
@@ -322,7 +388,10 @@ fn inactive_time_rest_end<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn time_range_rest_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn time_range_rest_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
// We pop off the most recent context element to get a context tree with just the active/inactive_time_rest_end exit matcher (removing this function from the exit matcher chain) because the 2nd time in the range does not end when a "-TIME" pattern is found. // We pop off the most recent context element to get a context tree with just the active/inactive_time_rest_end exit matcher (removing this function from the exit matcher chain) because the 2nd time in the range does not end when a "-TIME" pattern is found.
let parent_node = context.iter().next().expect("Two context elements are added to the tree when adding this exit matcher, so it should be impossible for this to return None."); let parent_node = context.iter().next().expect("Two context elements are added to the tree when adding this exit matcher, so it should be impossible for this to return None.");
let parent_tree = ContextTree::branch_from(parent_node); let parent_tree = ContextTree::branch_from(parent_node);
@@ -332,7 +401,10 @@ fn time_range_rest_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn repeater<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn repeater<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
// + for cumulative type // + for cumulative type
// ++ for catch-up type // ++ for catch-up type
// .+ for restart type // .+ for restart type
@@ -345,7 +417,10 @@ fn repeater<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn warning_delay<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn warning_delay<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
// - for all type // - for all type
// -- for first type // -- for first type
let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?; let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?;

View File

@@ -1,7 +1,6 @@
use nom::branch::alt; use nom::branch::alt;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::multispace0;
use nom::character::complete::none_of; use nom::character::complete::none_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::combinator::eof; use nom::combinator::eof;
@@ -14,6 +13,7 @@ use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::parser_context::ContextElement; use super::parser_context::ContextElement;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
@@ -48,90 +48,34 @@ pub fn immediate_in_section<'r, 's, 'x>(context: Context<'r, 's>, section_name:
false false
} }
/// Get one character from before the current position.
pub fn get_one_before<'s>(document: &'s str, current_position: &'s str) -> Option<&'s str> {
assert!(is_slice_of(document, current_position));
if document.as_ptr() as usize == current_position.as_ptr() as usize {
return None;
}
let offset = current_position.as_ptr() as usize - document.as_ptr() as usize;
let previous_character_offset = document.floor_char_boundary(offset - 1);
Some(&document[previous_character_offset..offset])
}
/// Get the line current_position is on up until current_position
pub fn get_current_line_before_position<'s>(
document: &'s str,
current_position: &'s str,
) -> Option<&'s str> {
assert!(is_slice_of(document, current_position));
if document.as_ptr() as usize == current_position.as_ptr() as usize {
return None;
}
let offset = current_position.as_ptr() as usize - document.as_ptr() as usize;
let mut previous_character_offset = offset;
loop {
let new_offset = document.floor_char_boundary(previous_character_offset - 1);
let new_line = &document[new_offset..offset];
let leading_char = new_line
.chars()
.next()
.expect("Impossible to not have at least 1 character to read.");
if "\r\n".contains(leading_char) || new_offset == 0 {
break;
}
previous_character_offset = new_offset;
}
Some(&document[previous_character_offset..offset])
}
/// Check if the child string slice is a slice of the parent string slice.
fn is_slice_of(parent: &str, child: &str) -> bool {
let parent_start = parent.as_ptr() as usize;
let parent_end = parent_start + parent.len();
let child_start = child.as_ptr() as usize;
let child_end = child_start + child.len();
child_start >= parent_start && child_end <= parent_end
}
/// 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. /// 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.
pub fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str { pub fn get_consumed<'s>(input: OrgSource<'s>, remaining: OrgSource<'s>) -> OrgSource<'s> {
assert!(is_slice_of(input, remaining)); input.get_until(remaining)
let source = {
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
&input[..offset]
};
source
} }
/// A line containing only whitespace and then a line break /// A line containing only whitespace and then a line break
/// ///
/// It is up to the caller to ensure this is called at the start of a line. /// It is up to the caller to ensure this is called at the start of a line.
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn blank_line(input: &str) -> Res<&str, &str> { pub fn blank_line(input: OrgSource<'_>) -> Res<OrgSource<'_>, OrgSource<'_>> {
not(eof)(input)?; not(eof)(input)?;
recognize(tuple((space0, alt((line_ending, eof)))))(input) recognize(tuple((space0, alt((line_ending, eof)))))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn element_trailing_whitespace<'r, 's>( pub fn element_trailing_whitespace<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
context: Context<'r, 's>, start_of_line(input)?;
input: &'s str,
) -> Res<&'s str, &'s str> {
start_of_line(context, input)?;
alt((eof, recognize(many0(blank_line))))(input) alt((eof, recognize(many0(blank_line))))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn maybe_consume_trailing_whitespace_if_not_exiting<'r, 's>( pub fn maybe_consume_trailing_whitespace_if_not_exiting<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Option<&'s str>> { ) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
if context.should_consume_trailing_whitespace() && exit_matcher_parser(context, input).is_err() if context.should_consume_trailing_whitespace() && exit_matcher_parser(context, input).is_err()
{ {
Ok(opt(parser_with_context!(element_trailing_whitespace)( Ok(opt(element_trailing_whitespace)(input)?)
context,
))(input)?)
} else { } else {
Ok((input, None)) Ok((input, None))
} }
@@ -140,59 +84,37 @@ pub fn maybe_consume_trailing_whitespace_if_not_exiting<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn maybe_consume_trailing_whitespace<'r, 's>( pub fn maybe_consume_trailing_whitespace<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, Option<&'s str>> { ) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
if context.should_consume_trailing_whitespace() { if context.should_consume_trailing_whitespace() {
Ok(opt(parser_with_context!(element_trailing_whitespace)( Ok(opt(element_trailing_whitespace)(input)?)
context,
))(input)?)
} else { } else {
Ok((input, None)) Ok((input, None))
} }
} }
/// Check that we are at the start of a line
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn trailing_whitespace(input: &str) -> Res<&str, &str> { pub fn start_of_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
alt((eof, recognize(tuple((line_ending, many0(blank_line))))))(input) if input.is_at_start_of_line() {
Ok((input, ()))
} else {
Err(nom::Err::Error(CustomError::MyError(MyError(
"Not at start of line".into(),
))))
}
} }
/// Check that we are at the start of a line /// Check that we are at the start of a line
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn start_of_line<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> { pub fn preceded_by_whitespace<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let document_root = context.get_document_root().unwrap(); let preceding_character = input.get_preceding_character();
let preceding_character = get_one_before(document_root, input)
.map(|slice| slice.chars().next())
.flatten();
match preceding_character {
Some('\n') => {}
Some(_) => {
// Not at start of line, cannot be a heading
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not at start of line",
))));
}
// If None, we are at the start of the file which allows for headings
None => {}
};
Ok((input, ()))
}
/// Check that we are at the start of a line
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn preceded_by_whitespace<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, ()> {
let document_root = context.get_document_root().unwrap();
let preceding_character = get_one_before(document_root, input)
.map(|slice| slice.chars().next())
.flatten();
match preceding_character { match preceding_character {
Some('\n') | Some('\r') | Some(' ') | Some('\t') => {} Some('\n') | Some('\r') | Some(' ') | Some('\t') => {}
// If None, we are at the start of the file which is not allowed // If None, we are at the start of the file which is not allowed
None | Some(_) => { None | Some(_) => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not preceded by whitespace.", "Not preceded by whitespace.".into(),
)))); ))));
} }
}; };
@@ -203,7 +125,7 @@ pub fn preceded_by_whitespace<'r, 's>(
/// ///
/// This function only operates on spaces, tabs, carriage returns, and line feeds. It does not handle fancy unicode whitespace. /// This function only operates on spaces, tabs, carriage returns, and line feeds. It does not handle fancy unicode whitespace.
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn non_whitespace_character(input: &str) -> Res<&str, char> { pub fn non_whitespace_character(input: OrgSource<'_>) -> Res<OrgSource<'_>, char> {
none_of(" \t\r\n")(input) none_of(" \t\r\n")(input)
} }
@@ -211,25 +133,16 @@ pub fn non_whitespace_character(input: &str) -> Res<&str, char> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn exit_matcher_parser<'r, 's>( pub fn exit_matcher_parser<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: OrgSource<'s>,
) -> Res<&'s str, &'s str> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
peek(|i| context.check_exit_matcher(i))(input) peek(|i| context.check_exit_matcher(i))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn always_fail<'r, 's>(_context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { pub fn text_until_exit<'r, 's>(
Err(nom::Err::Error(CustomError::MyError(MyError( context: Context<'r, 's>,
"Always fail", input: OrgSource<'s>,
)))) ) -> Res<OrgSource<'s>, OrgSource<'s>> {
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn whitespace_eof(input: &str) -> Res<&str, &str> {
recognize(tuple((multispace0, eof)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn text_until_exit<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
recognize(verify( recognize(verify(
many_till(anychar, parser_with_context!(exit_matcher_parser)(context)), many_till(anychar, parser_with_context!(exit_matcher_parser)(context)),
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),
@@ -237,23 +150,21 @@ pub fn text_until_exit<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn not_yet_implemented() -> Res<&'static str, ()> { pub fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not implemented yet.", "Not implemented yet.".into(),
)))); ))));
} }
#[cfg(test)] #[allow(dead_code)]
mod tests { #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
use super::*; /// Text from the current point until the next line break or end of file
///
#[test] /// Useful for debugging.
fn get_one_before_unicode() { pub fn text_until_eol<'r, 's>(
let input = "🧡💛💚💙💜"; input: OrgSource<'s>,
let (green_heart_index, _) = input.char_indices().skip(2).next().unwrap(); ) -> Result<&'s str, nom::Err<CustomError<OrgSource<'s>>>> {
let starting_with_green_heart = &input[green_heart_index..]; let line = recognize(many_till(anychar, alt((line_ending, eof))))(input)
let yellow_heart = get_one_before(input, starting_with_green_heart).unwrap(); .map(|(_remaining, line)| Into::<&str>::into(line))?;
assert!(is_slice_of(input, yellow_heart)); Ok(line.trim())
assert_eq!(yellow_heart, "💛");
}
} }

View File

@@ -1 +1,2 @@
#[cfg(feature = "compare")]
include!(concat!(env!("OUT_DIR"), "/tests.rs")); include!(concat!(env!("OUT_DIR"), "/tests.rs"));

View File

@@ -11,7 +11,7 @@ fn {name}() {{
let diff_result = let diff_result =
compare_document(&parsed_sexp, &rust_parsed).expect("Compare parsed documents."); compare_document(&parsed_sexp, &rust_parsed).expect("Compare parsed documents.");
diff_result diff_result
.print() .print(org_contents.as_str())
.expect("Print document parse tree diff."); .expect("Print document parse tree diff.");
assert!(!diff_result.is_bad()); assert!(!diff_result.is_bad());
assert_eq!(remaining, ""); assert_eq!(remaining, "");