Compare commits
41 Commits
feature_ma
...
14b1d0526c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14b1d0526c | ||
|
|
288350daef | ||
|
|
c683516620 | ||
|
|
e731e8ff6b | ||
|
|
4c2037ec44 | ||
|
|
a46b358549 | ||
|
|
ec813e3b3f | ||
|
|
f11f7bcc73 | ||
|
|
9e0e5f6f0a | ||
|
|
16e788c36c | ||
|
|
1952d175c0 | ||
|
|
20c17c40be | ||
|
|
b35d785e73 | ||
|
|
b6b869df25 | ||
|
|
18a396b7cb | ||
|
|
085490476e | ||
|
|
9c9964c66f | ||
|
|
1a3e26c148 | ||
|
|
e9e6a8ff64 | ||
|
|
b124317f30 | ||
|
|
ad389f0776 | ||
|
|
75dfc7f812 | ||
|
|
c17de8ef5e | ||
|
|
378b6bb391 | ||
|
|
cc86591a6c | ||
|
|
f25dbc1d7c | ||
|
|
daee50c160 | ||
|
|
3e143796f7 | ||
|
|
9cc5e63c1b | ||
|
|
be6197e4c7 | ||
|
|
2d4e54845b | ||
|
|
d5ea650b96 | ||
|
|
60363579b5 | ||
|
|
1b678fe81f | ||
|
|
bfea828e62 | ||
|
|
bc5745a95f | ||
|
|
efa372a9e9 | ||
|
|
2fb57daaec | ||
|
|
3a38f4cd35 | ||
|
|
45e16fea2d | ||
|
|
5134cece7b |
@@ -14,10 +14,6 @@ spec:
|
||||
- name: path-to-dockerfile
|
||||
description: The path to the Dockerfile
|
||||
type: string
|
||||
- name: command
|
||||
type: array
|
||||
description: Command to run.
|
||||
default: []
|
||||
tasks:
|
||||
- name: report-pending
|
||||
taskRef:
|
||||
@@ -92,8 +88,6 @@ spec:
|
||||
runAfter:
|
||||
- build-image
|
||||
params:
|
||||
- name: command
|
||||
value: ["$(params.command[*])"]
|
||||
- name: args
|
||||
value: ["--no-default-features"]
|
||||
- name: docker-image
|
||||
@@ -109,8 +103,6 @@ spec:
|
||||
runAfter:
|
||||
- run-image-none
|
||||
params:
|
||||
- name: command
|
||||
value: ["$(params.command[*])"]
|
||||
- name: args
|
||||
value: ["--no-default-features", "--features", "tracing"]
|
||||
- name: docker-image
|
||||
@@ -126,8 +118,6 @@ spec:
|
||||
runAfter:
|
||||
- run-image-tracing
|
||||
params:
|
||||
- name: command
|
||||
value: ["$(params.command[*])"]
|
||||
- name: args
|
||||
value: ["--no-default-features", "--features", "compare"]
|
||||
- name: docker-image
|
||||
@@ -143,8 +133,6 @@ spec:
|
||||
runAfter:
|
||||
- run-image-compare
|
||||
params:
|
||||
- name: command
|
||||
value: ["$(params.command[*])"]
|
||||
- name: args
|
||||
value: []
|
||||
- name: docker-image
|
||||
@@ -160,8 +148,6 @@ spec:
|
||||
runAfter:
|
||||
- run-image-default
|
||||
params:
|
||||
- name: command
|
||||
value: ["$(params.command[*])"]
|
||||
- name: args
|
||||
value: ["--no-default-features", "--features", "tracing,compare"]
|
||||
- name: docker-image
|
||||
@@ -256,5 +242,3 @@ spec:
|
||||
value: docker/organic_build/
|
||||
- name: path-to-dockerfile
|
||||
value: docker/organic_build/Dockerfile
|
||||
- name: command
|
||||
value: [cargo, build]
|
||||
|
||||
@@ -18,14 +18,6 @@ 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:
|
||||
@@ -117,10 +109,8 @@ spec:
|
||||
runAfter:
|
||||
- build-image
|
||||
params:
|
||||
- name: command
|
||||
value: ["$(params.command[*])"]
|
||||
- name: args
|
||||
value: ["$(params.args[*])"]
|
||||
value: [--no-fail-fast, --lib, --test, test_loader]
|
||||
- name: docker-image
|
||||
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||
finally:
|
||||
@@ -212,7 +202,3 @@ 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]
|
||||
|
||||
@@ -14,14 +14,6 @@ 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
|
||||
@@ -119,10 +111,6 @@ 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
|
||||
@@ -240,7 +228,3 @@ spec:
|
||||
value: docker/cargo_fmt/
|
||||
- name: path-to-dockerfile
|
||||
value: docker/cargo_fmt/Dockerfile
|
||||
- name: command
|
||||
value: [cargo, fmt]
|
||||
- name: args
|
||||
value: []
|
||||
|
||||
@@ -44,11 +44,13 @@ 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
|
||||
|
||||
2
Makefile
2
Makefile
@@ -40,7 +40,7 @@ test:
|
||||
.PHONY: dockertest
|
||||
dockertest:
|
||||
> $(MAKE) -C docker/organic_test
|
||||
> docker run --init --rm -i -t -v "$$(readlink -f ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test cargo test --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
||||
> docker run --init --rm -i -t -v "$$(readlink -f ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
||||
|
||||
.PHONY: dockerclean
|
||||
dockerclean:
|
||||
|
||||
@@ -26,10 +26,11 @@ 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:
|
||||
docker run --rm -i -t $(IMAGE_NAME)
|
||||
run: build
|
||||
docker run --rm --init -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME)
|
||||
|
||||
.PHONY: shell
|
||||
shell:
|
||||
docker run --rm -i -t --entrypoint /bin/bash $(IMAGE_NAME)
|
||||
shell: build
|
||||
docker run --rm -i -t --entrypoint /bin/sh -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME)
|
||||
|
||||
@@ -2,3 +2,5 @@ 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"]
|
||||
|
||||
@@ -25,11 +25,13 @@ 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:
|
||||
docker run --rm -i -t $(IMAGE_NAME)
|
||||
run: build
|
||||
docker run --rm --init -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME)
|
||||
|
||||
.PHONY: shell
|
||||
shell:
|
||||
docker run --rm -i -t --entrypoint /bin/bash $(IMAGE_NAME)
|
||||
shell: build
|
||||
docker run --rm -i -t --entrypoint /bin/sh -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME)
|
||||
|
||||
@@ -30,3 +30,5 @@ 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"]
|
||||
|
||||
@@ -25,11 +25,12 @@ 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:
|
||||
docker run --rm -i -t $(IMAGE_NAME)
|
||||
run: build
|
||||
docker run --rm --init -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME) --no-fail-fast --lib --test test_loader
|
||||
|
||||
.PHONY: shell
|
||||
shell:
|
||||
docker run --rm -i -t --entrypoint /bin/bash $(IMAGE_NAME)
|
||||
shell: build
|
||||
docker run --rm -i -t --entrypoint /bin/sh -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
1. foo
|
||||
1. plain-list
|
||||
#+begin_center
|
||||
|
||||
|
||||
#+end_center
|
||||
2. bar
|
||||
|
||||
Is this still in the plain list?
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This folder is an investigation into whether or not my exit matchers should operate from the top down or bottom up.
|
||||
@@ -1 +0,0 @@
|
||||
foo *bar baz * lorem* ipsum
|
||||
@@ -1,3 +0,0 @@
|
||||
Looks like 2 blank lines always exits the top-level plain list.
|
||||
|
||||
Plain lists do not seem to go inside paragraphs but rather exist beside them.
|
||||
@@ -1,12 +0,0 @@
|
||||
1. foo
|
||||
|
||||
bar
|
||||
|
||||
1. baz
|
||||
|
||||
lorem
|
||||
|
||||
ipsum
|
||||
|
||||
|
||||
dolar
|
||||
@@ -1 +0,0 @@
|
||||
Looks like table cells cannot contain lists but can contain bolds
|
||||
@@ -1,5 +0,0 @@
|
||||
ip *su* m
|
||||
|
||||
| foo | bar |
|
||||
|----------+-----|
|
||||
| 1. lo *re* m | |
|
||||
25
org_mode_samples/greater_element/dynamic_block/simple.org
Normal file
25
org_mode_samples/greater_element/dynamic_block/simple.org
Normal file
@@ -0,0 +1,25 @@
|
||||
#+BEGIN: clocktable :scope file :maxlevel 2
|
||||
#+CAPTION: Clock summary at [2023-08-25 Fri 05:34]
|
||||
| Headline | Time |
|
||||
|--------------+--------|
|
||||
| *Total time* | *0:00* |
|
||||
#+END:
|
||||
|
||||
#+BEGIN: columnview :hlines 1 :id global
|
||||
| ITEM | TODO | PRIORITY | TAGS |
|
||||
|-------+------+----------+------------------------------|
|
||||
| Foo | | B | |
|
||||
|-------+------+----------+------------------------------|
|
||||
| Bar | TODO | B | |
|
||||
|-------+------+----------+------------------------------|
|
||||
| Baz | | B | :thisisatag: |
|
||||
| Lorem | | B | :thisshouldinheritfromabove: |
|
||||
| Ipsum | | B | :multiple:tags: |
|
||||
#+END:
|
||||
* Foo
|
||||
* TODO Bar
|
||||
* Baz :thisisatag:
|
||||
** Lorem :thisshouldinheritfromabove:
|
||||
*** Ipsum :multiple:tags:
|
||||
* Dolar ::
|
||||
* cat :dog: bat
|
||||
@@ -0,0 +1,7 @@
|
||||
1. foo
|
||||
2.
|
||||
bar
|
||||
1.
|
||||
#+begin_center
|
||||
Still in the list
|
||||
#+end_center
|
||||
22
org_mode_samples/object/citation/balanced_brackets.org
Normal file
22
org_mode_samples/object/citation/balanced_brackets.org
Normal file
@@ -0,0 +1,22 @@
|
||||
# Extra open
|
||||
[cite/a/b-_/foo:unbalancedglobal[prefix;keyprefix @foo keysuffix;globalsuffix]
|
||||
|
||||
[cite/a/b-_/foo:globalprefix;unbalancedkey[prefix @foo keysuffix;globalsuffix]
|
||||
|
||||
[cite/a/b-_/foo:globalprefix;keyprefix @foo unbalancedkey[suffix;globalsuffix]
|
||||
|
||||
[cite/a/b-_/foo:globalprefix;keyprefix @foo keysuffix;unbalancedglobal[suffix]
|
||||
|
||||
|
||||
# Extra close
|
||||
[cite/a/b-_/foo:unbalancedglobal]prefix;keyprefix @foo keysuffix;globalsuffix]
|
||||
|
||||
[cite/a/b-_/foo:globalprefix;unbalancedkey]prefix @foo keysuffix;globalsuffix]
|
||||
|
||||
[cite/a/b-_/foo:globalprefix;keyprefix @foo unbalancedkey]suffix;globalsuffix]
|
||||
|
||||
[cite/a/b-_/foo:globalprefix;keyprefix @foo keysuffix;unbalancedglobal]suffix]
|
||||
|
||||
|
||||
# balanced:
|
||||
[cite/a/b-_/foo:gl[obalpref]ix;ke[ypref]ix @foo ke[ysuff]ix;gl[obalsuff]ix]
|
||||
@@ -0,0 +1,2 @@
|
||||
[fn:2:This footnote [ has balanced ] brackets inside it]
|
||||
[fn::This footnote does not have balanced [ brackets inside it]
|
||||
6
org_mode_samples/object/latex_fragment/three_lines.org
Normal file
6
org_mode_samples/object/latex_fragment/three_lines.org
Normal file
@@ -0,0 +1,6 @@
|
||||
$foo
|
||||
bar
|
||||
baz
|
||||
lorem
|
||||
ipsum
|
||||
dolar$
|
||||
17
org_mode_samples/object/text_markup/three_lines.org
Normal file
17
org_mode_samples/object/text_markup/three_lines.org
Normal file
@@ -0,0 +1,17 @@
|
||||
foo *bar
|
||||
baz* lorem
|
||||
|
||||
text *markup
|
||||
can
|
||||
span* more
|
||||
|
||||
than *three
|
||||
lines.
|
||||
foo
|
||||
bar* baz
|
||||
|
||||
foo *bar \\
|
||||
baz \\
|
||||
lorem \\
|
||||
ipsum \\
|
||||
dolar* cat
|
||||
@@ -8,11 +8,22 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
cd "$DIR/../"
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
echo "You probably want to go to https://profiler.firefox.com/"
|
||||
echo "Either that or run hotspot"
|
||||
# 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 "${@}"
|
||||
|
||||
@@ -7,6 +7,8 @@ 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)
|
||||
@@ -24,23 +26,31 @@ function build_container {
|
||||
function launch_container {
|
||||
local additional_flags=()
|
||||
local additional_args=()
|
||||
local features=(compare)
|
||||
|
||||
if [ "$SHELL" != "YES" ]; then
|
||||
additional_args+=(cargo run)
|
||||
else
|
||||
additional_flags+=(-t)
|
||||
if [ "$NO_COLOR" != "" ]; then
|
||||
additional_flags+=(--env "NO_COLOR=$NO_COLOR")
|
||||
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")
|
||||
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 -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test "${additional_args[@]}"
|
||||
docker run "${additional_flags[@]}" --init --rm -i -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}"
|
||||
}
|
||||
|
||||
main "${@}"
|
||||
|
||||
@@ -4,6 +4,8 @@ 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)
|
||||
@@ -40,7 +42,11 @@ function get_test_names {
|
||||
|
||||
function launch_container {
|
||||
local test="$1"
|
||||
local additional_args=()
|
||||
local additional_flags=()
|
||||
|
||||
if [ "$NO_COLOR" != "" ]; then
|
||||
additional_flags+=(--env "NO_COLOR=$NO_COLOR")
|
||||
fi
|
||||
|
||||
local init_script=$(cat <<EOF
|
||||
set -euo pipefail
|
||||
@@ -50,7 +56,7 @@ cargo test --no-fail-fast --lib --test test_loader "$test" -- --show-output
|
||||
EOF
|
||||
)
|
||||
|
||||
docker run --init --rm -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test sh -c "$init_script"
|
||||
docker run "${additional_flags[@]}" --init --rm -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script"
|
||||
}
|
||||
|
||||
|
||||
|
||||
23
scripts/time_parse.bash
Executable file
23
scripts/time_parse.bash
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Time running a single parse without invoking a compare with emacs.
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
: ${PROFILE:="release-lto"}
|
||||
|
||||
cd "$DIR/../"
|
||||
|
||||
function main {
|
||||
local additional_flags=()
|
||||
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
|
||||
PROFILE="debug"
|
||||
else
|
||||
additional_flags+=(--profile "$PROFILE")
|
||||
fi
|
||||
cargo build --no-default-features "${additional_flags[@]}"
|
||||
time ./target/${PROFILE}/compare
|
||||
}
|
||||
|
||||
main "${@}"
|
||||
@@ -1,5 +1,8 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::util::assert_bounds;
|
||||
use super::util::assert_name;
|
||||
use crate::parser::sexp::unquote;
|
||||
use crate::parser::sexp::Token;
|
||||
use crate::parser::AngleLink;
|
||||
use crate::parser::Bold;
|
||||
@@ -84,12 +87,24 @@ impl DiffResult {
|
||||
match self.status {
|
||||
DiffStatus::Good => {
|
||||
if self.has_bad_children() {
|
||||
"BADCHILD"
|
||||
format!(
|
||||
"{color}BADCHILD{reset}",
|
||||
color = DiffResult::foreground_color(255, 255, 0),
|
||||
reset = DiffResult::reset_color(),
|
||||
)
|
||||
} else {
|
||||
"GOOD"
|
||||
format!(
|
||||
"{color}GOOD{reset}",
|
||||
color = DiffResult::foreground_color(0, 255, 0),
|
||||
reset = DiffResult::reset_color(),
|
||||
)
|
||||
}
|
||||
}
|
||||
DiffStatus::Bad => "BAD",
|
||||
DiffStatus::Bad => format!(
|
||||
"{color}BAD{reset}",
|
||||
color = DiffResult::foreground_color(255, 0, 0),
|
||||
reset = DiffResult::reset_color(),
|
||||
),
|
||||
}
|
||||
};
|
||||
println!(
|
||||
@@ -117,6 +132,45 @@ impl DiffResult {
|
||||
DiffStatus::Bad => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn foreground_color(red: u8, green: u8, blue: u8) -> String {
|
||||
if DiffResult::should_use_color() {
|
||||
format!(
|
||||
"\x1b[38;2;{red};{green};{blue}m",
|
||||
red = red,
|
||||
green = green,
|
||||
blue = blue
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn background_color(red: u8, green: u8, blue: u8) -> String {
|
||||
if DiffResult::should_use_color() {
|
||||
format!(
|
||||
"\x1b[48;2;{red};{green};{blue}m",
|
||||
red = red,
|
||||
green = green,
|
||||
blue = blue
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_color() -> &'static str {
|
||||
if DiffResult::should_use_color() {
|
||||
"\x1b[0m"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
fn should_use_color() -> bool {
|
||||
!std::env::var("NO_COLOR").is_ok_and(|val| !val.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_element<'s>(
|
||||
@@ -272,6 +326,7 @@ fn compare_heading<'s>(
|
||||
let children = emacs.as_list()?;
|
||||
let mut child_status = Vec::new();
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let mut message = None;
|
||||
let emacs_name = "headline";
|
||||
if assert_name(emacs, emacs_name).is_err() {
|
||||
this_status = DiffStatus::Bad;
|
||||
@@ -281,6 +336,45 @@ fn compare_heading<'s>(
|
||||
this_status = DiffStatus::Bad;
|
||||
}
|
||||
|
||||
// Compare tags
|
||||
let emacs_tags = get_tags_from_heading(emacs)?;
|
||||
let emacs_tags: HashSet<_> = emacs_tags.iter().map(|val| val.as_str()).collect();
|
||||
let rust_tags: HashSet<&str> = rust.tags.iter().map(|val| *val).collect();
|
||||
let difference: Vec<&str> = emacs_tags
|
||||
.symmetric_difference(&rust_tags)
|
||||
.map(|val| *val)
|
||||
.collect();
|
||||
if !difference.is_empty() {
|
||||
this_status = DiffStatus::Bad;
|
||||
message = Some(format!("Mismatched tags: {}", difference.join(", ")));
|
||||
}
|
||||
|
||||
// Compare todo-keyword
|
||||
let todo_keyword = {
|
||||
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 todo_keyword = attributes_map
|
||||
.get(":todo-keyword")
|
||||
.ok_or("Missing :todo-keyword attribute.");
|
||||
todo_keyword?.as_atom()?
|
||||
};
|
||||
match (todo_keyword, rust.todo_keyword, unquote(todo_keyword)) {
|
||||
("nil", None, _) => {}
|
||||
(_, Some(rust_todo), Ok(emacs_todo)) if emacs_todo == rust_todo => {}
|
||||
(emacs_todo, rust_todo, _) => {
|
||||
this_status = DiffStatus::Bad;
|
||||
message = Some(format!(
|
||||
"(emacs != rust) {:?} != {:?}",
|
||||
emacs_todo, rust_todo
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Compare title
|
||||
let title = {
|
||||
let children = emacs.as_list()?;
|
||||
let attributes_child = children
|
||||
@@ -297,6 +391,9 @@ fn compare_heading<'s>(
|
||||
child_status.push(compare_object(source, emacs_child, rust_child)?);
|
||||
}
|
||||
|
||||
// TODO: Compare todo-type, level, priority
|
||||
|
||||
// Compare section
|
||||
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
|
||||
match rust_child {
|
||||
DocumentElement::Heading(rust_heading) => {
|
||||
@@ -311,11 +408,44 @@ fn compare_heading<'s>(
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
message: None,
|
||||
message,
|
||||
children: child_status,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_tags_from_heading<'s>(
|
||||
emacs: &'s Token<'s>,
|
||||
) -> Result<HashSet<String>, 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 tags = attributes_map
|
||||
.get(":tags")
|
||||
.ok_or("Missing :tags attribute.")?;
|
||||
match tags.as_atom() {
|
||||
Ok("nil") => {
|
||||
return Ok(HashSet::new());
|
||||
}
|
||||
Ok(val) => panic!("Unexpected value for tags: {:?}", val),
|
||||
Err(_) => {}
|
||||
};
|
||||
let tags = {
|
||||
let tags = tags.as_list()?;
|
||||
let strings = tags
|
||||
.iter()
|
||||
.map(Token::as_atom)
|
||||
.collect::<Result<Vec<&str>, _>>()?;
|
||||
strings
|
||||
.into_iter()
|
||||
.map(unquote)
|
||||
.collect::<Result<HashSet<String>, _>>()?
|
||||
};
|
||||
Ok(tags)
|
||||
}
|
||||
|
||||
fn compare_paragraph<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
@@ -974,7 +1104,7 @@ fn compare_plain_text<'s>(
|
||||
rust.source.len()
|
||||
));
|
||||
}
|
||||
let unquoted_text = text.unquote()?;
|
||||
let unquoted_text = unquote(text.text)?;
|
||||
if unquoted_text != rust.source {
|
||||
this_status = DiffStatus::Bad;
|
||||
message = Some(format!(
|
||||
|
||||
@@ -47,9 +47,7 @@ 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 a begin.")?,
|
||||
standard_properties.end.ok_or("Token should have an end.")?,
|
||||
);
|
||||
let (rust_begin, rust_end) = get_offsets(source, rust);
|
||||
if (rust_begin + 1) != begin || (rust_end + 1) != end {
|
||||
|
||||
@@ -11,17 +11,16 @@ 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::OrgSource;
|
||||
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,13 +37,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) = opt(parser_with_context!(global_prefix)(context))(remaining)?;
|
||||
let (remaining, _prefix) =
|
||||
must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?;
|
||||
|
||||
let (remaining, _references) =
|
||||
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
|
||||
let (remaining, _suffix) = opt(tuple((
|
||||
let (remaining, _suffix) = must_balance_bracket(opt(tuple((
|
||||
tag(";"),
|
||||
parser_with_context!(global_suffix)(context),
|
||||
)))(remaining)?;
|
||||
))))(remaining)?;
|
||||
let (remaining, _) = tag("]")(remaining)?;
|
||||
let (remaining, _) = space0(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
@@ -83,15 +84,11 @@ fn global_prefix<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
||||
position: input,
|
||||
depth: 0,
|
||||
}))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
let exit_with_depth = global_prefix_end(input.get_bracket_depth());
|
||||
let parser_context =
|
||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &global_prefix_end,
|
||||
exit_matcher: &exit_with_depth,
|
||||
}));
|
||||
let (remaining, (children, _exit_contents)) = verify(
|
||||
many_till(
|
||||
@@ -104,28 +101,24 @@ fn global_prefix<'r, 's>(
|
||||
Ok((remaining, children))
|
||||
}
|
||||
|
||||
fn global_prefix_end(
|
||||
starting_bracket_depth: isize,
|
||||
) -> 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: isize,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
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.")
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||
@@ -144,15 +137,11 @@ fn global_suffix<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
||||
position: input,
|
||||
depth: 0,
|
||||
}))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
let exit_with_depth = global_suffix_end(input.get_bracket_depth());
|
||||
let parser_context =
|
||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &global_suffix_end,
|
||||
exit_matcher: &exit_with_depth,
|
||||
}));
|
||||
let (remaining, (children, _exit_contents)) = verify(
|
||||
many_till(
|
||||
@@ -164,28 +153,24 @@ fn global_suffix<'r, 's>(
|
||||
Ok((remaining, children))
|
||||
}
|
||||
|
||||
fn global_suffix_end(
|
||||
starting_bracket_depth: isize,
|
||||
) -> 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: isize,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
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.")
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||
|
||||
@@ -13,11 +13,11 @@ use nom::sequence::tuple;
|
||||
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;
|
||||
@@ -31,9 +31,11 @@ pub fn citation_reference<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, CitationReference<'s>> {
|
||||
let (remaining, _prefix) = opt(parser_with_context!(key_prefix)(context))(input)?;
|
||||
let (remaining, _prefix) =
|
||||
must_balance_bracket(opt(parser_with_context!(key_prefix)(context)))(input)?;
|
||||
let (remaining, _key) = parser_with_context!(citation_reference_key)(context)(remaining)?;
|
||||
let (remaining, _suffix) = opt(parser_with_context!(key_suffix)(context))(remaining)?;
|
||||
let (remaining, _suffix) =
|
||||
must_balance_bracket(opt(parser_with_context!(key_suffix)(context)))(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
|
||||
Ok((
|
||||
@@ -69,15 +71,11 @@ fn key_prefix<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
||||
position: input,
|
||||
depth: 0,
|
||||
}))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
let exit_with_depth = key_prefix_end(input.get_bracket_depth());
|
||||
let parser_context =
|
||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &key_prefix_end,
|
||||
exit_matcher: &exit_with_depth,
|
||||
}));
|
||||
let (remaining, (children, _exit_contents)) = verify(
|
||||
many_till(
|
||||
@@ -94,15 +92,11 @@ fn key_suffix<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
||||
position: input,
|
||||
depth: 0,
|
||||
}))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
let exit_with_depth = key_suffix_end(input.get_bracket_depth());
|
||||
let parser_context =
|
||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &key_suffix_end,
|
||||
exit_matcher: &exit_with_depth,
|
||||
}));
|
||||
let (remaining, (children, _exit_contents)) = verify(
|
||||
many_till(
|
||||
@@ -114,39 +108,24 @@ fn key_suffix<'r, 's>(
|
||||
Ok((remaining, children))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r CitationBracket<'s>> {
|
||||
for node in context.iter() {
|
||||
match node.get_data() {
|
||||
ContextElement::CitationBracket(depth) => return Some(depth),
|
||||
_ => {}
|
||||
fn key_prefix_end(
|
||||
starting_bracket_depth: isize,
|
||||
) -> 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)
|
||||
}
|
||||
}
|
||||
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: isize,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
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.")
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||
@@ -160,28 +139,24 @@ fn key_prefix_end<'r, 's>(
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn key_suffix_end(
|
||||
starting_bracket_depth: isize,
|
||||
) -> 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: isize,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
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.")
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||
@@ -191,3 +166,21 @@ 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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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;
|
||||
@@ -12,6 +14,7 @@ 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;
|
||||
@@ -50,7 +53,10 @@ 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>>,
|
||||
}
|
||||
|
||||
@@ -267,7 +273,8 @@ fn heading<'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||
not(|i| context.check_exit_matcher(i))(input)?;
|
||||
let (remaining, (star_count, _ws, title)) = headline(context, input)?;
|
||||
let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) =
|
||||
headline(context, input)?;
|
||||
let section_matcher = parser_with_context!(section)(context);
|
||||
let heading_matcher = parser_with_context!(heading)(context);
|
||||
let (remaining, children) = many0(alt((
|
||||
@@ -283,7 +290,10 @@ fn heading<'r, 's>(
|
||||
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,
|
||||
},
|
||||
))
|
||||
@@ -293,30 +303,83 @@ fn heading<'r, 's>(
|
||||
fn headline<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, (usize, OrgSource<'s>, Vec<Object<'s>>)> {
|
||||
) -> Res<
|
||||
OrgSource<'s>,
|
||||
(
|
||||
usize,
|
||||
OrgSource<'s>,
|
||||
Option<(OrgSource<'s>, OrgSource<'s>)>,
|
||||
Vec<Object<'s>>,
|
||||
Vec<&'s str>,
|
||||
),
|
||||
> {
|
||||
let parser_context =
|
||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Document,
|
||||
exit_matcher: &headline_end,
|
||||
exit_matcher: &headline_title_end,
|
||||
}));
|
||||
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
||||
|
||||
let (remaining, (_sol, star_count, ws, title, _line_ending)) = tuple((
|
||||
let (
|
||||
remaining,
|
||||
(_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending),
|
||||
) = tuple((
|
||||
start_of_line,
|
||||
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, title)))
|
||||
Ok((
|
||||
remaining,
|
||||
(
|
||||
star_count,
|
||||
ws,
|
||||
maybe_todo_keyword,
|
||||
title,
|
||||
maybe_tags
|
||||
.map(|(_ws, tags)| {
|
||||
tags.into_iter()
|
||||
.map(|single_tag| Into::<&str>::into(single_tag))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or(Vec::new()),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn headline_end<'r, 's>(
|
||||
fn headline_title_end<'r, 's>(
|
||||
_context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
line_ending(input)
|
||||
recognize(tuple((
|
||||
opt(tuple((space0, tags, space0))),
|
||||
alt((line_ending, eof)),
|
||||
)))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn tags<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
|
||||
let (remaining, (_open, tags, _close)) =
|
||||
tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?;
|
||||
Ok((remaining, tags))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
recognize(many1(verify(anychar, |c| {
|
||||
c.is_alphanumeric() || "_@#%".contains(*c)
|
||||
})))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn heading_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
// TODO: This should take into account the value of "#+TODO:" ref https://orgmode.org/manual/Per_002dfile-keywords.html and possibly the configurable variable org-todo-keywords ref https://orgmode.org/manual/Workflow-states.html. Case is significant.
|
||||
alt((tag("TODO"), tag("DONE")))(input)
|
||||
}
|
||||
|
||||
impl<'s> Document<'s> {
|
||||
|
||||
@@ -45,7 +45,13 @@ fn name<'r, 's>(
|
||||
// TODO: This should be defined by org-entities and optionally org-entities-user
|
||||
|
||||
// TODO: Add the rest of the entities, this is a very incomplete list
|
||||
let (remaining, proto) = alt((alt((tag_no_case("delta"), tag_no_case("pi"))),))(input)?;
|
||||
let (remaining, proto) = alt((alt((
|
||||
tag_no_case("delta"),
|
||||
tag_no_case("pi"),
|
||||
tag_no_case("ast"),
|
||||
tag_no_case("lt"),
|
||||
tag_no_case("gt"),
|
||||
)),))(input)?;
|
||||
Ok((remaining, proto))
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ 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,18 +38,12 @@ fn anonymous_footnote<'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
|
||||
let (remaining, _) = tag_no_case("[fn::")(input)?;
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::FootnoteReferenceDefinition(
|
||||
FootnoteReferenceDefinition {
|
||||
position: remaining,
|
||||
depth: 0,
|
||||
},
|
||||
))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
|
||||
let parser_context =
|
||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Beta,
|
||||
exit_matcher: &footnote_definition_end,
|
||||
exit_matcher: &exit_with_depth,
|
||||
}));
|
||||
// TODO: I could insert FootnoteReferenceDefinition entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
||||
let (remaining, (children, _exit_contents)) = verify(
|
||||
many_till(
|
||||
parser_with_context!(standard_set_object)(&parser_context),
|
||||
@@ -80,18 +73,12 @@ fn inline_footnote<'r, 's>(
|
||||
let (remaining, _) = tag_no_case("[fn:")(input)?;
|
||||
let (remaining, label_contents) = label(remaining)?;
|
||||
let (remaining, _) = tag(":")(remaining)?;
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::FootnoteReferenceDefinition(
|
||||
FootnoteReferenceDefinition {
|
||||
position: remaining,
|
||||
depth: 0,
|
||||
},
|
||||
))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
|
||||
let parser_context =
|
||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Beta,
|
||||
exit_matcher: &footnote_definition_end,
|
||||
exit_matcher: &exit_with_depth,
|
||||
}));
|
||||
// TODO: I could insert FootnoteReferenceDefinition entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
||||
let (remaining, (children, _exit_contents)) = verify(
|
||||
many_till(
|
||||
parser_with_context!(standard_set_object)(&parser_context),
|
||||
@@ -133,47 +120,30 @@ fn footnote_reference_only<'r, 's>(
|
||||
))
|
||||
}
|
||||
|
||||
fn footnote_definition_end(
|
||||
starting_bracket_depth: isize,
|
||||
) -> 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: isize,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
||||
if current_depth > 0 {
|
||||
// Its impossible for the next character to end the footnote reference definition if we're any amount of brackets deep
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoFootnoteReferenceDefinitionEnd".into(),
|
||||
))));
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -58,15 +58,15 @@ pub fn greater_block<'r, 's>(
|
||||
"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))
|
||||
.with_additional_node(ContextElement::GreaterBlock(name.into()))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Alpha,
|
||||
exit_matcher: &greater_block_end,
|
||||
exit_matcher: &exit_with_name,
|
||||
}));
|
||||
let parameters = match parameters {
|
||||
Some((_ws, parameters)) => Some(parameters),
|
||||
@@ -94,7 +94,7 @@ pub fn greater_block<'r, 's>(
|
||||
(remaining, children)
|
||||
}
|
||||
};
|
||||
let (remaining, _end) = greater_block_end(&parser_context, remaining)?;
|
||||
let (remaining, _end) = exit_with_name(&parser_context, remaining)?;
|
||||
|
||||
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
|
||||
|
||||
@@ -120,31 +120,27 @@ 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>(
|
||||
context: Context<'r, 's>,
|
||||
fn _greater_block_end<'r, 's, 'x>(
|
||||
_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(current_name),
|
||||
tag_no_case(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
|
||||
}
|
||||
|
||||
@@ -12,9 +12,10 @@ use nom::multi::many_till;
|
||||
|
||||
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::parser_context::BabelHeaderBracket;
|
||||
use crate::parser::parser_context::ContextElement;
|
||||
use crate::parser::parser_context::ExitMatcherNode;
|
||||
use crate::parser::parser_with_context::parser_with_context;
|
||||
@@ -74,14 +75,11 @@ fn header<'r, 's>(
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let (remaining, _) = tag("[")(input)?;
|
||||
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket {
|
||||
position: remaining,
|
||||
depth: 0,
|
||||
}))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
let exit_with_depth = header_end(remaining.get_bracket_depth());
|
||||
let parser_context =
|
||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &header_end,
|
||||
exit_matcher: &exit_with_depth,
|
||||
}));
|
||||
|
||||
let (remaining, name) = recognize(many_till(
|
||||
@@ -92,28 +90,30 @@ fn header<'r, 's>(
|
||||
Ok((remaining, name))
|
||||
}
|
||||
|
||||
fn header_end(
|
||||
starting_bracket_depth: isize,
|
||||
) -> 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: isize,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
||||
if current_depth > 0 {
|
||||
// Its impossible for the next character to end the header if we're any amount of bracket deep
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoHeaderEnd".into(),
|
||||
))));
|
||||
}
|
||||
if current_depth < 0 {
|
||||
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
|
||||
unreachable!("Exceeded header bracket depth.")
|
||||
}
|
||||
alt((tag("]"), line_ending))(input)
|
||||
}
|
||||
@@ -125,14 +125,11 @@ fn argument<'r, 's>(
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let (remaining, _) = tag("(")(input)?;
|
||||
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket {
|
||||
position: remaining,
|
||||
depth: 0,
|
||||
}))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
let exit_with_depth = argument_end(remaining.get_parenthesis_depth());
|
||||
let parser_context =
|
||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &argument_end,
|
||||
exit_matcher: &exit_with_depth,
|
||||
}));
|
||||
|
||||
let (remaining, name) = recognize(many_till(
|
||||
@@ -143,39 +140,30 @@ fn argument<'r, 's>(
|
||||
Ok((remaining, name))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn argument_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 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;
|
||||
fn argument_end(
|
||||
starting_parenthesis_depth: isize,
|
||||
) -> 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)
|
||||
}
|
||||
']' if current_depth == 0 => {
|
||||
panic!("Exceeded inline babel call argument bracket depth.")
|
||||
}
|
||||
']' if current_depth > 0 => {
|
||||
current_depth -= 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
alt((tag(")"), line_ending))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r BabelHeaderBracket<'s>> {
|
||||
for node in context.iter() {
|
||||
match node.get_data() {
|
||||
ContextElement::BabelHeaderBracket(depth) => return Some(depth),
|
||||
_ => {}
|
||||
fn _argument_end<'r, 's>(
|
||||
_context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
starting_parenthesis_depth: isize,
|
||||
) -> 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(),
|
||||
))));
|
||||
}
|
||||
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.")
|
||||
}
|
||||
None
|
||||
alt((tag(")"), line_ending))(input)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::bytes::complete::tag_no_case;
|
||||
use nom::character::complete::anychar;
|
||||
@@ -14,11 +15,11 @@ use tracing::span;
|
||||
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::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;
|
||||
@@ -75,16 +76,11 @@ fn header<'r, 's>(
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let (remaining, _) = tag("[")(input)?;
|
||||
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::InlineSourceBlockBracket(
|
||||
InlineSourceBlockBracket {
|
||||
position: remaining,
|
||||
depth: 0,
|
||||
},
|
||||
))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
let exit_with_depth = header_end(remaining.get_bracket_depth());
|
||||
let parser_context =
|
||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Beta,
|
||||
exit_matcher: &header_end,
|
||||
exit_matcher: &exit_with_depth,
|
||||
}));
|
||||
|
||||
let (remaining, header_contents) = recognize(many_till(
|
||||
@@ -95,37 +91,32 @@ fn header<'r, 's>(
|
||||
Ok((remaining, header_contents))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn header_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 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 source block header bracket 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;
|
||||
}
|
||||
fn header_end(
|
||||
starting_bracket_depth: isize,
|
||||
) -> 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)
|
||||
}
|
||||
}
|
||||
|
||||
line_ending(input)
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn _header_end<'r, 's>(
|
||||
_context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
starting_bracket_depth: isize,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
||||
if current_depth > 0 {
|
||||
// Its impossible for the next character to end the header if we're any amount of bracket deep
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoHeaderEnd".into(),
|
||||
))));
|
||||
}
|
||||
if current_depth < 0 {
|
||||
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
|
||||
unreachable!("Exceeded header bracket depth.")
|
||||
}
|
||||
alt((tag("]"), line_ending))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
@@ -135,16 +126,11 @@ fn body<'r, 's>(
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let (remaining, _) = tag("{")(input)?;
|
||||
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::InlineSourceBlockBracket(
|
||||
InlineSourceBlockBracket {
|
||||
position: remaining,
|
||||
depth: 0,
|
||||
},
|
||||
))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
let exit_with_depth = body_end(remaining.get_brace_depth());
|
||||
let parser_context =
|
||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Beta,
|
||||
exit_matcher: &body_end,
|
||||
exit_matcher: &exit_with_depth,
|
||||
}));
|
||||
|
||||
let (remaining, body_contents) = recognize(many_till(
|
||||
@@ -165,60 +151,28 @@ fn body<'r, 's>(
|
||||
Ok((remaining, body_contents))
|
||||
}
|
||||
|
||||
fn body_end(
|
||||
starting_brace_depth: isize,
|
||||
) -> 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>,
|
||||
fn _body_end<'r, 's>(
|
||||
_context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
starting_brace_depth: isize,
|
||||
) -> 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;
|
||||
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(),
|
||||
))));
|
||||
}
|
||||
'}' if current_depth == 0 => {
|
||||
panic!("Exceeded inline source block body bracket 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.")
|
||||
}
|
||||
'}' 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"))]
|
||||
pub fn get_bracket_depth<'r, 's>(
|
||||
context: Context<'r, 's>,
|
||||
) -> Option<&'r InlineSourceBlockBracket<'s>> {
|
||||
for node in context.iter() {
|
||||
match node.get_data() {
|
||||
ContextElement::InlineSourceBlockBracket(depth) => return Some(depth),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
alt((tag("}"), line_ending))(input)
|
||||
}
|
||||
|
||||
@@ -202,17 +202,13 @@ 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)?;
|
||||
|
||||
// TODO: As an optimization it would be nice to exit early upon hitting the 3rd line break
|
||||
let (remaining, _) = verify(
|
||||
recognize(many_till(
|
||||
let (remaining, _) = recognize(many_till(
|
||||
anychar,
|
||||
peek(alt((
|
||||
parser_with_context!(exit_matcher_parser)(context),
|
||||
tag("$"),
|
||||
))),
|
||||
)),
|
||||
|body: &OrgSource<'_>| Into::<&str>::into(body).lines().take(4).count() <= 3,
|
||||
)(remaining)?;
|
||||
))(remaining)?;
|
||||
|
||||
let (_, _) = peek(parser_with_context!(close_border)(context))(remaining)?;
|
||||
let (remaining, _) = tag("$")(remaining)?;
|
||||
|
||||
@@ -189,3 +189,36 @@ pub fn regular_link_description_object_set<'r, 's>(
|
||||
parser_with_context!(minimal_set_object)(context),
|
||||
))(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>> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -11,13 +11,25 @@ use nom::Slice;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct OrgSource<'s> {
|
||||
full_source: &'s str,
|
||||
start: usize,
|
||||
end: usize, // exclusive
|
||||
start_of_line: usize,
|
||||
preceding_character: Option<char>,
|
||||
line_number: usize,
|
||||
bracket_depth: isize, // []
|
||||
brace_depth: isize, // {}
|
||||
parenthesis_depth: isize, // ()
|
||||
}
|
||||
|
||||
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> {
|
||||
@@ -31,6 +43,10 @@ impl<'s> OrgSource<'s> {
|
||||
end: input.len(),
|
||||
start_of_line: 0,
|
||||
preceding_character: None,
|
||||
line_number: 1,
|
||||
bracket_depth: 0,
|
||||
brace_depth: 0,
|
||||
parenthesis_depth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +72,22 @@ impl<'s> OrgSource<'s> {
|
||||
assert!(other.end <= self.end);
|
||||
self.slice(..(other.start - self.start))
|
||||
}
|
||||
|
||||
pub fn get_line_number(&self) -> usize {
|
||||
self.line_number
|
||||
}
|
||||
|
||||
pub fn get_bracket_depth(&self) -> isize {
|
||||
self.bracket_depth
|
||||
}
|
||||
|
||||
pub fn get_brace_depth(&self) -> isize {
|
||||
self.brace_depth
|
||||
}
|
||||
|
||||
pub fn get_parenthesis_depth(&self) -> isize {
|
||||
self.parenthesis_depth
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> InputTake for OrgSource<'s> {
|
||||
@@ -119,10 +151,38 @@ where
|
||||
}
|
||||
|
||||
let skipped_text = &self.full_source[self.start..new_start];
|
||||
let start_of_line = skipped_text
|
||||
.rfind('\n')
|
||||
.map(|idx| self.start + idx + 1)
|
||||
.unwrap_or(self.start_of_line);
|
||||
let mut start_of_line = self.start_of_line;
|
||||
let mut line_number = self.line_number;
|
||||
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;
|
||||
line_number += 1;
|
||||
}
|
||||
b'[' => {
|
||||
bracket_depth += 1;
|
||||
}
|
||||
b']' => {
|
||||
bracket_depth -= 1;
|
||||
}
|
||||
b'{' => {
|
||||
brace_depth += 1;
|
||||
}
|
||||
b'}' => {
|
||||
brace_depth -= 1;
|
||||
}
|
||||
b'(' => {
|
||||
parenthesis_depth += 1;
|
||||
}
|
||||
b')' => {
|
||||
parenthesis_depth -= 1;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
OrgSource {
|
||||
full_source: self.full_source,
|
||||
@@ -130,6 +190,10 @@ where
|
||||
end: new_end,
|
||||
start_of_line,
|
||||
preceding_character: skipped_text.chars().last(),
|
||||
line_number,
|
||||
bracket_depth,
|
||||
brace_depth,
|
||||
parenthesis_depth,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,7 +310,9 @@ impl<'s> InputTakeAtPosition for OrgSource<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_error(err: nom::Err<CustomError<OrgSource<'_>>>) -> nom::Err<CustomError<&str>> {
|
||||
pub fn convert_error<'a, I: Into<CustomError<&'a str>>>(
|
||||
err: nom::Err<I>,
|
||||
) -> nom::Err<CustomError<&'a str>> {
|
||||
match err {
|
||||
nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed),
|
||||
nom::Err::Error(err) => nom::Err::Error(err.into()),
|
||||
@@ -369,4 +435,28 @@ mod tests {
|
||||
assert_eq!(input.get_preceding_character(), None);
|
||||
assert_eq!(input.slice(8..).get_preceding_character(), Some('💛'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_number() {
|
||||
let input = OrgSource::new("lorem\nfoo\nbar\nbaz\nipsum");
|
||||
assert_eq!(input.get_line_number(), 1);
|
||||
assert_eq!(input.slice(5..).get_line_number(), 1);
|
||||
assert_eq!(input.slice(6..).get_line_number(), 2);
|
||||
assert_eq!(input.slice(6..).slice(10..).get_line_number(), 4);
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,10 +110,9 @@ 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>),
|
||||
Context(&'r str),
|
||||
|
||||
/// Stores the name of the greater block.
|
||||
GreaterBlock(&'s str),
|
||||
/// Stores the name of the current element to prevent directly nesting elements of the same type.
|
||||
Context(&'r str),
|
||||
|
||||
/// Indicates if elements should consume the whitespace after them.
|
||||
ConsumeTrailingWhitespace(bool),
|
||||
@@ -124,71 +123,6 @@ 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> {
|
||||
@@ -196,36 +130,6 @@ 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");
|
||||
|
||||
@@ -37,9 +37,13 @@ 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, space1)),
|
||||
tuple((
|
||||
start_of_line,
|
||||
space0,
|
||||
bullet,
|
||||
alt((space1, line_ending, eof)),
|
||||
)),
|
||||
|(_start, indent, bull, _after_whitespace)| {
|
||||
Into::<&str>::into(bull) != "*" || indent.len() > 0
|
||||
},
|
||||
@@ -135,8 +139,7 @@ pub fn plain_list_item<'r, 's>(
|
||||
})(remaining)?;
|
||||
|
||||
// TODO: This isn't taking into account items that immediately line break and then have contents
|
||||
let maybe_contentless_item: Res<OrgSource<'_>, OrgSource<'_>> =
|
||||
alt((eof, line_ending))(remaining);
|
||||
let maybe_contentless_item: Res<OrgSource<'_>, OrgSource<'_>> = eof(remaining);
|
||||
match maybe_contentless_item {
|
||||
Ok((rem, _ws)) => {
|
||||
let source = get_consumed(input, rem);
|
||||
@@ -153,7 +156,7 @@ pub fn plain_list_item<'r, 's>(
|
||||
Err(_) => {}
|
||||
};
|
||||
|
||||
let (remaining, _ws) = space1(remaining)?;
|
||||
let (remaining, _ws) = alt((space1, line_ending))(remaining)?;
|
||||
let exit_matcher = plain_list_item_end(indent_level);
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
|
||||
@@ -162,12 +165,9 @@ pub fn plain_list_item<'r, 's>(
|
||||
exit_matcher: &exit_matcher,
|
||||
}));
|
||||
|
||||
let (remaining, (children, _exit_contents)) = verify(
|
||||
many_till(
|
||||
let (remaining, (children, _exit_contents)) = many_till(
|
||||
parser_with_context!(element(true))(&parser_context),
|
||||
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||
),
|
||||
|(children, _exit_contents)| !children.is_empty(),
|
||||
)(remaining)?;
|
||||
|
||||
let (remaining, _trailing_ws) =
|
||||
@@ -419,4 +419,40 @@ dolar"#,
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_line_break() {
|
||||
let input = OrgSource::new(
|
||||
r#"+
|
||||
"#,
|
||||
);
|
||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||
let result = detect_plain_list(&initial_context, input);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_eof() {
|
||||
let input = OrgSource::new(r#"+"#);
|
||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||
let result = detect_plain_list(&initial_context, input);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_no_gap() {
|
||||
let input = OrgSource::new(r#"+foo"#);
|
||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||
let result = detect_plain_list(&initial_context, input);
|
||||
// Since there is no whitespace after the '+' this is a paragraph, not a plain list.
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_with_gap() {
|
||||
let input = OrgSource::new(r#"+ foo"#);
|
||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||
let result = detect_plain_list(&initial_context, input);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,44 +35,6 @@ 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,
|
||||
@@ -82,28 +44,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 {:?}", self)),
|
||||
_ => Err(format!("wrong token type, expected vector: {:?}", self)),
|
||||
}?)
|
||||
}
|
||||
|
||||
pub fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
|
||||
Ok(match self {
|
||||
Token::List(children) => Ok(children),
|
||||
_ => Err(format!("wrong token type {:?}", self)),
|
||||
_ => Err(format!("wrong token type, expected list: {:?}", self)),
|
||||
}?)
|
||||
}
|
||||
|
||||
pub fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> {
|
||||
Ok(match self {
|
||||
Token::Atom(body) => Ok(*body),
|
||||
_ => Err(format!("wrong token type {:?}", self)),
|
||||
_ => Err(format!("wrong token type, expected atom: {:?}", self)),
|
||||
}?)
|
||||
}
|
||||
|
||||
pub fn as_text<'p>(&'p self) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> {
|
||||
Ok(match self {
|
||||
Token::TextWithProperties(body) => Ok(body),
|
||||
_ => Err(format!("wrong token type {:?}", self)),
|
||||
_ => Err(format!("wrong token type, expected text: {:?}", self)),
|
||||
}?)
|
||||
}
|
||||
|
||||
@@ -133,6 +95,42 @@ 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)?;
|
||||
|
||||
@@ -21,7 +21,6 @@ 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;
|
||||
@@ -154,16 +153,11 @@ fn script_with_braces<'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||
let (remaining, _) = tag("{")(input)?;
|
||||
let parser_context = context
|
||||
.with_additional_node(ContextElement::SubscriptSuperscriptBrace(
|
||||
SubscriptSuperscriptBrace {
|
||||
position: remaining.into(),
|
||||
depth: 0,
|
||||
},
|
||||
))
|
||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
let exit_with_depth = script_with_braces_end(remaining.get_brace_depth());
|
||||
let parser_context =
|
||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &script_with_braces_end,
|
||||
exit_matcher: &exit_with_depth,
|
||||
}));
|
||||
|
||||
let (remaining, (children, _exit_contents)) = many_till(
|
||||
@@ -175,49 +169,30 @@ fn script_with_braces<'r, 's>(
|
||||
Ok((remaining, children))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn script_with_braces_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 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;
|
||||
fn script_with_braces_end(
|
||||
starting_brace_depth: isize,
|
||||
) -> 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)
|
||||
}
|
||||
'}' 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(),
|
||||
))));
|
||||
}
|
||||
|
||||
#[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),
|
||||
_ => {}
|
||||
fn _script_with_braces_end<'r, 's>(
|
||||
_context: Context<'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
starting_brace_depth: isize,
|
||||
) -> 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
|
||||
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.")
|
||||
}
|
||||
None
|
||||
tag("}")(input)
|
||||
}
|
||||
|
||||
@@ -12,14 +12,13 @@ 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;
|
||||
@@ -162,14 +161,3 @@ 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.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user