Compare commits

..

46 Commits

Author SHA1 Message Date
Tom Alexander
1b4b8b4bdb
Merge branch 'howard_abrams'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-08 23:19:19 -04:00
Tom Alexander
5587e19f16
Cleanup. 2023-09-08 23:19:08 -04:00
Tom Alexander
80f7098f9b
Compare table formulas. 2023-09-08 23:05:04 -04:00
Tom Alexander
84d2babda9
Parse table formulas. 2023-09-08 22:47:07 -04:00
Tom Alexander
cc56b79683
Add a test showing we're not handling table formulas. 2023-09-08 22:42:24 -04:00
Tom Alexander
0105b49d0d
Handle empty statistics cookies. 2023-09-08 22:21:49 -04:00
Tom Alexander
d79035e14d
Add a test showing we are not handling empty statistics cookies. 2023-09-08 22:21:19 -04:00
Tom Alexander
7545fb7e1a
Support keywords with colons in the key and without a space between the colon and value. 2023-09-08 22:17:10 -04:00
Tom Alexander
f30069efe7
Add a test showing we're not handling colons in keyword keys correctly. 2023-09-08 21:59:02 -04:00
Tom Alexander
d1fe2f6b09
Update the rest of the scripts to work with relative paths. 2023-09-08 21:50:32 -04:00
Tom Alexander
21c60d1036
Do not consume trailing whitespace on the footnote definition's final element. 2023-09-08 21:30:03 -04:00
Tom Alexander
6a1bdd5fee
Support blank lines before content in footnote definitions. 2023-09-08 21:11:47 -04:00
Tom Alexander
5d20d3e99b
Add a test showing we are not handling empty space for footnote definitions correctly. 2023-09-08 20:28:21 -04:00
Tom Alexander
a8fbf01124
Handle tabs for plain list descriptions.
This bug probably exists in hundreds of places across the code base. I am going to have to write a "fuzzer" that replaces random whitespace with tabs to find them all.
2023-09-08 20:02:49 -04:00
Tom Alexander
344ef04453
Add tests showing we are not handling tabs appropriately for description list tags. 2023-09-08 19:53:58 -04:00
Tom Alexander
ceb722e476
Check exit matcher after each space consumed for object trailing whitespace.
Since description list tags need to end with a space unconsumed for " ::", we need to check the exit matcher after each space consumed.
2023-09-08 19:38:49 -04:00
Tom Alexander
b04341882c
Add test showing that we are not handling trailing spaces in description list tags correctly. 2023-09-08 19:24:03 -04:00
Tom Alexander
494fe5cceb
Handle contentless list items mid-document. 2023-09-08 19:01:46 -04:00
Tom Alexander
0110d23387
Update empty list test to show that we're not handling trailing whitespace for empty list items properly. 2023-09-08 18:41:57 -04:00
Tom Alexander
0d7a15bfeb
Handle spaces after statistics cookies. 2023-09-08 18:35:33 -04:00
Tom Alexander
352c20d1d8
Fix run_docker_compare_bisect with relative paths. 2023-09-08 18:05:10 -04:00
Tom Alexander
f82d2aada1
Fix run_docker_compare with relative paths. 2023-09-08 18:03:50 -04:00
Tom Alexander
669da4073e
Accept the end condition as a parameter to the plain text parser so it can adapt to the context. 2023-09-08 17:54:49 -04:00
Tom Alexander
0056657b65
Add a test showing the plain text parser is not handling subsets of objects like inside a table cell. 2023-09-08 17:27:02 -04:00
Tom Alexander
8780976c15
Consume trailing whitespace after planning. 2023-09-08 16:30:40 -04:00
Tom Alexander
dc8b8d08ab
Add test showing we break on empty sections that contain a planning. 2023-09-08 16:25:18 -04:00
Tom Alexander
93d3d9471f
Compare priority, archived, and commented in headlines. 2023-09-08 16:00:16 -04:00
Tom Alexander
c7c0deed74
Parse priority cookie and COMMENT from headlines. 2023-09-08 16:00:16 -04:00
Tom Alexander
b32c21eb1d
Add a test for a comment heading. 2023-09-08 16:00:16 -04:00
Tom Alexander
2e6e6fdd2b
Move sections to their own source file. 2023-09-08 15:08:16 -04:00
Tom Alexander
3cc2294387
Move headlines into their own file. 2023-09-08 15:05:42 -04:00
Tom Alexander
40f22034da
Make the item tag exit matcher a lower class than all all others.
This is to allow for " :: " inside a description list item's tag if it is nested inside another object.
2023-09-08 14:37:30 -04:00
Tom Alexander
ab612f293f
Update org-mode version. 2023-09-08 13:11:58 -04:00
Tom Alexander
57c2922e4a
Add test showing problem is description list parser. 2023-09-08 12:50:51 -04:00
Tom Alexander
c2eb1f51c8
Support blank lines between nested headlines. 2023-09-08 12:41:48 -04:00
Tom Alexander
b0930df788
Support zero skipped text in OrgSource slicing. 2023-09-07 04:16:00 -04:00
Tom Alexander
69512f559a
Fix end conditions for subscript and superscript. 2023-09-07 04:16:00 -04:00
Tom Alexander
76a81b73ac
Add a detect object function similar to the detect element function. 2023-09-07 04:16:00 -04:00
Tom Alexander
ba291c6776
Unify two places checking if text was preceded by whitespace. 2023-09-07 04:16:00 -04:00
Tom Alexander
6b82b46e09
Prevent nesting of text markup of the same type.
This greatly reduces the amount of detect element calls that are occurring.
2023-09-07 04:15:59 -04:00
Tom Alexander
6676012eb1
Change footnote reference class to Gamma. 2023-09-07 04:15:59 -04:00
Tom Alexander
facbe716e9
Cleanup 2023-09-07 01:23:26 -04:00
Tom Alexander
827f3e1c98
Add the rest of the relevant howard abrams repos. 2023-09-06 21:37:09 -04:00
Tom Alexander
fcea7e5a4b
Add howard abrams demo-it and the upstreeam doomemacs repo to compare. 2023-09-06 21:11:46 -04:00
Tom Alexander
dda2b1e69f
Compare howard abrams hamacs. 2023-09-06 20:56:36 -04:00
Tom Alexander
f79d07a7c8
Compare howard abrams dotfiles. 2023-09-06 19:49:04 -04:00
47 changed files with 1091 additions and 558 deletions

View File

@ -14,7 +14,7 @@ RUN make DESTDIR="/root/dist" install
FROM build AS build-org-mode FROM build AS build-org-mode
ARG ORG_VERSION=7bdec435ff5d86220d13c431e799c5ed44a57da1 ARG ORG_VERSION=163bafb43dcc2bc94a2c7ccaa77d3d1dd488f1af
COPY --from=build-emacs /root/dist/ / COPY --from=build-emacs /root/dist/ /
RUN mkdir /root/dist RUN mkdir /root/dist
# Savannah does not allow fetching specific revisions, so we're going to have to put unnecessary load on their server by cloning main and then checking out the revision we want. # Savannah does not allow fetching specific revisions, so we're going to have to put unnecessary load on their server by cloning main and then checking out the revision we want.
@ -36,7 +36,57 @@ ENTRYPOINT ["cargo", "test"]
FROM build as foreign-document-gather FROM build as foreign-document-gather
ARG HOWARD_ABRAMS_DOT_FILES_VERSION=1b54fe75d74670dc7bcbb6b01ea560c45528c628
ARG HOWARD_ABRAMS_DOT_FILES_PATH=/foreign_documents/howardabrams/dot-files
ARG HOWARD_ABRAMS_DOT_FILES_REPO=https://github.com/howardabrams/dot-files.git
RUN mkdir /foreign_documents RUN mkdir /foreign_documents
RUN mkdir -p $HOWARD_ABRAMS_DOT_FILES_PATH && git -C $HOWARD_ABRAMS_DOT_FILES_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_DOT_FILES_PATH remote add origin $HOWARD_ABRAMS_DOT_FILES_REPO && git -C $HOWARD_ABRAMS_DOT_FILES_PATH fetch origin $HOWARD_ABRAMS_DOT_FILES_VERSION && git -C $HOWARD_ABRAMS_DOT_FILES_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_HAMACS_VERSION=da51188cc195d41882175d412fe40a8bc5730c5c
ARG HOWARD_ABRAMS_HAMACS_PATH=/foreign_documents/howardabrams/hamacs
ARG HOWARD_ABRAMS_HAMACS_REPO=https://github.com/howardabrams/hamacs.git
RUN mkdir -p $HOWARD_ABRAMS_HAMACS_PATH && git -C $HOWARD_ABRAMS_HAMACS_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_HAMACS_PATH remote add origin $HOWARD_ABRAMS_HAMACS_REPO && git -C $HOWARD_ABRAMS_HAMACS_PATH fetch origin $HOWARD_ABRAMS_HAMACS_VERSION && git -C $HOWARD_ABRAMS_HAMACS_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_DEMO_IT_VERSION=e399fd7ceb73caeae7cb50b247359bafcaee2a3f
ARG HOWARD_ABRAMS_DEMO_IT_PATH=/foreign_documents/howardabrams/demo-it
ARG HOWARD_ABRAMS_DEMO_IT_REPO=https://github.com/howardabrams/demo-it.git
RUN mkdir -p $HOWARD_ABRAMS_DEMO_IT_PATH && git -C $HOWARD_ABRAMS_DEMO_IT_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_DEMO_IT_PATH remote add origin $HOWARD_ABRAMS_DEMO_IT_REPO && git -C $HOWARD_ABRAMS_DEMO_IT_PATH fetch origin $HOWARD_ABRAMS_DEMO_IT_VERSION && git -C $HOWARD_ABRAMS_DEMO_IT_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_MAGIT_DEMO_VERSION=59e82f6bc7c18f550478d86a8f680c3f2da66985
ARG HOWARD_ABRAMS_MAGIT_DEMO_PATH=/foreign_documents/howardabrams/magit-demo
ARG HOWARD_ABRAMS_MAGIT_DEMO_REPO=https://github.com/howardabrams/magit-demo.git
RUN mkdir -p $HOWARD_ABRAMS_MAGIT_DEMO_PATH && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH remote add origin $HOWARD_ABRAMS_MAGIT_DEMO_REPO && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH fetch origin $HOWARD_ABRAMS_MAGIT_DEMO_VERSION && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_PDX_EMACS_HACKERS_VERSION=bfb7bd640fdf0ce3def21f9fc591ed35d776b26d
ARG HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH=/foreign_documents/howardabrams/pdx-emacs-hackers
ARG HOWARD_ABRAMS_PDX_EMACS_HACKERS_REPO=https://github.com/howardabrams/pdx-emacs-hackers.git
RUN mkdir -p $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH remote add origin $HOWARD_ABRAMS_PDX_EMACS_HACKERS_REPO && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH fetch origin $HOWARD_ABRAMS_PDX_EMACS_HACKERS_VERSION && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_FLORA_SIMULATOR_VERSION=50de13068722b9e3878f8598b749b7ccd14e7f8e
ARG HOWARD_ABRAMS_FLORA_SIMULATOR_PATH=/foreign_documents/howardabrams/flora-simulator
ARG HOWARD_ABRAMS_FLORA_SIMULATOR_REPO=https://github.com/howardabrams/flora-simulator.git
RUN mkdir -p $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH remote add origin $HOWARD_ABRAMS_FLORA_SIMULATOR_REPO && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH fetch origin $HOWARD_ABRAMS_FLORA_SIMULATOR_VERSION && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_VERSION=2d7a5e41001a1adf7ec24aeb6acc8525a72d7892
ARG HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH=/foreign_documents/howardabrams/literate-devops-demo
ARG HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_REPO=https://github.com/howardabrams/literate-devops-demo.git
RUN mkdir -p $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH remote add origin $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_REPO && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH fetch origin $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_VERSION && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_CLOJURE_YESQL_XP_VERSION=b651c7f8b47b2710e99fce9652980902bbc1c6c9
ARG HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH=/foreign_documents/howardabrams/clojure-yesql-xp
ARG HOWARD_ABRAMS_CLOJURE_YESQL_XP_REPO=https://github.com/howardabrams/clojure-yesql-xp.git
RUN mkdir -p $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH remote add origin $HOWARD_ABRAMS_CLOJURE_YESQL_XP_REPO && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH fetch origin $HOWARD_ABRAMS_CLOJURE_YESQL_XP_VERSION && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_VEEP_VERSION=e37fcf63a5c4a526255735ee34955528b3b280ae
ARG HOWARD_ABRAMS_VEEP_PATH=/foreign_documents/howardabrams/veep
ARG HOWARD_ABRAMS_VEEP_REPO=https://github.com/howardabrams/veep.git
RUN mkdir -p $HOWARD_ABRAMS_VEEP_PATH && git -C $HOWARD_ABRAMS_VEEP_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_VEEP_PATH remote add origin $HOWARD_ABRAMS_VEEP_REPO && git -C $HOWARD_ABRAMS_VEEP_PATH fetch origin $HOWARD_ABRAMS_VEEP_VERSION && git -C $HOWARD_ABRAMS_VEEP_PATH checkout FETCH_HEAD
ARG DOOMEMACS_VERSION=42d5fd83504f8aa80f3248036006fbcd49222943
ARG DOOMEMACS_PATH=/foreign_documents/doomemacs
ARG DOOMEMACS_REPO=https://github.com/doomemacs/doomemacs.git
RUN mkdir -p $DOOMEMACS_PATH && git -C $DOOMEMACS_PATH init --initial-branch=main && git -C $DOOMEMACS_PATH remote add origin $DOOMEMACS_REPO && git -C $DOOMEMACS_PATH fetch origin $DOOMEMACS_VERSION && git -C $DOOMEMACS_PATH checkout FETCH_HEAD
FROM tester as foreign-document-test FROM tester as foreign-document-test
@ -44,6 +94,8 @@ RUN apk add --no-cache bash coreutils
RUN mkdir /foreign_documents RUN mkdir /foreign_documents
COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode
COPY --from=build-emacs /root/emacs /foreign_documents/emacs COPY --from=build-emacs /root/emacs /foreign_documents/emacs
COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams
COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs
COPY foreign_document_test_entrypoint.sh /entrypoint.sh COPY foreign_document_test_entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"] ENTRYPOINT ["/entrypoint.sh"]

View File

@ -25,8 +25,25 @@ function main {
fi fi
PARSE="${CARGO_TARGET_DIR}/release-lto/parse" PARSE="${CARGO_TARGET_DIR}/release-lto/parse"
run_compare_function "org-mode" compare_all_org_document "/foreign_documents/org-mode" local all_status=0
run_compare_function "emacs" compare_all_org_document "/foreign_documents/emacs" set +e
(run_compare_function "org-mode" compare_all_org_document "/foreign_documents/org-mode")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "emacs" compare_all_org_document "/foreign_documents/emacs")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "howard_abrams" compare_howard_abrams)
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "doomemacs" compare_all_org_document "/foreign_documents/doomemacs")
if [ "$?" -ne 0 ]; then all_status=1; fi
set -e
if [ "$all_status" -ne 0 ]; then
echo "$(red_text "Some tests failed.")"
else
echo "$(green_text "All tests passed.")"
fi
return "$all_status"
} }
function green_text { function green_text {
@ -72,17 +89,22 @@ function run_compare_function {
function compare_all_org_document { function compare_all_org_document {
local root_dir="$1" local root_dir="$1"
local target_document local target_document
find "$root_dir" -type f -iname '*.org' | while read target_document; do local all_status=0
while read target_document; do
local relative_path=$($REALPATH --relative-to "$root_dir" "$target_document") local relative_path=$($REALPATH --relative-to "$root_dir" "$target_document")
set +e
(run_compare "$relative_path" "$target_document") (run_compare "$relative_path" "$target_document")
done if [ "$?" -ne 0 ]; then all_status=1; fi
set -e
done<<<$(find "$root_dir" -type f -iname '*.org')
return "$all_status"
} }
function run_compare { function run_compare {
local name="$1" local name="$1"
local target_document="$2" local target_document="$2"
set +e set +e
$PARSE "$target_document" &> /dev/null ($PARSE "$target_document" &> /dev/null)
local status=$? local status=$?
set -e set -e
if [ "$status" -eq 0 ]; then if [ "$status" -eq 0 ]; then
@ -93,4 +115,31 @@ function run_compare {
fi fi
} }
function compare_howard_abrams {
local all_status=0
set +e
(run_compare_function "dot-files" compare_all_org_document "/foreign_documents/howardabrams/dot-files")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "hamacs" compare_all_org_document "/foreign_documents/howardabrams/hamacs")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "demo-it" compare_all_org_document "/foreign_documents/howardabrams/demo-it")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "magit-demo" compare_all_org_document "/foreign_documents/howardabrams/magit-demo")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "pdx-emacs-hackers" compare_all_org_document "/foreign_documents/howardabrams/pdx-emacs-hackers")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "flora-simulator" compare_all_org_document "/foreign_documents/howardabrams/flora-simulator")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "literate-devops-demo" compare_all_org_document "/foreign_documents/howardabrams/literate-devops-demo")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "clojure-yesql-xp" compare_all_org_document "/foreign_documents/howardabrams/clojure-yesql-xp")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "veep" compare_all_org_document "/foreign_documents/howardabrams/veep")
if [ "$?" -ne 0 ]; then all_status=1; fi
set -e
return "$all_status"
}
main "${@}" main "${@}"

View File

@ -0,0 +1,8 @@
* Footnotes
[fn:1]
#+BEGIN_EXAMPLE
baz
#+END_EXAMPLE

View File

@ -1 +1,2 @@
- {{{foo(bar)}}} :: baz - {{{foo(bar)}}} :: baz
- =foo= :: bar

View File

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

View File

@ -0,0 +1 @@
- =foo :: bar= :: baz

View File

@ -1,3 +1,5 @@
1. 1.
2. 2.
3. 3.
* headline

View File

@ -0,0 +1,6 @@
src_elisp{(bar)}
*src_elisp{(bar)}*
| foo *bar* |
| foo src_elisp{(bar)} |
| foo *src_elisp{(bar)}* |

View File

@ -0,0 +1,8 @@
| Name | Price | Quantity | Total |
|------+-------+----------+-------|
| foo | 7 | 4 | 28 |
| bar | 3.5 | 3 | 10.5 |
|------+-------+----------+-------|
| | | 7 | 38.5 |
#+tblfm: $4=$2*$3::@>$4=vsum(@2..@-1)
#+tblfm: @>$3=vsum(@2..@-1)

View File

@ -0,0 +1 @@
#+title:foo:bar: baz: lorem: ipsum

View File

@ -0,0 +1,3 @@
*[fn:: /abcdef[fn::ghijklmnopqrstuvw]xyz/ r]*
*[fn:: /abcdef[fn::ghijk *lmnopq* rstuvw]xyz/ r]*

View File

@ -0,0 +1,4 @@
[/]
[/2]
[3/]
[%]

View File

@ -0,0 +1,2 @@
* TODO [#A] COMMENT foo bar
baz

View File

@ -0,0 +1,4 @@
* DONE foo
DEADLINE: <2023-09-08 Fri>
* DONE bar

View File

@ -0,0 +1 @@
* [0/4] foo

View File

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

View File

@ -6,8 +6,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${PROFILE:="perf"} : ${PROFILE:="perf"}
cd "$DIR/../"
function main { function main {
local additional_flags=() local additional_flags=()
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
@ -15,12 +13,12 @@ function main {
else else
additional_flags+=(--profile "$PROFILE") additional_flags+=(--profile "$PROFILE")
fi fi
cargo build --no-default-features "${additional_flags[@]}" (cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}")
perf record --freq=2000 --call-graph dwarf --output=perf.data target/${PROFILE}/parse "${@}" perf record --freq=2000 --call-graph dwarf --output="$DIR/../perf.data" "$DIR/../target/${PROFILE}/parse" "${@}"
# Convert to a format firefox will read # Convert to a format firefox will read
# flags to consider --show-info # flags to consider --show-info
perf script -F +pid --input perf.data > perf.firefox perf script -F +pid --input "$DIR/../perf.data" > "$DIR/../perf.firefox"
echo "You probably want to go to https://profiler.firefox.com/" echo "You probably want to go to https://profiler.firefox.com/"
echo "Either that or run hotspot" echo "Either that or run hotspot"

View File

@ -9,7 +9,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking : ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking
: ${NO_COLOR:=""} # Set to anything to disable color output : ${NO_COLOR:=""} # Set to anything to disable color output
cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath) REALPATH=$(command -v uu-realpath || command -v realpath)
MAKE=$(command -v gmake || command -v make) MAKE=$(command -v gmake || command -v make)
@ -56,10 +55,10 @@ function launch_container {
local full_path=$($REALPATH "$path") local full_path=$($REALPATH "$path")
local containing_folder=$(dirname "$full_path") local containing_folder=$(dirname "$full_path")
local file_name=$(basename "$full_path") local file_name=$(basename "$full_path")
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "${containing_folder}:/input:ro" -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}" -- "/input/$file_name" docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "${containing_folder}:/input:ro" -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}" -- "/input/$file_name"
done done
else else
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}" docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}"
fi fi
} }

View File

@ -5,7 +5,6 @@ set -euo pipefail
IFS=$'\n\t' IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath) REALPATH=$(command -v uu-realpath || command -v realpath)
############## Setup ######################### ############## Setup #########################

View File

@ -6,7 +6,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${NO_COLOR:=""} # Set to anything to disable color output : ${NO_COLOR:=""} # Set to anything to disable color output
cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath) REALPATH=$(command -v uu-realpath || command -v realpath)
MAKE=$(command -v gmake || command -v make) MAKE=$(command -v gmake || command -v make)
@ -56,7 +55,7 @@ cargo test --no-default-features --features compare --no-fail-fast --lib --test
EOF EOF
) )
docker run "${additional_flags[@]}" --init --rm --read-only --mount type=tmpfs,destination=/tmp -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script" docker run "${additional_flags[@]}" --init --rm --read-only --mount type=tmpfs,destination=/tmp -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script"
} }

View File

@ -4,7 +4,6 @@ set -euo pipefail
IFS=$'\n\t' IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath) REALPATH=$(command -v uu-realpath || command -v realpath)
function main { function main {
@ -12,7 +11,7 @@ function main {
local test local test
while read test; do while read test; do
cargo test --no-default-features --features compare --no-fail-fast --test test_loader "$test" -- --show-output (cd "$DIR/../" && cargo test --no-default-features --features compare --no-fail-fast --test test_loader "$test" -- --show-output)
done<<<"$test_names" done<<<"$test_names"
} }

View File

@ -7,8 +7,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${PROFILE:="release-lto"} : ${PROFILE:="release-lto"}
cd "$DIR/../"
function main { function main {
local additional_flags=() local additional_flags=()
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
@ -16,8 +14,8 @@ function main {
else else
additional_flags+=(--profile "$PROFILE") additional_flags+=(--profile "$PROFILE")
fi fi
cargo build --no-default-features "${additional_flags[@]}" (cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}")
time ./target/${PROFILE}/parse "${@}" time "$DIR/../target/${PROFILE}/parse" "${@}"
} }
main "${@}" main "${@}"

View File

@ -1,3 +1,4 @@
use std::collections::BTreeSet;
use std::collections::HashSet; use std::collections::HashSet;
use super::util::assert_bounds; use super::util::assert_bounds;
@ -45,6 +46,7 @@ use crate::types::PlainList;
use crate::types::PlainListItem; use crate::types::PlainListItem;
use crate::types::PlainText; use crate::types::PlainText;
use crate::types::Planning; use crate::types::Planning;
use crate::types::PriorityCookie;
use crate::types::PropertyDrawer; use crate::types::PropertyDrawer;
use crate::types::RadioLink; use crate::types::RadioLink;
use crate::types::RadioTarget; use crate::types::RadioTarget;
@ -490,7 +492,7 @@ fn compare_heading<'s>(
if rust.stars.to_string() != level { if rust.stars.to_string() != level {
this_status = DiffStatus::Bad; this_status = DiffStatus::Bad;
message = Some(format!( message = Some(format!(
"Headline level do not much (emacs != rust): {} != {}", "Headline level do not match (emacs != rust): {} != {}",
level, rust.stars level, rust.stars
)) ))
} }
@ -553,7 +555,57 @@ fn compare_heading<'s>(
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
child_status.push(artificial_diff_scope("title".to_owned(), title_status)?); child_status.push(artificial_diff_scope("title".to_owned(), title_status)?);
// TODO: Compare priority, :footnote-section-p, :archivedp, :commentedp // Compare priority
let priority = get_property(emacs, ":priority")?;
match (priority, rust.priority_cookie) {
(None, None) => {}
(None, Some(_)) | (Some(_), None) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Priority cookie mismatch (emacs != rust) {:?} != {:?}",
priority, rust.priority_cookie
));
}
(Some(emacs_priority_cookie), Some(rust_priority_cookie)) => {
let emacs_priority_cookie =
emacs_priority_cookie.as_atom()?.parse::<PriorityCookie>()?;
if emacs_priority_cookie != rust_priority_cookie {
this_status = DiffStatus::Bad;
message = Some(format!(
"Priority cookie mismatch (emacs != rust) {:?} != {:?}",
emacs_priority_cookie, rust_priority_cookie
));
}
}
}
// Compare archived
let archived = get_property(emacs, ":archivedp")?;
match (archived, rust.is_archived) {
(None, true) | (Some(_), false) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"archived mismatch (emacs != rust) {:?} != {:?}",
archived, rust.is_archived
));
}
(None, false) | (Some(_), true) => {}
}
// Compare commented
let commented = get_property(emacs, ":commentedp")?;
match (commented, rust.is_comment) {
(None, true) | (Some(_), false) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"commented mismatch (emacs != rust) {:?} != {:?}",
commented, rust.is_comment
));
}
(None, false) | (Some(_), true) => {}
}
// TODO: Compare :footnote-section-p
// Compare section // Compare section
let section_status = children let section_status = children
@ -1024,6 +1076,44 @@ fn compare_table<'s>(
Ok(_) => {} Ok(_) => {}
}; };
// Compare formulas
//
// :tblfm is either nil or a list () filled with quoted strings containing the value for any tblfm keywords at the end of the table.
let emacs_formulas = get_property(emacs, ":tblfm")?;
if let Some(emacs_formulas) = emacs_formulas {
let emacs_formulas = emacs_formulas.as_list()?;
if emacs_formulas.len() != rust.formulas.len() {
this_status = DiffStatus::Bad;
message = Some(format!(
"Formulas do not match (emacs != rust): {:?} != {:?}",
emacs_formulas, rust.formulas
))
} else {
let atoms = emacs_formulas
.into_iter()
.map(Token::as_atom)
.collect::<Result<Vec<_>, _>>()?;
let unquoted = atoms
.into_iter()
.map(unquote)
.collect::<Result<BTreeSet<_>, _>>()?;
for kw in &rust.formulas {
if !unquoted.contains(kw.value) {
this_status = DiffStatus::Bad;
message = Some(format!("Could not find formula in emacs: {}", kw.value))
}
}
}
} else {
if !rust.formulas.is_empty() {
this_status = DiffStatus::Bad;
message = Some(format!(
"Formulas do not match (emacs != rust): {:?} != {:?}",
emacs_formulas, rust.formulas
))
}
}
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_table_row(source, emacs_child, rust_child)?); child_status.push(compare_table_row(source, emacs_child, rust_child)?);
} }
@ -1061,6 +1151,10 @@ fn compare_table_row<'s>(
Ok(_) => {} Ok(_) => {}
}; };
// TODO: Compare :type
//
// :type is an unquoted atom of either standard or rule
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_table_cell(source, emacs_child, rust_child)?); child_status.push(compare_table_cell(source, emacs_child, rust_child)?);
} }

View File

@ -141,6 +141,11 @@ fn maybe_token_to_usize(
.map_or(Ok(None), |r| r.map(Some))?) .map_or(Ok(None), |r| r.map(Some))?)
} }
/// Get a named property from the emacs token.
///
/// Returns Ok(None) if value is nil.
///
/// Returns error if the attribute is not specified on the token at all.
pub fn get_property<'s, 'x>( pub fn get_property<'s, 'x>(
emacs: &'s Token<'s>, emacs: &'s Token<'s>,
key: &'x str, key: &'x str,

View File

@ -21,6 +21,9 @@ pub enum ContextElement<'r, 's> {
/// Stores the name of the current element to prevent directly nesting elements of the same type. /// Stores the name of the current element to prevent directly nesting elements of the same type.
Context(&'r str), Context(&'r str),
/// Stores the name of the current object to prevent directly nesting elements of the same type.
ContextObject(&'r str),
/// Indicates if elements should consume the whitespace after them. /// Indicates if elements should consume the whitespace after them.
ConsumeTrailingWhitespace(bool), ConsumeTrailingWhitespace(bool),
@ -105,7 +108,7 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
&'r self, &'r self,
i: OrgSource<'s>, i: OrgSource<'s>,
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> { ) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> {
let mut current_class_filter = ExitClass::Gamma; let mut current_class_filter = ExitClass::Delta;
for current_node in self.iter_context() { for current_node in self.iter_context() {
let context_element = current_node.get_data(); let context_element = current_node.get_data();
match context_element { match context_element {

View File

@ -1,16 +1,10 @@
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum ExitClass { pub enum ExitClass {
/// Headlines and sections.
Document = 1, Document = 1,
Alpha = 2,
/// Elements who take priority over beta elements when matching. Beta = 3,
Alpha = 20, Gamma = 4,
Delta = 5,
/// Elements who cede priority to alpha elements when matching.
Beta = 300,
/// Elements who cede priority to alpha and beta elements when matching.
Gamma = 4000,
} }
impl std::fmt::Display for ExitClass { impl std::fmt::Display for ExitClass {

View File

@ -1,57 +1,28 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::all_consuming; use nom::combinator::all_consuming;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0; use nom::multi::many0;
use nom::multi::many1;
use nom::multi::many1_count;
use nom::multi::many_till;
use nom::multi::separated_list1;
use nom::sequence::tuple;
use super::headline::heading;
use super::in_buffer_settings::apply_in_buffer_settings; use super::in_buffer_settings::apply_in_buffer_settings;
use super::in_buffer_settings::scan_for_in_buffer_settings; use super::in_buffer_settings::scan_for_in_buffer_settings;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::section::zeroth_section;
use super::token::AllTokensIterator; use super::token::AllTokensIterator;
use super::token::Token; use super::token::Token;
use super::util::exit_matcher_parser;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::start_of_line;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::Context; use crate::context::Context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::comment::comment;
use crate::parser::element_parser::element;
use crate::parser::object_parser::standard_set_object;
use crate::parser::org_source::convert_error; use crate::parser::org_source::convert_error;
use crate::parser::planning::planning;
use crate::parser::property_drawer::property_drawer;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::types::Document; use crate::types::Document;
use crate::types::DocumentElement;
use crate::types::Element;
use crate::types::Heading;
use crate::types::Object; use crate::types::Object;
use crate::types::Section;
use crate::types::TodoKeywordType;
/// Parse a full org-mode document. /// Parse a full org-mode document.
/// ///
@ -185,312 +156,6 @@ fn _document<'b, 'g, 'r, 's>(
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn zeroth_section<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("section"),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &section_end,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let without_consuming_whitespace_context = ContextElement::ConsumeTrailingWhitespace(false);
let without_consuming_whitespace_context =
parser_context.with_additional_node(&without_consuming_whitespace_context);
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, comment_and_property_drawer_element) = opt(tuple((
opt(parser_with_context!(comment)(
&without_consuming_whitespace_context,
)),
parser_with_context!(property_drawer)(context),
many0(blank_line),
)))(input)?;
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!children.is_empty() || comment_and_property_drawer_element.is_some()
},
)(remaining)?;
comment_and_property_drawer_element.map(|(comment, property_drawer, _ws)| {
children.insert(0, Element::PropertyDrawer(property_drawer));
comment
.map(Element::Comment)
.map(|ele| children.insert(0, ele));
});
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
mut input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("section"),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &section_end,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (mut remaining, (planning_element, property_drawer_element)) = tuple((
opt(parser_with_context!(planning)(&parser_context)),
opt(parser_with_context!(property_drawer)(&parser_context)),
))(input)?;
if planning_element.is_none() && property_drawer_element.is_none() {
let (remain, _ws) = many0(blank_line)(remaining)?;
remaining = remain;
input = remain;
}
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!children.is_empty() || property_drawer_element.is_some() || planning_element.is_some()
},
)(remaining)?;
property_drawer_element
.map(Element::PropertyDrawer)
.map(|ele| children.insert(0, ele));
planning_element
.map(Element::Planning)
.map(|ele| children.insert(0, ele));
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(detect_headline)(input)
}
const fn heading(
parent_stars: usize,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Heading<'s>> {
move |context, input: OrgSource<'_>| _heading(context, input, parent_stars)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _heading<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<OrgSource<'s>, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?;
let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) =
headline(context, input, parent_stars)?;
let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading(star_count))(context);
let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, mut children) =
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
if let Some(section) = maybe_section {
children.insert(0, section);
}
let remaining = if children.is_empty() {
// Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?;
remain
} else {
remaining
};
let source = get_consumed(input, remaining);
Ok((
remaining,
Heading {
source: source.into(),
stars: star_count,
todo_keyword: maybe_todo_keyword.map(|((todo_keyword_type, todo_keyword), _ws)| {
(todo_keyword_type, Into::<&str>::into(todo_keyword))
}),
title,
tags: heading_tags,
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, many1(tag("*")), space1))(input)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<
OrgSource<'s>,
(
usize,
OrgSource<'s>,
Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>,
Vec<Object<'s>>,
Vec<&'s str>,
),
> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &headline_title_end,
});
let parser_context = context.with_additional_node(&parser_context);
let (
remaining,
(_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending),
) = tuple((
start_of_line,
verify(many1_count(tag("*")), |star_count| {
*star_count > parent_stars
}),
space1,
opt(tuple((
parser_with_context!(heading_keyword)(&parser_context),
space1,
))),
many1(parser_with_context!(standard_set_object)(&parser_context)),
opt(tuple((space0, tags))),
space0,
alt((line_ending, eof)),
))(input)?;
Ok((
remaining,
(
star_count,
ws,
maybe_todo_keyword,
title,
maybe_tags
.map(|(_ws, tags)| {
tags.into_iter()
.map(|single_tag| Into::<&str>::into(single_tag))
.collect()
})
.unwrap_or(Vec::new()),
),
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline_title_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
opt(tuple((space0, tags, space0))),
alt((line_ending, eof)),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn tags<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
let (remaining, (_open, tags, _close)) =
tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?;
Ok((remaining, tags))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_@#%".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn heading_keyword<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (TodoKeywordType, OrgSource<'s>)> {
let global_settings = context.get_global_settings();
if global_settings.in_progress_todo_keywords.is_empty()
&& global_settings.complete_todo_keywords.is_empty()
{
alt((
map(tag("TODO"), |capture| (TodoKeywordType::Todo, capture)),
map(tag("DONE"), |capture| (TodoKeywordType::Done, capture)),
))(input)
} else {
for todo_keyword in global_settings
.in_progress_todo_keywords
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Todo, ent)));
}
Err(_) => {}
}
}
for todo_keyword in global_settings
.complete_todo_keywords
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Done, ent)));
}
Err(_) => {}
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoTodoKeyword".into(),
))))
}
}
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>> {
AllTokensIterator::new(Token::Document(self)) AllTokensIterator::new(Token::Document(self))

View File

@ -4,13 +4,16 @@ use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while; use nom::bytes::complete::take_while;
use nom::character::complete::digit1; use nom::character::complete::digit1;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1; use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::include_input;
use super::util::WORD_CONSTITUENT_CHARACTERS; use super::util::WORD_CONSTITUENT_CHARACTERS;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
@ -41,8 +44,15 @@ pub fn footnote_definition<'b, 'g, 'r, 's>(
} }
start_of_line(input)?; start_of_line(input)?;
// Cannot be indented. // Cannot be indented.
let (remaining, (_lead_in, lbl, _lead_out, _ws)) = let (remaining, (_, lbl, _, _, _)) = tuple((
tuple((tag_no_case("[fn:"), label, tag("]"), space0))(input)?; tag_no_case("[fn:"),
label,
tag("]"),
space0,
opt(verify(many0(blank_line), |lines: &Vec<OrgSource<'_>>| {
lines.len() <= 2
})),
))(input)?;
let contexts = [ let contexts = [
ContextElement::ConsumeTrailingWhitespace(true), ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("footnote definition"), ContextElement::Context("footnote definition"),
@ -54,11 +64,22 @@ pub fn footnote_definition<'b, 'g, 'r, 's>(
let parser_context = context.with_additional_node(&contexts[0]); let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]); let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]); let parser_context = parser_context.with_additional_node(&contexts[2]);
// TODO: The problem is we are not accounting for trailing whitespace like we do in section. Maybe it would be easier if we passed down whether or not to parse trailing whitespace into the element matcher similar to how tag takes in parameters.
let element_matcher = parser_with_context!(element(true))(&parser_context); let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) = let (mut remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?; many_till(include_input(element_matcher), exit_matcher)(remaining)?;
// Re-parse the last element of the footnote definition with consume trailing whitespace off because the trailing whitespace needs to belong to the footnote definition, not the contents.
if context.should_consume_trailing_whitespace() {
if let Some((final_item_input, _)) = children.pop() {
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
let final_item_context = parser_context.with_additional_node(&final_item_context);
let (remain, reparsed_final_item) =
parser_with_context!(element(true))(&final_item_context)(final_item_input)?;
children.push((final_item_input, reparsed_final_item));
remaining = remain;
}
}
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@ -66,7 +87,7 @@ pub fn footnote_definition<'b, 'g, 'r, 's>(
FootnoteDefinition { FootnoteDefinition {
source: source.into(), source: source.into(),
label: lbl.into(), label: lbl.into(),
children, children: children.into_iter().map(|(_, item)| item).collect(),
}, },
)) ))
} }

View File

@ -42,7 +42,7 @@ fn anonymous_footnote<'b, 'g, 'r, 's>(
let (remaining, _) = tag_no_case("[fn::")(input)?; let (remaining, _) = tag_no_case("[fn::")(input)?;
let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth()); let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
}); });
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);
@ -78,7 +78,7 @@ fn inline_footnote<'b, 'g, 'r, 's>(
let (remaining, _) = tag(":")(remaining)?; let (remaining, _) = tag(":")(remaining)?;
let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth()); let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
}); });
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);

258
src/parser/headline.rs Normal file
View File

@ -0,0 +1,258 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1;
use nom::multi::many1_count;
use nom::multi::separated_list1;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::section::section;
use super::util::get_consumed;
use super::util::start_of_line;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::object_parser::standard_set_object;
use crate::parser::util::blank_line;
use crate::types::DocumentElement;
use crate::types::Heading;
use crate::types::Object;
use crate::types::PriorityCookie;
use crate::types::TodoKeywordType;
pub const fn heading(
parent_stars: usize,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Heading<'s>> {
move |context, input: OrgSource<'_>| _heading(context, input, parent_stars)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _heading<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<OrgSource<'s>, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?;
let (
remaining,
(star_count, maybe_todo_keyword, maybe_priority, maybe_comment, title, heading_tags),
) = headline(context, input, parent_stars)?;
let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading(star_count))(context);
let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
let (remaining, mut children) =
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
if let Some(section) = maybe_section {
children.insert(0, section);
}
let remaining = if children.is_empty() {
// Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?;
remain
} else {
remaining
};
let is_archived = heading_tags.contains(&"ARCHIVE");
let source = get_consumed(input, remaining);
Ok((
remaining,
Heading {
source: source.into(),
stars: star_count,
todo_keyword: maybe_todo_keyword.map(|((todo_keyword_type, todo_keyword), _ws)| {
(todo_keyword_type, Into::<&str>::into(todo_keyword))
}),
priority_cookie: maybe_priority.map(|(priority, _)| priority),
title,
tags: heading_tags,
children,
is_comment: maybe_comment.is_some(),
is_archived,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, many1(tag("*")), space1))(input)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<
OrgSource<'s>,
(
usize,
Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>,
Option<(PriorityCookie, OrgSource<'s>)>,
Option<(OrgSource<'s>, OrgSource<'s>)>,
Vec<Object<'s>>,
Vec<&'s str>,
),
> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &headline_title_end,
});
let parser_context = context.with_additional_node(&parser_context);
let (
remaining,
(
_,
star_count,
_,
maybe_todo_keyword,
maybe_priority,
maybe_comment,
title,
maybe_tags,
_,
_,
),
) = tuple((
start_of_line,
verify(many1_count(tag("*")), |star_count| {
*star_count > parent_stars
}),
space1,
opt(tuple((
parser_with_context!(heading_keyword)(&parser_context),
space1,
))),
opt(tuple((priority_cookie, space1))),
opt(tuple((tag("COMMENT"), space1))),
many1(parser_with_context!(standard_set_object)(&parser_context)),
opt(tuple((space0, tags))),
space0,
alt((line_ending, eof)),
))(input)?;
Ok((
remaining,
(
star_count,
maybe_todo_keyword,
maybe_priority,
maybe_comment,
title,
maybe_tags
.map(|(_ws, tags)| {
tags.into_iter()
.map(|single_tag| Into::<&str>::into(single_tag))
.collect()
})
.unwrap_or(Vec::new()),
),
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline_title_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
opt(tuple((space0, tags, space0))),
alt((line_ending, eof)),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn tags<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
let (remaining, (_open, tags, _close)) =
tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?;
Ok((remaining, tags))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_@#%".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn heading_keyword<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (TodoKeywordType, OrgSource<'s>)> {
let global_settings = context.get_global_settings();
if global_settings.in_progress_todo_keywords.is_empty()
&& global_settings.complete_todo_keywords.is_empty()
{
alt((
map(tag("TODO"), |capture| (TodoKeywordType::Todo, capture)),
map(tag("DONE"), |capture| (TodoKeywordType::Done, capture)),
))(input)
} else {
for todo_keyword in global_settings
.in_progress_todo_keywords
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Todo, ent)));
}
Err(_) => {}
}
}
for todo_keyword in global_settings
.complete_todo_keywords
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Done, ent)));
}
Err(_) => {}
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoTodoKeyword".into(),
))))
}
}
fn priority_cookie<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PriorityCookie> {
let (remaining, (_, priority_character, _)) = tuple((
tag("[#"),
verify(anychar, |c| c.is_alphanumeric()),
tag("]"),
))(input)?;
let cookie = PriorityCookie::try_from(priority_character).map_err(|_| {
nom::Err::Error(CustomError::MyError(MyError(
"Failed to cast priority cookie to number.".into(),
)))
})?;
Ok((remaining, cookie))
}

View File

@ -5,8 +5,8 @@ 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::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed; use nom::combinator::consumed;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::not; use nom::combinator::not;
@ -66,7 +66,7 @@ fn _filtered_keyword<'s, F: Matcher>(
} }
Err(_) => {} Err(_) => {}
}; };
let (remaining, _ws) = space1(remaining)?; let (remaining, _ws) = space0(remaining)?;
let (remaining, parsed_value) = recognize(many_till( let (remaining, parsed_value) = recognize(many_till(
anychar, anychar,
peek(tuple((space0, alt((line_ending, eof))))), peek(tuple((space0, alt((line_ending, eof))))),
@ -111,13 +111,30 @@ fn babel_call_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>
tag_no_case("call")(input) tag_no_case("call")(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_formula_keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(table_formula_key)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn table_formula_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag_no_case("tblfm")(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn regular_keyword_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn regular_keyword_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple(( not(peek(alt((tag_no_case("call"), tag_no_case("begin")))))(input)?;
not(peek(tag_no_case("call"))), recognize(many_till(
not(peek(tag_no_case("begin"))), anychar,
is_not(" \t\r\n:"), peek(alt((
)))(input) recognize(one_of(" \t\r\n")), // Give up if we hit whitespace
recognize(tuple((tag(":"), one_of(" \t\r\n")))), // Stop if we see a colon followed by whitespace
recognize(tuple((tag(":"), is_not(" \t\r\n:"), not(tag(":"))))), // Stop if we see a colon that is the last colon before whitespace. This is for keywords like "#+foo:bar:baz: lorem: ipsum" which would have the key "foo:bar:baz".
))),
))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@ -14,6 +14,7 @@ mod fixed_width_area;
mod footnote_definition; mod footnote_definition;
mod footnote_reference; mod footnote_reference;
mod greater_block; mod greater_block;
mod headline;
mod horizontal_rule; mod horizontal_rule;
mod in_buffer_settings; mod in_buffer_settings;
mod inline_babel_call; mod inline_babel_call;
@ -35,6 +36,7 @@ mod planning;
mod property_drawer; mod property_drawer;
mod radio_link; mod radio_link;
mod regular_link; mod regular_link;
mod section;
pub mod sexp; pub mod sexp;
mod statistics_cookie; mod statistics_cookie;
mod subscript_and_superscript; mod subscript_and_superscript;

View File

@ -4,8 +4,11 @@ use nom::combinator::map;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::plain_text::plain_text; use super::plain_text::plain_text;
use super::regular_link::regular_link; use super::regular_link::regular_link;
use super::subscript_and_superscript::detect_subscript_or_superscript;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
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::citation::citation;
@ -34,54 +37,11 @@ pub fn standard_set_object<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( let (remaining, object) = alt((
map(parser_with_context!(timestamp)(context), Object::Timestamp), parser_with_context!(standard_set_object_sans_plain_text)(context),
map(parser_with_context!(subscript)(context), Object::Subscript),
map( map(
parser_with_context!(superscript)(context), parser_with_context!(plain_text(detect_standard_set_object_sans_plain_text))(context),
Object::Superscript, Object::PlainText,
), ),
map(
parser_with_context!(statistics_cookie)(context),
Object::StatisticsCookie,
),
map(parser_with_context!(target)(context), Object::Target),
map(parser_with_context!(line_break)(context), Object::LineBreak),
map(
parser_with_context!(inline_source_block)(context),
Object::InlineSourceBlock,
),
map(
parser_with_context!(inline_babel_call)(context),
Object::InlineBabelCall,
),
map(parser_with_context!(citation)(context), Object::Citation),
map(
parser_with_context!(footnote_reference)(context),
Object::FootnoteReference,
),
map(
parser_with_context!(export_snippet)(context),
Object::ExportSnippet,
),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
map(parser_with_context!(radio_link)(context), Object::RadioLink),
map(
parser_with_context!(radio_target)(context),
Object::RadioTarget,
),
parser_with_context!(text_markup)(context),
map(
parser_with_context!(regular_link)(context),
Object::RegularLink,
),
map(parser_with_context!(plain_link)(context), Object::PlainLink),
map(parser_with_context!(angle_link)(context), Object::AngleLink),
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
map(parser_with_context!(plain_text)(context), Object::PlainText),
))(input)?; ))(input)?;
Ok((remaining, object)) Ok((remaining, object))
} }
@ -92,24 +52,17 @@ pub fn minimal_set_object<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( let (remaining, object) = alt((
map(parser_with_context!(subscript)(context), Object::Subscript), parser_with_context!(minimal_set_object_sans_plain_text)(context),
map( map(
parser_with_context!(superscript)(context), parser_with_context!(plain_text(detect_minimal_set_object_sans_plain_text))(context),
Object::Superscript, Object::PlainText,
), ),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
parser_with_context!(text_markup)(context),
map(parser_with_context!(plain_text)(context), Object::PlainText),
))(input)?; ))(input)?;
Ok((remaining, object)) Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn any_object_except_plain_text<'b, 'g, 'r, 's>( fn standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
@ -166,7 +119,80 @@ pub fn any_object_except_plain_text<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link_description_object_set<'b, 'g, 'r, 's>( fn minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
map(parser_with_context!(subscript)(context), Object::Subscript),
map(
parser_with_context!(superscript)(context),
Object::Superscript,
),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
parser_with_context!(text_markup)(context),
))(input)?;
Ok((remaining, object))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn detect_standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if detect_subscript_or_superscript(input).is_ok() {
return Ok((input, ()));
}
if standard_set_object_sans_plain_text(context, input).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if detect_subscript_or_superscript(input).is_ok() {
return Ok((input, ()));
}
if minimal_set_object_sans_plain_text(context, input).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link_description_set_object<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
let (remaining, object) = alt((
parser_with_context!(regular_link_description_set_object_sans_plain_text)(context),
map(
parser_with_context!(plain_text(
detect_regular_link_description_set_object_sans_plain_text
))(context),
Object::PlainText,
),
))(input)?;
Ok((remaining, object))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
@ -189,15 +215,47 @@ pub fn regular_link_description_object_set<'b, 'g, 'r, 's>(
Object::InlineBabelCall, Object::InlineBabelCall,
), ),
map(parser_with_context!(org_macro)(context), Object::OrgMacro), map(parser_with_context!(org_macro)(context), Object::OrgMacro),
parser_with_context!(minimal_set_object)(context), parser_with_context!(minimal_set_object_sans_plain_text)(context),
))(input)?; ))(input)?;
Ok((remaining, object)) Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn detect_regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if detect_subscript_or_superscript(input).is_ok() {
return Ok((input, ()));
}
if regular_link_description_set_object_sans_plain_text(context, input).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_cell_set_object<'b, 'g, 'r, 's>( pub fn table_cell_set_object<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
parser_with_context!(table_cell_set_object_sans_plain_text)(context),
map(
parser_with_context!(plain_text(detect_table_cell_set_object_sans_plain_text))(context),
Object::PlainText,
),
))(input)?;
Ok((remaining, object))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( let (remaining, object) = alt((
map(parser_with_context!(citation)(context), Object::Citation), map(parser_with_context!(citation)(context), Object::Citation),
@ -223,7 +281,24 @@ pub fn table_cell_set_object<'b, 'g, 'r, 's>(
), ),
map(parser_with_context!(target)(context), Object::Target), map(parser_with_context!(target)(context), Object::Target),
map(parser_with_context!(timestamp)(context), Object::Timestamp), map(parser_with_context!(timestamp)(context), Object::Timestamp),
parser_with_context!(minimal_set_object)(context), parser_with_context!(minimal_set_object_sans_plain_text)(context),
))(input)?; ))(input)?;
Ok((remaining, object)) Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn detect_table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if detect_subscript_or_superscript(input).is_ok() {
return Ok((input, ()));
}
if table_cell_set_object_sans_plain_text(context, input).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
}

View File

@ -145,6 +145,9 @@ where
if new_end > self.end { if new_end > self.end {
panic!("Attempted to extend past the end of the WrappedInput.") panic!("Attempted to extend past the end of the WrappedInput.")
} }
if new_start == self.start && new_end == self.end {
return self.clone();
}
let skipped_text = &self.full_source[self.start..new_start]; let skipped_text = &self.full_source[self.start..new_start];
let mut start_of_line = self.start_of_line; let mut start_of_line = self.start_of_line;
@ -183,7 +186,7 @@ where
start: new_start, start: new_start,
end: new_end, end: new_end,
start_of_line, start_of_line,
preceding_character: skipped_text.chars().last(), preceding_character: skipped_text.chars().last().or(self.preceding_character),
bracket_depth, bracket_depth,
brace_depth, brace_depth,
parenthesis_depth, parenthesis_depth,

View File

@ -19,6 +19,7 @@ use nom::sequence::tuple;
use super::element_parser::element; use super::element_parser::element;
use super::object_parser::standard_set_object; use super::object_parser::standard_set_object;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::include_input;
use super::util::non_whitespace_character; use super::util::non_whitespace_character;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
@ -152,13 +153,12 @@ pub fn plain_list_item<'b, 'g, 'r, 's>(
// TODO: parse checkbox // TODO: parse checkbox
let (remaining, maybe_tag) = opt(tuple(( let (remaining, maybe_tag) =
space1, opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)?;
parser_with_context!(item_tag)(context),
tag(" ::"), let maybe_contentless_item: Res<OrgSource<'_>, ()> = peek(parser_with_context!(
)))(remaining)?; detect_contentless_item_contents
let maybe_contentless_item: Res<OrgSource<'_>, OrgSource<'_>> = )(context))(remaining);
peek(recognize(tuple((many0(blank_line), eof))))(remaining);
match maybe_contentless_item { match maybe_contentless_item {
Ok((_rem, _ws)) => { Ok((_rem, _ws)) => {
let (remaining, _trailing_ws) = opt(blank_line)(remaining)?; let (remaining, _trailing_ws) = opt(blank_line)(remaining)?;
@ -170,7 +170,7 @@ pub fn plain_list_item<'b, 'g, 'r, 's>(
indentation: indent_level, indentation: indent_level,
bullet: bull.into(), bullet: bull.into(),
tag: maybe_tag tag: maybe_tag
.map(|(_ws, item_tag, _divider)| item_tag) .map(|(_ws, item_tag)| item_tag)
.unwrap_or(Vec::new()), .unwrap_or(Vec::new()),
children: Vec::new(), children: Vec::new(),
}, },
@ -219,25 +219,13 @@ pub fn plain_list_item<'b, 'g, 'r, 's>(
indentation: indent_level, indentation: indent_level,
bullet: bull.into(), bullet: bull.into(),
tag: maybe_tag tag: maybe_tag
.map(|(_ws, item_tag, _divider)| item_tag) .map(|(_ws, item_tag)| item_tag)
.unwrap_or(Vec::new()), .unwrap_or(Vec::new()),
children: children.into_iter().map(|(_start, item)| item).collect(), children: children.into_iter().map(|(_start, item)| item).collect(),
}, },
)); ));
} }
fn include_input<'s, F, O>(
mut inner: F,
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, (OrgSource<'s>, O)>
where
F: FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O>,
{
move |input: OrgSource<'_>| {
let (remaining, output) = inner(input)?;
Ok((remaining, (input, output)))
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn bullet<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn bullet<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt(( alt((
@ -313,11 +301,18 @@ fn item_tag<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let contexts = [
class: ExitClass::Gamma, ContextElement::ExitMatcherNode(ExitMatcherNode {
exit_matcher: &item_tag_end, class: ExitClass::Gamma,
}); exit_matcher: &item_tag_line_ending_end,
let parser_context = context.with_additional_node(&parser_context); }),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Delta,
exit_matcher: &item_tag_end,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
// TODO: Should this be using a different set like the minimal set? // TODO: Should this be using a different set like the minimal set?
@ -326,6 +321,7 @@ fn item_tag<'b, 'g, 'r, 's>(
), ),
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),
)(input)?; )(input)?;
let (remaining, _) = tuple((one_of(" \t"), tag("::")))(remaining)?;
Ok((remaining, children)) Ok((remaining, children))
} }
@ -334,13 +330,21 @@ fn item_tag_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(alt(( recognize(tuple((
line_ending, one_of(" \t"),
tag(" :: "), tag("::"),
recognize(tuple((tag(" ::"), alt((line_ending, eof))))), alt((recognize(one_of(" \t")), line_ending, eof)),
)))(input) )))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag_line_ending_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
line_ending(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag_post_gap<'b, 'g, 'r, 's>( fn item_tag_post_gap<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
@ -362,6 +366,18 @@ fn item_tag_post_gap<'b, 'g, 'r, 's>(
)(input) )(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let (remaining, _) = recognize(many_till(
blank_line,
parser_with_context!(exit_matcher_parser)(context),
))(input)?;
Ok((remaining, ()))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -7,7 +7,6 @@ use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::object_parser::any_object_except_plain_text;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::radio_link::RematchObject; use super::radio_link::RematchObject;
use super::util::exit_matcher_parser; use super::util::exit_matcher_parser;
@ -17,17 +16,42 @@ use crate::error::Res;
use crate::types::Object; use crate::types::Object;
use crate::types::PlainText; use crate::types::PlainText;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub fn plain_text<F>(
pub fn plain_text<'b, 'g, 'r, 's>( end_condition: F,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainText<'s>>
where
F: for<'bb, 'gg, 'rr, 'ss> Fn(
RefContext<'bb, 'gg, 'rr, 'ss>,
OrgSource<'ss>,
) -> Res<OrgSource<'ss>, ()>,
{
move |context, input| _plain_text(&end_condition, context, input)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(end_condition))
)]
fn _plain_text<'b, 'g, 'r, 's, F>(
end_condition: F,
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainText<'s>> { ) -> Res<OrgSource<'s>, PlainText<'s>>
where
F: for<'bb, 'gg, 'rr, 'ss> Fn(
RefContext<'bb, 'gg, 'rr, 'ss>,
OrgSource<'ss>,
) -> Res<OrgSource<'ss>, ()>,
{
let (remaining, source) = recognize(verify( let (remaining, source) = recognize(verify(
many_till( many_till(
anychar, anychar,
peek(alt(( peek(alt((
parser_with_context!(exit_matcher_parser)(context), parser_with_context!(exit_matcher_parser)(context),
parser_with_context!(plain_text_end)(context), recognize(parser_with_context!(end_condition)(context)),
))), ))),
), ),
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),
@ -41,14 +65,6 @@ pub fn plain_text<'b, 'g, 'r, 's>(
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn plain_text_end<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
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> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn rematch_object<'b, 'g, 'r, 's>( fn rematch_object<'b, 'g, 'r, 's>(
@ -73,6 +89,7 @@ mod tests {
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::parser::object_parser::detect_standard_set_object_sans_plain_text;
use crate::types::Source; use crate::types::Source;
#[test] #[test]
@ -81,7 +98,9 @@ mod tests {
let global_settings = GlobalSettings::default(); let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context(); let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context)); let initial_context = Context::new(&global_settings, List::new(&initial_context));
let plain_text_matcher = parser_with_context!(plain_text)(&initial_context); let plain_text_matcher = parser_with_context!(plain_text(
detect_standard_set_object_sans_plain_text
))(&initial_context);
let (remaining, result) = map(plain_text_matcher, Object::PlainText)(input).unwrap(); let (remaining, result) = map(plain_text_matcher, Object::PlainText)(input).unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_source(), Into::<&str>::into(input)); assert_eq!(result.get_source(), Into::<&str>::into(input));

View File

@ -10,6 +10,7 @@ use nom::multi::separated_list1;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
@ -18,7 +19,7 @@ use crate::types::Planning;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn planning<'b, 'g, 'r, 's>( pub fn planning<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Planning<'s>> { ) -> Res<OrgSource<'s>, Planning<'s>> {
start_of_line(input)?; start_of_line(input)?;
@ -26,6 +27,8 @@ pub fn planning<'b, 'g, 'r, 's>(
let (remaining, _planning_parameters) = separated_list1(space1, planning_parameter)(remaining)?; let (remaining, _planning_parameters) = separated_list1(space1, planning_parameter)(remaining)?;
let (remaining, _trailing_ws) = tuple((space0, alt((line_ending, eof))))(remaining)?; let (remaining, _trailing_ws) = tuple((space0, alt((line_ending, eof))))(remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((

View File

@ -6,7 +6,7 @@ use nom::character::complete::one_of;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::object_parser::regular_link_description_object_set; use super::object_parser::regular_link_description_set_object;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::exit_matcher_parser; use super::util::exit_matcher_parser;
use super::util::get_consumed; use super::util::get_consumed;
@ -99,7 +99,7 @@ pub fn description<'b, 'g, 'r, 's>(
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
parser_with_context!(regular_link_description_object_set)(&parser_context), parser_with_context!(regular_link_description_set_object)(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context),
), ),
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),

144
src/parser/section.rs Normal file
View File

@ -0,0 +1,144 @@
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::headline::detect_headline;
use super::org_source::OrgSource;
use super::util::exit_matcher_parser;
use super::util::get_consumed;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::Res;
use crate::parser::comment::comment;
use crate::parser::element_parser::element;
use crate::parser::planning::planning;
use crate::parser::property_drawer::property_drawer;
use crate::parser::util::blank_line;
use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::types::Element;
use crate::types::Section;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn zeroth_section<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("section"),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &section_end,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let without_consuming_whitespace_context = ContextElement::ConsumeTrailingWhitespace(false);
let without_consuming_whitespace_context =
parser_context.with_additional_node(&without_consuming_whitespace_context);
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, comment_and_property_drawer_element) = opt(tuple((
opt(parser_with_context!(comment)(
&without_consuming_whitespace_context,
)),
parser_with_context!(property_drawer)(context),
many0(blank_line),
)))(input)?;
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!children.is_empty() || comment_and_property_drawer_element.is_some()
},
)(remaining)?;
comment_and_property_drawer_element.map(|(comment, property_drawer, _ws)| {
children.insert(0, Element::PropertyDrawer(property_drawer));
comment
.map(Element::Comment)
.map(|ele| children.insert(0, ele));
});
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn section<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
mut input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("section"),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &section_end,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (mut remaining, (planning_element, property_drawer_element)) = tuple((
opt(parser_with_context!(planning)(&parser_context)),
opt(parser_with_context!(property_drawer)(&parser_context)),
))(input)?;
if planning_element.is_none() && property_drawer_element.is_none() {
let (remain, _ws) = many0(blank_line)(remaining)?;
remaining = remain;
input = remain;
}
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!children.is_empty() || property_drawer_element.is_some() || planning_element.is_some()
},
)(remaining)?;
property_drawer_element
.map(Element::PropertyDrawer)
.map(|ele| children.insert(0, ele));
planning_element
.map(Element::Planning)
.map(|ele| children.insert(0, ele));
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(detect_headline)(input)
}

View File

@ -1,9 +1,11 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_consumed;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::RefContext; use crate::context::RefContext;
@ -26,10 +28,14 @@ pub fn percent_statistics_cookie<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, StatisticsCookie<'s>> { ) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
let (remaining, source) = let (remaining, _) = recognize(tuple((
recognize(tuple((tag("["), nom::character::complete::u64, tag("%]"))))(input)?; tag("["),
opt(nom::character::complete::u64),
tag("%]"),
)))(input)?;
let (remaining, _trailing_whitespace) = let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
StatisticsCookie { StatisticsCookie {
@ -43,15 +49,16 @@ pub fn fraction_statistics_cookie<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, StatisticsCookie<'s>> { ) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
let (remaining, source) = recognize(tuple(( let (remaining, _) = recognize(tuple((
tag("["), tag("["),
nom::character::complete::u64, opt(nom::character::complete::u64),
tag("/"), tag("/"),
nom::character::complete::u64, opt(nom::character::complete::u64),
tag("]"), tag("]"),
)))(input)?; )))(input)?;
let (remaining, _trailing_whitespace) = let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
StatisticsCookie { StatisticsCookie {

View File

@ -1,5 +1,6 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::take_while;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::combinator::map; use nom::combinator::map;
@ -9,12 +10,14 @@ use nom::combinator::peek;
use nom::combinator::recognize; 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::tuple;
use super::object_parser::standard_set_object; use super::object_parser::standard_set_object;
use super::org_source::BracketDepth; use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::exit_matcher_parser; use super::util::exit_matcher_parser;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::util::preceded_by_whitespace;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::ContextMatcher; use crate::context::ContextMatcher;
@ -29,6 +32,19 @@ use crate::types::Object;
use crate::types::Subscript; use crate::types::Subscript;
use crate::types::Superscript; use crate::types::Superscript;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn detect_subscript_or_superscript<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
// This does not have to detect all valid subscript/superscript but all that it detects must be valid.
let (remaining, _) = one_of("_^")(input)?;
pre(input)?;
if tag::<_, _, CustomError<_>>("*")(remaining).is_ok() {
return Ok((input, ()));
}
let (remaining, _) = opt(one_of("+-"))(remaining)?;
let (_remaining, _) = verify(anychar, |c| c.is_alphanumeric())(remaining)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn subscript<'b, 'g, 'r, 's>( pub fn subscript<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
@ -36,7 +52,7 @@ pub fn subscript<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, Subscript<'s>> { ) -> Res<OrgSource<'s>, Subscript<'s>> {
// We check for the underscore first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily. // We check for the underscore first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
let (remaining, _) = tag("_")(input)?; let (remaining, _) = tag("_")(input)?;
pre(context, input)?; pre(input)?;
let (remaining, _body) = script_body(context, remaining)?; let (remaining, _body) = script_body(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
@ -56,7 +72,7 @@ pub fn superscript<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, Superscript<'s>> { ) -> Res<OrgSource<'s>, Superscript<'s>> {
// We check for the circumflex first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily. // We check for the circumflex first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
let (remaining, _) = tag("^")(input)?; let (remaining, _) = tag("^")(input)?;
pre(context, input)?; pre(input)?;
let (remaining, _body) = script_body(context, remaining)?; let (remaining, _body) = script_body(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
@ -70,19 +86,8 @@ pub fn superscript<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn pre<'b, 'g, 'r, 's>( fn pre<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
_context: RefContext<'b, 'g, 'r, 's>, not(preceded_by_whitespace(true))(input)?;
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let preceding_character = input.get_preceding_character();
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.".into(),
))));
}
};
Ok((input, ())) Ok((input, ()))
} }
@ -120,36 +125,29 @@ fn script_asterisk<'b, 'g, 'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn script_alphanum<'b, 'g, 'r, 's>( fn script_alphanum<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _sign) = opt(recognize(one_of("+-")))(input)?; let (remaining, _sign) = opt(recognize(one_of("+-")))(input)?;
let (remaining, _script) = many_till( let (remaining, _script) =
parser_with_context!(script_alphanum_character)(context), many_till(script_alphanum_character, end_script_alphanum_character)(remaining)?;
parser_with_context!(end_script_alphanum_character)(context),
)(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, source)) Ok((remaining, source))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn script_alphanum_character<'b, 'g, 'r, 's>( fn script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(verify(anychar, |c| { recognize(verify(anychar, |c| {
c.is_alphanumeric() || r#",.\"#.contains(*c) c.is_alphanumeric() || r#",.\"#.contains(*c)
}))(input) }))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn end_script_alphanum_character<'b, 'g, 'r, 's>( fn end_script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?; let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?;
peek(not(parser_with_context!(script_alphanum_character)( peek(tuple((
context, take_while(|c| r#",.\"#.contains(c)),
not(script_alphanum_character),
)))(remaining)?; )))(remaining)?;
Ok((remaining, final_char)) Ok((remaining, final_char))
} }

View File

@ -8,10 +8,12 @@ use nom::combinator::not;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1; use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::keyword::table_formula_keyword;
use super::object_parser::table_cell_set_object; use super::object_parser::table_cell_set_object;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::exit_matcher_parser; use super::util::exit_matcher_parser;
@ -56,6 +58,9 @@ pub fn org_mode_table<'b, 'g, 'r, 's>(
let (remaining, (children, _exit_contents)) = let (remaining, (children, _exit_contents)) =
many_till(org_mode_table_row_matcher, exit_matcher)(input)?; many_till(org_mode_table_row_matcher, exit_matcher)(input)?;
let (remaining, formulas) =
many0(parser_with_context!(table_formula_keyword)(context))(remaining)?;
// TODO: Consume trailing formulas // TODO: Consume trailing formulas
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@ -63,6 +68,7 @@ pub fn org_mode_table<'b, 'g, 'r, 's>(
remaining, remaining,
Table { Table {
source: source.into(), source: source.into(),
formulas,
children, children,
}, },
)) ))

View File

@ -18,6 +18,7 @@ use tracing::span;
use super::object_parser::standard_set_object; use super::object_parser::standard_set_object;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::radio_link::RematchObject; use super::radio_link::RematchObject;
use super::util::in_object_section;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
@ -177,15 +178,26 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
input: OrgSource<'s>, input: OrgSource<'s>,
marker_symbol: &'c str, marker_symbol: &'c str,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
if in_object_section(context, marker_symbol) {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same type".into(),
))));
}
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?; let (remaining, _peek_not_whitespace) =
peek(verify(anychar, |c| !c.is_whitespace() && *c != '\u{200B}'))(remaining)?;
let text_markup_end_specialized = text_markup_end(open.into()); let text_markup_end_specialized = text_markup_end(open.into());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let contexts = [
class: ExitClass::Gamma, ContextElement::ContextObject(marker_symbol),
exit_matcher: &text_markup_end_specialized, ContextElement::ExitMatcherNode(ExitMatcherNode {
}); class: ExitClass::Gamma,
let parser_context = context.with_additional_node(&parser_context); exit_matcher: &text_markup_end_specialized,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
@ -229,16 +241,25 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
input: OrgSource<'s>, input: OrgSource<'s>,
marker_symbol: &'c str, marker_symbol: &'c str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
if in_object_section(context, marker_symbol) {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same type".into(),
))));
}
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) = let (remaining, _peek_not_whitespace) =
peek(verify(anychar, |c| !c.is_whitespace() && *c != '\u{200B}'))(remaining)?; peek(verify(anychar, |c| !c.is_whitespace() && *c != '\u{200B}'))(remaining)?;
let text_markup_end_specialized = text_markup_end(open.into()); let text_markup_end_specialized = text_markup_end(open.into());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let contexts = [
class: ExitClass::Gamma, ContextElement::ContextObject(marker_symbol),
exit_matcher: &text_markup_end_specialized, ContextElement::ExitMatcherNode(ExitMatcherNode {
}); class: ExitClass::Gamma,
let parser_context = context.with_additional_node(&parser_context); exit_matcher: &text_markup_end_specialized,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let (remaining, contents) = recognize(verify( let (remaining, contents) = recognize(verify(
many_till( many_till(
@ -277,7 +298,6 @@ pub fn pre<'b, 'g, 'r, 's>(
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(_) => { Some(_) => {
// Not at start of line, cannot be a heading
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid pre character for text markup.".into(), "Not a valid pre character for text markup.".into(),
)))); ))));
@ -305,7 +325,7 @@ fn _text_markup_end<'b, 'g, 'r, 's, 'c>(
input: OrgSource<'s>, input: OrgSource<'s>,
marker_symbol: &'c str, marker_symbol: &'c str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
not(preceded_by_whitespace)(input)?; not(preceded_by_whitespace(false))(input)?;
let (remaining, _marker) = terminated( let (remaining, _marker) = terminated(
tag(marker_symbol), tag(marker_symbol),
peek(parser_with_context!(post)(context)), peek(parser_with_context!(post)(context)),

View File

@ -2,6 +2,7 @@ use nom::branch::alt;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::none_of; use nom::character::complete::none_of;
use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::not; use nom::combinator::not;
@ -24,7 +25,6 @@ pub const WORD_CONSTITUENT_CHARACTERS: &str =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
/// Check if we are below a section of the given section type regardless of depth /// Check if we are below a section of the given section type regardless of depth
#[allow(dead_code)]
pub fn in_section<'b, 'g, 'r, 's, 'x>( pub fn in_section<'b, 'g, 'r, 's, 'x>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
section_name: &'x str, section_name: &'x str,
@ -53,6 +53,20 @@ pub fn immediate_in_section<'b, 'g, 'r, 's, 'x>(
false false
} }
/// Check if we are below a section of the given section type regardless of depth
pub fn in_object_section<'b, 'g, 'r, 's, 'x>(
context: RefContext<'b, 'g, 'r, 's>,
section_name: &'x str,
) -> bool {
for thing in context.iter() {
match thing {
ContextElement::ContextObject(name) if *name == section_name => return true,
_ => {}
}
}
false
}
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser. /// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
pub fn get_consumed<'s>(input: OrgSource<'s>, remaining: OrgSource<'s>) -> OrgSource<'s> { pub fn get_consumed<'s>(input: OrgSource<'s>, remaining: OrgSource<'s>) -> OrgSource<'s> {
input.get_until(remaining) input.get_until(remaining)
@ -78,11 +92,15 @@ pub fn maybe_consume_object_trailing_whitespace_if_not_exiting<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> { ) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
if exit_matcher_parser(context, input).is_err() { // We have to check exit matcher after each character because description list tags need to end with a space unconsumed (" ::").
opt(space0)(input) let (remaining, _) = many_till(
} else { one_of(" \t"),
Ok((input, None)) alt((
} peek(recognize(none_of(" \t"))),
parser_with_context!(exit_matcher_parser)(context),
)),
)(input)?;
Ok((remaining, None))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@ -122,16 +140,25 @@ pub fn start_of_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
} }
} }
pub fn preceded_by_whitespace(
allow_start_of_file: bool,
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
move |input| _preceded_by_whitespace(allow_start_of_file, input)
}
/// Check that we are at the start of a line /// Check that we are at the start of a line
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn preceded_by_whitespace<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { fn _preceded_by_whitespace<'s>(
allow_start_of_file: bool,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let preceding_character = input.get_preceding_character(); let preceding_character = input.get_preceding_character();
if !preceding_character if !preceding_character
.map(|c| c.is_whitespace() || c == '\u{200B}') // 200B = Zero-width space .map(|c| c.is_whitespace() || c == '\u{200B}') // 200B = Zero-width space
.unwrap_or(false) .unwrap_or(allow_start_of_file)
{ {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not preceded by whitespace.".into(), "Must be preceded by a whitespace character.".into(),
)))); ))));
} }
Ok((input, ())) Ok((input, ()))
@ -184,3 +211,15 @@ pub fn text_until_eol<'r, 's>(
.map(|(_remaining, line)| Into::<&str>::into(line))?; .map(|(_remaining, line)| Into::<&str>::into(line))?;
Ok(line.trim()) Ok(line.trim())
} }
pub fn include_input<'s, F, O>(
mut inner: F,
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, (OrgSource<'s>, O)>
where
F: FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O>,
{
move |input: OrgSource<'_>| {
let (remaining, output) = inner(input)?;
Ok((remaining, (input, output)))
}
}

View File

@ -2,6 +2,8 @@ use super::Element;
use super::Object; use super::Object;
use super::Source; use super::Source;
pub type PriorityCookie = u8;
#[derive(Debug)] #[derive(Debug)]
pub struct Document<'s> { pub struct Document<'s> {
pub source: &'s str, pub source: &'s str,
@ -14,10 +16,12 @@ pub struct Heading<'s> {
pub source: &'s str, pub source: &'s str,
pub stars: usize, pub stars: usize,
pub todo_keyword: Option<(TodoKeywordType, &'s str)>, pub todo_keyword: Option<(TodoKeywordType, &'s str)>,
// TODO: add todo-type enum pub priority_cookie: Option<PriorityCookie>,
pub title: Vec<Object<'s>>, pub title: Vec<Object<'s>>,
pub tags: Vec<&'s str>, pub tags: Vec<&'s str>,
pub children: Vec<DocumentElement<'s>>, pub children: Vec<DocumentElement<'s>>,
pub is_comment: bool,
pub is_archived: bool,
} }
#[derive(Debug)] #[derive(Debug)]

View File

@ -1,5 +1,6 @@
use super::element::Element; use super::element::Element;
use super::lesser_element::TableCell; use super::lesser_element::TableCell;
use super::Keyword;
use super::Object; use super::Object;
use super::Source; use super::Source;
@ -63,6 +64,7 @@ pub struct NodeProperty<'s> {
#[derive(Debug)] #[derive(Debug)]
pub struct Table<'s> { pub struct Table<'s> {
pub source: &'s str, pub source: &'s str,
pub formulas: Vec<Keyword<'s>>,
pub children: Vec<TableRow<'s>>, pub children: Vec<TableRow<'s>>,
} }

View File

@ -7,6 +7,7 @@ mod source;
pub use document::Document; pub use document::Document;
pub use document::DocumentElement; pub use document::DocumentElement;
pub use document::Heading; pub use document::Heading;
pub use document::PriorityCookie;
pub use document::Section; pub use document::Section;
pub use document::TodoKeywordType; pub use document::TodoKeywordType;
pub use element::Element; pub use element::Element;