1 Commits

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

View File

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

View File

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

View File

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

View File

@@ -44,13 +44,11 @@ default = ["compare"]
compare = [] compare = []
tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"] tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"]
# Optimized build for any sort of release.
[profile.release-lto] [profile.release-lto]
inherits = "release" inherits = "release"
lto = true lto = true
strip = "symbols" strip = "symbols"
# Profile for performance testing with the "perf" tool. Notably keeps debug enabled and does not strip symbols to make reading the perf output easier.
[profile.perf] [profile.perf]
inherits = "release" inherits = "release"
lto = true lto = true

View File

@@ -40,7 +40,7 @@ test:
.PHONY: dockertest .PHONY: dockertest
dockertest: dockertest:
> $(MAKE) -C docker/organic_test > $(MAKE) -C docker/organic_test
> docker run --init --rm -i -t -v "$$(readlink -f ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS) > docker run --init --rm -i -t -v "$$(readlink -f ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test cargo test --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: dockerclean .PHONY: dockerclean
dockerclean: dockerclean:

View File

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

View File

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

View File

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

View File

@@ -30,5 +30,3 @@ RUN apk add --no-cache musl-dev ncurses gnutls
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
COPY --from=build-emacs /root/dist/ / COPY --from=build-emacs /root/dist/ /
COPY --from=build-org-mode /root/dist/ / COPY --from=build-org-mode /root/dist/ /
ENTRYPOINT ["cargo", "test"]

View File

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

0
foo Normal file
View File

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,5 @@
use std::collections::HashSet;
use super::util::assert_bounds; use super::util::assert_bounds;
use super::util::assert_name; use super::util::assert_name;
use crate::parser::sexp::unquote;
use crate::parser::sexp::Token; use crate::parser::sexp::Token;
use crate::parser::AngleLink; use crate::parser::AngleLink;
use crate::parser::Bold; use crate::parser::Bold;
@@ -87,24 +84,12 @@ impl DiffResult {
match self.status { match self.status {
DiffStatus::Good => { DiffStatus::Good => {
if self.has_bad_children() { if self.has_bad_children() {
format!( "BADCHILD"
"{color}BADCHILD{reset}",
color = DiffResult::foreground_color(255, 255, 0),
reset = DiffResult::reset_color(),
)
} else { } else {
format!( "GOOD"
"{color}GOOD{reset}",
color = DiffResult::foreground_color(0, 255, 0),
reset = DiffResult::reset_color(),
)
} }
} }
DiffStatus::Bad => format!( DiffStatus::Bad => "BAD",
"{color}BAD{reset}",
color = DiffResult::foreground_color(255, 0, 0),
reset = DiffResult::reset_color(),
),
} }
}; };
println!( println!(
@@ -132,45 +117,6 @@ impl DiffResult {
DiffStatus::Bad => true, 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>( fn compare_element<'s>(
@@ -326,7 +272,6 @@ fn compare_heading<'s>(
let children = emacs.as_list()?; let children = emacs.as_list()?;
let mut child_status = Vec::new(); let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good; let mut this_status = DiffStatus::Good;
let mut message = None;
let emacs_name = "headline"; let emacs_name = "headline";
if assert_name(emacs, emacs_name).is_err() { if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad; this_status = DiffStatus::Bad;
@@ -336,45 +281,6 @@ fn compare_heading<'s>(
this_status = DiffStatus::Bad; 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 title = {
let children = emacs.as_list()?; let children = emacs.as_list()?;
let attributes_child = children let attributes_child = children
@@ -391,9 +297,6 @@ fn compare_heading<'s>(
child_status.push(compare_object(source, emacs_child, rust_child)?); 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()) { for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
match rust_child { match rust_child {
DocumentElement::Heading(rust_heading) => { DocumentElement::Heading(rust_heading) => {
@@ -408,44 +311,11 @@ fn compare_heading<'s>(
Ok(DiffResult { Ok(DiffResult {
status: this_status, status: this_status,
name: emacs_name.to_owned(), name: emacs_name.to_owned(),
message, message: None,
children: child_status, 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>( fn compare_paragraph<'s>(
source: &'s str, source: &'s str,
emacs: &'s Token<'s>, emacs: &'s Token<'s>,
@@ -1104,7 +974,7 @@ fn compare_plain_text<'s>(
rust.source.len() rust.source.len()
)); ));
} }
let unquoted_text = unquote(text.text)?; let unquoted_text = text.unquote()?;
if unquoted_text != rust.source { if unquoted_text != rust.source {
this_status = DiffStatus::Bad; this_status = DiffStatus::Bad;
message = Some(format!( message = Some(format!(

View File

@@ -47,7 +47,9 @@ pub fn assert_bounds<'s, S: Source<'s>>(
standard_properties standard_properties
.begin .begin
.ok_or("Token should have a begin.")?, .ok_or("Token should have a begin.")?,
standard_properties.end.ok_or("Token should have an end.")?, standard_properties
.end
.ok_or("Token should have a begin.")?,
); );
let (rust_begin, rust_end) = get_offsets(source, rust); let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1) != begin || (rust_end + 1) != end { if (rust_begin + 1) != begin || (rust_end + 1) != end {

View File

@@ -11,16 +11,17 @@ use nom::multi::many_till;
use nom::multi::separated_list1; use nom::multi::separated_list1;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::citation_reference::must_balance_bracket;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::Res; use crate::error::Res;
use crate::parser::citation_reference::citation_reference; use crate::parser::citation_reference::citation_reference;
use crate::parser::citation_reference::citation_reference_key; use crate::parser::citation_reference::citation_reference_key;
use crate::parser::citation_reference::get_bracket_depth;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
use crate::parser::object::Citation; use crate::parser::object::Citation;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::CitationBracket;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
@@ -37,15 +38,13 @@ pub fn citation<'r, 's>(
let (remaining, _) = tag_no_case("[cite")(input)?; let (remaining, _) = tag_no_case("[cite")(input)?;
let (remaining, _) = opt(citestyle)(remaining)?; let (remaining, _) = opt(citestyle)(remaining)?;
let (remaining, _) = tag(":")(remaining)?; let (remaining, _) = tag(":")(remaining)?;
let (remaining, _prefix) = let (remaining, _prefix) = opt(parser_with_context!(global_prefix)(context))(remaining)?;
must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?;
let (remaining, _references) = let (remaining, _references) =
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?; separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
let (remaining, _suffix) = must_balance_bracket(opt(tuple(( let (remaining, _suffix) = opt(tuple((
tag(";"), tag(";"),
parser_with_context!(global_suffix)(context), parser_with_context!(global_suffix)(context),
))))(remaining)?; )))(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -84,11 +83,15 @@ fn global_prefix<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let exit_with_depth = global_prefix_end(input.get_bracket_depth()); // 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 = let parser_context = context
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::CitationBracket(CitationBracket {
position: input,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &exit_with_depth, exit_matcher: &global_prefix_end,
})); }));
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
@@ -101,24 +104,28 @@ fn global_prefix<'r, 's>(
Ok((remaining, children)) 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"))] #[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>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: isize,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let context_depth = get_bracket_depth(context)
if current_depth < 0 { .expect("This function should only be called from inside a citation.");
// This shouldn't be possible because if depth is 0 then a closing bracket should end the citation. let text_since_context_entry = get_consumed(context_depth.position, input);
unreachable!("Exceeded citation global prefix bracket depth.") let mut current_depth = context_depth.depth;
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'[' => {
current_depth += 1;
}
']' if current_depth == 0 => {
panic!("Exceeded citation global prefix bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input); let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
@@ -137,11 +144,15 @@ fn global_suffix<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let exit_with_depth = global_suffix_end(input.get_bracket_depth()); // 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 = let parser_context = context
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::CitationBracket(CitationBracket {
position: input,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &exit_with_depth, exit_matcher: &global_suffix_end,
})); }));
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
@@ -153,24 +164,28 @@ fn global_suffix<'r, 's>(
Ok((remaining, children)) 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"))] #[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>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: isize,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let context_depth = get_bracket_depth(context)
if current_depth < 0 { .expect("This function should only be called from inside a citation.");
// This shouldn't be possible because if depth is 0 then a closing bracket should end the citation. let text_since_context_entry = get_consumed(context_depth.position, input);
unreachable!("Exceeded citation global suffix bracket depth.") let mut current_depth = context_depth.depth;
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'[' => {
current_depth += 1;
}
']' if current_depth == 0 => {
panic!("Exceeded citation global suffix bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input); let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);

View File

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

View File

@@ -1,8 +1,6 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::map; use nom::combinator::map;
@@ -14,7 +12,6 @@ use nom::multi::many0;
use nom::multi::many1; use nom::multi::many1;
use nom::multi::many1_count; use nom::multi::many1_count;
use nom::multi::many_till; use nom::multi::many_till;
use nom::multi::separated_list1;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::element::Element; use super::element::Element;
@@ -53,10 +50,7 @@ pub struct Document<'s> {
pub struct Heading<'s> { pub struct Heading<'s> {
pub source: &'s str, pub source: &'s str,
pub stars: usize, pub stars: usize,
pub todo_keyword: Option<&'s str>,
// TODO: add todo-type enum
pub title: Vec<Object<'s>>, pub title: Vec<Object<'s>>,
pub tags: Vec<&'s str>,
pub children: Vec<DocumentElement<'s>>, pub children: Vec<DocumentElement<'s>>,
} }
@@ -273,8 +267,7 @@ fn heading<'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Heading<'s>> { ) -> Res<OrgSource<'s>, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?; not(|i| context.check_exit_matcher(i))(input)?;
let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) = let (remaining, (star_count, _ws, title)) = headline(context, input)?;
headline(context, input)?;
let section_matcher = parser_with_context!(section)(context); let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading)(context); let heading_matcher = parser_with_context!(heading)(context);
let (remaining, children) = many0(alt(( let (remaining, children) = many0(alt((
@@ -290,10 +283,7 @@ fn heading<'r, 's>(
Heading { Heading {
source: source.into(), source: source.into(),
stars: star_count, stars: star_count,
todo_keyword: maybe_todo_keyword
.map(|(todo_keyword, _ws)| Into::<&str>::into(todo_keyword)),
title, title,
tags: heading_tags,
children, children,
}, },
)) ))
@@ -303,83 +293,30 @@ fn heading<'r, 's>(
fn headline<'r, 's>( fn headline<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res< ) -> Res<OrgSource<'s>, (usize, OrgSource<'s>, Vec<Object<'s>>)> {
OrgSource<'s>,
(
usize,
OrgSource<'s>,
Option<(OrgSource<'s>, OrgSource<'s>)>,
Vec<Object<'s>>,
Vec<&'s str>,
),
> {
let parser_context = let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document, class: ExitClass::Document,
exit_matcher: &headline_title_end, exit_matcher: &headline_end,
})); }));
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context); let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let ( let (remaining, (_sol, star_count, ws, title, _line_ending)) = tuple((
remaining,
(_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending),
) = tuple((
start_of_line, start_of_line,
many1_count(tag("*")), many1_count(tag("*")),
space1, space1,
opt(tuple((heading_keyword, space1))),
many1(standard_set_object_matcher), many1(standard_set_object_matcher),
opt(tuple((space0, tags))),
space0,
alt((line_ending, eof)), alt((line_ending, eof)),
))(input)?; ))(input)?;
Ok(( Ok((remaining, (star_count, ws, title)))
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"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline_title_end<'r, 's>( fn headline_end<'r, 's>(
_context: Context<'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple(( line_ending(input)
opt(tuple((space0, tags, space0))),
alt((line_ending, eof)),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn tags<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
let (remaining, (_open, tags, _close)) =
tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?;
Ok((remaining, tags))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_@#%".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn heading_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should take into account the value of "#+TODO:" ref https://orgmode.org/manual/Per_002dfile-keywords.html and possibly the configurable variable org-todo-keywords ref https://orgmode.org/manual/Workflow-states.html. Case is significant.
alt((tag("TODO"), tag("DONE")))(input)
} }
impl<'s> Document<'s> { impl<'s> Document<'s> {

View File

@@ -45,13 +45,7 @@ fn name<'r, 's>(
// TODO: This should be defined by org-entities and optionally org-entities-user // TODO: This should be defined by org-entities and optionally org-entities-user
// TODO: Add the rest of the entities, this is a very incomplete list // TODO: Add the rest of the entities, this is a very incomplete list
let (remaining, proto) = alt((alt(( let (remaining, proto) = alt((alt((tag_no_case("delta"), tag_no_case("pi"))),))(input)?;
tag_no_case("delta"),
tag_no_case("pi"),
tag_no_case("ast"),
tag_no_case("lt"),
tag_no_case("gt"),
)),))(input)?;
Ok((remaining, proto)) Ok((remaining, proto))
} }

View File

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

View File

@@ -58,15 +58,15 @@ pub fn greater_block<'r, 's>(
"Cannot nest objects of the same element".into(), "Cannot nest objects of the same element".into(),
)))); ))));
} }
let exit_with_name = greater_block_end(name.into());
let (remaining, parameters) = opt(tuple((space1, parameters)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, parameters)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = line_ending(remaining)?;
let parser_context = context let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::Context(context_name)) .with_additional_node(ContextElement::Context(context_name))
.with_additional_node(ContextElement::GreaterBlock(name.into()))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Alpha,
exit_matcher: &exit_with_name, exit_matcher: &greater_block_end,
})); }));
let parameters = match parameters { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
@@ -94,7 +94,7 @@ pub fn greater_block<'r, 's>(
(remaining, children) (remaining, children)
} }
}; };
let (remaining, _end) = exit_with_name(&parser_context, remaining)?; let (remaining, _end) = greater_block_end(&parser_context, remaining)?;
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block // Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
@@ -120,27 +120,31 @@ fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not("\r\n")(input) is_not("\r\n")(input)
} }
fn greater_block_end<'x>(
name: &'x str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: Can this be done without making an owned copy?
let name = name.to_owned();
move |context: Context, input: OrgSource<'_>| _greater_block_end(context, input, name.as_str())
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _greater_block_end<'r, 's, 'x>( fn greater_block_end<'r, 's>(
_context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
name: &'x str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; 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, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, _name, _ws)) = tuple(( let (remaining, (_begin, _name, _ws)) = tuple((
tag_no_case("#+end_"), tag_no_case("#+end_"),
tag_no_case(name), tag_no_case(current_name),
alt((eof, line_ending)), alt((eof, line_ending)),
))(remaining)?; ))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, source)) Ok((remaining, source))
} }
fn get_context_greater_block_name<'r, 's>(context: Context<'r, 's>) -> Option<&'s str> {
for thing in context.iter() {
match thing.get_data() {
ContextElement::GreaterBlock(name) => return Some(name),
_ => {}
};
}
None
}

View File

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

View File

@@ -1,4 +1,3 @@
use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar; use nom::character::complete::anychar;
@@ -15,11 +14,11 @@ use tracing::span;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_context::InlineSourceBlockBracket;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
@@ -76,11 +75,16 @@ fn header<'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("[")(input)?; let (remaining, _) = tag("[")(input)?;
let exit_with_depth = header_end(remaining.get_bracket_depth()); let parser_context = context
let parser_context = .with_additional_node(ContextElement::InlineSourceBlockBracket(
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { InlineSourceBlockBracket {
position: remaining,
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
exit_matcher: &exit_with_depth, exit_matcher: &header_end,
})); }));
let (remaining, header_contents) = recognize(many_till( let (remaining, header_contents) = recognize(many_till(
@@ -91,32 +95,37 @@ fn header<'r, 's>(
Ok((remaining, header_contents)) Ok((remaining, header_contents))
} }
fn header_end( #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
starting_bracket_depth: isize, fn header_end<'r, 's>(
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { context: Context<'r, 's>,
move |context: Context, input: OrgSource<'_>| { input: OrgSource<'s>,
_header_end(context, input, starting_bracket_depth) ) -> 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;
} }
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] line_ending(input)
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"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -126,11 +135,16 @@ fn body<'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("{")(input)?; let (remaining, _) = tag("{")(input)?;
let exit_with_depth = body_end(remaining.get_brace_depth()); let parser_context = context
let parser_context = .with_additional_node(ContextElement::InlineSourceBlockBracket(
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { InlineSourceBlockBracket {
position: remaining,
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
exit_matcher: &exit_with_depth, exit_matcher: &body_end,
})); }));
let (remaining, body_contents) = recognize(many_till( let (remaining, body_contents) = recognize(many_till(
@@ -151,28 +165,60 @@ fn body<'r, 's>(
Ok((remaining, body_contents)) Ok((remaining, body_contents))
} }
fn body_end( #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
starting_brace_depth: isize, fn body_end<'r, 's>(
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { context: Context<'r, 's>,
move |context: Context, input: OrgSource<'_>| _body_end(context, input, starting_brace_depth) input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside an inline source block body.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'{' => {
current_depth += 1;
}
'}' if current_depth == 0 => {
panic!("Exceeded inline source block body bracket depth.")
}
'}' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
}
{
#[cfg(feature = "tracing")]
let span = span!(
tracing::Level::DEBUG,
"inside end body",
remaining = Into::<&str>::into(input),
current_depth = current_depth
);
#[cfg(feature = "tracing")]
let _enter = span.enter();
if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("}")(input);
if close_bracket.is_ok() {
return close_bracket;
}
}
}
line_ending(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _body_end<'r, 's>( pub fn get_bracket_depth<'r, 's>(
_context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, ) -> Option<&'r InlineSourceBlockBracket<'s>> {
starting_brace_depth: isize, for node in context.iter() {
) -> Res<OrgSource<'s>, OrgSource<'s>> { match node.get_data() {
let current_depth = input.get_brace_depth() - starting_brace_depth; ContextElement::InlineSourceBlockBracket(depth) => return Some(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 {
// This shouldn't be possible because if depth is 0 then a closing brace should end the body.
unreachable!("Exceeded body brace depth.")
} }
alt((tag("}"), line_ending))(input) None
} }

View File

@@ -202,13 +202,17 @@ fn bordered_dollar_fragment<'r, 's>(
// TODO: I'm assuming I should be peeking at the borders but the documentation is not clear. Test to figure out. // TODO: I'm assuming I should be peeking at the borders but the documentation is not clear. Test to figure out.
let (_, _) = peek(parser_with_context!(open_border)(context))(remaining)?; let (_, _) = peek(parser_with_context!(open_border)(context))(remaining)?;
let (remaining, _) = recognize(many_till( // TODO: As an optimization it would be nice to exit early upon hitting the 3rd line break
let (remaining, _) = verify(
recognize(many_till(
anychar, anychar,
peek(alt(( peek(alt((
parser_with_context!(exit_matcher_parser)(context), parser_with_context!(exit_matcher_parser)(context),
tag("$"), tag("$"),
))), ))),
))(remaining)?; )),
|body: &OrgSource<'_>| Into::<&str>::into(body).lines().take(4).count() <= 3,
)(remaining)?;
let (_, _) = peek(parser_with_context!(close_border)(context))(remaining)?; let (_, _) = peek(parser_with_context!(close_border)(context))(remaining)?;
let (remaining, _) = tag("$")(remaining)?; let (remaining, _) = tag("$")(remaining)?;

View File

@@ -189,36 +189,3 @@ pub fn regular_link_description_object_set<'r, 's>(
parser_with_context!(minimal_set_object)(context), parser_with_context!(minimal_set_object)(context),
))(input) ))(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)
}

View File

@@ -11,25 +11,13 @@ use nom::Slice;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
#[derive(Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct OrgSource<'s> { pub struct OrgSource<'s> {
full_source: &'s str, full_source: &'s str,
start: usize, start: usize,
end: usize, // exclusive end: usize, // exclusive
start_of_line: usize, start_of_line: usize,
preceding_character: Option<char>, 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> { impl<'s> OrgSource<'s> {
@@ -43,10 +31,6 @@ impl<'s> OrgSource<'s> {
end: input.len(), end: input.len(),
start_of_line: 0, start_of_line: 0,
preceding_character: None, preceding_character: None,
line_number: 1,
bracket_depth: 0,
brace_depth: 0,
parenthesis_depth: 0,
} }
} }
@@ -72,22 +56,6 @@ impl<'s> OrgSource<'s> {
assert!(other.end <= self.end); assert!(other.end <= self.end);
self.slice(..(other.start - self.start)) 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> { impl<'s> InputTake for OrgSource<'s> {
@@ -151,38 +119,10 @@ where
} }
let skipped_text = &self.full_source[self.start..new_start]; let skipped_text = &self.full_source[self.start..new_start];
let mut start_of_line = self.start_of_line; let start_of_line = skipped_text
let mut line_number = self.line_number; .rfind('\n')
let mut bracket_depth = self.bracket_depth; .map(|idx| self.start + idx + 1)
let mut brace_depth = self.brace_depth; .unwrap_or(self.start_of_line);
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 { OrgSource {
full_source: self.full_source, full_source: self.full_source,
@@ -190,10 +130,6 @@ where
end: new_end, end: new_end,
start_of_line, start_of_line,
preceding_character: skipped_text.chars().last(), preceding_character: skipped_text.chars().last(),
line_number,
bracket_depth,
brace_depth,
parenthesis_depth,
} }
} }
} }
@@ -310,9 +246,7 @@ impl<'s> InputTakeAtPosition for OrgSource<'s> {
} }
} }
pub fn convert_error<'a, I: Into<CustomError<&'a str>>>( pub fn convert_error(err: nom::Err<CustomError<OrgSource<'_>>>) -> nom::Err<CustomError<&str>> {
err: nom::Err<I>,
) -> nom::Err<CustomError<&'a str>> {
match err { match err {
nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed), nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed),
nom::Err::Error(err) => nom::Err::Error(err.into()), nom::Err::Error(err) => nom::Err::Error(err.into()),
@@ -435,28 +369,4 @@ mod tests {
assert_eq!(input.get_preceding_character(), None); assert_eq!(input.get_preceding_character(), None);
assert_eq!(input.slice(8..).get_preceding_character(), Some('💛')); 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);
}
} }

View File

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

View File

@@ -37,13 +37,9 @@ pub fn detect_plain_list<'r, 's>(
_context: Context<'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
// TODO: Add support for plain list items that do not have content on the first line.
if verify( if verify(
tuple(( tuple((start_of_line, space0, bullet, space1)),
start_of_line,
space0,
bullet,
alt((space1, line_ending, eof)),
)),
|(_start, indent, bull, _after_whitespace)| { |(_start, indent, bull, _after_whitespace)| {
Into::<&str>::into(bull) != "*" || indent.len() > 0 Into::<&str>::into(bull) != "*" || indent.len() > 0
}, },
@@ -139,7 +135,8 @@ pub fn plain_list_item<'r, 's>(
})(remaining)?; })(remaining)?;
// TODO: This isn't taking into account items that immediately line break and then have contents // TODO: This isn't taking into account items that immediately line break and then have contents
let maybe_contentless_item: Res<OrgSource<'_>, OrgSource<'_>> = eof(remaining); let maybe_contentless_item: Res<OrgSource<'_>, OrgSource<'_>> =
alt((eof, line_ending))(remaining);
match maybe_contentless_item { match maybe_contentless_item {
Ok((rem, _ws)) => { Ok((rem, _ws)) => {
let source = get_consumed(input, rem); let source = get_consumed(input, rem);
@@ -156,7 +153,7 @@ pub fn plain_list_item<'r, 's>(
Err(_) => {} Err(_) => {}
}; };
let (remaining, _ws) = alt((space1, line_ending))(remaining)?; let (remaining, _ws) = space1(remaining)?;
let exit_matcher = plain_list_item_end(indent_level); let exit_matcher = plain_list_item_end(indent_level);
let parser_context = context let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
@@ -165,9 +162,12 @@ pub fn plain_list_item<'r, 's>(
exit_matcher: &exit_matcher, exit_matcher: &exit_matcher,
})); }));
let (remaining, (children, _exit_contents)) = many_till( let (remaining, (children, _exit_contents)) = verify(
many_till(
parser_with_context!(element(true))(&parser_context), parser_with_context!(element(true))(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context),
),
|(children, _exit_contents)| !children.is_empty(),
)(remaining)?; )(remaining)?;
let (remaining, _trailing_ws) = let (remaining, _trailing_ws) =
@@ -419,40 +419,4 @@ dolar"#,
"# "#
); );
} }
#[test]
fn detect_line_break() {
let input = OrgSource::new(
r#"+
"#,
);
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let result = detect_plain_list(&initial_context, input);
assert!(result.is_ok());
}
#[test]
fn detect_eof() {
let input = OrgSource::new(r#"+"#);
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let result = detect_plain_list(&initial_context, input);
assert!(result.is_ok());
}
#[test]
fn detect_no_gap() {
let input = OrgSource::new(r#"+foo"#);
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let result = detect_plain_list(&initial_context, input);
// Since there is no whitespace after the '+' this is a paragraph, not a plain list.
assert!(result.is_err());
}
#[test]
fn detect_with_gap() {
let input = OrgSource::new(r#"+ foo"#);
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let result = detect_plain_list(&initial_context, input);
assert!(result.is_ok());
}
} }

View File

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

View File

@@ -21,6 +21,7 @@ use crate::parser::exiting::ExitClass;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_context::SubscriptSuperscriptBrace;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
@@ -153,11 +154,16 @@ fn script_with_braces<'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let (remaining, _) = tag("{")(input)?; let (remaining, _) = tag("{")(input)?;
let exit_with_depth = script_with_braces_end(remaining.get_brace_depth()); let parser_context = context
let parser_context = .with_additional_node(ContextElement::SubscriptSuperscriptBrace(
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { SubscriptSuperscriptBrace {
position: remaining.into(),
depth: 0,
},
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &exit_with_depth, exit_matcher: &script_with_braces_end,
})); }));
let (remaining, (children, _exit_contents)) = many_till( let (remaining, (children, _exit_contents)) = many_till(
@@ -169,30 +175,49 @@ fn script_with_braces<'r, 's>(
Ok((remaining, children)) Ok((remaining, children))
} }
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)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _script_with_braces_end<'r, 's>( fn script_with_braces_end<'r, 's>(
_context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_brace_depth: isize,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_brace_depth() - starting_brace_depth; let context_depth = get_bracket_depth(context)
if current_depth > 0 { .expect("This function should only be called from inside a subscript or superscript.");
// Its impossible for the next character to end the subscript or superscript if we're any amount of braces deep let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'{' => {
current_depth += 1;
}
'}' if current_depth == 0 => {
panic!("Exceeded subscript or superscript brace depth.")
}
'}' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
}
if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("}")(input);
if close_bracket.is_ok() {
return close_bracket;
}
}
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not a valid end for subscript or superscript.".into(), "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. #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
unreachable!("Exceeded subscript or superscript brace depth.") 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),
_ => {}
} }
tag("}")(input) }
None
} }

View File

@@ -12,13 +12,14 @@ use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::object_parser::table_cell_set_object;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
use crate::parser::greater_element::TableRow; use crate::parser::greater_element::TableRow;
use crate::parser::lesser_element::TableCell; use crate::parser::lesser_element::TableCell;
use crate::parser::object::Object;
use crate::parser::object_parser::minimal_set_object;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
@@ -161,3 +162,14 @@ fn org_mode_table_cell_end<'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((space0, alt((tag("|"), peek(line_ending))))))(input) recognize(tuple((space0, alt((tag("|"), peek(line_ending))))))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_cell_set_object<'r, 's>(
context: Context<'r, 's>,
input: 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.
}