1 Commits

Author SHA1 Message Date
Tom Alexander
678106bb65 Use tekton matrix for specifying all combinations of features for the build CI job.
All checks were successful
rust-test Build rust-test has succeeded
2023-08-25 03:56:08 -04:00
87 changed files with 1262 additions and 3020 deletions

View File

@@ -77,9 +77,19 @@ spec:
workspace: docker-credentials
runAfter:
- fetch-repository
- name: run-image-none
- name: build-organic
taskRef:
name: run-docker-image
matrix:
params:
- name: feature-compare
value:
- "true"
- "false"
- name: feature-tracing
value:
- "true"
- "false"
workspaces:
- name: source
workspace: git-source
@@ -88,68 +98,22 @@ spec:
runAfter:
- build-image
params:
- name: command
value: ["/bin/sh", "-c"]
- name: args
value: ["--no-default-features"]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-tracing
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-none
params:
- name: args
value: ["--no-default-features", "--features", "tracing"]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-compare
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-tracing
params:
- name: args
value: ["--no-default-features", "--features", "compare"]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-default
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-compare
params:
- name: args
value: []
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-all
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-default
params:
- name: args
value: ["--no-default-features", "--features", "tracing,compare"]
value:
- |
set -euo pipefail
IFS=$$'\n\t'
features=()
if [ $(params.feature-compare) = "true" ]; then features+=(compare); fi
if [ $(params.feature-tracing) = "true" ]; then features+=(tracing); fi
if [ $${#features[@]} -eq 0 ]; then
exec cargo build --no-default-features
else
featurelist=$$(IFS="," ; echo "$${features[*]}")
exec cargo build --no-default-features --features "$$featurelist"
fi
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
finally:

View File

@@ -18,6 +18,14 @@ spec:
- name: path-to-dockerfile
description: The path to the Dockerfile
type: string
- name: command
type: array
description: Command to run.
default: []
- name: args
type: array
description: Arguments passed to command.
default: []
tasks:
- name: do-stuff
taskSpec:
@@ -99,41 +107,22 @@ spec:
runAfter:
- fetch-repository
- name: run-image
taskSpec:
metadata: {}
stepTemplate:
name: ""
workingDir: "$(workspaces.source.path)"
workspaces:
- name: source
mountPath: /source
- name: cargo-cache
mountPath: /usr/local/cargo/registry
optional: true
steps:
- name: run
image: "$(params.IMAGE)"
command: []
args:
[
--no-default-features,
--features,
compare,
--no-fail-fast,
--lib,
--test,
test_loader,
]
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
params:
- name: IMAGE
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
runAfter:
- build-image
params:
- name: command
value: ["$(params.command[*])"]
- name: args
value: ["$(params.args[*])"]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
finally:
- name: report-success
when:
@@ -223,3 +212,7 @@ spec:
value: docker/organic_test/
- name: path-to-dockerfile
value: docker/organic_test/Dockerfile
- name: command
value: [cargo, test]
- name: args
value: [--lib, --test, test_loader]

View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "organic"
version = "0.1.4"
version = "0.1.2"
authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser."
edition = "2021"
@@ -13,7 +13,8 @@ resolver = "2"
include = [
"LICENSE",
"**/*.rs",
"Cargo.toml"
"Cargo.toml",
"tests/*"
]
[lib]
@@ -39,17 +40,15 @@ tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-fil
walkdir = "2.3.3"
[features]
default = []
default = ["compare"]
compare = []
tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"]
# Optimized build for any sort of release.
[profile.release-lto]
inherits = "release"
lto = true
strip = "symbols"
# Profile for performance testing with the "perf" tool. Notably keeps debug enabled and does not strip symbols to make reading the perf output easier.
[profile.perf]
inherits = "release"
lto = true

View File

@@ -35,12 +35,12 @@ clean:
.PHONY: test
test:
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
> cargo test --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: dockertest
dockertest:
> $(MAKE) -C docker/organic_test
> docker run --init --rm -i -t --read-only -v "$$(readlink -f ./):/source:ro" --mount type=tmpfs,destination=/tmp --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
> docker run --init --rm -i -t -v "$$(readlink -f ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test cargo test --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: dockerclean
dockerclean:
@@ -49,18 +49,18 @@ dockerclean:
.PHONY: integrationtest
integrationtest:
> cargo test --no-default-features --features compare --no-fail-fast --test test_loader -- --test-threads $(TESTJOBS)
> cargo test --no-fail-fast --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: unittest
unittest:
> cargo test --no-default-features --lib -- --test-threads $(TESTJOBS)
> cargo test --lib -- --test-threads $(TESTJOBS)
.PHONY: jaeger
jaeger:
# 4317 for OTLP gRPC, 4318 for OTLP HTTP. We currently use gRPC but I forward both ports regardless.
#
# These flags didn't help even though they seem like they would: --collector.queue-size=20000 --collector.num-workers=100
> docker run -d --rm --name organicdocker --read-only -p 4317:4317 -p 4318:4318 -p 16686:16686 -e COLLECTOR_OTLP_ENABLED=true jaegertracing/all-in-one:1.47 --collector.grpc-server.max-message-size=20000000 --collector.otlp.grpc.max-message-size=20000000
> docker run -d --rm --name organicdocker -p 4317:4317 -p 4318:4318 -p 16686:16686 -e COLLECTOR_OTLP_ENABLED=true jaegertracing/all-in-one:1.47 --collector.grpc-server.max-message-size=20000000 --collector.otlp.grpc.max-message-size=20000000
.PHONY: jaegerweb
jaegerweb:

View File

@@ -1,16 +1,10 @@
#[cfg(feature = "compare")]
use std::env;
#[cfg(feature = "compare")]
use std::fs::File;
#[cfg(feature = "compare")]
use std::io::Write;
#[cfg(feature = "compare")]
use std::path::Path;
#[cfg(feature = "compare")]
use walkdir::WalkDir;
#[cfg(feature = "compare")]
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let destination = Path::new(&out_dir).join("tests.rs");
@@ -37,10 +31,6 @@ fn main() {
}
}
#[cfg(not(feature = "compare"))]
fn main() {}
#[cfg(feature = "compare")]
fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
let test_name = test
.path()
@@ -65,7 +55,6 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
.unwrap();
}
#[cfg(feature = "compare")]
fn write_header(test_file: &mut File) {
write!(
test_file,
@@ -81,7 +70,6 @@ use organic::parser::sexp::sexp_with_padding;
.unwrap();
}
#[cfg(feature = "compare")]
fn is_expect_fail(name: &str) -> Option<&str> {
match name {
"autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),

View File

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

View File

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

View File

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

View File

@@ -26,10 +26,7 @@ RUN make DESTDIR="/root/dist" install
FROM rustlang/rust:nightly-alpine3.17
ENV LANG=en_US.UTF-8
RUN apk add --no-cache musl-dev ncurses gnutls
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
COPY --from=build-emacs /root/dist/ /
COPY --from=build-org-mode /root/dist/ /
ENTRYPOINT ["cargo", "test"]

View File

@@ -25,12 +25,11 @@ ifdef REMOTE_REPO
else
@echo "REMOTE_REPO not defined, not removing from remote repo."
endif
docker volume rm rust-cache cargo-cache
.PHONY: run
run: build
docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME) --no-default-features --features compare --no-fail-fast --lib --test test_loader
run:
docker run --rm -i -t $(IMAGE_NAME)
.PHONY: shell
shell: build
docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)
shell:
docker run --rm -i -t --entrypoint /bin/bash $(IMAGE_NAME)

View File

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

View File

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

View File

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

0
foo Normal file
View File

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,18 +0,0 @@
#+begin_defun
foo
#+begin_lorem
,#+begin_center
bar
,#+end_center
ipsum
#+end_lorem
baz
#+end_defun
#+begin_center
#+begin_quote
#+begin_center
lorem
#+end_center
#+end_quote
#+end_center

View File

@@ -1,12 +0,0 @@
#+begin_defun
foo
#+begin_lorem
ipsum
#+end_lorem
bar
#+begin_center
#+begin_quote
baz
#+end_quote
#+end_center
#+end_defun

View File

@@ -1,5 +0,0 @@
#+begin_defun
foo
{{{bar(baz)}}}
#+end_defun

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,8 +4,6 @@ set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${NO_COLOR:=""} # Set to anything to disable color output
cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath)
MAKE=$(command -v gmake || command -v make)
@@ -33,7 +31,7 @@ function get_test_names {
local test_file_full_path=$($REALPATH "$test_file")
local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path")
local without_extension="${relative_to_samples%.org}"
echo "autogen_${without_extension//\//_}" | tr '[:upper:]' '[:lower:]'
echo "${without_extension/\//_}" | tr '[:upper:]' '[:lower:]'
else
echo "$test_file" | tr '[:upper:]' '[:lower:]'
fi
@@ -42,21 +40,17 @@ function get_test_names {
function launch_container {
local test="$1"
local additional_flags=()
if [ "$NO_COLOR" != "" ]; then
additional_flags+=(--env "NO_COLOR=$NO_COLOR")
fi
local additional_args=()
local init_script=$(cat <<EOF
set -euo pipefail
IFS=\$'\n\t'
cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader "$test" -- --show-output
cargo test --no-fail-fast --lib --test test_loader "$test" -- --show-output
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 --init --rm -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test sh -c "$init_script"
}

View File

@@ -12,7 +12,7 @@ function main {
local test
while read test; do
cargo test --no-default-features --features compare --no-fail-fast --test test_loader "$test" -- --show-output
cargo test --no-fail-fast --test test_loader "$test" -- --show-output
done<<<"$test_names"
}
@@ -25,7 +25,7 @@ function get_test_names {
local test_file_full_path=$($REALPATH "$test_file")
local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path")
local without_extension="${relative_to_samples%.org}"
echo "${without_extension//\//_}" | tr '[:upper:]' '[:lower:]'
echo "${without_extension/\//_}" | tr '[:upper:]' '[:lower:]'
else
echo "$test_file" | tr '[:upper:]' '[:lower:]'
fi

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
/// Get the offset into source that the rust object exists at.
///
/// These offsets are zero-based unlike the elisp ones.
fn get_offsets<'s, S: Source<'s>>(source: &'s str, rust_object: &'s S) -> (usize, usize) {
pub fn get_offsets<'s, S: Source<'s>>(source: &'s str, rust_object: &'s S) -> (usize, usize) {
let rust_object_source = rust_object.get_source();
assert!(is_slice_of(source, rust_object_source));
let offset = rust_object_source.as_ptr() as usize - source.as_ptr() as usize;
@@ -47,14 +47,13 @@ pub fn assert_bounds<'s, S: Source<'s>>(
standard_properties
.begin
.ok_or("Token should have a begin.")?,
standard_properties.end.ok_or("Token should have an end.")?,
standard_properties
.end
.ok_or("Token should have a begin.")?,
);
let (rust_begin, rust_end) = get_offsets(source, rust);
let rust_begin_char_offset = (&source[..rust_begin]).chars().count();
let rust_end_char_offset =
rust_begin_char_offset + (&source[rust_begin..rust_end]).chars().count();
if (rust_begin_char_offset + 1) != begin || (rust_end_char_offset + 1) != end {
Err(format!("Rust bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset + 1, rust_end = rust_end_char_offset + 1, emacs_begin=begin, emacs_end=end))?;
if (rust_begin + 1) != begin || (rust_end + 1) != end {
Err(format!("Rust bounds ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin + 1, rust_end = rust_end + 1, emacs_begin=begin, emacs_end=end))?;
}
Ok(())
@@ -140,23 +139,3 @@ fn maybe_token_to_usize(
.flatten() // Outer option is whether or not the param exists, inner option is whether or not it is nil
.map_or(Ok(None), |r| r.map(Some))?)
}
pub fn get_property<'s, 'x>(
emacs: &'s Token<'s>,
key: &'x str,
) -> Result<Option<&'s Token<'s>>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let prop = attributes_map
.get(key)
.ok_or(format!("Missing {} attribute.", key))?;
match prop.as_atom() {
Ok("nil") => return Ok(None),
_ => {}
};
Ok(Some(*prop))
}

View File

@@ -55,21 +55,20 @@ fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
let emacs_version = get_emacs_version()?;
let org_mode_version = get_org_mode_version()?;
let org_contents = org_contents.as_ref();
eprintln!("Using emacs version: {}", emacs_version.trim());
eprintln!("Using org-mode version: {}", org_mode_version.trim());
let (remaining, rust_parsed) = document(org_contents).map_err(|e| e.to_string())?;
let org_sexp = emacs_parse_org_document(org_contents)?;
let (remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?;
let org_sexp = emacs_parse_org_document(org_contents.as_ref())?;
let (_remaining, parsed_sexp) =
sexp_with_padding(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents);
println!("{}\n\n\n", org_contents.as_ref());
println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
// We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print(org_contents)?;
diff_result.print()?;
if diff_result.is_bad() {
Err("Diff results do not match.")?;
@@ -86,7 +85,7 @@ fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error:
eprintln!(
"This program was built with compare disabled. Only parsing with organic, not comparing."
);
let (_remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?;
let (remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?;
println!("{:#?}", rust_parsed);
Ok(())
}

View File

@@ -5,7 +5,6 @@ use nom::combinator::recognize;
use nom::multi::many_till;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
@@ -27,8 +26,6 @@ pub fn angle_link<'r, 's>(
let (remaining, _separator) = tag(":")(remaining)?;
let (remaining, path) = path_angle(context, remaining)?;
let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,

View File

@@ -2,6 +2,7 @@ use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
@@ -10,18 +11,17 @@ use nom::multi::many_till;
use nom::multi::separated_list1;
use nom::sequence::tuple;
use super::citation_reference::must_balance_bracket;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::citation_reference::citation_reference;
use crate::parser::citation_reference::citation_reference_key;
use crate::parser::citation_reference::get_bracket_depth;
use crate::parser::exiting::ExitClass;
use crate::parser::object::Citation;
use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::CitationBracket;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
@@ -38,18 +38,15 @@ pub fn citation<'r, 's>(
let (remaining, _) = tag_no_case("[cite")(input)?;
let (remaining, _) = opt(citestyle)(remaining)?;
let (remaining, _) = tag(":")(remaining)?;
let (remaining, _prefix) =
must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?;
let (remaining, _prefix) = opt(parser_with_context!(global_prefix)(context))(remaining)?;
let (remaining, _references) =
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
let (remaining, _suffix) = must_balance_bracket(opt(tuple((
let (remaining, _suffix) = opt(tuple((
tag(";"),
parser_with_context!(global_suffix)(context),
))))(remaining)?;
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -86,11 +83,15 @@ fn global_prefix<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let exit_with_depth = global_prefix_end(input.get_bracket_depth());
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
let parser_context = context
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
position: input,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &exit_with_depth,
exit_matcher: &global_prefix_end,
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
@@ -103,24 +104,28 @@ fn global_prefix<'r, 's>(
Ok((remaining, children))
}
fn global_prefix_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_global_prefix_end(context, input, starting_bracket_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _global_prefix_end<'r, 's>(
fn global_prefix_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
unreachable!("Exceeded citation global prefix bracket depth.")
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a citation.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'[' => {
current_depth += 1;
}
']' if current_depth == 0 => {
panic!("Exceeded citation global prefix bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
}
if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
@@ -139,11 +144,15 @@ fn global_suffix<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let exit_with_depth = global_suffix_end(input.get_bracket_depth());
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
let parser_context = context
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
position: input,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &exit_with_depth,
exit_matcher: &global_suffix_end,
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
@@ -155,24 +164,28 @@ fn global_suffix<'r, 's>(
Ok((remaining, children))
}
fn global_suffix_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_global_suffix_end(context, input, starting_bracket_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _global_suffix_end<'r, 's>(
fn global_suffix_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
unreachable!("Exceeded citation global suffix bracket depth.")
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a citation.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'[' => {
current_depth += 1;
}
']' if current_depth == 0 => {
panic!("Exceeded citation global suffix bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
}
if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);

View File

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

View File

@@ -1,12 +1,9 @@
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::combinator::eof;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::OrgSource;
@@ -27,11 +24,6 @@ pub fn diary_sexp<'r, 's>(
let (remaining, _clock) = tag("%%")(remaining)?;
let (remaining, _gap_whitespace) = space0(remaining)?;
let (remaining, _sexp) = recognize(sexp)(remaining)?;
let (remaining, _trailing_comment) = opt(tuple((
space0,
tag(";"),
many_till(anychar, alt((line_ending, eof))),
)))(remaining)?;
let (remaining, _trailing_whitespace) =
recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;

View File

@@ -1,8 +1,6 @@
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;
@@ -14,7 +12,6 @@ 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::element::Element;
@@ -53,10 +50,7 @@ pub struct Document<'s> {
pub struct Heading<'s> {
pub source: &'s str,
pub stars: usize,
pub todo_keyword: Option<&'s str>,
// TODO: add todo-type enum
pub title: Vec<Object<'s>>,
pub tags: Vec<&'s str>,
pub children: Vec<DocumentElement<'s>>,
}
@@ -139,7 +133,7 @@ fn _document<'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Document<'s>> {
let zeroth_section_matcher = parser_with_context!(zeroth_section)(context);
let heading_matcher = parser_with_context!(heading(0))(context);
let heading_matcher = parser_with_context!(heading)(context);
let (remaining, _blank_lines) = many0(blank_line)(input)?;
let (remaining, zeroth_section) = opt(zeroth_section_matcher)(remaining)?;
let (remaining, children) = many0(heading_matcher)(remaining)?;
@@ -260,149 +254,69 @@ fn section<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section_end<'r, 's>(
_context: Context<'r, 's>,
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(detect_headline)(input)
}
const fn heading(
parent_stars: usize,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Heading<'s>> {
move |context: Context, input: OrgSource<'_>| _heading(context, input, parent_stars)
let headline_matcher = parser_with_context!(headline)(context);
recognize(headline_matcher)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _heading<'r, 's>(
fn heading<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<OrgSource<'s>, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?;
let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) =
headline(context, input, parent_stars)?;
let (remaining, (star_count, _ws, title)) = headline(context, input)?;
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 heading_matcher = parser_with_context!(heading)(context);
let (remaining, children) = many0(alt((
map(
verify(heading_matcher, |h| h.stars > star_count),
DocumentElement::Heading,
),
map(section_matcher, DocumentElement::Section),
)))(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Heading {
source: source.into(),
stars: star_count,
todo_keyword: maybe_todo_keyword
.map(|(todo_keyword, _ws)| Into::<&str>::into(todo_keyword)),
title,
tags: heading_tags,
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, many1(tag("*")), space1))(input)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<
OrgSource<'s>,
(
usize,
OrgSource<'s>,
Option<(OrgSource<'s>, OrgSource<'s>)>,
Vec<Object<'s>>,
Vec<&'s str>,
),
> {
) -> Res<OrgSource<'s>, (usize, OrgSource<'s>, Vec<Object<'s>>)> {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &headline_title_end,
exit_matcher: &headline_end,
}));
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let (
remaining,
(_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending),
) = tuple((
let (remaining, (_sol, star_count, ws, title, _line_ending)) = tuple((
start_of_line,
verify(many1_count(tag("*")), |star_count| {
*star_count > parent_stars
}),
many1_count(tag("*")),
space1,
opt(tuple((heading_keyword, space1))),
many1(standard_set_object_matcher),
opt(tuple((space0, tags))),
space0,
alt((line_ending, eof)),
))(input)?;
Ok((
remaining,
(
star_count,
ws,
maybe_todo_keyword,
title,
maybe_tags
.map(|(_ws, tags)| {
tags.into_iter()
.map(|single_tag| Into::<&str>::into(single_tag))
.collect()
})
.unwrap_or(Vec::new()),
),
))
Ok((remaining, (star_count, ws, title)))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline_title_end<'r, 's>(
fn headline_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
opt(tuple((space0, tags, space0))),
alt((line_ending, eof)),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn tags<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
let (remaining, (_open, tags, _close)) =
tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?;
Ok((remaining, tags))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_@#%".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn heading_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should take into account the value of "#+TODO:" ref https://orgmode.org/manual/Per_002dfile-keywords.html and possibly the configurable variable org-todo-keywords ref https://orgmode.org/manual/Workflow-states.html. Case is significant.
alt((tag("TODO"), tag("DONE")))(input)
line_ending(input)
}
impl<'s> Document<'s> {

View File

@@ -12,7 +12,6 @@ use super::fixed_width_area::fixed_width_area;
use super::footnote_definition::footnote_definition;
use super::greater_block::greater_block;
use super::horizontal_rule::horizontal_rule;
use super::keyword::affiliated_keyword;
use super::keyword::keyword;
use super::latex_environment::latex_environment;
use super::lesser_block::comment_block;
@@ -63,12 +62,10 @@ fn _element<'r, 's>(
let fixed_width_area_matcher = parser_with_context!(fixed_width_area)(context);
let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context);
let keyword_matcher = parser_with_context!(keyword)(context);
let affiliated_keyword_matcher = parser_with_context!(affiliated_keyword)(context);
let paragraph_matcher = parser_with_context!(paragraph)(context);
let latex_environment_matcher = parser_with_context!(latex_environment)(context);
// TODO: Affiliated keywords cannot be on comments, clocks, headings, inlinetasks, items, node properties, planning, property drawers, sections, and table rows
let (remaining, mut affiliated_keywords) = many0(affiliated_keyword_matcher)(input)?;
let (remaining, mut affiliated_keywords) = many0(keyword_matcher)(input)?;
let (remaining, mut element) = match alt((
map(plain_list_matcher, Element::PlainList),
map(greater_block_matcher, Element::GreaterBlock),
@@ -87,7 +84,6 @@ fn _element<'r, 's>(
map(fixed_width_area_matcher, Element::FixedWidthArea),
map(horizontal_rule_matcher, Element::HorizontalRule),
map(latex_environment_matcher, Element::LatexEnvironment),
map(keyword_matcher, Element::Keyword),
))(remaining)
{
the_ok @ Ok(_) => the_ok,
@@ -97,12 +93,12 @@ fn _element<'r, 's>(
the_ok @ Ok(_) => the_ok,
Err(_) => {
affiliated_keywords.clear();
map(affiliated_keyword_matcher, Element::Keyword)(input)
map(keyword_matcher, Element::Keyword)(input)
}
}
} else {
affiliated_keywords.clear();
map(affiliated_keyword_matcher, Element::Keyword)(input)
map(keyword_matcher, Element::Keyword)(input)
}
}
}?;
@@ -128,7 +124,7 @@ fn _detect_element<'r, 's>(
input: OrgSource<'s>,
can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> {
if detect_plain_list(input).is_ok() {
if detect_plain_list(context, input).is_ok() {
return Ok((input, ()));
}
if _element(context, input, can_be_paragraph).is_ok() {

View File

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

View File

@@ -8,7 +8,6 @@ use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
@@ -36,8 +35,6 @@ pub fn export_snippet<'r, 's>(
parser_with_context!(contents)(&parser_context),
)))(remaining)?;
let (remaining, _) = tag("@@")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,

View File

@@ -80,10 +80,21 @@ fn footnote_definition_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let allow_nesting_context =
context.with_additional_node(ContextElement::Context("allow nesting footnotes"));
let footnote_definition_matcher = parser_with_context!(footnote_definition)(
if immediate_in_section(context, "footnote definition") {
&allow_nesting_context
} else {
context
},
);
let maybe_consume_trailing_whitespace_matcher =
parser_with_context!(maybe_consume_trailing_whitespace)(context);
let (remaining, source) = alt((
recognize(tuple((
parser_with_context!(maybe_consume_trailing_whitespace)(context),
detect_footnote_definition,
maybe_consume_trailing_whitespace_matcher,
footnote_definition_matcher,
))),
recognize(tuple((
start_of_line,
@@ -96,12 +107,6 @@ fn footnote_definition_end<'r, 's>(
Ok((remaining, source))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
Ok((input, ()))
}
#[cfg(test)]
mod tests {
use super::*;

View File

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

View File

@@ -12,7 +12,6 @@ use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::in_section;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
@@ -27,6 +26,7 @@ use crate::parser::source::SetSource;
use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line;
use crate::parser::Element;
use crate::parser::Paragraph;
@@ -49,24 +49,24 @@ pub fn greater_block<'r, 's>(
}),
))(remaining)?;
let context_name = match Into::<&str>::into(name).to_lowercase().as_str() {
"center" => "center block".to_owned(),
"quote" => "quote block".to_owned(),
name @ _ => format!("special block {}", name),
"center" => "center block",
"quote" => "quote block",
_ => "greater block",
};
if in_section(context, context_name.as_str()) {
if immediate_in_section(context, context_name) {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(),
))));
}
let exit_with_name = greater_block_end(name.into());
let (remaining, parameters) = opt(tuple((space1, parameters)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?;
let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::Context(context_name.as_str()))
.with_additional_node(ContextElement::Context(context_name))
.with_additional_node(ContextElement::GreaterBlock(name.into()))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha,
exit_matcher: &exit_with_name,
exit_matcher: &greater_block_end,
}));
let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters),
@@ -94,7 +94,7 @@ pub fn greater_block<'r, 's>(
(remaining, children)
}
};
let (remaining, _end) = exit_with_name(&parser_context, remaining)?;
let (remaining, _end) = greater_block_end(&parser_context, remaining)?;
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
@@ -120,27 +120,31 @@ fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not("\r\n")(input)
}
fn greater_block_end<'x>(
name: &'x str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: Can this be done without making an owned copy?
let name = name.to_owned();
move |context: Context, input: OrgSource<'_>| _greater_block_end(context, input, name.as_str())
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _greater_block_end<'r, 's, 'x>(
_context: Context<'r, 's>,
fn greater_block_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
name: &'x str,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let current_name: &str = get_context_greater_block_name(context).ok_or(nom::Err::Error(
CustomError::MyError(MyError("Not inside a greater block".into())),
))?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, _name, _ws)) = tuple((
tag_no_case("#+end_"),
tag_no_case(name),
tag_no_case(current_name),
alt((eof, line_ending)),
))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
fn get_context_greater_block_name<'r, 's>(context: Context<'r, 's>) -> Option<&'s str> {
for thing in context.iter() {
match thing.get_data() {
ContextElement::GreaterBlock(name) => return Some(name),
_ => {}
};
}
None
}

View File

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

View File

@@ -4,19 +4,17 @@ use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::BabelHeaderBracket;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
@@ -34,8 +32,7 @@ pub fn inline_babel_call<'r, 's>(
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _argument) = argument(context, remaining)?;
let (remaining, _header2) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -77,11 +74,14 @@ fn header<'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("[")(input)?;
let exit_with_depth = header_end(remaining.get_bracket_depth());
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
let parser_context = context
.with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket {
position: remaining,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &exit_with_depth,
exit_matcher: &header_end,
}));
let (remaining, name) = recognize(many_till(
@@ -92,30 +92,28 @@ fn header<'r, 's>(
Ok((remaining, name))
}
fn header_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_header_end(context, input, starting_bracket_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _header_end<'r, 's>(
_context: Context<'r, 's>,
fn header_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 {
// Its impossible for the next character to end the header if we're any amount of bracket deep
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoHeaderEnd".into(),
))));
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside an inline babel call header.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'(' => {
current_depth += 1;
}
')' if current_depth == 0 => {
panic!("Exceeded inline babel call header bracket depth.")
}
')' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
unreachable!("Exceeded header bracket depth.")
}
alt((tag("]"), line_ending))(input)
}
@@ -127,11 +125,14 @@ fn argument<'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("(")(input)?;
let exit_with_depth = argument_end(remaining.get_parenthesis_depth());
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
let parser_context = context
.with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket {
position: remaining,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &exit_with_depth,
exit_matcher: &argument_end,
}));
let (remaining, name) = recognize(many_till(
@@ -142,30 +143,39 @@ fn argument<'r, 's>(
Ok((remaining, name))
}
fn argument_end(
starting_parenthesis_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_argument_end(context, input, starting_parenthesis_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _argument_end<'r, 's>(
_context: Context<'r, 's>,
fn argument_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
starting_parenthesis_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth;
if current_depth > 0 {
// Its impossible for the next character to end the argument if we're any amount of parenthesis deep
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoArgumentEnd".into(),
))));
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside an inline babel call argument.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'[' => {
current_depth += 1;
}
']' if current_depth == 0 => {
panic!("Exceeded inline babel call argument bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing parenthesis should end the argument.
unreachable!("Exceeded argument parenthesis depth.")
}
alt((tag(")"), line_ending))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r BabelHeaderBracket<'s>> {
for node in context.iter() {
match node.get_data() {
ContextElement::BabelHeaderBracket(depth) => return Some(depth),
_ => {}
}
}
None
}

View File

@@ -1,9 +1,9 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
@@ -11,16 +11,14 @@ use nom::multi::many_till;
#[cfg(feature = "tracing")]
use tracing::span;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_context::InlineSourceBlockBracket;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
@@ -35,8 +33,7 @@ pub fn inline_source_block<'r, 's>(
let (remaining, _) = lang(context, remaining)?;
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _body) = body(context, remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -78,11 +75,16 @@ fn header<'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("[")(input)?;
let exit_with_depth = header_end(remaining.get_bracket_depth());
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
let parser_context = context
.with_additional_node(ContextElement::InlineSourceBlockBracket(
InlineSourceBlockBracket {
position: remaining,
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &exit_with_depth,
exit_matcher: &header_end,
}));
let (remaining, header_contents) = recognize(many_till(
@@ -93,32 +95,37 @@ fn header<'r, 's>(
Ok((remaining, header_contents))
}
fn header_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_header_end(context, input, starting_bracket_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _header_end<'r, 's>(
_context: Context<'r, 's>,
fn header_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 {
// Its impossible for the next character to end the header if we're any amount of bracket deep
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoHeaderEnd".into(),
))));
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside an inline source block header.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'[' => {
current_depth += 1;
}
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
unreachable!("Exceeded header bracket depth.")
']' if current_depth == 0 => {
panic!("Exceeded inline source block header bracket depth.")
}
alt((tag("]"), line_ending))(input)
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
}
if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
if close_bracket.is_ok() {
return close_bracket;
}
}
line_ending(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -128,11 +135,16 @@ fn body<'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("{")(input)?;
let exit_with_depth = body_end(remaining.get_brace_depth());
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
let parser_context = context
.with_additional_node(ContextElement::InlineSourceBlockBracket(
InlineSourceBlockBracket {
position: remaining,
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &exit_with_depth,
exit_matcher: &body_end,
}));
let (remaining, body_contents) = recognize(many_till(
@@ -153,28 +165,60 @@ fn body<'r, 's>(
Ok((remaining, body_contents))
}
fn body_end(
starting_brace_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| _body_end(context, input, starting_brace_depth)
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn body_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside an inline source block body.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'{' => {
current_depth += 1;
}
'}' if current_depth == 0 => {
panic!("Exceeded inline source block body bracket depth.")
}
'}' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
}
{
#[cfg(feature = "tracing")]
let span = span!(
tracing::Level::DEBUG,
"inside end body",
remaining = Into::<&str>::into(input),
current_depth = current_depth
);
#[cfg(feature = "tracing")]
let _enter = span.enter();
if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("}")(input);
if close_bracket.is_ok() {
return close_bracket;
}
}
}
line_ending(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _body_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
starting_brace_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_brace_depth() - starting_brace_depth;
if current_depth > 0 {
// Its impossible for the next character to end the body if we're any amount of brace deep
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoBodyEnd".into(),
))));
pub fn get_bracket_depth<'r, 's>(
context: Context<'r, 's>,
) -> Option<&'r InlineSourceBlockBracket<'s>> {
for node in context.iter() {
match node.get_data() {
ContextElement::InlineSourceBlockBracket(depth) => return Some(depth),
_ => {}
}
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing brace should end the body.
unreachable!("Exceeded body brace depth.")
}
alt((tag("}"), line_ending))(input)
None
}

View File

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

View File

@@ -5,6 +5,7 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::none_of;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
@@ -13,7 +14,6 @@ use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
@@ -36,8 +36,7 @@ pub fn latex_fragment<'r, 's>(
parser_with_context!(dollar_char_fragment)(context),
parser_with_context!(bordered_dollar_fragment)(context),
))(input)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -203,13 +202,17 @@ fn bordered_dollar_fragment<'r, 's>(
// TODO: I'm assuming I should be peeking at the borders but the documentation is not clear. Test to figure out.
let (_, _) = peek(parser_with_context!(open_border)(context))(remaining)?;
let (remaining, _) = recognize(many_till(
// TODO: As an optimization it would be nice to exit early upon hitting the 3rd line break
let (remaining, _) = verify(
recognize(many_till(
anychar,
peek(alt((
parser_with_context!(exit_matcher_parser)(context),
tag("$"),
))),
))(remaining)?;
)),
|body: &OrgSource<'_>| Into::<&str>::into(body).lines().take(4).count() <= 3,
)(remaining)?;
let (_, _) = peek(parser_with_context!(close_border)(context))(remaining)?;
let (remaining, _) = tag("$")(remaining)?;

View File

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

View File

@@ -33,7 +33,7 @@ pub fn standard_set_object<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
alt((
map(parser_with_context!(timestamp)(context), Object::Timestamp),
map(parser_with_context!(subscript)(context), Object::Subscript),
map(
@@ -82,8 +82,7 @@ pub fn standard_set_object<'r, 's>(
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)?;
Ok((remaining, object))
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -91,7 +90,7 @@ pub fn minimal_set_object<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
alt((
map(parser_with_context!(subscript)(context), Object::Subscript),
map(
parser_with_context!(superscript)(context),
@@ -104,8 +103,7 @@ pub fn minimal_set_object<'r, 's>(
),
parser_with_context!(text_markup)(context),
map(parser_with_context!(plain_text)(context), Object::PlainText),
))(input)?;
Ok((remaining, object))
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -113,7 +111,7 @@ pub fn any_object_except_plain_text<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
alt((
map(parser_with_context!(timestamp)(context), Object::Timestamp),
map(parser_with_context!(subscript)(context), Object::Subscript),
map(
@@ -161,8 +159,7 @@ pub fn any_object_except_plain_text<'r, 's>(
map(parser_with_context!(plain_link)(context), Object::PlainLink),
map(parser_with_context!(angle_link)(context), Object::AngleLink),
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
))(input)?;
Ok((remaining, object))
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -171,7 +168,7 @@ pub fn regular_link_description_object_set<'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((
alt((
map(
parser_with_context!(export_snippet)(context),
Object::ExportSnippet,
@@ -190,40 +187,5 @@ pub fn regular_link_description_object_set<'r, 's>(
),
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
parser_with_context!(minimal_set_object)(context),
))(input)?;
Ok((remaining, object))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_cell_set_object<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
map(parser_with_context!(citation)(context), Object::Citation),
map(
parser_with_context!(export_snippet)(context),
Object::ExportSnippet,
),
map(
parser_with_context!(footnote_reference)(context),
Object::FootnoteReference,
),
map(parser_with_context!(radio_link)(context), Object::RadioLink),
map(
parser_with_context!(regular_link)(context),
Object::RegularLink,
),
map(parser_with_context!(plain_link)(context), Object::PlainLink),
map(parser_with_context!(angle_link)(context), Object::AngleLink),
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
map(
parser_with_context!(radio_target)(context),
Object::RadioTarget,
),
map(parser_with_context!(target)(context), Object::Target),
map(parser_with_context!(timestamp)(context), Object::Timestamp),
parser_with_context!(minimal_set_object)(context),
))(input)?;
Ok((remaining, object))
))(input)
}

View File

@@ -8,9 +8,7 @@ use nom::multi::many0;
use nom::multi::separated_list0;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::object::OrgMacro;
use crate::parser::parser_with_context::parser_with_context;
@@ -26,8 +24,6 @@ pub fn org_macro<'r, 's>(
let (remaining, macro_name) = org_macro_name(context, remaining)?;
let (remaining, macro_args) = opt(parser_with_context!(org_macro_args)(context))(remaining)?;
let (remaining, _) = tag("}}}")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -88,11 +84,6 @@ fn org_macro_arg<'r, 's>(
}
if next_char == '\\' {
escaping = true;
if peek(tag::<_, _, CustomError<_>>(")"))(new_remaining).is_ok() {
// Special case for backslash at the end of a macro
remaining = new_remaining;
break;
}
}
if next_char == ',' || next_char == ')' {
break;

View File

@@ -11,28 +11,15 @@ use nom::Slice;
use crate::error::CustomError;
use crate::error::MyError;
pub type BracketDepth = i16;
#[derive(Copy, Clone)]
#[derive(Debug, Copy, Clone)]
pub struct OrgSource<'s> {
full_source: &'s str,
start: usize,
end: usize, // exclusive
start_of_line: usize,
bracket_depth: BracketDepth, // []
brace_depth: BracketDepth, // {}
parenthesis_depth: BracketDepth, // ()
preceding_character: Option<char>,
}
impl<'s> std::fmt::Debug for OrgSource<'s> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("OrgSource")
.field(&Into::<&str>::into(self))
.finish()
}
}
impl<'s> OrgSource<'s> {
/// Returns a wrapped string that keeps track of values we need for parsing org-mode.
///
@@ -44,9 +31,6 @@ impl<'s> OrgSource<'s> {
end: input.len(),
start_of_line: 0,
preceding_character: None,
bracket_depth: 0,
brace_depth: 0,
parenthesis_depth: 0,
}
}
@@ -72,18 +56,6 @@ impl<'s> OrgSource<'s> {
assert!(other.end <= self.end);
self.slice(..(other.start - self.start))
}
pub fn get_bracket_depth(&self) -> BracketDepth {
self.bracket_depth
}
pub fn get_brace_depth(&self) -> BracketDepth {
self.brace_depth
}
pub fn get_parenthesis_depth(&self) -> BracketDepth {
self.parenthesis_depth
}
}
impl<'s> InputTake for OrgSource<'s> {
@@ -147,36 +119,10 @@ where
}
let skipped_text = &self.full_source[self.start..new_start];
let mut start_of_line = self.start_of_line;
let mut bracket_depth = self.bracket_depth;
let mut brace_depth = self.brace_depth;
let mut parenthesis_depth = self.parenthesis_depth;
for (offset, byte) in skipped_text.bytes().enumerate() {
match byte {
b'\n' => {
start_of_line = self.start + offset + 1;
}
b'[' => {
bracket_depth += 1;
}
b']' => {
bracket_depth -= 1;
}
b'{' => {
brace_depth += 1;
}
b'}' => {
brace_depth -= 1;
}
b'(' => {
parenthesis_depth += 1;
}
b')' => {
parenthesis_depth -= 1;
}
_ => {}
};
}
let start_of_line = skipped_text
.rfind('\n')
.map(|idx| self.start + idx + 1)
.unwrap_or(self.start_of_line);
OrgSource {
full_source: self.full_source,
@@ -184,9 +130,6 @@ where
end: new_end,
start_of_line,
preceding_character: skipped_text.chars().last(),
bracket_depth,
brace_depth,
parenthesis_depth,
}
}
}
@@ -303,9 +246,7 @@ impl<'s> InputTakeAtPosition for OrgSource<'s> {
}
}
pub fn convert_error<'a, I: Into<CustomError<&'a str>>>(
err: nom::Err<I>,
) -> nom::Err<CustomError<&'a str>> {
pub fn convert_error(err: nom::Err<CustomError<OrgSource<'_>>>) -> nom::Err<CustomError<&str>> {
match err {
nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed),
nom::Err::Error(err) => nom::Err::Error(err.into()),
@@ -428,19 +369,4 @@ mod tests {
assert_eq!(input.get_preceding_character(), None);
assert_eq!(input.slice(8..).get_preceding_character(), Some('💛'));
}
#[test]
fn depth() {
let input = OrgSource::new("[][()][({)]}}}}");
assert_eq!(input.get_bracket_depth(), 0);
assert_eq!(input.get_brace_depth(), 0);
assert_eq!(input.get_parenthesis_depth(), 0);
assert_eq!(input.slice(4..).get_bracket_depth(), 1);
assert_eq!(input.slice(4..).get_brace_depth(), 0);
assert_eq!(input.slice(4..).get_parenthesis_depth(), 1);
assert_eq!(input.slice(4..).slice(6..).get_bracket_depth(), 1);
assert_eq!(input.slice(4..).slice(6..).get_brace_depth(), 1);
assert_eq!(input.slice(4..).slice(6..).get_parenthesis_depth(), 0);
assert_eq!(input.slice(14..).get_brace_depth(), -2);
}
}

View File

@@ -110,10 +110,11 @@ impl<'r, 's> ContextTree<'r, 's> {
pub enum ContextElement<'r, 's> {
/// Stores a parser that indicates that children should exit upon matching an exit matcher.
ExitMatcherNode(ExitMatcherNode<'r>),
/// Stores the name of the current element to prevent directly nesting elements of the same type.
Context(&'r str),
/// Stores the name of the greater block.
GreaterBlock(&'s str),
/// Indicates if elements should consume the whitespace after them.
ConsumeTrailingWhitespace(bool),
@@ -123,6 +124,71 @@ pub enum ContextElement<'r, 's> {
/// org-mode document since text needs to be re-parsed to look for
/// radio links matching the contents of radio targets.
RadioTarget(Vec<&'r Vec<Object<'s>>>),
/// Stores the current bracket depth inside a footnote reference's definition.
///
/// The definition inside a footnote reference must have balanced
/// brackets [] inside the definition, so this stores the amount
/// of opening brackets subtracted by the amount of closing
/// brackets within the definition must equal zero.
///
/// A reference to the position in the string is also included so
/// unbalanced brackets can be detected in the middle of an
/// object.
FootnoteReferenceDefinition(FootnoteReferenceDefinition<'s>),
/// Stores the current bracket depth inside a citation.
///
/// The global prefix, global suffix, key prefix, and key suffix
/// inside a footnote reference must have balanced brackets []
/// inside the definition, so this stores the amount of opening
/// brackets subtracted by the amount of closing brackets within
/// the definition must equal zero. None of the prefixes or
/// suffixes can be nested inside each other so we can use a
/// single type for this without conflict.
///
/// A reference to the position in the string is also included so
/// unbalanced brackets can be detected in the middle of an
/// object.
CitationBracket(CitationBracket<'s>),
/// Stores the current bracket or parenthesis depth inside an inline babel call.
///
/// Inside an inline babel call the headers must have balanced
/// parentheses () and the arguments must have balanced brackets
/// [], so this stores the amount of opening brackets subtracted
/// by the amount of closing brackets within the definition must
/// equal zero.
///
/// A reference to the position in the string is also included so
/// unbalanced brackets can be detected in the middle of an
/// object.
BabelHeaderBracket(BabelHeaderBracket<'s>),
/// Stores the current bracket or parenthesis depth inside an inline babel call.
///
/// Inside an inline babel call the headers must have balanced
/// parentheses () and the arguments must have balanced brackets
/// [], so this stores the amount of opening brackets subtracted
/// by the amount of closing brackets within the definition must
/// equal zero.
///
/// A reference to the position in the string is also included so
/// unbalanced brackets can be detected in the middle of an
/// object.
InlineSourceBlockBracket(InlineSourceBlockBracket<'s>),
/// Stores the current bracket or parenthesis depth inside a
/// superscript or superscript.
///
/// Inside the braces of a subscript or superscript there must be
/// balanced braces {}, so this stores the amount of opening
/// braces subtracted by the amount of closing braces within the
/// definition must equal zero.
///
/// A reference to the position in the string is also included so
/// unbalanced braces can be detected in the middle of an object.
SubscriptSuperscriptBrace(SubscriptSuperscriptBrace<'s>),
}
pub struct ExitMatcherNode<'r> {
@@ -130,6 +196,36 @@ pub struct ExitMatcherNode<'r> {
pub class: ExitClass,
}
#[derive(Debug)]
pub struct FootnoteReferenceDefinition<'s> {
pub position: OrgSource<'s>,
pub depth: usize,
}
#[derive(Debug)]
pub struct CitationBracket<'s> {
pub position: OrgSource<'s>,
pub depth: usize,
}
#[derive(Debug)]
pub struct BabelHeaderBracket<'s> {
pub position: OrgSource<'s>,
pub depth: usize,
}
#[derive(Debug)]
pub struct InlineSourceBlockBracket<'s> {
pub position: OrgSource<'s>,
pub depth: usize,
}
#[derive(Debug)]
pub struct SubscriptSuperscriptBrace<'s> {
pub position: OrgSource<'s>,
pub depth: usize,
}
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut formatter = f.debug_struct("ExitMatcherNode");

View File

@@ -7,7 +7,6 @@ use nom::character::complete::one_of;
use nom::combinator::eof;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till;
use super::org_source::OrgSource;
@@ -24,33 +23,6 @@ use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
// TODO: Make this a user-provided variable corresponding to elisp's org-link-parameters
const ORG_LINK_PARAMETERS: [&'static str; 23] = [
"id",
"eww",
"rmail",
"mhe",
"irc",
"info",
"gnus",
"docview",
"bibtex",
"bbdb",
"w3m",
"doi",
"file+sys",
"file+emacs",
"shell",
"news",
"mailto",
"https",
"http",
"ftp",
"help",
"file",
"elisp",
];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn plain_link<'r, 's>(
context: Context<'r, 's>,
@@ -101,19 +73,36 @@ pub fn protocol<'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should be defined by org-link-parameters
for link_parameter in ORG_LINK_PARAMETERS {
let result = tag_no_case::<_, _, CustomError<_>>(link_parameter)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, ent));
}
Err(_) => {}
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoLinkProtocol".into(),
))))
let (remaining, proto) = alt((
alt((
tag_no_case("id"),
tag_no_case("eww"),
tag_no_case("rmail"),
tag_no_case("mhe"),
tag_no_case("irc"),
tag_no_case("info"),
tag_no_case("gnus"),
tag_no_case("docview"),
tag_no_case("bibtex"),
tag_no_case("bbdb"),
tag_no_case("w3m"),
)),
alt((
tag_no_case("doi"),
tag_no_case("file+sys"),
tag_no_case("file+emacs"),
tag_no_case("shell"),
tag_no_case("news"),
tag_no_case("mailto"),
tag_no_case("https"),
tag_no_case("http"),
tag_no_case("ftp"),
tag_no_case("help"),
tag_no_case("file"),
tag_no_case("elisp"),
)),
))(input)?;
Ok((remaining, proto))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -130,11 +119,7 @@ fn path_plain<'r, 's>(
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, path) = recognize(verify(
many_till(anychar, peek(exit_matcher)),
|(children, _exit_contents)| !children.is_empty(),
))(input)?;
let (remaining, path) = recognize(many_till(anychar, peek(exit_matcher)))(input)?;
Ok((remaining, path))
}
@@ -143,10 +128,5 @@ fn path_plain_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many_till(
verify(anychar, |c| {
*c != '/' && (c.is_ascii_punctuation() || c.is_whitespace())
}),
one_of(" \t\r\n()[]<>"),
))(input)
recognize(one_of(" \t\r\n()[]<>"))(input)
}

View File

@@ -6,24 +6,19 @@ use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::greater_element::PlainList;
use super::greater_element::PlainListItem;
use super::object_parser::standard_set_object;
use super::org_source::OrgSource;
use super::parser_with_context::parser_with_context;
use super::util::non_whitespace_character;
use super::Context;
use super::Object;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
@@ -38,14 +33,13 @@ use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::parser::util::start_of_line;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn detect_plain_list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
pub fn detect_plain_list<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
// TODO: Add support for plain list items that do not have content on the first line.
if verify(
tuple((
start_of_line,
space0,
bullet,
alt((space1, line_ending, eof)),
)),
tuple((start_of_line, space0, bullet, space1)),
|(_start, indent, bull, _after_whitespace)| {
Into::<&str>::into(bull) != "*" || indent.len() > 0
},
@@ -66,7 +60,6 @@ pub fn plain_list<'r, 's>(
) -> Res<OrgSource<'s>, PlainList<'s>> {
let parser_context = context
.with_additional_node(ContextElement::Context("plain list"))
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &plain_list_end,
@@ -141,33 +134,26 @@ pub fn plain_list_item<'r, 's>(
Into::<&str>::into(bull) != "*" || indent_level > 0
})(remaining)?;
let (remaining, maybe_tag) = opt(tuple((
space1,
parser_with_context!(item_tag)(context),
tag(" ::"),
)))(remaining)?;
// TODO: This isn't taking into account items that immediately line break and then have contents
let maybe_contentless_item: Res<OrgSource<'_>, OrgSource<'_>> =
peek(recognize(tuple((many0(blank_line), eof))))(remaining);
alt((eof, line_ending))(remaining);
match maybe_contentless_item {
Ok((_rem, _ws)) => {
let (remaining, _trailing_ws) = opt(blank_line)(remaining)?;
let source = get_consumed(input, remaining);
Ok((rem, _ws)) => {
let source = get_consumed(input, rem);
return Ok((
remaining,
rem,
PlainListItem {
source: source.into(),
indentation: indent_level,
bullet: bull.into(),
tag: maybe_tag
.map(|(_ws, item_tag, _divider)| item_tag)
.unwrap_or(Vec::new()),
children: Vec::new(),
},
));
}
Err(_) => {}
};
let (remaining, _ws) = item_tag_post_gap(context, remaining)?;
let (remaining, _ws) = space1(remaining)?;
let exit_matcher = plain_list_item_end(indent_level);
let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
@@ -176,24 +162,14 @@ pub fn plain_list_item<'r, 's>(
exit_matcher: &exit_matcher,
}));
let (mut remaining, (mut children, _exit_contents)) = many_till(
include_input(parser_with_context!(element(true))(&parser_context)),
let (remaining, (children, _exit_contents)) = verify(
many_till(
parser_with_context!(element(true))(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context),
),
|(children, _exit_contents)| !children.is_empty(),
)(remaining)?;
if !children.is_empty() && !context.should_consume_trailing_whitespace() {
let final_item_context =
parser_context.with_additional_node(ContextElement::ConsumeTrailingWhitespace(false));
let (final_child_start, _original_final_child) = children
.pop()
.expect("if-statement already checked that children was non-empty.");
let (remain, reparsed_final_element) = include_input(parser_with_context!(element(true))(
&final_item_context,
))(final_child_start)?;
remaining = remain;
children.push(reparsed_final_element);
}
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
@@ -204,26 +180,11 @@ pub fn plain_list_item<'r, 's>(
source: source.into(),
indentation: indent_level,
bullet: bull.into(),
tag: maybe_tag
.map(|(_ws, item_tag, _divider)| item_tag)
.unwrap_or(Vec::new()),
children: children.into_iter().map(|(_start, item)| item).collect(),
children,
},
));
}
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"))]
fn bullet<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((
@@ -301,60 +262,6 @@ fn _line_indented_lte<'r, 's>(
Ok(matched)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &item_tag_end,
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
// TODO: Should this be using a different set like the minimal set?
parser_with_context!(standard_set_object)(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context),
),
|(children, _exit_contents)| !children.is_empty(),
)(input)?;
Ok((remaining, children))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(alt((
line_ending,
tag(" :: "),
recognize(tuple((tag(" ::"), alt((line_ending, eof))))),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag_post_gap<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
verify(
recognize(tuple((
alt((blank_line, space0)),
many_till(
blank_line,
alt((
peek(recognize(not(blank_line))),
peek(recognize(tuple((many0(blank_line), eof)))),
parser_with_context!(exit_matcher_parser)(context),
)),
),
))),
|gap| gap.len() > 0,
)(input)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -512,36 +419,4 @@ dolar"#,
"#
);
}
#[test]
fn detect_line_break() {
let input = OrgSource::new(
r#"+
"#,
);
let result = detect_plain_list(input);
assert!(result.is_ok());
}
#[test]
fn detect_eof() {
let input = OrgSource::new(r#"+"#);
let result = detect_plain_list(input);
assert!(result.is_ok());
}
#[test]
fn detect_no_gap() {
let input = OrgSource::new(r#"+foo"#);
let result = detect_plain_list(input);
// Since there is no whitespace after the '+' this is a paragraph, not a plain list.
assert!(result.is_err());
}
#[test]
fn detect_with_gap() {
let input = OrgSource::new(r#"+ foo"#);
let result = detect_plain_list(input);
assert!(result.is_ok());
}
}

View File

@@ -2,14 +2,13 @@ use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till;
use nom::sequence::tuple;
@@ -24,6 +23,7 @@ use crate::parser::greater_element::PropertyDrawer;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::plain_text::plain_text;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
@@ -147,16 +147,11 @@ fn node_property_name<'r, 's>(
}));
let (remaining, name) = recognize(tuple((
verify(
many_till(
anychar,
parser_with_context!(exit_matcher_parser)(&parser_context),
),
|(children, _exit_contents)| !children.is_empty(),
),
map(parser_with_context!(plain_text)(&parser_context), |pt| {
pt.source
}),
opt(tag("+")),
)))(input)?;
Ok((remaining, name))
}

View File

@@ -6,7 +6,6 @@ use nom::combinator::verify;
use nom::multi::many_till;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use super::Object;
use crate::error::CustomError;
@@ -105,8 +104,7 @@ pub fn radio_target<'r, 's>(
)(remaining)?;
let (remaining, _closing) = tag(">>>")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,

View File

@@ -3,13 +3,13 @@ use nom::bytes::complete::escaped;
use nom::bytes::complete::tag;
use nom::bytes::complete::take_till1;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::verify;
use nom::multi::many_till;
use super::org_source::OrgSource;
use super::parser_with_context::parser_with_context;
use super::util::get_consumed;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use super::Object;
use super::RegularLink;
@@ -39,8 +39,7 @@ pub fn regular_link_without_description<'r, 's>(
let (remaining, _opening_bracket) = tag("[[")(input)?;
let (remaining, _path) = pathreg(context, remaining)?;
let (remaining, _closing_bracket) = tag("]]")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -60,8 +59,7 @@ pub fn regular_link_with_description<'r, 's>(
let (remaining, _closing_bracket) = tag("][")(remaining)?;
let (remaining, _description) = description(context, remaining)?;
let (remaining, _closing_bracket) = tag("]]")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,

View File

@@ -35,6 +35,44 @@ pub struct TextWithProperties<'s> {
pub properties: Vec<Token<'s>>,
}
impl<'s> TextWithProperties<'s> {
pub fn unquote(&self) -> Result<String, Box<dyn std::error::Error>> {
let mut out = String::with_capacity(self.text.len());
if !self.text.starts_with(r#"""#) {
return Err("Quoted text does not start with quote.".into());
}
if !self.text.ends_with(r#"""#) {
return Err("Quoted text does not end with quote.".into());
}
let interior_text = &self.text[1..(self.text.len() - 1)];
let mut state = ParseState::Normal;
for current_char in interior_text.chars().into_iter() {
state = match (state, current_char) {
(ParseState::Normal, '\\') => ParseState::Escape,
(ParseState::Normal, _) => {
out.push(current_char);
ParseState::Normal
}
(ParseState::Escape, 'n') => {
out.push('\n');
ParseState::Normal
}
(ParseState::Escape, '\\') => {
out.push('\\');
ParseState::Normal
}
(ParseState::Escape, '"') => {
out.push('"');
ParseState::Normal
}
_ => todo!(),
};
}
Ok(out)
}
}
enum ParseState {
Normal,
Escape,
@@ -44,28 +82,28 @@ impl<'s> Token<'s> {
pub fn as_vector<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
Ok(match self {
Token::Vector(children) => Ok(children),
_ => Err(format!("wrong token type, expected vector: {:?}", self)),
_ => Err(format!("wrong token type {:?}", self)),
}?)
}
pub fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
Ok(match self {
Token::List(children) => Ok(children),
_ => Err(format!("wrong token type, expected list: {:?}", self)),
_ => Err(format!("wrong token type {:?}", self)),
}?)
}
pub fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> {
Ok(match self {
Token::Atom(body) => Ok(*body),
_ => Err(format!("wrong token type, expected atom: {:?}", self)),
_ => Err(format!("wrong token type {:?}", self)),
}?)
}
pub fn as_text<'p>(&'p self) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> {
Ok(match self {
Token::TextWithProperties(body) => Ok(body),
_ => Err(format!("wrong token type, expected text: {:?}", self)),
_ => Err(format!("wrong token type {:?}", self)),
}?)
}
@@ -95,42 +133,6 @@ impl<'s> Token<'s> {
}
}
pub fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
let mut out = String::with_capacity(text.len());
if !text.starts_with(r#"""#) {
return Err("Quoted text does not start with quote.".into());
}
if !text.ends_with(r#"""#) {
return Err("Quoted text does not end with quote.".into());
}
let interior_text = &text[1..(text.len() - 1)];
let mut state = ParseState::Normal;
for current_char in interior_text.chars().into_iter() {
state = match (state, current_char) {
(ParseState::Normal, '\\') => ParseState::Escape,
(ParseState::Normal, _) => {
out.push(current_char);
ParseState::Normal
}
(ParseState::Escape, 'n') => {
out.push('\n');
ParseState::Normal
}
(ParseState::Escape, '\\') => {
out.push('\\');
ParseState::Normal
}
(ParseState::Escape, '"') => {
out.push('"');
ParseState::Normal
}
_ => todo!(),
};
}
Ok(out)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = multispace0(input)?;

View File

@@ -1,10 +1,10 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::space0;
use nom::combinator::recognize;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
@@ -23,13 +23,12 @@ pub fn statistics_cookie<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn percent_statistics_cookie<'r, 's>(
context: Context<'r, 's>,
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
let (remaining, source) =
recognize(tuple((tag("["), nom::character::complete::u64, tag("%]"))))(input)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
Ok((
remaining,
StatisticsCookie {
@@ -40,7 +39,7 @@ pub fn percent_statistics_cookie<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn fraction_statistics_cookie<'r, 's>(
context: Context<'r, 's>,
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
let (remaining, source) = recognize(tuple((
@@ -50,8 +49,7 @@ pub fn fraction_statistics_cookie<'r, 's>(
nom::character::complete::u64,
tag("]"),
)))(input)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
Ok((
remaining,
StatisticsCookie {

View File

@@ -2,6 +2,7 @@ use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::opt;
@@ -10,9 +11,7 @@ use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use super::Object;
use crate::error::CustomError;
@@ -22,6 +21,7 @@ use crate::parser::exiting::ExitClass;
use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_context::SubscriptSuperscriptBrace;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
@@ -37,8 +37,7 @@ pub fn subscript<'r, 's>(
let (remaining, _) = tag("_")(input)?;
pre(context, input)?;
let (remaining, _body) = script_body(context, remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -57,8 +56,7 @@ pub fn superscript<'r, 's>(
let (remaining, _) = tag("^")(input)?;
pre(context, input)?;
let (remaining, _body) = script_body(context, remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -156,11 +154,16 @@ fn script_with_braces<'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let (remaining, _) = tag("{")(input)?;
let exit_with_depth = script_with_braces_end(remaining.get_brace_depth());
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
let parser_context = context
.with_additional_node(ContextElement::SubscriptSuperscriptBrace(
SubscriptSuperscriptBrace {
position: remaining.into(),
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &exit_with_depth,
exit_matcher: &script_with_braces_end,
}));
let (remaining, (children, _exit_contents)) = many_till(
@@ -172,30 +175,49 @@ fn script_with_braces<'r, 's>(
Ok((remaining, children))
}
fn script_with_braces_end(
starting_brace_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_script_with_braces_end(context, input, starting_brace_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _script_with_braces_end<'r, 's>(
_context: Context<'r, 's>,
fn script_with_braces_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
starting_brace_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_brace_depth() - starting_brace_depth;
if current_depth > 0 {
// Its impossible for the next character to end the subscript or superscript if we're any amount of braces deep
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a subscript or superscript.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'{' => {
current_depth += 1;
}
'}' if current_depth == 0 => {
panic!("Exceeded subscript or superscript brace depth.")
}
'}' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
}
if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("}")(input);
if close_bracket.is_ok() {
return close_bracket;
}
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid end for subscript or superscript.".into(),
))));
}
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing brace should end the subscript or superscript.
unreachable!("Exceeded subscript or superscript brace depth.")
}
tag("}")(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn get_bracket_depth<'r, 's>(
context: Context<'r, 's>,
) -> Option<&'r SubscriptSuperscriptBrace<'s>> {
for node in context.iter() {
match node.get_data() {
ContextElement::SubscriptSuperscriptBrace(depth) => return Some(depth),
_ => {}
}
}
None
}

View File

@@ -12,13 +12,14 @@ use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::object_parser::table_cell_set_object;
use super::org_source::OrgSource;
use super::Context;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::greater_element::TableRow;
use crate::parser::lesser_element::TableCell;
use crate::parser::object::Object;
use crate::parser::object_parser::minimal_set_object;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
@@ -161,3 +162,14 @@ fn org_mode_table_cell_end<'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((space0, alt((tag("|"), peek(line_ending))))))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_cell_set_object<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
not(|i| context.check_exit_matcher(i))(input)?;
parser_with_context!(minimal_set_object)(context)(input)
// TODO: add citations, export snippets, footnote references, links, macros, radio targets, targets, and timestamps.
}

View File

@@ -1,13 +1,13 @@
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
@@ -49,8 +49,7 @@ pub fn target<'r, 's>(
))));
}
let (remaining, _) = tag(">>")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((

View File

@@ -17,7 +17,6 @@ use tracing::span;
use super::org_source::OrgSource;
use super::radio_link::RematchObject;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
@@ -205,8 +204,7 @@ fn _text_markup_object<'r, 's, 'x>(
}
let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?;
Ok((remaining, children))
}
@@ -256,8 +254,7 @@ fn _text_markup_string<'r, 's, 'x>(
}
let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?;
Ok((remaining, contents))
}
@@ -280,7 +277,7 @@ pub fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSo
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn post<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (remaining, _) = alt((recognize(one_of(" \r\n\t-.,;:!?')}[\"")), line_ending))(input)?;
let (remaining, _) = alt((recognize(one_of(" \r\n\t-.,;:!?')}[\">")), line_ending))(input)?;
Ok((remaining, ()))
}

View File

@@ -3,6 +3,7 @@ use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::digit1;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::opt;
use nom::combinator::recognize;
@@ -11,7 +12,6 @@ use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
@@ -49,8 +49,7 @@ fn diary_timestamp<'r, 's>(
let (remaining, _) = tag("<%%(")(input)?;
let (remaining, _body) = sexp(context, remaining)?;
let (remaining, _) = tag(")>")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -113,8 +112,7 @@ fn active_timestamp<'r, 's>(
)))(remaining)?;
let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -147,8 +145,7 @@ fn inactive_timestamp<'r, 's>(
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -169,8 +166,7 @@ fn active_date_range_timestamp<'r, 's>(
let (remaining, _separator) = tag("--")(remaining)?;
let (remaining, _second_timestamp) = active_timestamp(context, remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -210,8 +206,7 @@ fn active_time_range_timestamp<'r, 's>(
)))(remaining)?;
let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -232,8 +227,7 @@ fn inactive_date_range_timestamp<'r, 's>(
let (remaining, _separator) = tag("--")(remaining)?;
let (remaining, _second_timestamp) = inactive_timestamp(context, remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -273,8 +267,7 @@ fn inactive_time_range_timestamp<'r, 's>(
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((

View File

@@ -68,18 +68,6 @@ pub fn element_trailing_whitespace<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s
alt((eof, recognize(many0(blank_line))))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn maybe_consume_object_trailing_whitespace_if_not_exiting<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
if exit_matcher_parser(context, input).is_err() {
opt(space0)(input)
} else {
Ok((input, None))
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn maybe_consume_trailing_whitespace_if_not_exiting<'r, 's>(
context: Context<'r, 's>,
@@ -167,16 +155,3 @@ pub fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
"Not implemented yet.".into(),
))));
}
#[allow(dead_code)]
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
/// Text from the current point until the next line break or end of file
///
/// Useful for debugging.
pub fn text_until_eol<'r, 's>(
input: OrgSource<'s>,
) -> Result<&'s str, nom::Err<CustomError<OrgSource<'s>>>> {
let line = recognize(many_till(anychar, alt((line_ending, eof))))(input)
.map(|(_remaining, line)| Into::<&str>::into(line))?;
Ok(line.trim())
}

View File

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

View File

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