121 Commits

Author SHA1 Message Date
Tom Alexander
012c192aed Bump version to 0.1.2 and change README to markdown.
All checks were successful
rustfmt Build rustfmt has succeeded
crates.io does not support org-mode for README files so I am changing this to markdown.
2023-08-11 00:00:49 -04:00
Tom Alexander
67ca0fe8dd Merge branch 'feature_gate'
Some checks failed
rustfmt Build rustfmt has started
rust-test Build rust-test has failed
rust-build Build rust-build has failed
2023-08-10 23:56:44 -04:00
Tom Alexander
290a700a22 New, updated opentelemetry tracing exporter. 2023-08-10 23:27:15 -04:00
Tom Alexander
729be9302b Update tekton pipeline to build all the permutations of Organic.
This is to catch regressions in feature-gating since I won't be building every possible permutation all the time.
2023-08-10 20:46:54 -04:00
Tom Alexander
44ad6753ca Fix feature gating the compare code. 2023-08-10 20:22:34 -04:00
Tom Alexander
cd1b4ba785 Make the tracing macros optional. 2023-08-10 20:22:34 -04:00
Tom Alexander
1f10d3d064 Disable all the old tracing stuff. 2023-08-10 20:22:34 -04:00
Tom Alexander
f6e539a40b Remove run targets from makefile.
These no longer make sense since we have to pass a parameter to the script for the path to the org-mode document.
2023-08-10 19:34:43 -04:00
Tom Alexander
3ee18072c2 Merge branch 'toy_cleanup'
Some checks are pending
rustfmt Build rustfmt has started
rust-test Build rust-test has started
2023-08-10 18:53:08 -04:00
Tom Alexander
77de97703f Remove all the old references to "toy language"
This is a relic from the early development days in this repo. When I first started this repo, it was a clean-slate playground to test ideas for solving the road blocks I hit with my previous attempt at an org-mode parser. To keep things simple, I originally only had a very basic set of syntax rules that only vaguely looked similar to org-mode. Once I had things figured out, I kept developing in this repo, morphing it into a full org-mode parser. A couple of references to those early days still remained, and this patch should get rid of the last of them.
2023-08-10 18:52:57 -04:00
Tom Alexander
023dd05267 Remove outdated notes. 2023-08-10 18:52:57 -04:00
Tom Alexander
66c71e7e40 Switch the compiled bin to running a diff just like the automated tests.
This is mostly so I can test a variety of org-mode documents without needing to integrate them into the org samples folder.
2023-08-10 18:46:19 -04:00
Tom Alexander
6941825e75 Fix package category.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-27 21:22:24 -04:00
Tom Alexander
bda291f771 Remove detect-tag from pipeline.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-27 21:13:14 -04:00
Tom Alexander
06b83f9156 Merge branch 'publish_crate'
Some checks failed
rustfmt Build rustfmt has failed
rust-test Build rust-test has succeeded
2023-07-27 21:06:05 -04:00
Tom Alexander
ef31900b51 Remove semver pipeline.
Since I'm publishing this to crates.io it doesn't make sense to have a separate version string from the one in Cargo.toml.
2023-07-27 21:05:27 -04:00
Tom Alexander
8a221e0e0d Add metadata for publishing crate. 2023-07-27 21:05:27 -04:00
Tom Alexander
f359676e28 Separate out the persistent volumes for cargo cache for tekton pipelines.
All checks were successful
semver Build semver has succeeded
rust-test Build rust-test has succeeded
rustfmt Build rustfmt has succeeded
Both pipelines running simultaneously is causing non-deterministic failures.
2023-07-27 20:46:18 -04:00
Tom Alexander
7d7446d843 Merge branch 'timestamp'
Some checks failed
semver Build semver has succeeded
rustfmt Build rustfmt has failed
rust-test Build rust-test has succeeded
2023-07-27 20:39:51 -04:00
Tom Alexander
17e523b74c Do not consume space in time rest when leading into a repeater or delay. 2023-07-27 20:39:13 -04:00
Tom Alexander
ece8fcd0c4 Implement parser for active/inactive timestamp time ranges. 2023-07-27 20:26:56 -04:00
Tom Alexander
1a5b7ca30c Implement parser for active/inactive timestamp date ranges. 2023-07-27 19:59:36 -04:00
Tom Alexander
b5a029e2bf Implement parser for inactive timestamps. 2023-07-27 19:54:33 -04:00
Tom Alexander
c5f81298ba Switch to passing in the exit matcher from a higher context to reduce the permutations of functions. 2023-07-27 19:52:35 -04:00
Tom Alexander
529418a9d1 Implement parser for active timestamps. 2023-07-27 19:45:57 -04:00
Tom Alexander
d4a3628481 Implement parser for date. 2023-07-27 19:20:28 -04:00
Tom Alexander
70f2747291 Implement parser for diary timestamps. 2023-07-27 18:59:32 -04:00
Tom Alexander
49d5a4e4b5 Call out to the parsers for the various timestamp types.
The parsers for those types remain unimplemented.
2023-07-24 17:54:49 -04:00
Tom Alexander
fa5fc41121 Create structure for timestamps. 2023-07-24 17:34:07 -04:00
Tom Alexander
73e15286dc Add test cases. 2023-07-24 17:15:27 -04:00
fluxcdbot
efa56a6bef CI: autofix rust code.
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-24 16:57:08 -04:00
Tom Alexander
3b11d8fb61 Merge branch 'subscript_superscript'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-24 16:39:35 -04:00
Tom Alexander
63fcad2ac6 Enable tests that needed subscript/superscript implemented. 2023-07-24 16:32:56 -04:00
Tom Alexander
23d587c699 Implement parser for subscript/superscript with braces. 2023-07-24 16:29:31 -04:00
Tom Alexander
f717d5e7df Implement parser for braceless subscript/superscript. 2023-07-24 15:41:14 -04:00
Tom Alexander
6d4379d029 Add a License section to the README. 2023-07-24 14:44:27 -04:00
Tom Alexander
8c00ee24ba Add a test to prove that subscript/superscript cannot start without a leading character even though its at the start of the file. 2023-07-24 14:33:04 -04:00
Tom Alexander
4a565601c1 Add test cases. 2023-07-24 14:29:20 -04:00
Tom Alexander
993c73dc9f Create structure for subscript and superscript. 2023-07-24 14:23:36 -04:00
Tom Alexander
7d73ac4bf6 Merge branch 'statistics_cookie'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-22 02:12:30 -04:00
Tom Alexander
b8b2e33137 Implement the statistics cookie parser. 2023-07-22 02:12:21 -04:00
Tom Alexander
c73e26e2d6 Create structure for statistics cookies. 2023-07-22 02:12:20 -04:00
fluxcdbot
abb0aeacaf CI: autofix rust code.
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-22 05:43:17 +00:00
Tom Alexander
cb8775f2f4 Merge branch 'target'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-22 01:40:44 -04:00
Tom Alexander
caa6c41798 Add a test case. 2023-07-22 01:40:27 -04:00
Tom Alexander
e54218c0d7 Implement the target parser. 2023-07-22 01:36:00 -04:00
Tom Alexander
d60cad07e0 Create structure for targets. 2023-07-22 01:15:04 -04:00
Tom Alexander
537fc00fd3 Merge branch 'line_break'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-22 00:44:03 -04:00
Tom Alexander
5c1d913c99 Check that the preceding line for a line break is non-empty. 2023-07-22 00:43:02 -04:00
Tom Alexander
a1f3e9ea47 Implement line break parser. 2023-07-22 00:03:21 -04:00
Tom Alexander
4d114206ef Create structure for line breaks. 2023-07-21 23:48:37 -04:00
Tom Alexander
6b82214ec3 Add test case. 2023-07-21 23:41:29 -04:00
Tom Alexander
fb83e8d453 Merge branch 'inline_source_block'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-21 23:21:22 -04:00
Tom Alexander
80039aa605 Fix bracket counting in inline babel calls. 2023-07-21 23:20:10 -04:00
Tom Alexander
0b41e12424 Fix counting brackets in inline source block. 2023-07-21 23:14:52 -04:00
Tom Alexander
e8979513aa Implement parser for inline source blocks. 2023-07-21 22:51:19 -04:00
Tom Alexander
e0d2bb8213 Create structure for inline source blocks. 2023-07-21 22:29:04 -04:00
Tom Alexander
b323a407c4 Merge branch 'inline_babel'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-21 21:40:18 -04:00
Tom Alexander
45b01012b3 Implement the parser for inline babel calls. 2023-07-21 21:39:53 -04:00
Tom Alexander
2773b35438 Add test case. 2023-07-21 19:53:18 -04:00
Tom Alexander
eef2944307 Create structure for inline babel calls. 2023-07-21 19:53:02 -04:00
Tom Alexander
1e2ea17a9c Merge branch 'citation'
Some checks failed
semver Build semver has succeeded
rustfmt Build rustfmt has failed
rust-test Build rust-test has failed
2023-07-21 18:52:33 -04:00
Tom Alexander
7ce9dafd96 Fix parsing citations inside paragraphs. 2023-07-21 18:52:02 -04:00
Tom Alexander
d678391789 Fix parsing semicolons. 2023-07-21 18:42:22 -04:00
Tom Alexander
640a9375bc Add a testcase populating all the optional fields for citations. 2023-07-21 18:28:16 -04:00
Tom Alexander
1a8bf01fba Fix simple citations by making prefixes and suffixes optional. 2023-07-21 18:19:39 -04:00
Tom Alexander
4ad297f58a Add a test for a simple citation. 2023-07-21 18:00:19 -04:00
Tom Alexander
6b47a6c6c3 Initial implementation for citations.
This implementation definitely has bugs and is completely untested at this point. I'm just committing the initial "assume everything works" version before I did into debugging and fixing.
2023-07-21 17:52:18 -04:00
Tom Alexander
e24b413cd0 Finish first implementation of citation reference. 2023-07-21 16:38:49 -04:00
Tom Alexander
339ff5cd26 Implement key parser and begin key_suffix parser. 2023-07-20 01:13:49 -04:00
Tom Alexander
d5c611674e Create structure for citations. 2023-07-20 00:38:16 -04:00
Tom Alexander
4e791b175e Merge branch 'footnote_reference'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-19 23:40:59 -04:00
Tom Alexander
7611deb1ff Forgot to add the exit matcher nodes to the context and match the trailing ']'. 2023-07-19 23:40:26 -04:00
Tom Alexander
b850f59640 Implement parser for inline footnotes. 2023-07-19 23:20:17 -04:00
Tom Alexander
a36a820e84 Implement parser for the simplest form of footnote reference. 2023-07-19 21:32:08 -04:00
Tom Alexander
6822069c2f Implement the parser for anonymous footnotes. 2023-07-19 21:14:09 -04:00
Tom Alexander
5fb66a586d Implement a function to detect the end of a footnote reference definition with balanced brackets. 2023-07-19 20:52:09 -04:00
Tom Alexander
9c2eb3b122 Create structure for footnote references. 2023-07-19 18:56:46 -04:00
Tom Alexander
c1a99a03f8 Add test case. 2023-07-19 17:52:59 -04:00
Tom Alexander
8cdca061f8 Ignore test export_snippet_paragraph_break_precedent.
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
This test is failing on the CI because emacs 28 allows for export snippets without a closing @@.
2023-07-19 00:46:36 -04:00
Tom Alexander
d3c265415c Merge branch 'export_snippet'
Some checks failed
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has failed
2023-07-19 00:38:40 -04:00
Tom Alexander
95e033a99b Implement the export snippet parser. 2023-07-19 00:38:19 -04:00
Tom Alexander
1fb8ce9af6 Create structure for export snippets. 2023-07-19 00:09:16 -04:00
Tom Alexander
eb03342506 Add test cases. 2023-07-18 23:46:10 -04:00
Tom Alexander
8be47c551d Merge branch 'latex_fragment'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-18 23:30:05 -04:00
Tom Alexander
3328c94a21 Enable tests which are now passing. 2023-07-18 23:29:41 -04:00
Tom Alexander
5e73ca74d5 Fix embedding text that coincidentally matches org-mode object syntax inside a LaTeX environment.
I was using the plain_text matcher before which adds its own exit condition that does not apply to LaTeX environments.
2023-07-18 23:27:48 -04:00
Tom Alexander
0d6f6288c9 Support escaping backslash in sexp. 2023-07-18 23:08:58 -04:00
Tom Alexander
a817eefce7 Add form 6 of LaTeX fragment. 2023-07-18 22:39:05 -04:00
Tom Alexander
4f5c40cd4b Add forms 4 and 5 of LaTeX fragment. 2023-07-18 22:21:52 -04:00
Tom Alexander
faf81d0143 Add forms 2 and 3 of LaTeX fragment. 2023-07-18 22:02:38 -04:00
Tom Alexander
c79b8c7833 Implement the first form of LaTeX fragment. 2023-07-18 21:50:29 -04:00
Tom Alexander
5ad8fdf4b2 Add test cases. 2023-07-18 21:14:49 -04:00
Tom Alexander
3ab0dd4531 Create structure for LaTeX fragments. 2023-07-18 21:14:49 -04:00
Tom Alexander
11e76814f4 Merge branch 'entity'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-18 20:39:22 -04:00
Tom Alexander
d46b3b9a58 Disable the entity test because the failure case needs LaTeX fragments implemented first to work. 2023-07-18 20:39:03 -04:00
Tom Alexander
754e9ff7d7 Implement entity parser. 2023-07-18 20:35:55 -04:00
Tom Alexander
21f46d09e6 Create structure for entities. 2023-07-18 20:05:39 -04:00
Tom Alexander
2966b91a09 Add an example for org-mode entities. 2023-07-18 19:40:09 -04:00
Tom Alexander
a596dcff39 Merge branch 'readme'
Some checks are pending
rust-test Build rust-test has started
rustfmt Build rustfmt has started
semver Build semver has succeeded
2023-07-18 19:28:03 -04:00
Tom Alexander
322a10dc38 Add title.
All checks were successful
rust-test Build rust-test has succeeded
2023-07-18 14:27:17 -04:00
Tom Alexander
87c378bc4e Disable the TOC. 2023-07-18 14:27:16 -04:00
Tom Alexander
b6e268bdbb Add a README.
All checks were successful
rust-test Build rust-test has succeeded
2023-07-18 14:22:45 -04:00
fluxcdbot
bab9232ebc CI: autofix rust code.
All checks were successful
semver Build semver has succeeded
rust-test Build rust-test has succeeded
rustfmt Build rustfmt has succeeded
2023-07-18 17:51:28 +00:00
Tom Alexander
c9ee61eae9 Move the cargo cache to the correct task.
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-18 13:48:50 -04:00
fluxcdbot
0cd398c30c CI: autofix rust code.
Some checks failed
semver Build semver has succeeded
rustfmt Build rustfmt has failed
rust-test Build rust-test has succeeded
2023-07-18 17:48:21 +00:00
Tom Alexander
bf8bd5bcbd Add musl-dev to rustfmt image for crti.o.
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-18 13:46:15 -04:00
Tom Alexander
b3c638428b Also automatically run cargo-fix after rustfmt.
Some checks failed
semver Build semver has succeeded
rustfmt Build rustfmt has failed
rust-test Build rust-test has succeeded
2023-07-18 13:42:09 -04:00
Tom Alexander
db0ea7394e Merge branch 'radio_link'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-14 21:00:47 -04:00
Tom Alexander
97f956d0bf Enable another test. 2023-07-14 20:59:00 -04:00
Tom Alexander
167ffa650c Enable another test. 2023-07-14 20:58:11 -04:00
Tom Alexander
27d863b875 Fix the simple test by allowing bold to start/end with <> and by capturing trailing whitespace from radio links. 2023-07-14 20:55:16 -04:00
Tom Alexander
e608b73d1a Implement all-token iteration.
Some checks failed
rust-test Build rust-test has failed
Radio targets are now being properly detected and they trigger re-parses but the tests do not yet pass.
2023-07-14 20:45:31 -04:00
Tom Alexander
b27f911ff3 Finish implementing token iteration. 2023-07-14 20:24:06 -04:00
Tom Alexander
08e6efe5f5 Filling in more of the iter_tokens tree. 2023-07-14 20:18:30 -04:00
Tom Alexander
0e73b83bf3 Filling in more of the iter_tokens tree. 2023-07-14 20:09:24 -04:00
Tom Alexander
793e560bd5 Boxing made it work. 2023-07-14 19:57:27 -04:00
Tom Alexander
0073af19e2 Running into an issue returning different iterators from the same function. 2023-07-14 19:54:41 -04:00
Tom Alexander
76187a0cb9 Enable radio_link_simple test.
This test does not yet pass, but this is goal-setting.
2023-07-14 19:11:51 -04:00
Tom Alexander
688779ba40 Fix tests. 2023-07-14 19:09:54 -04:00
Tom Alexander
bd04451d58 Implement the second parsing pass. 2023-07-14 19:06:58 -04:00
79 changed files with 3731 additions and 312 deletions

View File

@@ -0,0 +1,260 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: rust-build
spec:
pipelineSpec:
params:
- name: image-name
description: The name for the built image
type: string
- name: path-to-image-context
description: The path to the build context
type: string
- name: path-to-dockerfile
description: The path to the Dockerfile
type: string
- name: command
type: array
description: Command to run.
default: []
tasks:
- name: report-pending
taskRef:
name: gitea-set-status
runAfter:
- fetch-repository
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has started"
- name: STATE
value: pending
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: fetch-repository
taskRef:
name: git-clone
workspaces:
- name: output
workspace: git-source
params:
- name: url
value: $(params.REPO_URL)
- name: revision
value: $(params.PULL_BASE_SHA)
- name: deleteExisting
value: "true"
- name: build-image
taskRef:
name: kaniko
params:
- name: IMAGE
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: CONTEXT
value: $(params.path-to-image-context)
- name: DOCKERFILE
value: $(params.path-to-dockerfile)
- name: BUILDER_IMAGE
value: "gcr.io/kaniko-project/executor:v1.12.1"
- name: EXTRA_ARGS
value:
- --cache=true
- --cache-copy-layers
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
- --use-new-run # Should result in a speed-up
- --reproducible # To remove timestamps so layer caching works.
- --snapshot-mode=redo
- --skip-unused-stages=true
- --registry-mirror=dockerhub.dockerhub.svc.cluster.local
workspaces:
- name: source
workspace: git-source
- name: dockerconfig
workspace: docker-credentials
runAfter:
- fetch-repository
- name: run-image-none
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- build-image
params:
- name: command
value: ["$(params.command[*])"]
- name: args
value: ["--no-default-features"]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-tracing
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-none
params:
- name: command
value: ["$(params.command[*])"]
- name: args
value: ["--no-default-features", "--features", "tracing"]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-compare
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-tracing
params:
- name: command
value: ["$(params.command[*])"]
- name: args
value: ["--no-default-features", "--features", "compare"]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-default
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-compare
params:
- name: command
value: ["$(params.command[*])"]
- name: args
value: []
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-all
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-default
params:
- name: command
value: ["$(params.command[*])"]
- name: args
value: ["--no-default-features", "--features", "tracing,compare"]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
finally:
- name: report-success
when:
- input: "$(tasks.status)"
operator: in
values: ["Succeeded", "Completed"]
taskRef:
name: gitea-set-status
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has succeeded"
- name: STATE
value: success
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: report-failure
when:
- input: "$(tasks.status)"
operator: in
values: ["Failed"]
taskRef:
name: gitea-set-status
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has failed"
- name: STATE
value: failure
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: cargo-cache-autoclean
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
params:
- name: command
value: [cargo, cache, --autoclean]
- name: args
value: []
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
workspaces:
- name: git-source
- name: docker-credentials
- name: cargo-cache
workspaces:
- name: git-source
volumeClaimTemplate:
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
subPath: rust-source
- name: cargo-cache
persistentVolumeClaim:
claimName: organic-cargo-cache-build
- name: docker-credentials
secret:
secretName: harbor-plain
serviceAccountName: build-bot
timeout: 240h0m0s
params:
- name: image-name
value: "harbor.fizz.buzz/private/organic-build"
- name: path-to-image-context
value: docker/organic_build/
- name: path-to-dockerfile
value: docker/organic_build/Dockerfile
- name: command
value: [cargo, build]

View File

@@ -196,7 +196,7 @@ spec:
subPath: rust-source subPath: rust-source
- name: cargo-cache - name: cargo-cache
persistentVolumeClaim: persistentVolumeClaim:
claimName: cargo-cache claimName: organic-cargo-cache-test
- name: docker-credentials - name: docker-credentials
secret: secret:
secretName: harbor-plain secretName: harbor-plain

View File

@@ -14,13 +14,13 @@ 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 - name: rustfmt-command
type: array type: array
description: Command to run. description: Command to run rustfmt.
default: [] default: []
- name: args - name: rustfmt-args
type: array type: array
description: Arguments passed to command. description: Arguments passed to rustfmt.
default: [] default: []
- name: GIT_USER_NAME - name: GIT_USER_NAME
description: The username for git description: The username for git
@@ -81,14 +81,6 @@ spec:
value: $(params.PULL_BASE_SHA) value: $(params.PULL_BASE_SHA)
- name: deleteExisting - name: deleteExisting
value: "true" value: "true"
- name: detect-tag
taskRef:
name: detect-tag
workspaces:
- name: repo
workspace: git-source
runAfter:
- fetch-repository
- name: build-image - name: build-image
taskRef: taskRef:
name: kaniko name: kaniko
@@ -118,8 +110,7 @@ spec:
workspace: docker-credentials workspace: docker-credentials
runAfter: runAfter:
- fetch-repository - fetch-repository
- detect-tag - name: rustfmt
- name: run-image
taskRef: taskRef:
name: run-docker-image name: run-docker-image
workspaces: workspaces:
@@ -129,9 +120,26 @@ spec:
- build-image - build-image
params: params:
- name: command - name: command
value: ["$(params.command[*])"] value: ["$(params.rustfmt-command[*])"]
- name: args - name: args
value: ["$(params.args[*])"] value: ["$(params.rustfmt-args[*])"]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: cargo-fix
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- rustfmt
params:
- name: command
value: ["cargo", "fix"]
- name: args
value: ["--allow-dirty"]
- 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: commit-changes - name: commit-changes
@@ -148,7 +156,7 @@ spec:
git config --global --add safe.directory /workspace/source git config --global --add safe.directory /workspace/source
git_status=$(git status --porcelain) git_status=$(git status --porcelain)
if [ -n "$git_status" ]; then if [ -n "$git_status" ]; then
git commit -a -m "CI: format rust code." git commit -a -m "CI: autofix rust code."
git push origin HEAD:$(params.PULL_BASE_REF) git push origin HEAD:$(params.PULL_BASE_REF)
else else
echo "No changes to commit." echo "No changes to commit."
@@ -157,7 +165,7 @@ spec:
- name: source - name: source
workspace: git-source workspace: git-source
runAfter: runAfter:
- run-image - cargo-fix
finally: finally:
- name: report-success - name: report-success
when: when:
@@ -217,6 +225,9 @@ spec:
requests: requests:
storage: 10Gi storage: 10Gi
subPath: rust-source subPath: rust-source
- name: cargo-cache
persistentVolumeClaim:
claimName: organic-cargo-cache-fmt
- name: docker-credentials - name: docker-credentials
secret: secret:
secretName: harbor-plain secretName: harbor-plain

View File

@@ -2,31 +2,6 @@ apiVersion: config.lighthouse.jenkins-x.io/v1alpha1
kind: TriggerConfig kind: TriggerConfig
spec: spec:
postsubmits: postsubmits:
- name: semver
agent: tekton-pipeline
branches:
- ^main$
- ^master$
max_concurrency: 1
# Override https-based url from lighthouse events.
clone_uri: "git@code.fizz.buzz:talexander/organic.git"
pipeline_run_spec:
serviceAccountName: build-bot
pipelineRef:
name: semver
namespace: lighthouse
workspaces:
- name: git-source
volumeClaimTemplate:
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
subPath: organic-source
params: []
- name: rustfmt - name: rustfmt
source: "pipeline-rustfmt.yaml" source: "pipeline-rustfmt.yaml"
# Override https-based url from lighthouse events. # Override https-based url from lighthouse events.
@@ -41,3 +16,10 @@ spec:
skip_branches: skip_branches:
# We already run on every commit, so running when the semver tags get pushed is causing needless double-processing. # We already run on every commit, so running when the semver tags get pushed is causing needless double-processing.
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$" - "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
- name: rust-build
source: "pipeline-rust-build.yaml"
# Override https-based url from lighthouse events.
clone_uri: "git@code.fizz.buzz:talexander/organic.git"
skip_branches:
# We already run on every commit, so running when the semver tags get pushed is causing needless double-processing.
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$"

View File

@@ -1,31 +1,42 @@
[package] [package]
name = "organic" name = "organic"
version = "0.1.0" version = "0.1.2"
authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser."
edition = "2021" edition = "2021"
license = "0BSD" license = "0BSD"
repository = "https://code.fizz.buzz/talexander/organic"
readme = "README.md"
keywords = ["emacs", "org-mode"]
categories = ["parsing"]
resolver = "2"
[lib] [lib]
name = "organic" name = "organic"
path = "src/lib.rs" path = "src/lib.rs"
[[bin]] [[bin]]
name = "toy" # This bin exists for development purposes only. The real target of this crate is the library.
name = "compare"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
nom = "7.1.1" nom = "7.1.1"
opentelemetry = "0.17.0" opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] }
opentelemetry-jaeger = "0.16.0" opentelemetry-otlp = { version = "0.13.0", optional = true }
tracing = "0.1.37" opentelemetry-semantic-conventions = { version = "0.12.0", optional = true }
tracing-opentelemetry = "0.17.2" tokio = { version = "1.30.0", optional = true, default-features = false, features = ["rt", "rt-multi-thread"] }
tracing-subscriber = {version="0.3.16", features=["env-filter"]} tracing = { version = "0.1.37", optional = true }
tracing-opentelemetry = { version = "0.20.0", optional = true }
tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] }
[build-dependencies] [build-dependencies]
walkdir = "2.3.3" walkdir = "2.3.3"
[features] [features]
default = ["compare"] default = ["compare", "tracing"]
compare = [] compare = []
tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"]
[profile.release] [profile.release]
lto = true lto = true

View File

@@ -45,17 +45,12 @@ integrationtest:
unittest: unittest:
> cargo test --lib -- --test-threads $(TESTJOBS) > cargo test --lib -- --test-threads $(TESTJOBS)
.PHONY: run
run:
> cargo run
.PHONY: debug
debug:
> RUST_LOG=debug cargo run
.PHONY: jaeger .PHONY: jaeger
jaeger: jaeger:
> docker run -d --rm --name toylanguagedocker -p 6831:6831/udp -p 6832:6832/udp -p 16686:16686 -p 14268:14268 jaegertracing/all-in-one:latest # 4317 for OTLP gRPC, 4318 for OTLP HTTP. We currently use gRPC but I forward both ports regardless.
#
# These flags didn't help even though they seem like they would: --collector.otlp.grpc.max-message-size=10000000 --collector.queue-size=20000 --collector.num-workers=100
> docker run -d --rm --name organicdocker -p 4317:4317 -p 4318:4318 -p 16686:16686 -e COLLECTOR_OTLP_ENABLED=true jaegertracing/all-in-one:1.47 --collector.grpc-server.max-message-size=10000000
.PHONY: jaegerweb .PHONY: jaegerweb
jaegerweb: jaegerweb:
@@ -63,4 +58,4 @@ jaegerweb:
.PHONY: jaegerstop .PHONY: jaegerstop
jaegerstop: jaegerstop:
> docker stop toylanguagedocker > docker stop organicdocker

13
README.md Normal file
View File

@@ -0,0 +1,13 @@
# Organic - Free Range Org-Mode
Organic is an emacs-less implementation of an [org-mode](https://orgmode.org/) parser.
## Project Status
This project is a personal learning project to grow my experience in [rust](https://www.rust-lang.org/). It is under development and at this time I would not recommend anyone use this code. The goal is to turn this into a project others can use, at which point more information will appear in this README.
## License
This project is released under the public-domain-equivalent [0BSD license](https://www.tldrlegal.com/license/bsd-0-clause-license). This license puts no restrictions on the use of this code (you do not even have to include the copyright notice or license text when using it). HOWEVER, this project has a couple permissively licensed dependencies which do require their copyright notices and/or license texts to be included. I am not a lawyer and this is not legal advice but it is my layperson's understanding that if you distribute a binary with this library linked in, you will need to abide by their terms since their code will also be linked in your binary. I try to keep the dependencies to a minimum and the most restrictive dependency I will ever include is a permissively licensed one.

View File

@@ -73,15 +73,8 @@ fn is_expect_fail(name: &str) -> Option<&str> {
match name { match name {
"drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."), "drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
"element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."), "element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
"element_container_priority_drawer_greater_block" => Some("Need to implement subscript."),
"element_container_priority_dynamic_block_greater_block" => Some("Need to implement subscript."),
"element_container_priority_footnote_definition_greater_block" => Some("Need to implement subscript."),
"element_container_priority_greater_block_greater_block" => Some("Need to implement subscript."),
"element_container_priority_section_greater_block" => Some("Need to implement subscript."),
"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."), "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."),
"radio_link_before_and_after" => Some("Matching the contents of radio targets not yet implemented."), "export_snippet_paragraph_break_precedent" => Some("Emacs 28 has broken behavior so the tests in the CI fail."),
"radio_link_simple" => Some("Matching the contents of radio targets not yet implemented."),
"radio_link_identical_or_semantically_identical" => Some("Would require having the 2-pass parsing implemented."),
_ => None, _ => None,
} }
} }

View File

@@ -1,5 +1,6 @@
FROM rustlang/rust:nightly-alpine3.17 FROM rustlang/rust:nightly-alpine3.17
RUN apk add --no-cache musl-dev
RUN rustup component add rustfmt RUN rustup component add rustfmt
ENTRYPOINT ["cargo", "fmt"] ENTRYPOINT ["cargo", "fmt"]

View File

@@ -0,0 +1,4 @@
FROM rustlang/rust:nightly-alpine3.17
RUN apk add --no-cache musl-dev
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache

View File

@@ -0,0 +1,35 @@
IMAGE_NAME:=organic-build
# REMOTE_REPO:=harbor.fizz.buzz/private
.PHONY: all
all: build push
.PHONY: build
build:
docker build -t $(IMAGE_NAME) -f Dockerfile ../
.PHONY: push
push:
ifdef REMOTE_REPO
docker tag $(IMAGE_NAME) $(REMOTE_REPO)/$(IMAGE_NAME)
docker push $(REMOTE_REPO)/$(IMAGE_NAME)
else
@echo "REMOTE_REPO not defined, not pushing to a remote repo."
endif
.PHONY: clean
clean:
docker rmi $(IMAGE_NAME)
ifdef REMOTE_REPO
docker rmi $(REMOTE_REPO)/$(IMAGE_NAME)
else
@echo "REMOTE_REPO not defined, not removing from remote repo."
endif
.PHONY: run
run:
docker run --rm -i -t $(IMAGE_NAME)
.PHONY: shell
shell:
docker run --rm -i -t --entrypoint /bin/bash $(IMAGE_NAME)

View File

@@ -1,35 +0,0 @@
Headings add exit matcher for heading
Paragraphs add exit matcher for elements (but it should be sans paragraph)
* foo
* bar
* baz
context tree -> ()
match * foo
context tree -> exit(heading matcher)
check exit
invoke heading matcher
check exit
invoke heading matcher
check exit
invoke heading matcher
adds second heading matcher exit
Ways around this:
- Always parse SOMETHING before checking for exit
- Doesn't always seem possible
- Disable exit matchers during exit check
- Seems like it would break syntax
- Have separate parsers for the beginning of the exit condition (for example, checking for just the headline instead of the full heading parser)
- Won't be possible with paragraphs ending at any other element
- Check exit matchers in parent parser
- Will this work? seems like it would just create larger loops

View File

@@ -0,0 +1,5 @@
[cite:@foo]
[cite/a/b-_/foo:globalprefix;keyprefix @foo keysuffix;globalsuffix]
text before [cite:@bar] text after

View File

@@ -0,0 +1,3 @@
foo \Delta bar
foo \pibar
foo \pi{}bar

View File

@@ -0,0 +1,9 @@
@@latex:
\documentclass[margin,11pt]{res} % default is 10 pt
@@

View File

@@ -0,0 +1,4 @@
@@html:<b>@@This text is only bold for the html exporter@@html:</b>@@
@@latex:
\documentclass[margin,11pt]{res} % default is 10 pt
@@

View File

@@ -0,0 +1,8 @@
[fn:1] This is a regular footnote definition because it is on an unindented line while lacking a definition inside the brackets.
[fn:1] This is a footnote reference because the line is indented
[fn:2:This is a footnote reference since it has the definition inside the brackets. This style is referred to as an "inline footnote".]
[fn::This is a footnote reference since it has the definition inside the brackets. This style is referred to as an "anonymous footnote".]

View File

@@ -0,0 +1,4 @@
call_foo(arguments)
call_bar[header](arguments)
call_baz(arguments)[header]
call_lorem[header](arguments)[another header]

View File

@@ -0,0 +1,2 @@
before src_foo{ipsum} after
src_bar[lorem]{ipsum}

View File

@@ -0,0 +1,7 @@
\begin{itemize}
\item foo \sqrt{x}
\end{itemize}
\begin{itemize}
\item bar \sqrt{y}
\end{itemize} % Need text on this line to prevent it from becoming a LaTeX environment org-mode element

View File

@@ -0,0 +1 @@
tex can have math between dollar signs like $x^2=y$ and $$ x=+\sqrt{y} $$ but also braces and brackets like \( x=2 \) and \[ x=-\sqrt{2} \]

View File

@@ -0,0 +1,4 @@
\begin{itemize}
% this would be a LaTeX comment if this was a LaTeX document
\item Heres some math \sqrt{y}
\end{itemize} % Need text on this line to prevent it from becoming a LaTeX environment org-mode element

View File

@@ -0,0 +1,6 @@
foo\\
bar
lorem\\\
ipsum

View File

@@ -0,0 +1,3 @@
[70%]
[3/5]
[-3/5]

View File

@@ -0,0 +1,7 @@
foo^*
bar_*
baz^{hello *world*}
lorem_{}
ipsum^+,\.a5
dolar_,\.a5
text before foo_7 text afterwards

View File

@@ -0,0 +1 @@
_{foo}

View File

@@ -0,0 +1,3 @@
foo <<bar>> baz
lorem << ipsum >> dolar

View File

@@ -0,0 +1,14 @@
# diary
<%%(foo bar baz)>
# active
<1970-01-01 Thu 8:15rest +1w -1d>
# inactive
[1970-01-01 Thu 8:15rest +1w -1d]
# active date range
<1970-01-01 Thu 8:15rest +1w -1d>--<1970-01-01 Thu 8:15rest +1w -1d>
# active time range
<1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d>
# inactive date range
[1970-01-01 Thu 8:15rest +1w -1d]--[1970-01-01 Thu 8:15rest +1w -1d]
# inactive time range
[1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d]

View File

@@ -3,6 +3,8 @@ use super::util::assert_name;
use crate::parser::sexp::Token; use crate::parser::sexp::Token;
use crate::parser::AngleLink; use crate::parser::AngleLink;
use crate::parser::Bold; use crate::parser::Bold;
use crate::parser::Citation;
use crate::parser::CitationReference;
use crate::parser::Clock; use crate::parser::Clock;
use crate::parser::Code; use crate::parser::Code;
use crate::parser::Comment; use crate::parser::Comment;
@@ -13,16 +15,23 @@ use crate::parser::DocumentElement;
use crate::parser::Drawer; use crate::parser::Drawer;
use crate::parser::DynamicBlock; use crate::parser::DynamicBlock;
use crate::parser::Element; use crate::parser::Element;
use crate::parser::Entity;
use crate::parser::ExampleBlock; use crate::parser::ExampleBlock;
use crate::parser::ExportBlock; use crate::parser::ExportBlock;
use crate::parser::ExportSnippet;
use crate::parser::FixedWidthArea; use crate::parser::FixedWidthArea;
use crate::parser::FootnoteDefinition; use crate::parser::FootnoteDefinition;
use crate::parser::FootnoteReference;
use crate::parser::GreaterBlock; use crate::parser::GreaterBlock;
use crate::parser::Heading; use crate::parser::Heading;
use crate::parser::HorizontalRule; use crate::parser::HorizontalRule;
use crate::parser::InlineBabelCall;
use crate::parser::InlineSourceBlock;
use crate::parser::Italic; use crate::parser::Italic;
use crate::parser::Keyword; use crate::parser::Keyword;
use crate::parser::LatexEnvironment; use crate::parser::LatexEnvironment;
use crate::parser::LatexFragment;
use crate::parser::LineBreak;
use crate::parser::Object; use crate::parser::Object;
use crate::parser::OrgMacro; use crate::parser::OrgMacro;
use crate::parser::Paragraph; use crate::parser::Paragraph;
@@ -37,10 +46,15 @@ use crate::parser::RadioTarget;
use crate::parser::RegularLink; use crate::parser::RegularLink;
use crate::parser::Section; use crate::parser::Section;
use crate::parser::SrcBlock; use crate::parser::SrcBlock;
use crate::parser::StatisticsCookie;
use crate::parser::StrikeThrough; use crate::parser::StrikeThrough;
use crate::parser::Subscript;
use crate::parser::Superscript;
use crate::parser::Table; use crate::parser::Table;
use crate::parser::TableCell; use crate::parser::TableCell;
use crate::parser::TableRow; use crate::parser::TableRow;
use crate::parser::Target;
use crate::parser::Timestamp;
use crate::parser::Underline; use crate::parser::Underline;
use crate::parser::Verbatim; use crate::parser::Verbatim;
use crate::parser::VerseBlock; use crate::parser::VerseBlock;
@@ -154,6 +168,20 @@ fn compare_object<'s>(
Object::PlainLink(obj) => compare_plain_link(source, emacs, obj), Object::PlainLink(obj) => compare_plain_link(source, emacs, obj),
Object::AngleLink(obj) => compare_angle_link(source, emacs, obj), Object::AngleLink(obj) => compare_angle_link(source, emacs, obj),
Object::OrgMacro(obj) => compare_org_macro(source, emacs, obj), Object::OrgMacro(obj) => compare_org_macro(source, emacs, obj),
Object::Entity(obj) => compare_entity(source, emacs, obj),
Object::LatexFragment(obj) => compare_latex_fragment(source, emacs, obj),
Object::ExportSnippet(obj) => compare_export_snippet(source, emacs, obj),
Object::FootnoteReference(obj) => compare_footnote_reference(source, emacs, obj),
Object::Citation(obj) => compare_citation(source, emacs, obj),
Object::CitationReference(obj) => compare_citation_reference(source, emacs, obj),
Object::InlineBabelCall(obj) => compare_inline_babel_call(source, emacs, obj),
Object::InlineSourceBlock(obj) => compare_inline_source_block(source, emacs, obj),
Object::LineBreak(obj) => compare_line_break(source, emacs, obj),
Object::Target(obj) => compare_target(source, emacs, obj),
Object::StatisticsCookie(obj) => compare_statistics_cookie(source, emacs, obj),
Object::Subscript(obj) => compare_subscript(source, emacs, obj),
Object::Superscript(obj) => compare_superscript(source, emacs, obj),
Object::Timestamp(obj) => compare_timestamp(source, emacs, obj),
} }
} }
@@ -1238,3 +1266,325 @@ fn compare_org_macro<'s>(
children: Vec::new(), children: Vec::new(),
}) })
} }
fn compare_entity<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s Entity<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "entity";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}
fn compare_latex_fragment<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s LatexFragment<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "latex-fragment";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}
fn compare_export_snippet<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s ExportSnippet<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "export-snippet";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}
fn compare_footnote_reference<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s FootnoteReference<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "footnote-reference";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}
fn compare_citation<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s Citation<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "citation";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}
fn compare_citation_reference<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s CitationReference<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "citation-reference";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}
fn compare_inline_babel_call<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s InlineBabelCall<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "inline-babel-call";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}
fn compare_inline_source_block<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s InlineSourceBlock<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "inline-src-block";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}
fn compare_line_break<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s LineBreak<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "line-break";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}
fn compare_target<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s Target<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "target";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}
fn compare_statistics_cookie<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s StatisticsCookie<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "statistics-cookie";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}
fn compare_subscript<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s Subscript<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "subscript";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}
fn compare_superscript<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s Superscript<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "superscript";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}
fn compare_timestamp<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s Timestamp<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let emacs_name = "timestamp";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message: None,
children: Vec::new(),
})
}

View File

@@ -1,34 +1,70 @@
use tracing_subscriber::layer::SubscriberExt; #[cfg(feature = "tracing")]
use opentelemetry_otlp::WithExportConfig;
#[cfg(feature = "tracing")]
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
#[cfg(feature = "tracing")]
use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::util::SubscriberInitExt;
// use tracing_subscriber::EnvFilter;
const SERVICE_NAME: &'static str = "organic";
// Despite the obvious verbosity that fully-qualifying everything causes, in these functions I am fully-qualifying everything relating to tracing. This is because the tracing feature involves multiple libraries working together and so I think it is beneficial to see which libraries contribute which bits.
#[cfg(feature = "tracing")]
pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> { pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
// let env_filter = EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new("warn")); // by default it will hit http://localhost:4317 with a gRPC payload
// TODO: I think the endpoint can be controlled by the OTEL_EXPORTER_OTLP_TRACES_ENDPOINT env variable instead of hard-coded into this code base. Regardless, I am the only developer right now so I am not too concerned.
let exporter = opentelemetry_otlp::new_exporter()
.tonic()
.with_endpoint("http://localhost:4317/v1/traces");
// let stdout = tracing_subscriber::fmt::Layer::new() let tracer = opentelemetry_otlp::new_pipeline()
// .pretty() .tracing()
// .with_file(true) .with_exporter(exporter)
// .with_line_number(true) .with_trace_config(opentelemetry::sdk::trace::config().with_resource(
// .with_thread_ids(false) opentelemetry::sdk::Resource::new(vec![opentelemetry::KeyValue::new(
// .with_target(false); opentelemetry_semantic_conventions::resource::SERVICE_NAME,
SERVICE_NAME.to_string(),
)]),
))
// If I do install_batch then 1K+ spans will get orphaned off into their own trace and I get the error message "OpenTelemetry trace error occurred. cannot send message to batch processor as the channel is closed"
//
// If I do install_simple then it only creates 1 trace (which is good!) but my console gets spammed with this concerning log message that makes me think it might be dropping the extra spans on the floor: "OpenTelemetry trace error occurred. Exporter otlp encountered the following error(s): the grpc server returns error (Unknown error): , detailed error message: Service was not ready: transport error"
//
// I suspect it is related to this bug: https://github.com/open-telemetry/opentelemetry-rust/issues/888
//
// .install_simple()
.install_batch(opentelemetry::runtime::Tokio)
.expect("Error: Failed to initialize the tracer.");
opentelemetry::global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new()); let subscriber = tracing_subscriber::Registry::default();
let tracer = opentelemetry_jaeger::new_pipeline() let level_filter_layer = tracing_subscriber::EnvFilter::try_from_default_env()
.with_service_name("toy_language") .unwrap_or(tracing_subscriber::EnvFilter::new("WARN"));
.install_simple()?; let tracing_layer = tracing_opentelemetry::layer().with_tracer(tracer);
let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer); opentelemetry::global::set_text_map_propagator(
opentelemetry::sdk::propagation::TraceContextPropagator::new(),
);
tracing_subscriber::registry() subscriber
// .with(env_filter) .with(level_filter_layer)
.with(opentelemetry) .with(tracing_layer)
// .with(stdout)
.try_init()?; .try_init()?;
Ok(()) Ok(())
} }
#[cfg(feature = "tracing")]
pub fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> { pub fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
opentelemetry::global::shutdown_tracer_provider(); opentelemetry::global::shutdown_tracer_provider();
Ok(()) Ok(())
} }
#[cfg(not(feature = "tracing"))]
pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
#[cfg(not(feature = "tracing"))]
pub fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

View File

@@ -1,17 +1,74 @@
#![feature(round_char_boundary)] #![feature(round_char_boundary)]
use std::path::Path;
#[cfg(feature = "compare")]
use ::organic::parser::document; use ::organic::parser::document;
#[cfg(feature = "compare")]
use organic::compare_document;
#[cfg(feature = "compare")]
use organic::emacs_parse_org_document;
#[cfg(feature = "compare")]
use organic::parser::sexp::sexp_with_padding;
use crate::init_tracing::init_telemetry; use crate::init_tracing::init_telemetry;
use crate::init_tracing::shutdown_telemetry; use crate::init_tracing::shutdown_telemetry;
mod init_tracing; mod init_tracing;
const TEST_DOC: &'static str = include_str!("../toy_language.txt"); #[cfg(not(feature = "tracing"))]
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
main_body()
}
#[cfg(feature = "tracing")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
let result = rt.block_on(async { main_body() });
result
}
fn main_body() -> Result<(), Box<dyn std::error::Error>> {
init_telemetry()?; init_telemetry()?;
let parsed = document(TEST_DOC); run_compare(
println!("{}\n\n\n", TEST_DOC); std::env::args()
println!("{:#?}", parsed); .nth(1)
.expect("Pass a single file into this script."),
)?;
shutdown_telemetry()?; shutdown_telemetry()?;
Ok(()) Ok(())
} }
#[cfg(feature = "compare")]
fn run_compare<P: AsRef<Path>>(todo_org_path: P) -> Result<(), Box<dyn std::error::Error>> {
let org_contents = std::fs::read_to_string(todo_org_path.as_ref()).expect("Read org file.");
let (remaining, rust_parsed) = document(org_contents.as_str()).expect("Org Parse failure");
let org_sexp =
emacs_parse_org_document(todo_org_path.as_ref()).expect("Use emacs to parse org file.");
let (_remaining, parsed_sexp) =
sexp_with_padding(org_sexp.as_str()).expect("Sexp Parse failure");
println!("{}\n\n\n", org_contents.as_str());
println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
// We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result =
compare_document(&parsed_sexp, &rust_parsed).expect("Compare parsed documents.");
diff_result
.print()
.expect("Print document parse tree diff.");
if diff_result.is_bad() {
Err("Diff results do not match.")?;
}
if remaining != "" {
Err(format!("There was unparsed text remaining: {}", remaining))?;
}
Ok(())
}
#[cfg(not(feature = "compare"))]
fn run_compare<P: AsRef<Path>>(_todo_org_path: P) -> Result<(), Box<dyn std::error::Error>> {
println!("This program was built with compare disabled. Doing nothing.");
Ok(())
}

View File

@@ -15,7 +15,7 @@ use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::AngleLink; use crate::parser::AngleLink;
#[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: &'s str) -> Res<&'s str, AngleLink<'s>> {
let (remaining, _) = tag("<")(input)?; let (remaining, _) = tag("<")(input)?;
let (remaining, proto) = protocol(context, remaining)?; let (remaining, proto) = protocol(context, remaining)?;
@@ -33,7 +33,7 @@ pub fn angle_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
)) ))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
@@ -47,7 +47,7 @@ fn path_angle<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
Ok((remaining, path)) Ok((remaining, path))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
tag(">")(input) tag(">")(input)
} }

221
src/parser/citation.rs Normal file
View File

@@ -0,0 +1,221 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many1;
use nom::multi::many_till;
use nom::multi::separated_list1;
use nom::sequence::tuple;
use super::Context;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::citation_reference::citation_reference;
use crate::parser::citation_reference::citation_reference_key;
use crate::parser::citation_reference::get_bracket_depth;
use crate::parser::exiting::ExitClass;
use crate::parser::object::Citation;
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::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::Object;
#[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>> {
// 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, _) = opt(citestyle)(remaining)?;
let (remaining, _) = tag(":")(remaining)?;
let (remaining, _prefix) = opt(parser_with_context!(global_prefix)(context))(remaining)?;
let (remaining, _references) =
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
let (remaining, _suffix) = opt(tuple((
tag(";"),
parser_with_context!(global_suffix)(context),
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Citation { source }))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn citestyle<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
let (remaining, _) = tuple((tag("/"), style))(input)?;
let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn style<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_-".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn variant<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_-/".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_prefix<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, 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 parser_context = context
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
position: input,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &global_prefix_end,
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
parser_with_context!(standard_set_object)(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context),
),
|(children, _exit_contents)| !children.is_empty(),
)(input)?;
let (remaining, _) = tag(";")(remaining)?;
Ok((remaining, children))
}
#[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> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a citation.");
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 citation global prefix 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;
}
}
alt((
tag(";"),
recognize(parser_with_context!(citation_reference_key)(context)),
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_suffix<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, 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 parser_context = context
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
position: input,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &global_suffix_end,
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
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 global_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a citation.");
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 citation global suffix 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;
}
}
alt((
tag(";"),
recognize(parser_with_context!(citation_reference_key)(context)),
))(input)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::element_parser::element;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::Source;
#[test]
fn citation_simple() {
let input = "[cite:@foo]";
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_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 first_paragraph = match first_paragraph {
crate::parser::Element::Paragraph(paragraph) => paragraph,
_ => panic!("Should be a paragraph!"),
};
assert_eq!(remaining, "");
assert_eq!(first_paragraph.get_source(), "[cite:@foo]");
assert_eq!(first_paragraph.children.len(), 1);
assert_eq!(
first_paragraph
.children
.get(0)
.expect("Len already asserted to be 1"),
&Object::Citation(Citation {
source: "[cite:@foo]"
})
);
}
}

View File

@@ -0,0 +1,175 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::Context;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::object::CitationReference;
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::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::parser::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation_reference<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, CitationReference<'s>> {
let (remaining, _prefix) = opt(parser_with_context!(key_prefix)(context))(input)?;
let (remaining, _key) = parser_with_context!(citation_reference_key)(context)(remaining)?;
let (remaining, _suffix) = opt(parser_with_context!(key_suffix)(context))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, CitationReference { source }))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation_reference_key<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
let (remaining, source) = recognize(tuple((
tag("@"),
many1(verify(
preceded(
not(parser_with_context!(exit_matcher_parser)(context)),
anychar,
),
|c| {
WORD_CONSTITUENT_CHARACTERS.contains(*c) || "-.:?~`'/*@+|(){}<>&_^$#%~".contains(*c)
},
)),
)))(input)?;
Ok((remaining, source))
}
#[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>>> {
// 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 parser_context = context
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
position: input,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &key_prefix_end,
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
parser_with_context!(minimal_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 key_suffix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, 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 parser_context = context
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
position: input,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &key_suffix_end,
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
parser_with_context!(minimal_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"))]
pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r CitationBracket<'s>> {
for node in context.iter() {
match node.get_data() {
ContextElement::CitationBracket(depth) => return Some(depth),
_ => {}
}
}
None
}
#[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> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a citation reference.");
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 citation reference key prefix 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;
}
}
alt((
tag(";"),
recognize(parser_with_context!(citation_reference_key)(context)),
))(input)
}
#[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> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a citation reference.");
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 citation reference key prefix 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;
}
}
tag(";")(input)
}

View File

@@ -18,7 +18,7 @@ use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::Clock; use crate::parser::Clock;
#[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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Clock<'s>> {
start_of_line(context, input)?; start_of_line(context, input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
@@ -34,7 +34,7 @@ pub fn clock<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, C
Ok((remaining, Clock { source })) Ok((remaining, Clock { source }))
} }
#[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: &'s str,
@@ -56,7 +56,7 @@ fn inactive_timestamp_range_duration<'r, 's>(
)))(input) )))(input)
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
recognize(tuple(( recognize(tuple((
tag("["), tag("["),

View File

@@ -23,7 +23,7 @@ use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::Comment; use crate::parser::Comment;
#[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: &'s str) -> Res<&'s str, 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(
@@ -41,7 +41,7 @@ pub fn comment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
Ok((remaining, Comment { source })) Ok((remaining, Comment { source }))
} }
#[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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
start_of_line(context, input)?; start_of_line(context, input)?;
let (remaining, _indent) = space0(input)?; let (remaining, _indent) = space0(input)?;

View File

@@ -13,7 +13,7 @@ use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::DiarySexp; use crate::parser::DiarySexp;
#[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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, DiarySexp<'s>> {
start_of_line(context, input)?; start_of_line(context, input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;

View File

@@ -18,6 +18,7 @@ use super::element::Element;
use super::object::Object; use super::object::Object;
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::Token; use super::token::Token;
use super::util::exit_matcher_parser; use super::util::exit_matcher_parser;
use super::util::get_consumed; use super::util::get_consumed;
@@ -90,14 +91,41 @@ impl<'s> Source<'s> for Heading<'s> {
} }
} }
#[tracing::instrument(ret, level = "debug")] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
#[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 document_context =
initial_context.with_additional_node(ContextElement::DocumentRoot(input)); initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let zeroth_section_matcher = parser_with_context!(zeroth_section)(&document_context); let (remaining, document) = _document(&document_context, input)?;
let heading_matcher = parser_with_context!(heading)(&document_context); {
// 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
.iter_tokens()
.filter_map(|tkn| match tkn {
Token::Object(obj) => Some(obj),
_ => None,
})
.filter_map(|obj| match obj {
Object::RadioTarget(rt) => Some(rt),
_ => None,
})
.map(|rt| &rt.children)
.collect();
if !all_radio_targets.is_empty() {
let document_context = document_context
.with_additional_node(ContextElement::RadioTarget(all_radio_targets));
let (remaining, document) = _document(&document_context, input)?;
return Ok((remaining, document));
}
}
Ok((remaining, document))
}
#[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>> {
let zeroth_section_matcher = parser_with_context!(zeroth_section)(context);
let heading_matcher = parser_with_context!(heading)(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)?;
@@ -112,7 +140,7 @@ pub fn document(input: &str) -> Res<&str, Document> {
)) ))
} }
#[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: &'s str) -> Res<&'s str, 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
@@ -157,7 +185,7 @@ fn zeroth_section<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
Ok((remaining, Section { source, children })) Ok((remaining, Section { source, children }))
} }
#[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: &'s str) -> Res<&'s str, 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
@@ -198,13 +226,13 @@ fn section<'r, 's>(context: Context<'r, 's>, mut input: &'s str) -> Res<&'s str,
Ok((remaining, Section { source, children })) Ok((remaining, Section { source, children }))
} }
#[tracing::instrument(ret, level = "debug")] #[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> { 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); let headline_matcher = parser_with_context!(headline)(context);
recognize(headline_matcher)(input) recognize(headline_matcher)(input)
} }
#[tracing::instrument(ret, level = "debug")] #[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>> { fn heading<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?; not(|i| context.check_exit_matcher(i))(input)?;
let (remaining, (star_count, _ws, title)) = headline(context, input)?; let (remaining, (star_count, _ws, title)) = headline(context, input)?;
@@ -229,7 +257,7 @@ fn heading<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Hea
)) ))
} }
#[tracing::instrument(ret, level = "debug")] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'r, 's>( fn headline<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: &'s str,
@@ -252,35 +280,13 @@ fn headline<'r, 's>(
Ok((remaining, (star_count, ws, title))) Ok((remaining, (star_count, ws, title)))
} }
#[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 headline_end<'r, 's>(_context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
line_ending(input) line_ending(input)
} }
impl<'s> Document<'s> { impl<'s> Document<'s> {
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> { pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
self.zeroth_section AllTokensIterator::new(Token::Document(self))
.iter()
.map(Token::Section)
.chain(self.children.iter().map(Token::Heading))
}
}
impl<'s> Heading<'s> {
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
self.title.iter().map(Token::Object).chain(self.children.iter().map(
|de| {
match de {
DocumentElement::Heading(obj) => Token::Heading(obj),
DocumentElement::Section(obj) => Token::Section(obj),
}
}
))
}
}
impl<'s> Section<'s> {
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
self.children.iter().map(Token::Element)
} }
} }

View File

@@ -30,7 +30,7 @@ use crate::parser::Drawer;
use crate::parser::Element; use crate::parser::Element;
use crate::parser::Paragraph; use crate::parser::Paragraph;
#[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: &'s str) -> Res<&'s str, 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(
@@ -88,12 +88,12 @@ pub fn drawer<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
)) ))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input) take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input)
} }
#[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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
start_of_line(context, input)?; start_of_line(context, input)?;
recognize(tuple(( recognize(tuple((

View File

@@ -30,7 +30,7 @@ use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::Element; use crate::parser::Element;
#[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: &'s str,
@@ -94,17 +94,17 @@ pub fn dynamic_block<'r, 's>(
)) ))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
is_not(" \t\r\n")(input) is_not(" \t\r\n")(input)
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
is_not("\r\n")(input) is_not("\r\n")(input)
} }
#[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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
start_of_line(context, input)?; start_of_line(context, input)?;
let (remaining, source) = recognize(tuple(( let (remaining, source) = recognize(tuple((

View File

@@ -76,7 +76,7 @@ impl<'s> Source<'s> for Element<'s> {
} }
impl<'s> SetSource<'s> for Element<'s> { impl<'s> SetSource<'s> for Element<'s> {
#[tracing::instrument(ret, level = "debug")] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn set_source(&mut self, source: &'s str) { fn set_source(&mut self, source: &'s str) {
match self { match self {
Element::Paragraph(obj) => obj.source = source, Element::Paragraph(obj) => obj.source = source,

View File

@@ -35,7 +35,7 @@ pub fn element(
move |context: Context, input: &str| _element(context, input, can_be_paragraph) move |context: Context, input: &str| _element(context, input, can_be_paragraph)
} }
#[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: &'s str,

50
src/parser/entity.rs Normal file
View File

@@ -0,0 +1,50 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::satisfy;
use nom::character::complete::space0;
use nom::combinator::eof;
use nom::combinator::peek;
use nom::combinator::recognize;
use super::Context;
use crate::error::Res;
use crate::parser::object::Entity;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::get_consumed;
#[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>> {
let (remaining, _) = tag("\\")(input)?;
let (remaining, entity_name) = name(context, remaining)?;
let (remaining, _) = alt((
tag("{}"),
peek(recognize(parser_with_context!(entity_end)(context))),
))(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Entity {
source,
entity_name,
},
))
}
#[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> {
// TODO: This should be defined by org-entities and optionally org-entities-user
// TODO: Add the rest of the entities, this is a very incomplete list
let (remaining, proto) = alt((alt((tag_no_case("delta"), tag_no_case("pi"))),))(input)?;
Ok((remaining, proto))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn entity_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
let (remaining, _) = alt((eof, recognize(satisfy(|c| !c.is_alphabetic()))))(input)?;
Ok((remaining, ()))
}

View File

@@ -0,0 +1,61 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::Context;
use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::ExportSnippet;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn export_snippet<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, ExportSnippet<'s>> {
let (remaining, _) = tag("@@")(input)?;
let (remaining, backend_name) = backend(context, remaining)?;
let (remaining, backend_contents) =
opt(tuple((tag(":"), parser_with_context!(contents)(context))))(remaining)?;
let (remaining, _) = tag("@@")(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
ExportSnippet {
source,
backend: backend_name,
contents: backend_contents.map(|(_colon, backend_contents)| backend_contents),
},
))
}
#[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> {
let (remaining, backend_name) =
recognize(many1(verify(anychar, |c| c.is_alphanumeric() || *c == '-')))(input)?;
Ok((remaining, backend_name))
}
#[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> {
let (remaining, source) = recognize(verify(
many_till(
anychar,
peek(alt((
parser_with_context!(exit_matcher_parser)(context),
tag("@@"),
))),
),
|(children, _exit_contents)| !children.is_empty(),
))(input)?;
Ok((remaining, source))
}

View File

@@ -19,7 +19,7 @@ use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::FixedWidthArea; use crate::parser::FixedWidthArea;
#[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: &'s str,
@@ -34,7 +34,7 @@ pub fn fixed_width_area<'r, 's>(
Ok((remaining, FixedWidthArea { source })) Ok((remaining, FixedWidthArea { source }))
} }
#[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: &'s str,

View File

@@ -28,7 +28,7 @@ use crate::parser::util::immediate_in_section;
use crate::parser::util::maybe_consume_trailing_whitespace; use crate::parser::util::maybe_consume_trailing_whitespace;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
#[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: &'s str,
@@ -66,15 +66,15 @@ pub fn footnote_definition<'r, 's>(
)) ))
} }
#[tracing::instrument(ret, level = "debug")] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn label<'s>(input: &'s str) -> Res<&'s str, &'s str> { pub fn label<'s>(input: &'s str) -> Res<&'s str, &'s str> {
alt(( alt((
digit1, digit1,
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)), take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)),
))(input) ))(input)
} }
#[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: &'s str,

View File

@@ -0,0 +1,184 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::space0;
use nom::combinator::verify;
use nom::multi::many_till;
use super::parser_context::ContextElement;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::footnote_definition::label;
use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_context::FootnoteReferenceDefinition;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::FootnoteReference;
use crate::parser::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn footnote_reference<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, FootnoteReference<'s>> {
alt((
parser_with_context!(anonymous_footnote)(context),
parser_with_context!(footnote_reference_only)(context),
parser_with_context!(inline_footnote)(context),
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn anonymous_footnote<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn::")(input)?;
let parser_context = context
.with_additional_node(ContextElement::FootnoteReferenceDefinition(
FootnoteReferenceDefinition {
position: remaining,
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &footnote_definition_end,
}));
// 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(
many_till(
parser_with_context!(standard_set_object)(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context),
),
|(children, _exit_contents)| !children.is_empty(),
)(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
FootnoteReference {
source,
label: None,
definition: children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inline_footnote<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn:")(input)?;
let (remaining, label_contents) = label(remaining)?;
let (remaining, _) = tag(":")(remaining)?;
let parser_context = context
.with_additional_node(ContextElement::FootnoteReferenceDefinition(
FootnoteReferenceDefinition {
position: remaining,
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &footnote_definition_end,
}));
// 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(
many_till(
parser_with_context!(standard_set_object)(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context),
),
|(children, _exit_contents)| !children.is_empty(),
)(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
FootnoteReference {
source,
label: Some(label_contents),
definition: children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_reference_only<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn:")(input)?;
let (remaining, label_contents) = label(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
FootnoteReference {
source,
label: Some(label_contents),
definition: Vec::with_capacity(0),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn definition<'s>(input: &'s str) -> Res<&'s str, Vec<Object<'s>>> {
Ok((input, vec![]))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_definition_end<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a footnote definition.");
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 {
// Its impossible for the next character to end the footnote reference definition if we're any amount of brackets deep
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoFootnoteReferenceDefinitionEnd",
))));
}
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

@@ -30,7 +30,7 @@ use crate::parser::util::start_of_line;
use crate::parser::Element; use crate::parser::Element;
use crate::parser::Paragraph; use crate::parser::Paragraph;
#[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: &'s str,
@@ -107,17 +107,17 @@ pub fn greater_block<'r, 's>(
)) ))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
is_not(" \t\r\n")(input) is_not(" \t\r\n")(input)
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
is_not("\r\n")(input) is_not("\r\n")(input)
} }
#[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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
start_of_line(context, input)?; start_of_line(context, input)?;
let current_name: &str = get_context_greater_block_name(context).ok_or(nom::Err::Error( let current_name: &str = get_context_greater_block_name(context).ok_or(nom::Err::Error(

View File

@@ -13,7 +13,7 @@ use crate::error::Res;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::HorizontalRule; use crate::parser::HorizontalRule;
#[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: &'s str,

View File

@@ -0,0 +1,157 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till;
use super::Context;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::BabelHeaderBracket;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::InlineBabelCall;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn inline_babel_call<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, InlineBabelCall<'s>> {
let (remaining, _) = tag_no_case("call_")(input)?;
let (remaining, _name) = name(context, remaining)?;
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _argument) = argument(context, remaining)?;
let (remaining, _header2) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, InlineBabelCall { source }))
}
#[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> {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &name_end,
}));
let (remaining, name) = recognize(many_till(
verify(anychar, |c| !(c.is_whitespace() || "[]()".contains(*c))),
parser_with_context!(exit_matcher_parser)(&parser_context),
))(input)?;
Ok((remaining, name))
}
#[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> {
recognize(one_of("[("))(input)
}
#[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> {
let (remaining, _) = tag("[")(input)?;
let parser_context = context
.with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket {
position: remaining,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &header_end,
}));
let (remaining, name) = recognize(many_till(
anychar,
parser_with_context!(exit_matcher_parser)(&parser_context),
))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
Ok((remaining, name))
}
#[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> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside an inline babel call header.");
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 babel call header bracket depth.")
}
')' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
}
alt((tag("]"), line_ending))(input)
}
#[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> {
let (remaining, _) = tag("(")(input)?;
let parser_context = context
.with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket {
position: remaining,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &argument_end,
}));
let (remaining, name) = recognize(many_till(
anychar,
parser_with_context!(exit_matcher_parser)(&parser_context),
))(remaining)?;
let (remaining, _) = tag(")")(remaining)?;
Ok((remaining, name))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn argument_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside an inline babel call argument.");
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 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"))]
pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r BabelHeaderBracket<'s>> {
for node in context.iter() {
match node.get_data() {
ContextElement::BabelHeaderBracket(depth) => return Some(depth),
_ => {}
}
}
None
}

View File

@@ -0,0 +1,200 @@
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till;
#[cfg(feature = "tracing")]
use tracing::span;
use super::Context;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_context::InlineSourceBlockBracket;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::InlineSourceBlock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn inline_source_block<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, InlineSourceBlock<'s>> {
let (remaining, _) = tag_no_case("src_")(input)?;
let (remaining, _) = lang(context, remaining)?;
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _body) = body(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, InlineSourceBlock { source }))
}
#[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> {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &lang_end,
}));
let (remaining, lang) = recognize(many_till(
verify(anychar, |c| !(c.is_whitespace() || "[{".contains(*c))),
parser_with_context!(exit_matcher_parser)(&parser_context),
))(input)?;
Ok((remaining, lang))
}
#[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> {
recognize(one_of("[{"))(input)
}
#[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> {
let (remaining, _) = tag("[")(input)?;
let parser_context = context
.with_additional_node(ContextElement::InlineSourceBlockBracket(
InlineSourceBlockBracket {
position: remaining,
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &header_end,
}));
let (remaining, header_contents) = recognize(many_till(
anychar,
parser_with_context!(exit_matcher_parser)(&parser_context),
))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
Ok((remaining, header_contents))
}
#[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> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside an inline source block header.");
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 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"))]
fn body<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
let (remaining, _) = tag("{")(input)?;
let parser_context = context
.with_additional_node(ContextElement::InlineSourceBlockBracket(
InlineSourceBlockBracket {
position: remaining,
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &body_end,
}));
let (remaining, body_contents) = recognize(many_till(
anychar,
parser_with_context!(exit_matcher_parser)(&parser_context),
))(remaining)?;
let (remaining, _) = {
#[cfg(feature = "tracing")]
let span = span!(
tracing::Level::DEBUG,
"outside end body",
remaining = remaining
);
#[cfg(feature = "tracing")]
let _enter = span.enter();
tag("}")(remaining)?
};
Ok((remaining, body_contents))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn body_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside an inline source block body.");
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"))]
pub fn get_bracket_depth<'r, 's>(
context: Context<'r, 's>,
) -> Option<&'r InlineSourceBlockBracket<'s>> {
for node in context.iter() {
match node.get_data() {
ContextElement::InlineSourceBlockBracket(depth) => return Some(depth),
_ => {}
}
}
None
}

View File

@@ -16,7 +16,7 @@ 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;
#[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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Keyword<'s>> {
start_of_line(context, input)?; start_of_line(context, 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.

View File

@@ -2,10 +2,13 @@ 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::bytes::complete::take_while1; 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::combinator::eof; use nom::combinator::eof;
use nom::combinator::map; use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::util::get_consumed; use super::util::get_consumed;
@@ -15,11 +18,11 @@ 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_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::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::LatexEnvironment; use crate::parser::LatexEnvironment;
#[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: &'s str,
@@ -41,20 +44,38 @@ pub fn latex_environment<'r, 's>(
exit_matcher: &latex_environment_end_specialized, exit_matcher: &latex_environment_end_specialized,
})); }));
let (remaining, _contents) = map(parser_with_context!(plain_text)(&parser_context), |obj| { let (remaining, _contents) = contents(&latex_environment_end_specialized, context, remaining)?;
obj.source
})(remaining)?;
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 }))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
take_while1(|c: char| c.is_alphanumeric() || c == '*')(input) take_while1(|c: char| c.is_alphanumeric() || c == '*')(input)
} }
#[cfg_attr(
feature = "tracing",
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>>(
end_matcher: F,
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
let (remaining, source) = recognize(many_till(
anychar,
peek(alt((
parser_with_context!(exit_matcher_parser)(context),
parser_with_context!(end_matcher)(context),
))),
))(input)?;
Ok((remaining, source))
}
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>, &'s str) -> Res<&'s str, &'s str> {
@@ -64,7 +85,7 @@ fn latex_environment_end(
} }
} }
#[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: &'s str,

View File

@@ -0,0 +1,230 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::alpha1;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::none_of;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::get_one_before;
use crate::parser::LatexFragment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn latex_fragment<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, LatexFragment<'s>> {
let (remaining, _) = alt((
parser_with_context!(raw_latex_fragment)(context),
parser_with_context!(escaped_parenthesis_fragment)(context),
parser_with_context!(escaped_bracket_fragment)(context),
parser_with_context!(double_dollar_fragment)(context),
parser_with_context!(dollar_char_fragment)(context),
parser_with_context!(bordered_dollar_fragment)(context),
))(input)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, LatexFragment { source }))
}
#[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> {
let (remaining, _) = tag("\\")(input)?;
let (remaining, _) = name(context, remaining)?;
let (remaining, _) = many0(parser_with_context!(brackets)(context))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[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> {
alpha1(input)
}
#[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> {
let (remaining, body) = alt((
recognize(tuple((
tag("["),
many_till(
anychar,
peek(alt((
parser_with_context!(exit_matcher_parser)(context),
alt((recognize(one_of("{}[]")), line_ending)),
))),
),
tag("]"),
))),
recognize(tuple((
tag("{"),
many_till(
anychar,
peek(alt((
parser_with_context!(exit_matcher_parser)(context),
alt((recognize(one_of("{}")), line_ending)),
))),
),
tag("}"),
))),
))(input)?;
Ok((remaining, body))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn escaped_parenthesis_fragment<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
let (remaining, _) = tag("\\(")(input)?;
let (remaining, _) = recognize(many_till(
anychar,
peek(alt((
parser_with_context!(exit_matcher_parser)(context),
tag("\\)"),
))),
))(remaining)?;
let (remaining, _) = tag("\\)")(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn escaped_bracket_fragment<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
let (remaining, _) = tag("\\[")(input)?;
let (remaining, _) = recognize(many_till(
anychar,
peek(alt((
parser_with_context!(exit_matcher_parser)(context),
tag("\\]"),
))),
))(remaining)?;
let (remaining, _) = tag("\\]")(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn double_dollar_fragment<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
// 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, _) = recognize(many_till(
anychar,
peek(alt((
parser_with_context!(exit_matcher_parser)(context),
tag("$"),
))),
))(remaining)?;
let (remaining, _) = tag("$$")(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[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> {
let (_, _) = pre(context, input)?;
let (remaining, _) = tag("$")(input)?;
let (remaining, _) = verify(none_of(".,?;\""), |c| !c.is_whitespace())(remaining)?;
let (remaining, _) = tag("$")(remaining)?;
let (_, _) = peek(parser_with_context!(post)(context))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn pre<'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();
if let Some('$') = preceding_character {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for dollar char fragment.",
))));
}
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn post<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
// TODO: What about eof? Test to find out.
// TODO: Figure out which punctuation characters should be included.
let (remaining, _) = alt((recognize(one_of(" \t-.,;:!?'\"")), line_ending))(input)?;
Ok((remaining, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn bordered_dollar_fragment<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
let (_, _) = pre(context, 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.
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, _) = verify(
recognize(many_till(
anychar,
peek(alt((
parser_with_context!(exit_matcher_parser)(context),
tag("$"),
))),
)),
|body: &str| body.lines().take(4).count() <= 3,
)(remaining)?;
let (_, _) = peek(parser_with_context!(close_border)(context))(remaining)?;
let (remaining, _) = tag("$")(remaining)?;
let (_, _) = peek(parser_with_context!(post)(context))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[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> {
recognize(verify(none_of(".,;$"), |c| !c.is_whitespace()))(input)
}
#[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, ()> {
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 {
Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())),
_ => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for dollar char fragment.",
))));
}
}
}

View File

@@ -31,7 +31,7 @@ use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::util::text_until_exit; use crate::parser::util::text_until_exit;
#[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: &'s str,
@@ -80,7 +80,7 @@ pub fn verse_block<'r, 's>(
)) ))
} }
#[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: &'s str,
@@ -116,7 +116,7 @@ pub fn comment_block<'r, 's>(
)) ))
} }
#[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: &'s str,
@@ -152,7 +152,7 @@ pub fn example_block<'r, 's>(
)) ))
} }
#[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: &'s str,
@@ -189,7 +189,7 @@ pub fn export_block<'r, 's>(
)) ))
} }
#[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: &'s str) -> Res<&'s str, 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.
@@ -223,12 +223,12 @@ pub fn src_block<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
)) ))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
is_not(" \t\r\n")(input) is_not(" \t\r\n")(input)
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
is_not("\r\n")(input) is_not("\r\n")(input)
} }

59
src/parser/line_break.rs Normal file
View File

@@ -0,0 +1,59 @@
use nom::bytes::complete::tag;
use nom::character::complete::line_ending;
use nom::character::complete::one_of;
use nom::combinator::recognize;
use nom::multi::many0;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
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;
#[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>> {
let (remaining, _) = pre(context, input)?;
let (remaining, _) = tag(r#"\\"#)(remaining)?;
let (remaining, _) = recognize(many0(one_of(" \t")))(remaining)?;
let (remaining, _) = line_ending(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, LineBreak { source }))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn pre<'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 {
// If None, we are at the start of the file
None | Some('\\') => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for line break.",
))));
}
_ => {}
};
let current_line = get_current_line_before_position(document_root, input);
match current_line {
Some(line) => {
let is_non_empty_line = line.chars().any(|c| !c.is_whitespace());
if !is_non_empty_line {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre line for line break.",
))));
}
}
None => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No preceding line for line break.",
))));
}
}
Ok((input, ()))
}

View File

@@ -1,4 +1,6 @@
mod angle_link; mod angle_link;
mod citation;
mod citation_reference;
mod clock; mod clock;
mod comment; mod comment;
mod diary_sexp; mod diary_sexp;
@@ -7,16 +9,23 @@ mod drawer;
mod dynamic_block; mod dynamic_block;
mod element; mod element;
mod element_parser; mod element_parser;
mod entity;
mod exiting; mod exiting;
mod export_snippet;
mod fixed_width_area; mod fixed_width_area;
mod footnote_definition; mod footnote_definition;
mod footnote_reference;
mod greater_block; mod greater_block;
mod greater_element; mod greater_element;
mod horizontal_rule; mod horizontal_rule;
mod inline_babel_call;
mod inline_source_block;
mod keyword; mod keyword;
mod latex_environment; mod latex_environment;
mod latex_fragment;
mod lesser_block; mod lesser_block;
mod lesser_element; mod lesser_element;
mod line_break;
mod list; mod list;
mod object; mod object;
mod object_parser; mod object_parser;
@@ -33,8 +42,12 @@ mod radio_link;
mod regular_link; mod regular_link;
pub mod sexp; pub mod sexp;
mod source; mod source;
mod statistics_cookie;
mod subscript_and_superscript;
mod table; mod table;
mod target;
mod text_markup; mod text_markup;
mod timestamp;
mod token; mod token;
mod util; mod util;
pub use document::document; pub use document::document;
@@ -69,8 +82,17 @@ pub use lesser_element::TableCell;
pub use lesser_element::VerseBlock; pub use lesser_element::VerseBlock;
pub use object::AngleLink; pub use object::AngleLink;
pub use object::Bold; pub use object::Bold;
pub use object::Citation;
pub use object::CitationReference;
pub use object::Code; pub use object::Code;
pub use object::Entity;
pub use object::ExportSnippet;
pub use object::FootnoteReference;
pub use object::InlineBabelCall;
pub use object::InlineSourceBlock;
pub use object::Italic; pub use object::Italic;
pub use object::LatexFragment;
pub use object::LineBreak;
pub use object::Object; pub use object::Object;
pub use object::OrgMacro; pub use object::OrgMacro;
pub use object::PlainLink; pub use object::PlainLink;
@@ -78,7 +100,12 @@ pub use object::PlainText;
pub use object::RadioLink; pub use object::RadioLink;
pub use object::RadioTarget; pub use object::RadioTarget;
pub use object::RegularLink; pub use object::RegularLink;
pub use object::StatisticsCookie;
pub use object::StrikeThrough; pub use object::StrikeThrough;
pub use object::Subscript;
pub use object::Superscript;
pub use object::Target;
pub use object::Timestamp;
pub use object::Underline; pub use object::Underline;
pub use object::Verbatim; pub use object::Verbatim;
pub use source::Source; pub use source::Source;

View File

@@ -15,6 +15,20 @@ pub enum Object<'s> {
PlainLink(PlainLink<'s>), PlainLink(PlainLink<'s>),
AngleLink(AngleLink<'s>), AngleLink(AngleLink<'s>),
OrgMacro(OrgMacro<'s>), OrgMacro(OrgMacro<'s>),
Entity(Entity<'s>),
LatexFragment(LatexFragment<'s>),
ExportSnippet(ExportSnippet<'s>),
FootnoteReference(FootnoteReference<'s>),
Citation(Citation<'s>),
CitationReference(CitationReference<'s>),
InlineBabelCall(InlineBabelCall<'s>),
InlineSourceBlock(InlineSourceBlock<'s>),
LineBreak(LineBreak<'s>),
Target(Target<'s>),
StatisticsCookie(StatisticsCookie<'s>),
Subscript(Subscript<'s>),
Superscript(Superscript<'s>),
Timestamp(Timestamp<'s>),
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@@ -96,6 +110,81 @@ pub struct OrgMacro<'s> {
pub macro_args: Vec<&'s str>, pub macro_args: Vec<&'s str>,
} }
#[derive(Debug, PartialEq)]
pub struct Entity<'s> {
pub source: &'s str,
pub entity_name: &'s str,
}
#[derive(Debug, PartialEq)]
pub struct LatexFragment<'s> {
pub source: &'s str,
}
#[derive(Debug, PartialEq)]
pub struct ExportSnippet<'s> {
pub source: &'s str,
pub backend: &'s str,
pub contents: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
pub struct FootnoteReference<'s> {
pub source: &'s str,
pub label: Option<&'s str>,
pub definition: Vec<Object<'s>>,
}
#[derive(Debug, PartialEq)]
pub struct Citation<'s> {
pub source: &'s str,
}
#[derive(Debug, PartialEq)]
pub struct CitationReference<'s> {
pub source: &'s str,
}
#[derive(Debug, PartialEq)]
pub struct InlineBabelCall<'s> {
pub source: &'s str,
}
#[derive(Debug, PartialEq)]
pub struct InlineSourceBlock<'s> {
pub source: &'s str,
}
#[derive(Debug, PartialEq)]
pub struct LineBreak<'s> {
pub source: &'s str,
}
#[derive(Debug, PartialEq)]
pub struct Target<'s> {
pub source: &'s str,
}
#[derive(Debug, PartialEq)]
pub struct StatisticsCookie<'s> {
pub source: &'s str,
}
#[derive(Debug, PartialEq)]
pub struct Subscript<'s> {
pub source: &'s str,
}
#[derive(Debug, PartialEq)]
pub struct Superscript<'s> {
pub source: &'s str,
}
#[derive(Debug, PartialEq)]
pub struct Timestamp<'s> {
pub source: &'s str,
}
impl<'s> Source<'s> for Object<'s> { impl<'s> Source<'s> for Object<'s> {
fn get_source(&'s self) -> &'s str { fn get_source(&'s self) -> &'s str {
match self { match self {
@@ -112,6 +201,20 @@ impl<'s> Source<'s> for Object<'s> {
Object::PlainLink(obj) => obj.source, Object::PlainLink(obj) => obj.source,
Object::AngleLink(obj) => obj.source, Object::AngleLink(obj) => obj.source,
Object::OrgMacro(obj) => obj.source, Object::OrgMacro(obj) => obj.source,
Object::Entity(obj) => obj.source,
Object::LatexFragment(obj) => obj.source,
Object::ExportSnippet(obj) => obj.source,
Object::FootnoteReference(obj) => obj.source,
Object::Citation(obj) => obj.source,
Object::CitationReference(obj) => obj.source,
Object::InlineBabelCall(obj) => obj.source,
Object::InlineSourceBlock(obj) => obj.source,
Object::LineBreak(obj) => obj.source,
Object::Target(obj) => obj.source,
Object::Timestamp(obj) => obj.source,
Object::StatisticsCookie(obj) => obj.source,
Object::Subscript(obj) => obj.source,
Object::Superscript(obj) => obj.source,
} }
} }
} }
@@ -187,3 +290,87 @@ impl<'s> Source<'s> for OrgMacro<'s> {
self.source self.source
} }
} }
impl<'s> Source<'s> for Entity<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for LatexFragment<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for ExportSnippet<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for FootnoteReference<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for Citation<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for CitationReference<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for InlineBabelCall<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for InlineSourceBlock<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for LineBreak<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for Target<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for StatisticsCookie<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for Subscript<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for Superscript<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for Timestamp<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}

View File

@@ -8,22 +8,68 @@ use super::regular_link::regular_link;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::angle_link::angle_link; use crate::parser::angle_link::angle_link;
use crate::parser::citation::citation;
use crate::parser::entity::entity;
use crate::parser::export_snippet::export_snippet;
use crate::parser::footnote_reference::footnote_reference;
use crate::parser::inline_babel_call::inline_babel_call;
use crate::parser::inline_source_block::inline_source_block;
use crate::parser::latex_fragment::latex_fragment;
use crate::parser::line_break::line_break;
use crate::parser::object::Object; use crate::parser::object::Object;
use crate::parser::org_macro::org_macro; use crate::parser::org_macro::org_macro;
use crate::parser::plain_link::plain_link; use crate::parser::plain_link::plain_link;
use crate::parser::radio_link::radio_link; use crate::parser::radio_link::radio_link;
use crate::parser::radio_link::radio_target; use crate::parser::radio_link::radio_target;
use crate::parser::statistics_cookie::statistics_cookie;
use crate::parser::subscript_and_superscript::subscript;
use crate::parser::subscript_and_superscript::superscript;
use crate::parser::target::target;
use crate::parser::text_markup::text_markup; use crate::parser::text_markup::text_markup;
use crate::parser::timestamp::timestamp;
#[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: &'s str,
) -> Res<&'s str, Object<'s>> { ) -> Res<&'s str, Object<'s>> {
// TODO: add entities, LaTeX fragments, export snippets, footnote references, citations (NOT citation references), inline babel calls, inline source blocks, line breaks, links, macros, targets and radio targets, statistics cookies, subscript and superscript, timestamps, and text markup.
not(|i| context.check_exit_matcher(i))(input)?; not(|i| context.check_exit_matcher(i))(input)?;
alt(( alt((
map(parser_with_context!(timestamp)(context), Object::Timestamp),
map(parser_with_context!(subscript)(context), Object::Subscript),
map(
parser_with_context!(superscript)(context),
Object::Superscript,
),
map(
parser_with_context!(statistics_cookie)(context),
Object::StatisticsCookie,
),
map(parser_with_context!(target)(context), Object::Target),
map(parser_with_context!(line_break)(context), Object::LineBreak),
map(
parser_with_context!(inline_source_block)(context),
Object::InlineSourceBlock,
),
map(
parser_with_context!(inline_babel_call)(context),
Object::InlineBabelCall,
),
map(parser_with_context!(citation)(context), Object::Citation),
map(
parser_with_context!(footnote_reference)(context),
Object::FootnoteReference,
),
map(
parser_with_context!(export_snippet)(context),
Object::ExportSnippet,
),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
map(parser_with_context!(radio_link)(context), Object::RadioLink), map(parser_with_context!(radio_link)(context), Object::RadioLink),
map( map(
parser_with_context!(radio_target)(context), parser_with_context!(radio_target)(context),
@@ -41,27 +87,70 @@ pub fn standard_set_object<'r, 's>(
))(input) ))(input)
} }
#[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: &'s str,
) -> Res<&'s str, Object<'s>> { ) -> Res<&'s str, Object<'s>> {
// TODO: add entities, LaTeX fragments, superscripts and subscripts
not(|i| context.check_exit_matcher(i))(input)?; not(|i| context.check_exit_matcher(i))(input)?;
alt(( alt((
map(parser_with_context!(subscript)(context), Object::Subscript),
map(
parser_with_context!(superscript)(context),
Object::Superscript,
),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
parser_with_context!(text_markup)(context), 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)
} }
#[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: &'s str,
) -> Res<&'s str, Object<'s>> { ) -> Res<&'s str, Object<'s>> {
// Used for exit matchers so this does not check exit matcher condition. // Used for exit matchers so this does not check exit matcher condition.
alt(( alt((
map(parser_with_context!(timestamp)(context), Object::Timestamp),
map(parser_with_context!(subscript)(context), Object::Subscript),
map(
parser_with_context!(superscript)(context),
Object::Superscript,
),
map(
parser_with_context!(statistics_cookie)(context),
Object::StatisticsCookie,
),
map(parser_with_context!(target)(context), Object::Target),
map(parser_with_context!(line_break)(context), Object::LineBreak),
map(
parser_with_context!(inline_source_block)(context),
Object::InlineSourceBlock,
),
map(
parser_with_context!(inline_babel_call)(context),
Object::InlineBabelCall,
),
map(parser_with_context!(citation)(context), Object::Citation),
map(
parser_with_context!(footnote_reference)(context),
Object::FootnoteReference,
),
map(
parser_with_context!(export_snippet)(context),
Object::ExportSnippet,
),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
map(parser_with_context!(radio_link)(context), Object::RadioLink), map(parser_with_context!(radio_link)(context), Object::RadioLink),
map( map(
parser_with_context!(radio_target)(context), parser_with_context!(radio_target)(context),
@@ -78,11 +167,30 @@ pub fn any_object_except_plain_text<'r, 's>(
))(input) ))(input)
} }
#[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: &'s str,
) -> Res<&'s str, Object<'s>> { ) -> Res<&'s str, Object<'s>> {
// TODO: minimal set of objects as well as export snippets, inline babel calls, inline source blocks, macros, and statistics cookies. 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((parser_with_context!(minimal_set_object)(context),))(input) alt((
map(
parser_with_context!(export_snippet)(context),
Object::ExportSnippet,
),
map(
parser_with_context!(statistics_cookie)(context),
Object::StatisticsCookie,
),
map(
parser_with_context!(inline_source_block)(context),
Object::InlineSourceBlock,
),
map(
parser_with_context!(inline_babel_call)(context),
Object::InlineBabelCall,
),
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
parser_with_context!(minimal_set_object)(context),
))(input)
} }

View File

@@ -14,7 +14,7 @@ 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;
#[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: &'s str) -> Res<&'s str, 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)?;
@@ -32,7 +32,7 @@ pub fn org_macro<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
)) ))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
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| {
@@ -42,7 +42,7 @@ fn org_macro_name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
Ok((remaining, source)) Ok((remaining, source))
} }
#[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: &'s str) -> Res<&'s str, Vec<&'s str>> {
let (remaining, _) = tag("(")(input)?; let (remaining, _) = tag("(")(input)?;
let (remaining, args) = let (remaining, args) =
@@ -52,7 +52,7 @@ fn org_macro_args<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
Ok((remaining, args)) Ok((remaining, args))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
let mut remaining = input; let mut remaining = input;
let mut escaping: bool = false; let mut escaping: bool = false;

View File

@@ -20,7 +20,7 @@ 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::start_of_line; use crate::parser::util::start_of_line;
#[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: &'s str) -> Res<&'s str, Paragraph<'s>> {
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
@@ -42,7 +42,7 @@ pub fn paragraph<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
Ok((remaining, Paragraph { source, children })) Ok((remaining, Paragraph { source, children }))
} }
#[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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
let non_paragraph_element_matcher = parser_with_context!(element(false))(context); let non_paragraph_element_matcher = parser_with_context!(element(false))(context);
let start_of_line_matcher = parser_with_context!(start_of_line)(&context); let start_of_line_matcher = parser_with_context!(start_of_line)(&context);

View File

@@ -44,7 +44,7 @@ impl<'r, 's> ContextTree<'r, 's> {
self.tree.iter() self.tree.iter()
} }
#[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: &'s str,
@@ -140,7 +140,72 @@ pub enum ContextElement<'r, 's> {
/// If any are found, this will force a 2nd parse through the /// If any are found, this will force a 2nd parse through the
/// 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<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> {
@@ -148,6 +213,36 @@ 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

@@ -23,7 +23,7 @@ use crate::parser::util::get_consumed;
use crate::parser::util::get_one_before; use crate::parser::util::get_one_before;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS; use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
#[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: &'s str) -> Res<&'s str, PlainLink<'s>> {
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, proto) = protocol(context, remaining)?; let (remaining, proto) = protocol(context, remaining)?;
@@ -41,7 +41,7 @@ pub fn plain_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
)) ))
} }
#[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: &'s str) -> Res<&'s str, ()> {
let document_root = context.get_document_root().unwrap(); let document_root = context.get_document_root().unwrap();
let preceding_character = get_one_before(document_root, input) let preceding_character = get_one_before(document_root, input)
@@ -61,13 +61,13 @@ fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
Ok((input, ())) Ok((input, ()))
} }
#[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: &'s str) -> Res<&'s str, ()> {
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, ()))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
// TODO: This should be defined by org-link-parameters // TODO: This should be defined by org-link-parameters
let (remaining, proto) = alt(( let (remaining, proto) = alt((
@@ -102,7 +102,7 @@ pub fn protocol<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str
Ok((remaining, proto)) Ok((remaining, proto))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
// 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 =
@@ -117,7 +117,7 @@ fn path_plain<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
Ok((remaining, path)) Ok((remaining, path))
} }
#[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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
recognize(one_of(" \t\r\n()[]<>"))(input) recognize(one_of(" \t\r\n()[]<>"))(input)
} }

View File

@@ -14,6 +14,7 @@ use nom::multi::many_till;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::terminated; use nom::sequence::terminated;
use nom::sequence::tuple; use nom::sequence::tuple;
#[cfg(feature = "tracing")]
use tracing::span; use tracing::span;
use super::greater_element::PlainList; use super::greater_element::PlainList;
@@ -33,7 +34,7 @@ use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
#[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 plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, PlainList<'s>> {
let parser_context = context let parser_context = context
.with_additional_node(ContextElement::Context("plain list")) .with_additional_node(ContextElement::Context("plain list"))
@@ -63,7 +64,9 @@ pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
*/ */
{ {
// Don't consume, yes exit matcher // Don't consume, yes exit matcher
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "first"); let span = span!(tracing::Level::DEBUG, "first");
#[cfg(feature = "tracing")]
let _enter = span.enter(); let _enter = span.enter();
let last_item_then_exit = tuple((without_consume_matcher, exit_matcher))(remaining); let last_item_then_exit = tuple((without_consume_matcher, exit_matcher))(remaining);
@@ -82,7 +85,9 @@ pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
{ {
// Consume, additional item // Consume, additional item
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "second"); let span = span!(tracing::Level::DEBUG, "second");
#[cfg(feature = "tracing")]
let _enter = span.enter(); let _enter = span.enter();
let not_last_item = let not_last_item =
@@ -104,7 +109,9 @@ pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
{ {
// Don't consume, no additional item // Don't consume, no additional item
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "third"); let span = span!(tracing::Level::DEBUG, "third");
#[cfg(feature = "tracing")]
let _enter = span.enter(); let _enter = span.enter();
let last_item_then_exit = without_consume_matcher(remaining); let last_item_then_exit = without_consume_matcher(remaining);
@@ -138,7 +145,7 @@ pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
Ok((remaining, PlainList { source, children })) Ok((remaining, PlainList { source, children }))
} }
#[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: &'s str,
@@ -208,7 +215,7 @@ pub fn plain_list_item<'r, 's>(
}; };
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
alt(( alt((
tag("*"), tag("*"),
@@ -218,12 +225,12 @@ fn bullet<'s>(i: &'s str) -> Res<&'s str, &'s str> {
))(i) ))(i)
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
alt((recognize(one_of("abcdefghijklmnopqrstuvwxyz")), digit1))(i) alt((recognize(one_of("abcdefghijklmnopqrstuvwxyz")), digit1))(i)
} }
#[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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
let start_of_line_matcher = parser_with_context!(start_of_line)(context); let start_of_line_matcher = parser_with_context!(start_of_line)(context);
recognize(tuple(( recognize(tuple((
@@ -232,7 +239,7 @@ fn plain_list_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
)))(input) )))(input)
} }
#[tracing::instrument(ret, level = "debug")] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn plain_list_item_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn plain_list_item_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
let current_item_indent_level: &usize = let current_item_indent_level: &usize =
get_context_item_indent(context).ok_or(nom::Err::Error(CustomError::MyError(MyError( get_context_item_indent(context).ok_or(nom::Err::Error(CustomError::MyError(MyError(
@@ -248,7 +255,7 @@ fn plain_list_item_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<
))(input) ))(input)
} }
#[tracing::instrument(ret, level = "debug")] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn line_indented_lte<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { fn line_indented_lte<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
let current_item_indent_level: &usize = let current_item_indent_level: &usize =
get_context_item_indent(context).ok_or(nom::Err::Error(CustomError::MyError(MyError( get_context_item_indent(context).ok_or(nom::Err::Error(CustomError::MyError(MyError(

View File

@@ -16,7 +16,7 @@ use crate::parser::object_parser::any_object_except_plain_text;
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;
#[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: &'s str) -> Res<&'s str, PlainText<'s>> {
let (remaining, source) = recognize(verify( let (remaining, source) = recognize(verify(
many_till( many_till(
@@ -32,13 +32,13 @@ pub fn plain_text<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
Ok((remaining, PlainText { source })) Ok((remaining, PlainText { source }))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
recognize(parser_with_context!(any_object_except_plain_text)(context))(input) recognize(parser_with_context!(any_object_except_plain_text)(context))(input)
} }
impl<'x> RematchObject<'x> for PlainText<'x> { impl<'x> RematchObject<'x> for PlainText<'x> {
#[tracing::instrument(ret, level = "debug")] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn rematch_object<'r, 's>( fn rematch_object<'r, 's>(
&'x self, &'x self,
_context: Context<'r, 's>, _context: Context<'r, 's>,

View File

@@ -15,7 +15,7 @@ use crate::parser::lesser_element::Planning;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
#[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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Planning<'s>> {
start_of_line(context, input)?; start_of_line(context, input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
@@ -27,7 +27,7 @@ pub fn planning<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str
Ok((remaining, Planning { source })) Ok((remaining, Planning { source }))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
let (remaining, _planning_type) = alt(( let (remaining, _planning_type) = alt((
tag_no_case("DEADLINE"), tag_no_case("DEADLINE"),

View File

@@ -29,7 +29,7 @@ use crate::parser::util::immediate_in_section;
use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting; 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;
#[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: &'s str,
@@ -71,7 +71,7 @@ pub fn property_drawer<'r, 's>(
Ok((remaining, PropertyDrawer { source, children })) Ok((remaining, PropertyDrawer { source, children }))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
recognize(tuple(( recognize(tuple((
parser_with_context!(start_of_line)(context), parser_with_context!(start_of_line)(context),
@@ -82,7 +82,7 @@ fn property_drawer_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<
)))(input) )))(input)
} }
#[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: &'s str,
@@ -121,7 +121,7 @@ fn node_property<'r, 's>(
} }
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
@@ -138,7 +138,7 @@ fn node_property_name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&
Ok((remaining, name)) Ok((remaining, name))
} }
#[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: &'s str,

View File

@@ -20,7 +20,7 @@ use crate::parser::util::get_consumed;
use crate::parser::RadioLink; use crate::parser::RadioLink;
use crate::parser::RadioTarget; use crate::parser::RadioTarget;
#[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: &'s str) -> Res<&'s str, RadioLink<'s>> {
let radio_targets = context let radio_targets = context
.iter() .iter()
@@ -32,6 +32,7 @@ pub fn radio_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
for radio_target in radio_targets { for radio_target in radio_targets {
let rematched_target = rematch_target(context, radio_target, input); let rematched_target = rematch_target(context, radio_target, input);
if let Ok((remaining, rematched_target)) = rematched_target { if let Ok((remaining, rematched_target)) = rematched_target {
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
return Ok(( return Ok((
remaining, remaining,
@@ -47,7 +48,7 @@ pub fn radio_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
)))) ))))
} }
#[tracing::instrument(ret, level = "debug")] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
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>>,
@@ -78,7 +79,7 @@ pub fn rematch_target<'x, 'r, 's>(
Ok((remaining, new_matches)) Ok((remaining, new_matches))
} }
#[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: &'s str,
@@ -104,7 +105,7 @@ pub fn radio_target<'r, 's>(
Ok((remaining, RadioTarget { source, children })) Ok((remaining, RadioTarget { source, children }))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
alt((tag("<"), tag(">"), line_ending))(input) alt((tag("<"), tag(">"), line_ending))(input)
} }
@@ -131,12 +132,11 @@ mod tests {
#[test] #[test]
fn plain_text_radio_target() { fn plain_text_radio_target() {
let input = "foo bar baz"; let input = "foo bar baz";
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::DocumentRoot(input))
.with_additional_node(ContextElement::RadioTarget(vec![vec![Object::PlainText( .with_additional_node(ContextElement::RadioTarget(vec![&radio_target_match]));
PlainText { source: "bar" },
)]]));
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");
let first_paragraph = match first_paragraph { let first_paragraph = match first_paragraph {
@@ -161,15 +161,14 @@ mod tests {
#[test] #[test]
fn bold_radio_target() { fn bold_radio_target() {
let input = "foo *bar* baz"; let input = "foo *bar* baz";
let radio_target_match = vec![Object::Bold(Bold {
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::DocumentRoot(input))
.with_additional_node(ContextElement::RadioTarget(vec![vec![Object::Bold( .with_additional_node(ContextElement::RadioTarget(vec![&radio_target_match]));
Bold {
source: "*bar*",
children: vec![Object::PlainText(PlainText { source: "bar" })],
},
)]]));
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");
let first_paragraph = match first_paragraph { let first_paragraph = match first_paragraph {

View File

@@ -19,7 +19,7 @@ use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
#[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: &'s str,
@@ -30,7 +30,7 @@ pub fn regular_link<'r, 's>(
))(input) ))(input)
} }
#[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: &'s str,
@@ -43,7 +43,7 @@ pub fn regular_link_without_description<'r, 's>(
Ok((remaining, RegularLink { source })) Ok((remaining, RegularLink { source }))
} }
#[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: &'s str,
@@ -58,7 +58,7 @@ pub fn regular_link_with_description<'r, 's>(
Ok((remaining, RegularLink { source })) Ok((remaining, RegularLink { source }))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
let (remaining, path) = escaped( let (remaining, path) = escaped(
take_till1(|c| match c { take_till1(|c| match c {
@@ -71,7 +71,7 @@ pub fn pathreg<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
Ok((remaining, path)) Ok((remaining, path))
} }
#[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: &'s str,
@@ -92,7 +92,7 @@ pub fn description<'r, 's>(
Ok((remaining, children)) Ok((remaining, children))
} }
#[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: &'s str) -> Res<&'s str, &'s str> {
tag("]]")(input) tag("]]")(input)
} }

View File

@@ -120,7 +120,7 @@ impl<'s> Token<'s> {
} }
} }
#[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, tkn) = token(remaining)?;
@@ -128,18 +128,18 @@ pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
Ok((remaining, tkn)) Ok((remaining, tkn))
} }
#[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: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, tkn) = token(input)?; let (remaining, tkn) = token(input)?;
Ok((remaining, tkn)) Ok((remaining, tkn))
} }
#[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: &'s str) -> Res<&'s str, Token<'s>> {
alt((list, atom))(input) alt((list, atom))(input)
} }
#[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: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag("(")(input)?; let (remaining, _) = tag("(")(input)?;
let (remaining, children) = delimited( let (remaining, children) = delimited(
@@ -151,13 +151,13 @@ fn list<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
Ok((remaining, Token::List(children))) Ok((remaining, Token::List(children)))
} }
#[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: &'s str) -> Res<&'s str, Token<'s>> {
not(peek(tag(")")))(input)?; not(peek(tag(")")))(input)?;
alt((text_with_properties, quoted_atom, unquoted_atom))(input) alt((text_with_properties, quoted_atom, unquoted_atom))(input)
} }
#[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: &'s str) -> Res<&'s str, 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,
@@ -166,7 +166,7 @@ fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
Ok((remaining, Token::Atom(body))) Ok((remaining, Token::Atom(body)))
} }
#[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: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag(r#"""#)(input)?; let (remaining, _) = tag(r#"""#)(input)?;
let (remaining, _) = escaped( let (remaining, _) = escaped(
@@ -175,7 +175,7 @@ fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
_ => false, _ => false,
}), }),
'\\', '\\',
one_of(r#""n"#), one_of(r#""n\\"#),
)(remaining)?; )(remaining)?;
let (remaining, _) = tag(r#"""#)(remaining)?; let (remaining, _) = tag(r#"""#)(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -298,4 +298,27 @@ mod tests {
r#"baz"# r#"baz"#
); );
} }
#[test]
fn string_containing_escaped_characters() {
let input = r#" (foo "\\( x=2 \\)" bar) "#;
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
assert_eq!(remaining, "");
assert!(match parsed {
Token::Atom(_) => false,
Token::List(_) => true,
Token::TextWithProperties(_) => false,
});
let children = match parsed {
Token::List(children) => children,
_ => panic!("Should be a list."),
};
assert_eq!(
match children.get(1) {
Some(Token::Atom(body)) => *body,
_ => panic!("First child should be an atom."),
},
r#""\\( x=2 \\)""#
)
}
} }

View File

@@ -0,0 +1,48 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::space0;
use nom::combinator::recognize;
use nom::sequence::tuple;
use super::Context;
use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::StatisticsCookie;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn statistics_cookie<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, StatisticsCookie<'s>> {
alt((
parser_with_context!(percent_statistics_cookie)(context),
parser_with_context!(fraction_statistics_cookie)(context),
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn percent_statistics_cookie<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, StatisticsCookie<'s>> {
let (remaining, source) =
recognize(tuple((tag("["), nom::character::complete::u64, tag("%]"))))(input)?;
let (remaining, _) = space0(remaining)?;
Ok((remaining, StatisticsCookie { source }))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn fraction_statistics_cookie<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, StatisticsCookie<'s>> {
let (remaining, source) = recognize(tuple((
tag("["),
nom::character::complete::u64,
tag("/"),
nom::character::complete::u64,
tag("]"),
)))(input)?;
let (remaining, _) = space0(remaining)?;
Ok((remaining, StatisticsCookie { source }))
}

View File

@@ -0,0 +1,204 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till;
use super::Context;
use super::Object;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_context::SubscriptSuperscriptBrace;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::get_one_before;
use crate::parser::Subscript;
use crate::parser::Superscript;
#[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>> {
// We check for the underscore first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
let (remaining, _) = tag("_")(input)?;
pre(context, input)?;
let (remaining, _body) = script_body(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Subscript { source }))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn superscript<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, 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.
let (remaining, _) = tag("^")(input)?;
pre(context, input)?;
let (remaining, _body) = script_body(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Superscript { source }))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn pre<'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 {
Some(c) if !c.is_whitespace() => {}
_ => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Must be preceded by a non-whitespace character.",
))));
}
};
Ok((input, ()))
}
#[derive(Debug)]
enum ScriptBody<'s> {
Braceless(&'s str),
WithBraces(Vec<Object<'s>>),
}
#[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>> {
alt((
map(parser_with_context!(script_asterisk)(context), |body| {
ScriptBody::Braceless(body)
}),
map(parser_with_context!(script_alphanum)(context), |body| {
ScriptBody::Braceless(body)
}),
map(parser_with_context!(script_with_braces)(context), |body| {
ScriptBody::WithBraces(body)
}),
))(input)
}
#[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> {
tag("*")(input)
}
#[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> {
let (remaining, _sign) = opt(recognize(one_of("+-")))(input)?;
let (remaining, _script) = many_till(
parser_with_context!(script_alphanum_character)(context),
parser_with_context!(end_script_alphanum_character)(context),
)(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn script_alphanum_character<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
recognize(verify(anychar, |c| {
c.is_alphanumeric() || r#",.\"#.contains(*c)
}))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn end_script_alphanum_character<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?;
peek(not(parser_with_context!(script_alphanum_character)(
context,
)))(remaining)?;
Ok((remaining, final_char))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn script_with_braces<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, Vec<Object<'s>>> {
let (remaining, _) = tag("{")(input)?;
let parser_context = context
.with_additional_node(ContextElement::SubscriptSuperscriptBrace(
SubscriptSuperscriptBrace {
position: remaining,
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &script_with_braces_end,
}));
let (remaining, (children, _exit_contents)) = many_till(
parser_with_context!(standard_set_object)(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context),
)(remaining)?;
let (remaining, _) = tag("}")(remaining)?;
Ok((remaining, children))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn script_with_braces_end<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
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"))]
fn get_bracket_depth<'r, 's>(
context: Context<'r, 's>,
) -> Option<&'r SubscriptSuperscriptBrace<'s>> {
for node in context.iter() {
match node.get_data() {
ContextElement::SubscriptSuperscriptBrace(depth) => return Some(depth),
_ => {}
}
}
None
}

View File

@@ -30,7 +30,7 @@ use crate::parser::Table;
/// Parse an org-mode-style table /// Parse an org-mode-style table
/// ///
/// This is not the table.el style. /// This is not the table.el style.
#[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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Table<'s>> {
start_of_line(context, input)?; start_of_line(context, input)?;
peek(tuple((space0, tag("|"))))(input)?; peek(tuple((space0, tag("|"))))(input)?;
@@ -55,13 +55,13 @@ pub fn org_mode_table<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&
Ok((remaining, Table { source, children })) Ok((remaining, Table { source, children }))
} }
#[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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
start_of_line(context, input)?; start_of_line(context, input)?;
recognize(tuple((space0, not(tag("|")))))(input) recognize(tuple((space0, not(tag("|")))))(input)
} }
#[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: &'s str,
@@ -72,7 +72,7 @@ pub fn org_mode_table_row<'r, 's>(
))(input) ))(input)
} }
#[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: &'s str,
@@ -89,7 +89,7 @@ pub fn org_mode_table_row_rule<'r, 's>(
)) ))
} }
#[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: &'s str,
@@ -103,7 +103,7 @@ pub fn org_mode_table_row_regular<'r, 's>(
Ok((remaining, TableRow { source, children })) Ok((remaining, TableRow { source, children }))
} }
#[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: &'s str,
@@ -128,7 +128,7 @@ pub fn org_mode_table_cell<'r, 's>(
Ok((remaining, TableCell { source, children })) Ok((remaining, TableCell { source, children }))
} }
#[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: &'s str,
@@ -136,7 +136,7 @@ fn org_mode_table_cell_end<'r, 's>(
recognize(tuple((space0, alt((tag("|"), peek(line_ending))))))(input) recognize(tuple((space0, alt((tag("|"), peek(line_ending))))))(input)
} }
#[tracing::instrument(ret, level = "debug")] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_cell_set_object<'r, 's>( pub fn table_cell_set_object<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: &'s str,

60
src/parser/target.rs Normal file
View File

@@ -0,0 +1,60 @@
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::get_one_before;
use crate::parser::Target;
#[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>> {
let (remaining, _) = tag("<<")(input)?;
let (remaining, _) = peek(verify(anychar, |c| {
!c.is_whitespace() && !"<>\n".contains(*c)
}))(remaining)?;
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &target_end,
}));
let (remaining, _body) = recognize(many_till(
anychar,
parser_with_context!(exit_matcher_parser)(&parser_context),
))(remaining)?;
let document_root = context.get_document_root().unwrap();
let preceding_character = get_one_before(document_root, remaining)
.map(|slice| slice.chars().next())
.flatten()
.expect("We cannot be at the start of the file because we are inside a target.");
if preceding_character.is_whitespace() {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Targets cannot end with whitespace.",
))));
}
let (remaining, _) = tag(">>")(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Target { source }))
}
#[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> {
recognize(one_of("<>\n"))(input)
}

View File

@@ -12,7 +12,7 @@ use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::terminated; use nom::sequence::terminated;
use nom::sequence::tuple; #[cfg(feature = "tracing")]
use tracing::span; use tracing::span;
use super::radio_link::RematchObject; use super::radio_link::RematchObject;
@@ -38,7 +38,7 @@ use crate::parser::StrikeThrough;
use crate::parser::Underline; use crate::parser::Underline;
use crate::parser::Verbatim; use crate::parser::Verbatim;
#[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: &'s str) -> Res<&'s str, Object<'s>> {
alt(( alt((
map(parser_with_context!(bold)(context), Object::Bold), map(parser_with_context!(bold)(context), Object::Bold),
@@ -53,7 +53,7 @@ pub fn text_markup<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s
))(input) ))(input)
} }
#[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: &'s str) -> Res<&'s str, 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)?;
@@ -61,7 +61,7 @@ pub fn bold<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Bo
Ok((remaining, Bold { source, children })) Ok((remaining, Bold { source, children }))
} }
#[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: &'s str) -> Res<&'s str, 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)?;
@@ -69,7 +69,7 @@ pub fn italic<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
Ok((remaining, Italic { source, children })) Ok((remaining, Italic { source, children }))
} }
#[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: &'s str) -> Res<&'s str, 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)?;
@@ -77,7 +77,7 @@ pub fn underline<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
Ok((remaining, Underline { source, children })) Ok((remaining, Underline { source, children }))
} }
#[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: &'s str,
@@ -88,7 +88,7 @@ pub fn strike_through<'r, 's>(
Ok((remaining, StrikeThrough { source, children })) Ok((remaining, StrikeThrough { source, children }))
} }
#[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: &'s str) -> Res<&'s str, 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)?;
@@ -96,7 +96,7 @@ pub fn verbatim<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str
Ok((remaining, Verbatim { source, contents })) Ok((remaining, Verbatim { source, contents }))
} }
#[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: &'s str) -> Res<&'s str, 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)?;
@@ -111,7 +111,7 @@ fn text_markup_object(
move |context: Context, input: &str| _text_markup_object(context, input, marker_symbol.as_str()) move |context: Context, input: &str| _text_markup_object(context, input, marker_symbol.as_str())
} }
#[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: &'s str,
@@ -136,7 +136,9 @@ fn _text_markup_object<'r, 's, 'x>(
)(remaining)?; )(remaining)?;
{ {
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "Checking parent exit."); let span = span!(tracing::Level::DEBUG, "Checking parent exit.");
#[cfg(feature = "tracing")]
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(
@@ -157,7 +159,7 @@ fn text_markup_string(
move |context: Context, input: &str| _text_markup_string(context, input, marker_symbol.as_str()) move |context: Context, input: &str| _text_markup_string(context, input, marker_symbol.as_str())
} }
#[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: &'s str,
@@ -182,7 +184,9 @@ fn _text_markup_string<'r, 's, 'x>(
))(remaining)?; ))(remaining)?;
{ {
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "Checking parent exit."); let span = span!(tracing::Level::DEBUG, "Checking parent exit.");
#[cfg(feature = "tracing")]
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(
@@ -196,7 +200,7 @@ fn _text_markup_string<'r, 's, 'x>(
Ok((remaining, contents)) Ok((remaining, contents))
} }
#[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: &'s str) -> Res<&'s str, ()> {
let document_root = context.get_document_root().unwrap(); let document_root = context.get_document_root().unwrap();
let preceding_character = get_one_before(document_root, input) let preceding_character = get_one_before(document_root, input)
@@ -205,7 +209,7 @@ pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()>
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('(')
| Some('{') | Some('\'') | Some('"') => {} | Some('{') | Some('\'') | Some('"') | Some('<') => {}
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(
@@ -216,9 +220,9 @@ pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()>
Ok((input, ())) Ok((input, ()))
} }
#[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: &'s str) -> Res<&'s str, ()> {
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, ()))
} }
@@ -229,7 +233,7 @@ fn text_markup_end(
move |context: Context, input: &str| _text_markup_end(context, input, marker_symbol.as_str()) move |context: Context, input: &str| _text_markup_end(context, input, marker_symbol.as_str())
} }
#[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: &'s str,
@@ -245,7 +249,7 @@ fn _text_markup_end<'r, 's, 'x>(
} }
impl<'x> RematchObject<'x> for Bold<'x> { impl<'x> RematchObject<'x> for Bold<'x> {
#[tracing::instrument(ret, level = "debug")] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn rematch_object<'r, 's>( fn rematch_object<'r, 's>(
&'x self, &'x self,
_context: Context<'r, 's>, _context: Context<'r, 's>,
@@ -258,7 +262,7 @@ impl<'x> RematchObject<'x> for Bold<'x> {
} }
} }
#[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: &'s str,
@@ -280,7 +284,9 @@ fn _rematch_text_markup_object<'r, 's, 'x>(
rematch_target(&parser_context, original_match_children, remaining)?; rematch_target(&parser_context, original_match_children, remaining)?;
{ {
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "Checking parent exit."); let span = span!(tracing::Level::DEBUG, "Checking parent exit.");
#[cfg(feature = "tracing")]
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(

357
src/parser/timestamp.rs Normal file
View File

@@ -0,0 +1,357 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::digit1;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::Context;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::Timestamp;
#[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>> {
// 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((
// 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.
parser_with_context!(diary_timestamp)(context),
parser_with_context!(active_time_range_timestamp)(context),
parser_with_context!(inactive_time_range_timestamp)(context),
parser_with_context!(active_date_range_timestamp)(context),
parser_with_context!(inactive_date_range_timestamp)(context),
parser_with_context!(active_timestamp)(context),
parser_with_context!(inactive_timestamp)(context),
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn diary_timestamp<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, Timestamp<'s>> {
let (remaining, _) = tag("<%%(")(input)?;
let (remaining, _body) = sexp(context, remaining)?;
let (remaining, _) = tag(")>")(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source }))
}
#[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> {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &sexp_end,
}));
let (remaining, body) = recognize(verify(
many_till(
anychar,
parser_with_context!(exit_matcher_parser)(&parser_context),
),
|(body, _end_contents)| !body.is_empty(),
))(input)?;
Ok((remaining, body))
}
#[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> {
alt((tag(")>"), recognize(one_of(">\n"))))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn active_timestamp<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, Timestamp<'s>> {
let (remaining, _) = tag("<")(input)?;
let (remaining, _date) = date(context, remaining)?;
let time_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &active_time_rest_end,
}));
let (remaining, _time) =
opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?;
let (remaining, _repeater) =
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
let (remaining, _warning_delay) = opt(tuple((
space1,
parser_with_context!(warning_delay)(context),
)))(remaining)?;
let (remaining, _) = tag(">")(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source }))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, Timestamp<'s>> {
let (remaining, _) = tag("[")(input)?;
let (remaining, _date) = date(context, remaining)?;
let time_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &inactive_time_rest_end,
}));
let (remaining, _time) =
opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?;
let (remaining, _repeater) =
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
let (remaining, _warning_delay) = opt(tuple((
space1,
parser_with_context!(warning_delay)(context),
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source }))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn active_date_range_timestamp<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, Timestamp<'s>> {
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
let (remaining, _separator) = tag("--")(remaining)?;
let (remaining, _second_timestamp) = active_timestamp(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source }))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn active_time_range_timestamp<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, Timestamp<'s>> {
let (remaining, _) = tag("<")(input)?;
let (remaining, _date) = date(context, remaining)?;
let time_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &active_time_rest_end,
}));
let first_time_context =
time_context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &time_range_rest_end,
}));
let (remaining, _first_time) =
tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?;
let (remaining, _) = tag("-")(remaining)?;
let (remaining, _second_time) = parser_with_context!(time)(&time_context)(remaining)?;
let (remaining, _repeater) =
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
let (remaining, _warning_delay) = opt(tuple((
space1,
parser_with_context!(warning_delay)(context),
)))(remaining)?;
let (remaining, _) = tag(">")(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source }))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_date_range_timestamp<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, Timestamp<'s>> {
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
let (remaining, _separator) = tag("--")(remaining)?;
let (remaining, _second_timestamp) = inactive_timestamp(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source }))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_time_range_timestamp<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, Timestamp<'s>> {
let (remaining, _) = tag("[")(input)?;
let (remaining, _date) = date(context, remaining)?;
let time_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &inactive_time_rest_end,
}));
let first_time_context =
time_context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &time_range_rest_end,
}));
let (remaining, _first_time) =
tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?;
let (remaining, _) = tag("-")(remaining)?;
let (remaining, _second_time) = parser_with_context!(time)(&time_context)(remaining)?;
let (remaining, _repeater) =
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
let (remaining, _warning_delay) = opt(tuple((
space1,
parser_with_context!(warning_delay)(context),
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Timestamp { source }))
}
#[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> {
let (remaining, _year) = verify(digit1, |year: &str| year.len() == 4)(input)?;
let (remaining, _) = tag("-")(remaining)?;
let (remaining, _month) = verify(digit1, |month: &str| month.len() == 2)(remaining)?;
let (remaining, _) = tag("-")(remaining)?;
let (remaining, _day_of_month) =
verify(digit1, |day_of_month: &str| day_of_month.len() == 2)(remaining)?;
let (remaining, _dayname) =
opt(tuple((space1, parser_with_context!(dayname)(context))))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[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> {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &dayname_end,
}));
let (remaining, body) = recognize(verify(
many_till(
anychar,
parser_with_context!(exit_matcher_parser)(&parser_context),
),
|(body, _end_contents)| !body.is_empty(),
))(input)?;
Ok((remaining, body))
}
#[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> {
recognize(verify(anychar, |c| {
c.is_whitespace() || "+-]>0123456789\n".contains(*c)
}))(input)
}
#[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> {
let (remaining, _hour) =
verify(digit1, |hour: &str| hour.len() >= 1 && hour.len() <= 2)(input)?;
let (remaining, _) = tag(":")(remaining)?;
let (remaining, _minute) = verify(digit1, |minute: &str| minute.len() == 2)(remaining)?;
let (remaining, _time_rest) = opt(parser_with_context!(time_rest)(context))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[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> {
let (remaining, body) = recognize(verify(
many_till(anychar, parser_with_context!(exit_matcher_parser)(context)),
|(body, _end_contents)| !body.is_empty(),
))(input)?;
Ok((remaining, body))
}
#[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> {
alt((
recognize(verify(anychar, |c| ">\n".contains(*c))),
recognize(tuple((space1, parser_with_context!(repeater)(context)))),
recognize(tuple((
space1,
parser_with_context!(warning_delay)(context),
))),
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_time_rest_end<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
alt((
recognize(verify(anychar, |c| "]\n".contains(*c))),
recognize(tuple((space1, parser_with_context!(repeater)(context)))),
recognize(tuple((
space1,
parser_with_context!(warning_delay)(context),
))),
))(input)
}
#[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> {
// 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_tree = ContextTree::branch_from(parent_node);
let exit_contents =
recognize(tuple((tag("-"), parser_with_context!(time)(&parent_tree))))(input);
exit_contents
}
#[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> {
// + for cumulative type
// ++ for catch-up type
// .+ for restart type
let (remaining, _mark) = alt((tag("++"), tag("+"), tag(".+")))(input)?;
let (remaining, _value) = digit1(remaining)?;
// h = hour, d = day, w = week, m = month, y = year
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[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> {
// - for all type
// -- for first type
let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?;
let (remaining, _value) = digit1(remaining)?;
// h = hour, d = day, w = week, m = month, y = year
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}

View File

@@ -1,12 +1,129 @@
use std::collections::VecDeque;
use super::Document; use super::Document;
use super::Element; use super::Element;
use super::Heading; use super::Heading;
use super::Object; use super::Object;
use super::PlainListItem;
use super::Section; use super::Section;
use super::TableCell;
use super::TableRow;
use crate::parser::DocumentElement;
pub enum Token<'r, 's> { pub enum Token<'r, 's> {
Document(&'r Document<'s>),
Heading(&'r Heading<'s>), Heading(&'r Heading<'s>),
Section(&'r Section<'s>), Section(&'r Section<'s>),
Object(&'r Object<'s>), Object(&'r Object<'s>),
Element(&'r Element<'s>), Element(&'r Element<'s>),
PlainListItem(&'r PlainListItem<'s>),
TableRow(&'r TableRow<'s>),
TableCell(&'r TableCell<'s>),
}
impl<'r, 's> Token<'r, 's> {
pub fn iter_tokens(&self) -> Box<dyn Iterator<Item = Token<'r, 's>> + '_> {
match self {
Token::Document(document) => Box::new(
document
.zeroth_section
.iter()
.map(Token::Section)
.chain(document.children.iter().map(Token::Heading)),
),
Token::Heading(heading) => Box::new(heading.title.iter().map(Token::Object).chain(
heading.children.iter().map(|de| match de {
DocumentElement::Heading(ref obj) => Token::Heading(obj),
DocumentElement::Section(ref obj) => Token::Section(obj),
}),
)),
Token::Section(section) => Box::new(section.children.iter().map(Token::Element)),
Token::Object(obj) => match obj {
Object::Bold(inner) => Box::new(inner.children.iter().map(Token::Object)),
Object::Italic(inner) => Box::new(inner.children.iter().map(Token::Object)),
Object::Underline(inner) => Box::new(inner.children.iter().map(Token::Object)),
Object::StrikeThrough(inner) => Box::new(inner.children.iter().map(Token::Object)),
Object::Code(_) => Box::new(std::iter::empty()),
Object::Verbatim(_) => Box::new(std::iter::empty()),
Object::PlainText(_) => Box::new(std::iter::empty()),
Object::RegularLink(_) => Box::new(std::iter::empty()),
Object::RadioLink(inner) => Box::new(inner.children.iter().map(Token::Object)),
Object::RadioTarget(inner) => Box::new(inner.children.iter().map(Token::Object)),
Object::PlainLink(_) => Box::new(std::iter::empty()),
Object::AngleLink(_) => Box::new(std::iter::empty()),
Object::OrgMacro(_) => Box::new(std::iter::empty()),
Object::Entity(_) => Box::new(std::iter::empty()),
Object::LatexFragment(_) => Box::new(std::iter::empty()),
Object::ExportSnippet(_) => Box::new(std::iter::empty()),
Object::FootnoteReference(inner) => {
Box::new(inner.definition.iter().map(Token::Object))
}
Object::Citation(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
Object::CitationReference(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
Object::InlineBabelCall(_) => Box::new(std::iter::empty()),
Object::InlineSourceBlock(_) => Box::new(std::iter::empty()),
Object::LineBreak(_) => Box::new(std::iter::empty()),
Object::Target(_) => Box::new(std::iter::empty()),
Object::StatisticsCookie(_) => Box::new(std::iter::empty()),
Object::Subscript(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
Object::Superscript(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
Object::Timestamp(_) => Box::new(std::iter::empty()),
},
Token::Element(elem) => match elem {
Element::Paragraph(inner) => Box::new(inner.children.iter().map(Token::Object)),
Element::PlainList(inner) => {
Box::new(inner.children.iter().map(Token::PlainListItem))
}
Element::GreaterBlock(inner) => Box::new(inner.children.iter().map(Token::Element)),
Element::DynamicBlock(inner) => Box::new(inner.children.iter().map(Token::Element)),
Element::FootnoteDefinition(inner) => {
Box::new(inner.children.iter().map(Token::Element))
}
Element::Comment(_) => Box::new(std::iter::empty()),
Element::Drawer(inner) => Box::new(inner.children.iter().map(Token::Element)),
Element::PropertyDrawer(_) => Box::new(std::iter::empty()),
Element::Table(inner) => Box::new(inner.children.iter().map(Token::TableRow)),
Element::VerseBlock(inner) => Box::new(inner.children.iter().map(Token::Object)),
Element::CommentBlock(_) => Box::new(std::iter::empty()),
Element::ExampleBlock(_) => Box::new(std::iter::empty()),
Element::ExportBlock(_) => Box::new(std::iter::empty()),
Element::SrcBlock(_) => Box::new(std::iter::empty()),
Element::Clock(_) => Box::new(std::iter::empty()),
Element::DiarySexp(_) => Box::new(std::iter::empty()),
Element::Planning(_) => Box::new(std::iter::empty()),
Element::FixedWidthArea(_) => Box::new(std::iter::empty()),
Element::HorizontalRule(_) => Box::new(std::iter::empty()),
Element::Keyword(_) => Box::new(std::iter::empty()),
Element::LatexEnvironment(_) => Box::new(std::iter::empty()),
},
Token::PlainListItem(elem) => Box::new(elem.children.iter().map(Token::Element)),
Token::TableRow(elem) => Box::new(elem.children.iter().map(Token::TableCell)),
Token::TableCell(elem) => Box::new(elem.children.iter().map(Token::Object)),
}
}
}
pub struct AllTokensIterator<'r, 's> {
queued_tokens: VecDeque<Token<'r, 's>>,
}
impl<'r, 's> AllTokensIterator<'r, 's> {
pub fn new(tkn: Token<'r, 's>) -> Self {
let mut queued_tokens = VecDeque::new();
queued_tokens.push_back(tkn);
AllTokensIterator { queued_tokens }
}
}
impl<'r, 's> Iterator for AllTokensIterator<'r, 's> {
type Item = Token<'r, 's>;
fn next(&mut self) -> Option<Self::Item> {
let next_token = match self.queued_tokens.pop_front() {
Some(tkn) => tkn,
None => return None,
};
self.queued_tokens.extend(next_token.iter_tokens());
Some(next_token)
}
} }

View File

@@ -59,6 +59,32 @@ pub fn get_one_before<'s>(document: &'s str, current_position: &'s str) -> Optio
Some(&document[previous_character_offset..offset]) 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. /// Check if the child string slice is a slice of the parent string slice.
fn is_slice_of(parent: &str, child: &str) -> bool { fn is_slice_of(parent: &str, child: &str) -> bool {
let parent_start = parent.as_ptr() as usize; let parent_start = parent.as_ptr() as usize;
@@ -81,13 +107,13 @@ pub fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
/// 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.
#[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: &str) -> Res<&str, &str> {
not(eof)(input)?; not(eof)(input)?;
recognize(tuple((space0, alt((line_ending, eof)))))(input) recognize(tuple((space0, alt((line_ending, eof)))))(input)
} }
#[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<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: &'s str,
@@ -96,7 +122,7 @@ pub fn element_trailing_whitespace<'r, 's>(
alt((eof, recognize(many0(blank_line))))(input) alt((eof, recognize(many0(blank_line))))(input)
} }
#[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: &'s str,
@@ -111,7 +137,7 @@ pub fn maybe_consume_trailing_whitespace_if_not_exiting<'r, 's>(
} }
} }
#[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: &'s str,
@@ -125,13 +151,13 @@ pub fn maybe_consume_trailing_whitespace<'r, 's>(
} }
} }
#[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 trailing_whitespace(input: &str) -> Res<&str, &str> {
alt((eof, recognize(tuple((line_ending, many0(blank_line))))))(input) alt((eof, recognize(tuple((line_ending, many0(blank_line))))))(input)
} }
/// Check that we are at the start of a line /// Check that we are at the start of a line
#[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 start_of_line<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
let document_root = context.get_document_root().unwrap(); let document_root = context.get_document_root().unwrap();
let preceding_character = get_one_before(document_root, input) let preceding_character = get_one_before(document_root, input)
@@ -152,7 +178,7 @@ pub fn start_of_line<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'
} }
/// Check that we are at the start of a line /// Check that we are at the start of a line
#[tracing::instrument(ret, level = "debug")] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn preceded_by_whitespace<'r, 's>( pub fn preceded_by_whitespace<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: &'s str, input: &'s str,
@@ -176,13 +202,13 @@ pub fn preceded_by_whitespace<'r, 's>(
/// Pull one non-whitespace character. /// Pull one non-whitespace character.
/// ///
/// 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.
#[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: &str) -> Res<&str, char> {
none_of(" \t\r\n")(input) none_of(" \t\r\n")(input)
} }
/// Check that we are at the start of a line /// Check that we are at the start of a line
#[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: &'s str,
@@ -190,19 +216,19 @@ pub fn exit_matcher_parser<'r, 's>(
peek(|i| context.check_exit_matcher(i))(input) peek(|i| context.check_exit_matcher(i))(input)
} }
#[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 always_fail<'r, 's>(_context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::MyError(MyError(
"Always fail", "Always fail",
)))) ))))
} }
#[tracing::instrument(ret, level = "debug")] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn whitespace_eof(input: &str) -> Res<&str, &str> { pub fn whitespace_eof(input: &str) -> Res<&str, &str> {
recognize(tuple((multispace0, eof)))(input) recognize(tuple((multispace0, eof)))(input)
} }
#[tracing::instrument(ret, level = "debug")] #[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> { 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)),

View File

@@ -1,3 +0,0 @@
foo *bar /baz *lorem* ipsum/ dolar* alpha
foo *bar /baz _lorem_ ipsum/ dolar* alpha