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
127 changed files with 2781 additions and 5548 deletions

View File

@@ -1,203 +0,0 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: rust-foreign-document-test
spec:
pipelineSpec:
timeouts:
pipeline: "2h0m0s"
tasks: "1h0m40s"
finally: "0h30m0s"
params:
- name: image-name
description: The name for the built image
type: string
- name: path-to-image-context
description: The path to the build context
type: string
- name: path-to-dockerfile
description: The path to the Dockerfile
type: string
tasks:
- name: do-stuff
taskSpec:
metadata: {}
stepTemplate:
image: alpine:3.18
name: ""
resources:
requests:
cpu: 10m
memory: 600Mi
workingDir: /workspace/source
steps:
- image: alpine:3.18
name: do-stuff-step
script: |
#!/usr/bin/env sh
echo "hello world"
- name: report-pending
taskRef:
name: gitea-set-status
runAfter:
- fetch-repository
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has started"
- name: STATE
value: pending
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: fetch-repository
taskRef:
name: git-clone
workspaces:
- name: output
workspace: git-source
params:
- name: url
value: $(params.REPO_URL)
- name: revision
value: $(params.PULL_BASE_SHA)
- name: deleteExisting
value: "true"
- name: build-image
taskRef:
name: kaniko
params:
- name: IMAGE
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: CONTEXT
value: $(params.path-to-image-context)
- name: DOCKERFILE
value: $(params.path-to-dockerfile)
- name: BUILDER_IMAGE
value: "gcr.io/kaniko-project/executor:v1.12.1"
- name: EXTRA_ARGS
value:
- --target=foreign-document-test
- --cache=true
- --cache-copy-layers
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
- --use-new-run # Should result in a speed-up
- --reproducible # To remove timestamps so layer caching works.
- --snapshot-mode=redo
- --skip-unused-stages=true
- --registry-mirror=dockerhub.dockerhub.svc.cluster.local
workspaces:
- name: source
workspace: git-source
- name: dockerconfig
workspace: docker-credentials
runAfter:
- fetch-repository
- name: run-image
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- build-image
params:
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
finally:
- name: report-success
when:
- input: "$(tasks.status)"
operator: in
values: ["Succeeded", "Completed"]
taskRef:
name: gitea-set-status
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has succeeded"
- name: STATE
value: success
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: report-failure
when:
- input: "$(tasks.status)"
operator: in
values: ["Failed"]
taskRef:
name: gitea-set-status
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has failed"
- name: STATE
value: failure
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: cargo-cache-autoclean
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
params:
- name: command
value: [cargo, cache, --autoclean]
- name: args
value: []
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
workspaces:
- name: git-source
- name: docker-credentials
- name: cargo-cache
workspaces:
- name: git-source
volumeClaimTemplate:
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
subPath: rust-source
- name: cargo-cache
persistentVolumeClaim:
claimName: organic-cargo-cache-test-foreign-document
- name: docker-credentials
secret:
secretName: harbor-plain
serviceAccountName: build-bot
params:
- name: image-name
value: "harbor.fizz.buzz/private/organic-test-foreign-document"
- name: path-to-image-context
value: docker/organic_test/
- name: path-to-dockerfile
value: docker/organic_test/Dockerfile

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:
@@ -83,7 +91,6 @@ spec:
value: "gcr.io/kaniko-project/executor:v1.12.1" value: "gcr.io/kaniko-project/executor:v1.12.1"
- name: EXTRA_ARGS - name: EXTRA_ARGS
value: value:
- --target=tester
- --cache=true - --cache=true
- --cache-copy-layers - --cache-copy-layers
- --cache-repo=harbor.fizz.buzz/kanikocache/cache - --cache-repo=harbor.fizz.buzz/kanikocache/cache
@@ -110,17 +117,10 @@ spec:
runAfter: runAfter:
- build-image - build-image
params: params:
- name: command
value: ["$(params.command[*])"]
- name: args - name: args
value: value: ["$(params.args[*])"]
[
--no-default-features,
--features,
compare,
--no-fail-fast,
--lib,
--test,
test_loader,
]
- 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:
@@ -212,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

@@ -16,13 +16,6 @@ spec:
skip_branches: skip_branches:
# We already run on every commit, so running when the semver tags get pushed is causing needless double-processing. # We already run on every commit, so running when the semver tags get pushed is causing needless double-processing.
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$" - "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
- name: rust-foreign-document-test
source: "pipeline-foreign-document-test.yaml"
# Override https-based url from lighthouse events.
clone_uri: "git@code.fizz.buzz:talexander/organic.git"
skip_branches:
# We already run on every commit, so running when the semver tags get pushed is causing needless double-processing.
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
- name: rust-build - name: rust-build
source: "pipeline-rust-build.yaml" source: "pipeline-rust-build.yaml"
# Override https-based url from lighthouse events. # Override https-based url from lighthouse events.

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "organic" name = "organic"
version = "0.1.5" version = "0.1.2"
authors = ["Tom Alexander <tom@fizz.buzz>"] authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser." description = "An org-mode parser."
edition = "2021" edition = "2021"
@@ -13,7 +13,8 @@ resolver = "2"
include = [ include = [
"LICENSE", "LICENSE",
"**/*.rs", "**/*.rs",
"Cargo.toml" "Cargo.toml",
"tests/*"
] ]
[lib] [lib]
@@ -22,7 +23,7 @@ path = "src/lib.rs"
[[bin]] [[bin]]
# This bin exists for development purposes only. The real target of this crate is the library. # This bin exists for development purposes only. The real target of this crate is the library.
name = "parse" name = "compare"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
@@ -39,17 +40,15 @@ tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-fil
walkdir = "2.3.3" walkdir = "2.3.3"
[features] [features]
default = [] 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

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

View File

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

View File

@@ -6,7 +6,7 @@ all: build push
.PHONY: build .PHONY: build
build: build:
docker build -t $(IMAGE_NAME) -f Dockerfile . docker build -t $(IMAGE_NAME) -f Dockerfile ../../
.PHONY: push .PHONY: push
push: push:
@@ -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 --read-only --mount type=tmpfs,destination=/tmp -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 --mount type=tmpfs,destination=/tmp -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

@@ -6,7 +6,7 @@ all: build push
.PHONY: build .PHONY: build
build: build:
docker build -t $(IMAGE_NAME) -f Dockerfile . docker build -t $(IMAGE_NAME) -f Dockerfile ../../
.PHONY: push .PHONY: push
push: push:
@@ -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 --read-only --mount type=tmpfs,destination=/tmp -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 --mount type=tmpfs,destination=/tmp -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

@@ -25,24 +25,8 @@ RUN make compile
RUN make DESTDIR="/root/dist" install RUN make DESTDIR="/root/dist" install
FROM rustlang/rust:nightly-alpine3.17 AS tester FROM rustlang/rust:nightly-alpine3.17
ENV LANG=en_US.UTF-8
RUN apk add --no-cache musl-dev ncurses gnutls 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"]
FROM build as foreign-document-gather
RUN mkdir /foreign_documents
FROM tester as foreign-document-test
RUN apk add --no-cache bash
RUN mkdir /foreign_documents
COPY --from=build-org-mode /root/org-mode/doc /foreign_documents/org-mode
COPY foreign_document_test_entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -6,11 +6,7 @@ all: build push
.PHONY: build .PHONY: build
build: build:
docker build -t $(IMAGE_NAME) -f Dockerfile --target tester . docker build -t $(IMAGE_NAME) -f Dockerfile ../../
.PHONY: build_foreign_document_test
build_foreign_document_test:
docker build -t $(IMAGE_NAME)-foreign-document -f Dockerfile --target foreign-document-test .
.PHONY: push .PHONY: push
push: push:
@@ -29,16 +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 --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME) --no-default-features --features compare --no-fail-fast --lib --test test_loader docker run --rm -i -t $(IMAGE_NAME)
.PHONY: shell .PHONY: shell
shell: build shell:
docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME) docker run --rm -i -t --entrypoint /bin/bash $(IMAGE_NAME)
.PHONY: run_foreign_document_test
run_foreign_document_test: build_foreign_document_test
docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)-foreign-document

View File

@@ -1,57 +0,0 @@
#!/usr/bin/env bash
#
# Run the Organic compare script against a series of documents sourced from exterior places.
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
function log {
(>&2 echo "${@}")
}
function die {
local status_code="$1"
shift
(>&2 echo "${@}")
exit "$status_code"
}
function main {
cargo build --no-default-features --features compare --profile release-lto
if [ "${CARGO_TARGET_DIR:-}" = "" ]; then
CARGO_TARGET_DIR=$(realpath target/)
fi
PARSE="${CARGO_TARGET_DIR}/release-lto/parse"
run_compare "org-mode/org-guide.org" "/foreign_documents/org-mode/org-guide.org"
run_compare "org-mode/org-manual.org" "/foreign_documents/org-mode/org-manual.org"
}
function run_compare {
local name="$1"
local target_document="$2"
set +e
$PARSE "$target_document" &> /dev/null
local status=$?
set -e
if [ "$status" -eq 0 ]; then
echo "$(green_text "GOOD") $name"
else
echo "$(red_text "FAIL") $name"
return 1
fi
}
function green_text {
(IFS=' '; printf '\x1b[38;2;0;255;0m%s\x1b[0m' "${*}")
}
function red_text {
(IFS=' '; printf '\x1b[38;2;255;0;0m%s\x1b[0m' "${*}")
}
function yellow_text {
(IFS=' '; printf '\x1b[38;2;255;255;0m%s\x1b[0m' "${*}")
}
main "${@}"

View File

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

View File

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

View File

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

0
foo Normal file
View File

View File

@@ -1 +0,0 @@
This folder contains org-mode documents that get automatically included as tests using build.rs.

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,18 +0,0 @@
#+begin_defun
foo
#+begin_lorem
,#+begin_center
bar
,#+end_center
ipsum
#+end_lorem
baz
#+end_defun
#+begin_center
#+begin_quote
#+begin_center
lorem
#+end_center
#+end_quote
#+end_center

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +0,0 @@
foo ==>bar=.
# This uses a zero-width space to escape the equals signs to make the verbatim not end.
=lorem == ipsum=

View File

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

View File

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

View File

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

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=() perf record --freq=2000 --call-graph dwarf --output=perf.data target/${PROFILE}/compare
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then # Convert to a format firefox will read
PROFILE="debug" # flags to consider --show-info
else perf script -F +pid --input perf.data > perf.firefox
additional_flags+=(--profile "$PROFILE")
fi
cargo build --no-default-features "${additional_flags[@]}"
perf record --freq=2000 --call-graph dwarf --output=perf.data target/${PROFILE}/parse
# Convert to a format firefox will read echo "You probably want to go to https://profiler.firefox.com/"
# flags to consider --show-info echo "Either that or run hotspot"
perf script -F +pid --input perf.data > perf.firefox
echo "You probably want to go to https://profiler.firefox.com/"
echo "Either that or run hotspot"
}
main "${@}"

View File

@@ -7,7 +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)
@@ -15,7 +14,7 @@ MAKE=$(command -v gmake || command -v make)
function main { function main {
build_container build_container
launch_container "${@}" launch_container
} }
function build_container { function build_container {
@@ -25,42 +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")
additional_flags+=(--read-only)
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
if [ $# -gt 0 ]; then 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[@]}"
# If we passed in args, we need to forward them along
for path in "${@}"; do
local full_path=$($REALPATH "$path")
local containing_folder=$(dirname "$full_path")
local file_name=$(basename "$full_path")
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "${containing_folder}:/input:ro" -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}" -- "/input/$file_name"
done
else
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}"
fi
} }
main "${@}" main "${@}"

View File

@@ -1,93 +0,0 @@
#!/usr/bin/env bash
#
# Bisect parsing a file at various line cut-off points to see which line causes the parse to differ from emacs.
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath)
############## Setup #########################
function cleanup {
for f in "${folders[@]}"; do
log "Deleting $f"
rm -rf "$f"
done
}
folders=()
for sig in EXIT INT QUIT HUP TERM; do
trap "set +e; cleanup" "$sig"
done
function die {
local status_code="$1"
shift
(>&2 echo "${@}")
exit "$status_code"
}
function log {
(>&2 echo "${@}")
}
############## Program #########################
function main {
log "Is is recommended that the output of \`mktemp -d -t 'compare_bisect.XXXXXXXX'\` is inside a tmpfs filesystem since this script will make many writes to these folders."
local target_full_path=$($REALPATH "$1")
SOURCE_FOLDER=$(dirname "$target_full_path")
TARGET_DOCUMENT=$(basename "$target_full_path")
local good=0
local bad=$(wc -l "$SOURCE_FOLDER/$TARGET_DOCUMENT" | awk '{print $1}')
set +e
run_parse "$bad" &> /dev/null
local status=$?
set -e
if [ $status -eq 0 ]; then
log "Entire file passes."
exit 0
fi
while [[ "$((bad - good))" -gt 1 ]]; do
local next_line=$((((bad - good) / 2) + good))
log "Testing line $next_line"
set +e
run_parse "$next_line" &> /dev/null
local status=$?
set -e
if [ $status -eq 0 ]; then
good="$next_line"
log "Line $next_line good"
else
bad="$next_line"
log "Line $next_line bad"
fi
done
echo "Bad line: $bad"
}
function setup_temp_dir {
local temp_dir=$(mktemp -d -t 'compare_bisect.XXXXXXXX')
cp -r "$SOURCE_FOLDER/"* "$temp_dir/"
echo "$temp_dir"
}
function run_parse {
local lines="$1"
local temp_dir=$(setup_temp_dir)
folders+=("$temp_dir")
cat "$SOURCE_FOLDER/$TARGET_DOCUMENT" | head -n "$lines" > "$temp_dir/$TARGET_DOCUMENT"
"${DIR}/run_docker_compare.bash" "$temp_dir/$TARGET_DOCUMENT"
local status=$?
rm -rf "$temp_dir"
# TODO: Remove temp_dir from folders
return "$status"
}
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)
@@ -33,7 +31,7 @@ function get_test_names {
local test_file_full_path=$($REALPATH "$test_file") local test_file_full_path=$($REALPATH "$test_file")
local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path") local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path")
local without_extension="${relative_to_samples%.org}" local without_extension="${relative_to_samples%.org}"
echo "autogen_${without_extension//\//_}" | tr '[:upper:]' '[:lower:]' echo "${without_extension/\//_}" | tr '[:upper:]' '[:lower:]'
else else
echo "$test_file" | tr '[:upper:]' '[:lower:]' echo "$test_file" | tr '[:upper:]' '[:lower:]'
fi fi
@@ -42,21 +40,17 @@ 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
IFS=\$'\n\t' IFS=\$'\n\t'
cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader "$test" -- --show-output cargo test --no-fail-fast --lib --test test_loader "$test" -- --show-output
EOF EOF
) )
docker run "${additional_flags[@]}" --init --rm --read-only --mount type=tmpfs,destination=/tmp -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script" docker run --init --rm -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test sh -c "$init_script"
} }

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@ mod diff;
mod parse; mod parse;
mod util; mod util;
pub use diff::compare_document; pub use diff::compare_document;
pub use parse::emacs_parse_anonymous_org_document; pub use parse::emacs_parse_org_document;
pub use parse::emacs_parse_file_org_document;
pub use parse::get_emacs_version; pub use parse::get_emacs_version;
pub use parse::get_org_mode_version; pub use parse::get_org_mode_version;

View File

@@ -1,9 +1,6 @@
use std::path::Path;
use std::process::Command; use std::process::Command;
pub fn emacs_parse_anonymous_org_document<C>( pub fn emacs_parse_org_document<C>(file_contents: C) -> Result<String, Box<dyn std::error::Error>>
file_contents: C,
) -> Result<String, Box<dyn std::error::Error>>
where where
C: AsRef<str>, C: AsRef<str>,
{ {
@@ -18,46 +15,14 @@ where
escaped_file_contents = escaped_file_contents escaped_file_contents = escaped_file_contents
); );
let mut cmd = Command::new("emacs"); let mut cmd = Command::new("emacs");
let cmd = cmd let proc = cmd
.arg("-q") .arg("-q")
.arg("--no-site-file") .arg("--no-site-file")
.arg("--no-splash") .arg("--no-splash")
.arg("--batch") .arg("--batch")
.arg("--eval") .arg("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = cmd.output()?; let out = proc.output()?;
out.status.exit_ok()?;
let org_sexp = out.stderr;
Ok(String::from_utf8(org_sexp)?)
}
pub fn emacs_parse_file_org_document<P>(file_path: P) -> Result<String, Box<dyn std::error::Error>>
where
P: AsRef<Path>,
{
let file_path = file_path.as_ref().canonicalize()?;
let containing_directory = file_path.parent().ok_or(format!(
"Failed to get containing directory for path {}",
file_path.display()
))?;
let elisp_script = format!(
r#"(progn
(org-mode)
(message "%s" (pp-to-string (org-element-parse-buffer)))
)"#
);
let mut cmd = Command::new("emacs");
let cmd = cmd
.current_dir(containing_directory)
.arg("-q")
.arg("--no-site-file")
.arg("--no-splash")
.arg("--batch")
.arg("--insert")
.arg(file_path.as_os_str())
.arg("--eval")
.arg(elisp_script);
let out = cmd.output()?;
out.status.exit_ok()?; out.status.exit_ok()?;
let org_sexp = out.stderr; let org_sexp = out.stderr;
Ok(String::from_utf8(org_sexp)?) Ok(String::from_utf8(org_sexp)?)
@@ -90,7 +55,7 @@ pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
(message "%s" (version)) (message "%s" (version))
)"#; )"#;
let mut cmd = Command::new("emacs"); let mut cmd = Command::new("emacs");
let cmd = cmd let proc = cmd
.arg("-q") .arg("-q")
.arg("--no-site-file") .arg("--no-site-file")
.arg("--no-splash") .arg("--no-splash")
@@ -98,7 +63,7 @@ pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
.arg("--eval") .arg("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = cmd.output()?; let out = proc.output()?;
out.status.exit_ok()?; out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?) Ok(String::from_utf8(out.stderr)?)
} }
@@ -109,7 +74,7 @@ pub fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
(message "%s" (org-version nil t nil)) (message "%s" (org-version nil t nil))
)"#; )"#;
let mut cmd = Command::new("emacs"); let mut cmd = Command::new("emacs");
let cmd = cmd let proc = cmd
.arg("-q") .arg("-q")
.arg("--no-site-file") .arg("--no-site-file")
.arg("--no-splash") .arg("--no-splash")
@@ -117,7 +82,7 @@ pub fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
.arg("--eval") .arg("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = cmd.output()?; let out = proc.output()?;
out.status.exit_ok()?; out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?) Ok(String::from_utf8(out.stderr)?)
} }

View File

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

View File

@@ -1,181 +0,0 @@
use std::marker::PhantomData;
use nom::combinator::eof;
use nom::IResult;
use super::exiting::ExitClass;
use super::global_settings::GlobalSettings;
use super::list::List;
use super::DynContextMatcher;
use super::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::OrgSource;
#[derive(Debug)]
pub enum ContextElement<'r, 's> {
/// Stores a parser that indicates that children should exit upon matching an exit matcher.
ExitMatcherNode(ExitMatcherNode<'r>),
/// Stores the name of the current element to prevent directly nesting elements of the same type.
Context(&'r str),
/// Indicates if elements should consume the whitespace after them.
ConsumeTrailingWhitespace(bool),
/// This is just here to use the 's lifetime until I'm sure we can eliminate it from ContextElement.
Placeholder(PhantomData<&'s str>),
}
pub struct ExitMatcherNode<'r> {
// TODO: Should this be "&'r DynContextMatcher<'c>" ?
pub exit_matcher: &'r DynContextMatcher<'r>,
pub class: ExitClass,
}
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut formatter = f.debug_struct("ExitMatcherNode");
formatter.field("class", &self.class.to_string());
formatter.finish()
}
}
#[derive(Debug)]
pub struct Context<'g, 'r, 's> {
global_settings: &'g GlobalSettings<'g, 's>,
tree: List<'r, &'r ContextElement<'r, 's>>,
}
impl<'g, 'r, 's> Context<'g, 'r, 's> {
pub fn new(
global_settings: &'g GlobalSettings<'g, 's>,
tree: List<'r, &'r ContextElement<'r, 's>>,
) -> Self {
Self {
global_settings,
tree,
}
}
pub fn with_additional_node(&'r self, new_element: &'r ContextElement<'r, 's>) -> Self {
let new_tree = self.tree.push(new_element);
Self::new(self.global_settings, new_tree)
}
pub fn iter(&'r self) -> super::list::Iter<'r, &'r ContextElement<'r, 's>> {
self.tree.iter()
}
pub fn iter_context(&'r self) -> Iter<'g, 'r, 's> {
Iter {
next: self.tree.iter_list(),
global_settings: self.global_settings,
}
}
pub fn get_parent(&'r self) -> Option<Self> {
self.tree.get_parent().map(|parent_tree| Self {
global_settings: self.global_settings,
tree: parent_tree.clone(),
})
}
pub fn get_data(&self) -> &ContextElement<'r, 's> {
self.tree.get_data()
}
pub fn get_global_settings(&self) -> &'g GlobalSettings<'g, 's> {
self.global_settings
}
pub fn with_global_settings<'gg>(
&self,
new_settings: &'gg GlobalSettings<'gg, 's>,
) -> Context<'gg, 'r, 's> {
Context {
global_settings: new_settings,
tree: self.tree.clone(),
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn check_exit_matcher(
&'r self,
i: OrgSource<'s>,
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> {
let mut current_class_filter = ExitClass::Gamma;
for current_node in self.iter_context() {
let context_element = current_node.get_data();
match context_element {
ContextElement::ExitMatcherNode(exit_matcher) => {
if exit_matcher.class as u32 <= current_class_filter as u32 {
current_class_filter = exit_matcher.class;
let local_result = (exit_matcher.exit_matcher)(&current_node, i);
if local_result.is_ok() {
return local_result;
}
}
}
_ => {}
};
}
// TODO: Make this a specific error instead of just a generic MyError
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoExit".into(),
))));
}
/// Indicates if elements should consume the whitespace after them.
///
/// Defaults to true.
pub fn should_consume_trailing_whitespace(&self) -> bool {
self._should_consume_trailing_whitespace().unwrap_or(true)
}
fn _should_consume_trailing_whitespace(&self) -> Option<bool> {
for current_node in self.iter() {
match current_node {
ContextElement::ConsumeTrailingWhitespace(should) => {
return Some(*should);
}
_ => {}
}
}
None
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn document_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
eof(input)
}
pub struct Iter<'g, 'r, 's> {
global_settings: &'g GlobalSettings<'g, 's>,
next: super::list::IterList<'r, &'r ContextElement<'r, 's>>,
}
impl<'g, 'r, 's> Iterator for Iter<'g, 'r, 's> {
type Item = Context<'g, 'r, 's>;
fn next(&mut self) -> Option<Self::Item> {
let next_tree = self.next.next();
let ret =
next_tree.map(|parent_tree| Context::new(self.global_settings, parent_tree.clone()));
ret
}
}
impl<'r, 's> ContextElement<'r, 's> {
pub fn document_context() -> Self {
Self::ExitMatcherNode(ExitMatcherNode {
exit_matcher: &document_end,
class: ExitClass::Document,
})
}
}

View File

@@ -1,23 +0,0 @@
use std::fmt::Debug;
use std::path::PathBuf;
pub trait FileAccessInterface: Debug {
fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
}
#[derive(Debug, Clone)]
pub struct LocalFileAccessInterface {
pub working_directory: Option<PathBuf>,
}
impl FileAccessInterface for LocalFileAccessInterface {
fn read_file(&self, path: &str) -> Result<String, std::io::Error> {
let final_path = self
.working_directory
.as_ref()
.map(PathBuf::as_path)
.map(|pb| pb.join(path))
.unwrap_or_else(|| PathBuf::from(path));
Ok(std::fs::read_to_string(final_path)?)
}
}

View File

@@ -1,34 +0,0 @@
use std::collections::BTreeSet;
use super::FileAccessInterface;
use super::LocalFileAccessInterface;
use crate::types::Object;
// TODO: Ultimately, I think we'll need most of this: https://orgmode.org/manual/In_002dbuffer-Settings.html
#[derive(Debug, Clone)]
pub struct GlobalSettings<'g, 's> {
pub radio_targets: Vec<&'g Vec<Object<'s>>>,
pub file_access: &'g dyn FileAccessInterface,
pub in_progress_todo_keywords: BTreeSet<String>,
pub complete_todo_keywords: BTreeSet<String>,
}
impl<'g, 's> GlobalSettings<'g, 's> {
pub fn new() -> GlobalSettings<'g, 's> {
GlobalSettings {
radio_targets: Vec::new(),
file_access: &LocalFileAccessInterface {
working_directory: None,
},
in_progress_todo_keywords: BTreeSet::new(),
complete_todo_keywords: BTreeSet::new(),
}
}
}
impl<'g, 's> Default for GlobalSettings<'g, 's> {
fn default() -> GlobalSettings<'g, 's> {
GlobalSettings::new()
}
}

View File

@@ -1,69 +0,0 @@
use std::fmt::Debug;
#[derive(Debug, Clone)]
pub struct List<'parent, T> {
data: T,
parent: Link<'parent, T>,
}
type Link<'parent, T> = Option<&'parent List<'parent, T>>;
impl<'parent, T> List<'parent, T> {
pub fn new(first_item: T) -> Self {
Self {
data: first_item,
parent: None,
}
}
pub fn get_data(&self) -> &T {
&self.data
}
pub fn get_parent(&'parent self) -> Link<'parent, T> {
self.parent
}
pub fn iter(&self) -> Iter<'_, T> {
Iter { next: Some(self) }
}
pub fn iter_list(&self) -> IterList<'_, T> {
IterList { next: Some(self) }
}
pub fn push(&'parent self, item: T) -> Self {
Self {
data: item,
parent: Some(self),
}
}
}
pub struct Iter<'a, T> {
next: Link<'a, T>,
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
let ret = self.next.map(|link| link.get_data());
self.next = self.next.map(|link| link.get_parent()).flatten();
ret
}
}
pub struct IterList<'a, T> {
next: Link<'a, T>,
}
impl<'a, T> Iterator for IterList<'a, T> {
type Item = &'a List<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
let ret = self.next;
self.next = self.next.map(|this| this.get_parent()).flatten();
ret
}
}

View File

@@ -1,29 +0,0 @@
use crate::error::Res;
use crate::parser::OrgSource;
mod context;
mod exiting;
mod file_access_interface;
mod global_settings;
mod list;
mod parser_with_context;
pub type RefContext<'b, 'g, 'r, 's> = &'b Context<'g, 'r, 's>;
pub trait ContextMatcher = for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>>;
pub type DynContextMatcher<'c> = dyn ContextMatcher + 'c;
pub trait Matcher = for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>;
#[allow(dead_code)]
pub type DynMatcher<'c> = dyn Matcher + 'c;
pub use context::Context;
pub use context::ContextElement;
pub use context::ExitMatcherNode;
pub use exiting::ExitClass;
pub use file_access_interface::FileAccessInterface;
pub use file_access_interface::LocalFileAccessInterface;
pub use global_settings::GlobalSettings;
pub use list::List;
pub(crate) use parser_with_context::parser_with_context;

View File

@@ -5,14 +5,13 @@ use nom::IResult;
pub type Res<T, U> = IResult<T, U, CustomError<T>>; pub type Res<T, U> = IResult<T, U, CustomError<T>>;
// TODO: MyError probably shouldn't be based on the same type as the input type since it's used exclusively with static strings right now. // TODO: MyError probably shouldn't be based on the same type as the input type since it's used exclusively with static strings right now.
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub enum CustomError<I> { pub enum CustomError<I> {
MyError(MyError<I>), MyError(MyError<I>),
Nom(I, ErrorKind), Nom(I, ErrorKind),
IO(std::io::Error),
} }
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub struct MyError<I>(pub I); pub struct MyError<I>(pub I);
impl<I> ParseError<I> for CustomError<I> { impl<I> ParseError<I> for CustomError<I> {
@@ -25,9 +24,3 @@ impl<I> ParseError<I> for CustomError<I> {
other other
} }
} }
impl<I> From<std::io::Error> for CustomError<I> {
fn from(value: std::io::Error) -> Self {
CustomError::IO(value)
}
}

View File

@@ -1,24 +1,16 @@
#![feature(round_char_boundary)] #![feature(round_char_boundary)]
#![feature(exit_status_error)] #![feature(exit_status_error)]
#![feature(trait_alias)]
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
mod compare; mod compare;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
pub use compare::compare_document; pub use compare::compare_document;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
pub use compare::emacs_parse_anonymous_org_document; pub use compare::emacs_parse_org_document;
#[cfg(feature = "compare")]
pub use compare::emacs_parse_file_org_document;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
pub use compare::get_emacs_version; pub use compare::get_emacs_version;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
pub use compare::get_org_mode_version; pub use compare::get_org_mode_version;
mod context;
mod error; mod error;
pub mod parser; pub mod parser;
pub mod types;
pub use context::GlobalSettings;
pub use context::LocalFileAccessInterface;

View File

@@ -1,24 +1,17 @@
#![feature(round_char_boundary)] #![feature(round_char_boundary)]
#![feature(exact_size_is_empty)]
use std::io::Read; use std::io::Read;
use std::path::Path;
use ::organic::parser::parse; use ::organic::parser::document;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
use organic::compare_document; use organic::compare_document;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
use organic::emacs_parse_anonymous_org_document; use organic::emacs_parse_org_document;
#[cfg(feature = "compare")]
use organic::emacs_parse_file_org_document;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
use organic::get_emacs_version; use organic::get_emacs_version;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
use organic::get_org_mode_version; use organic::get_org_mode_version;
use organic::parser::parse_with_settings;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
use organic::parser::sexp::sexp_with_padding; use organic::parser::sexp::sexp_with_padding;
use organic::GlobalSettings;
use organic::LocalFileAccessInterface;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
use crate::init_tracing::init_telemetry; use crate::init_tracing::init_telemetry;
@@ -46,16 +39,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn main_body() -> Result<(), Box<dyn std::error::Error>> { fn main_body() -> Result<(), Box<dyn std::error::Error>> {
let args = std::env::args().skip(1); let org_contents = read_stdin_to_string()?;
if args.is_empty() { run_compare(org_contents)
let org_contents = read_stdin_to_string()?;
run_anonymous_parse(org_contents)
} else {
for arg in args {
run_parse_on_file(arg)?
}
Ok(())
}
} }
fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> { fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
@@ -67,98 +52,40 @@ fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
} }
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> { fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
let org_contents = org_contents.as_ref(); let emacs_version = get_emacs_version()?;
eprintln!("Using emacs version: {}", get_emacs_version()?.trim()); let org_mode_version = get_org_mode_version()?;
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim()); eprintln!("Using emacs version: {}", emacs_version.trim());
let rust_parsed = parse(org_contents)?; eprintln!("Using org-mode version: {}", org_mode_version.trim());
let org_sexp = emacs_parse_anonymous_org_document(org_contents)?; let (remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?;
let org_sexp = emacs_parse_org_document(org_contents.as_ref())?;
let (_remaining, parsed_sexp) = let (_remaining, parsed_sexp) =
sexp_with_padding(org_sexp.as_str()).map_err(|e| e.to_string())?; sexp_with_padding(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents); println!("{}\n\n\n", org_contents.as_ref());
println!("{}", org_sexp); println!("{}", org_sexp);
println!("{:#?}", rust_parsed); println!("{:#?}", rust_parsed);
// We do the diffing after printing out both parsed forms in case the diffing panics // We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?; let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print(org_contents)?; diff_result.print()?;
if diff_result.is_bad() { if diff_result.is_bad() {
Err("Diff results do not match.")?; Err("Diff results do not match.")?;
} }
if remaining != "" {
Err(format!("There was unparsed text remaining: {}", remaining))?;
}
Ok(()) Ok(())
} }
#[cfg(not(feature = "compare"))] #[cfg(not(feature = "compare"))]
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> { fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
eprintln!( eprintln!(
"This program was built with compare disabled. Only parsing with organic, not comparing." "This program was built with compare disabled. Only parsing with organic, not comparing."
); );
let rust_parsed = parse(org_contents.as_ref())?; let (remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?;
println!("{:#?}", rust_parsed);
Ok(())
}
#[cfg(feature = "compare")]
fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
let org_path = org_path.as_ref();
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
let parent_directory = org_path
.parent()
.ok_or("Should be contained inside a directory.")?;
let org_contents = std::fs::read_to_string(org_path)?;
let org_contents = org_contents.as_str();
let file_access_interface = LocalFileAccessInterface {
working_directory: Some(parent_directory.to_path_buf()),
};
let global_settings = {
let mut global_settings = GlobalSettings::default();
global_settings.file_access = &file_access_interface;
global_settings
};
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
let org_sexp = emacs_parse_file_org_document(org_path)?;
let (_remaining, parsed_sexp) =
sexp_with_padding(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents);
println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
// We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print(org_contents)?;
if diff_result.is_bad() {
Err("Diff results do not match.")?;
}
Ok(())
}
#[cfg(not(feature = "compare"))]
fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
let org_path = org_path.as_ref();
eprintln!(
"This program was built with compare disabled. Only parsing with organic, not comparing."
);
let parent_directory = org_path
.parent()
.ok_or("Should be contained inside a directory.")?;
let org_contents = std::fs::read_to_string(org_path)?;
let org_contents = org_contents.as_str();
let file_access_interface = LocalFileAccessInterface {
working_directory: Some(parent_directory.to_path_buf()),
};
let global_settings = {
let mut global_settings = GlobalSettings::default();
global_settings.file_access = &file_access_interface;
global_settings
};
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
println!("{:#?}", rust_parsed); println!("{:#?}", rust_parsed);
Ok(()) Ok(())
} }

View File

@@ -5,21 +5,20 @@ use nom::combinator::recognize;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use super::Context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::plain_link::protocol; use crate::parser::plain_link::protocol;
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;
use crate::types::AngleLink; use crate::parser::AngleLink;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn angle_link<'b, 'g, 'r, 's>( pub fn angle_link<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, AngleLink<'s>> { ) -> Res<OrgSource<'s>, AngleLink<'s>> {
let (remaining, _) = tag("<")(input)?; let (remaining, _) = tag("<")(input)?;
@@ -27,8 +26,6 @@ pub fn angle_link<'b, 'g, 'r, 's>(
let (remaining, _separator) = tag(":")(remaining)?; let (remaining, _separator) = tag(":")(remaining)?;
let (remaining, path) = path_angle(context, remaining)?; let (remaining, path) = path_angle(context, remaining)?;
let (remaining, _) = tag(">")(remaining)?; let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -41,15 +38,15 @@ pub fn angle_link<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_angle<'b, 'g, 'r, 's>( fn path_angle<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context =
class: ExitClass::Gamma, context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
exit_matcher: &path_angle_end, class: ExitClass::Gamma,
}); exit_matcher: &path_angle_end,
let parser_context = context.with_additional_node(&parser_context); }));
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
@@ -58,8 +55,8 @@ fn path_angle<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_angle_end<'b, 'g, 'r, 's>( fn path_angle_end<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag(">")(input) tag(">")(input)

View File

@@ -2,6 +2,7 @@ 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;
use nom::character::complete::space0;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -10,47 +11,42 @@ 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::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use super::Context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
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::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::ExitMatcherNode;
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;
use crate::types::Citation; use crate::parser::Object;
use crate::types::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation<'b, 'g, 'r, 's>( pub fn citation<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Citation<'s>> { ) -> Res<OrgSource<'s>, Citation<'s>> {
// TODO: Despite being a standard object, citations cannot exist inside the global prefix/suffix for other citations because citations must contain something that matches @key which is forbidden inside the global prefix/suffix. This TODO is to evaluate if its worth putting in an explicit check for this (which can be easily accomplished by checking the output of `get_bracket_depth()`). I suspect its not worth it because I expect, outside of intentionally crafted inputs, this parser will exit immediately inside a citation since it is unlikely to find the "[cite" substring inside a citation global prefix/suffix. // TODO: Despite being a standard object, citations cannot exist inside the global prefix/suffix for other citations because citations must contain something that matches @key which is forbidden inside the global prefix/suffix. This TODO is to evaluate if its worth putting in an explicit check for this (which can be easily accomplished by checking the output of `get_bracket_depth()`). I suspect its not worth it because I expect, outside of intentionally crafted inputs, this parser will exit immediately inside a citation since it is unlikely to find the "[cite" substring inside a citation global prefix/suffix.
let (remaining, _) = tag_no_case("[cite")(input)?; let (remaining, _) = 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, _trailing_whitespace) = let (remaining, _) = space0(remaining)?;
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -61,7 +57,7 @@ pub fn citation<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn citestyle<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn citestyle<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tuple((tag("/"), style))(input)?; let (remaining, _) = tuple((tag("/"), style))(input)?;
let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?; let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -69,30 +65,34 @@ fn citestyle<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn style<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn style<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| { recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_-".contains(*c) c.is_alphanumeric() || "_-".contains(*c)
})))(input) })))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn variant<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn variant<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| { recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_-/".contains(*c) c.is_alphanumeric() || "_-/".contains(*c)
})))(input) })))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_prefix<'b, 'g, 'r, 's>( fn global_prefix<'r, 's>(
context: RefContext<'b, 'g, '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 = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context = context
class: ExitClass::Gamma, .with_additional_node(ContextElement::CitationBracket(CitationBracket {
exit_matcher: &exit_with_depth, position: input,
}); depth: 0,
let parser_context = context.with_additional_node(&parser_context); }))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &global_prefix_end,
}));
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),
@@ -104,20 +104,28 @@ fn global_prefix<'b, 'g, 'r, 's>(
Ok((remaining, children)) Ok((remaining, children))
} }
fn global_prefix_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
move |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<'b, 'g, 'r, 's>( fn global_prefix_end<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> 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);
@@ -132,16 +140,20 @@ fn _global_prefix_end<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_suffix<'b, 'g, 'r, 's>( fn global_suffix<'r, 's>(
context: RefContext<'b, 'g, '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 = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context = context
class: ExitClass::Gamma, .with_additional_node(ContextElement::CitationBracket(CitationBracket {
exit_matcher: &exit_with_depth, position: input,
}); depth: 0,
let parser_context = context.with_additional_node(&parser_context); }))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &global_suffix_end,
}));
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),
@@ -152,20 +164,28 @@ fn global_suffix<'b, 'g, 'r, 's>(
Ok((remaining, children)) Ok((remaining, children))
} }
fn global_suffix_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
move |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<'b, 'g, 'r, 's>( fn global_suffix_end<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> 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);
@@ -182,23 +202,19 @@ fn _global_suffix_end<'b, 'g, 'r, 's>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::context::Context;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::types::Element; use crate::parser::parser_context::ContextTree;
use crate::types::Source; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::Source;
#[test] #[test]
fn citation_simple() { fn citation_simple() {
let input = OrgSource::new("[cite:@foo]"); let input = OrgSource::new("[cite:@foo]");
let global_settings = GlobalSettings::default(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let paragraph_matcher = parser_with_context!(element(true))(&initial_context); let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph"); let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
let first_paragraph = match first_paragraph { let first_paragraph = match first_paragraph {
Element::Paragraph(paragraph) => paragraph, crate::parser::Element::Paragraph(paragraph) => paragraph,
_ => panic!("Should be a paragraph!"), _ => panic!("Should be a paragraph!"),
}; };
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");

View File

@@ -10,34 +10,30 @@ use nom::multi::many_till;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use crate::context::parser_with_context; use super::Context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
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::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::ExitMatcherNode;
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;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS; use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::types::CitationReference; use crate::parser::Object;
use crate::types::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation_reference<'b, 'g, 'r, 's>( pub fn citation_reference<'r, 's>(
context: RefContext<'b, 'g, '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((
@@ -49,8 +45,8 @@ pub fn citation_reference<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation_reference_key<'b, 'g, 'r, 's>( pub fn citation_reference_key<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(tuple(( let (remaining, source) = recognize(tuple((
@@ -69,16 +65,20 @@ pub fn citation_reference_key<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_prefix<'b, 'g, 'r, 's>( fn key_prefix<'r, 's>(
context: RefContext<'b, 'g, '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 = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context = context
class: ExitClass::Gamma, .with_additional_node(ContextElement::CitationBracket(CitationBracket {
exit_matcher: &exit_with_depth, position: input,
}); depth: 0,
let parser_context = context.with_additional_node(&parser_context); }))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &key_prefix_end,
}));
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
parser_with_context!(minimal_set_object)(&parser_context), parser_with_context!(minimal_set_object)(&parser_context),
@@ -90,16 +90,20 @@ fn key_prefix<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_suffix<'b, 'g, 'r, 's>( fn key_suffix<'r, 's>(
context: RefContext<'b, 'g, '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 = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context = context
class: ExitClass::Gamma, .with_additional_node(ContextElement::CitationBracket(CitationBracket {
exit_matcher: &exit_with_depth, position: input,
}); depth: 0,
let parser_context = context.with_additional_node(&parser_context); }))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &key_suffix_end,
}));
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
parser_with_context!(minimal_set_object)(&parser_context), parser_with_context!(minimal_set_object)(&parser_context),
@@ -110,20 +114,39 @@ fn key_suffix<'b, 'g, 'r, 's>(
Ok((remaining, children)) Ok((remaining, children))
} }
fn key_prefix_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher { #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
move |context, input: OrgSource<'_>| _key_prefix_end(context, input, starting_bracket_depth) pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r CitationBracket<'s>> {
for node in context.iter() {
match node.get_data() {
ContextElement::CitationBracket(depth) => return Some(depth),
_ => {}
}
}
None
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _key_prefix_end<'b, 'g, 'r, 's>( fn key_prefix_end<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> 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);
@@ -137,20 +160,28 @@ fn _key_prefix_end<'b, 'g, 'r, 's>(
))(input) ))(input)
} }
fn key_suffix_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
move |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<'b, 'g, 'r, 's>( fn key_suffix_end<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> 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);
@@ -160,21 +191,3 @@ fn _key_suffix_end<'b, 'g, '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

@@ -12,16 +12,16 @@ use nom::combinator::verify;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use crate::context::parser_with_context; use super::Context;
use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::Clock; use crate::parser::Clock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn clock<'b, 'g, 'r, 's>( pub fn clock<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Clock<'s>> { ) -> Res<OrgSource<'s>, Clock<'s>> {
start_of_line(input)?; start_of_line(input)?;
@@ -44,8 +44,8 @@ pub fn clock<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp_range_duration<'b, 'g, 'r, 's>( fn inactive_timestamp_range_duration<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple(( recognize(tuple((
@@ -66,8 +66,8 @@ fn inactive_timestamp_range_duration<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp<'b, 'g, 'r, 's>( fn inactive_timestamp<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple(( recognize(tuple((

View File

@@ -13,20 +13,20 @@ use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_consumed; use super::util::get_consumed;
use crate::context::parser_with_context; use super::Context;
use crate::context::ContextElement;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::parser_context::ContextElement;
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::immediate_in_section; use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::Comment; use crate::parser::Comment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn comment<'b, 'g, 'r, 's>( pub fn comment<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Comment<'s>> { ) -> Res<OrgSource<'s>, Comment<'s>> {
if immediate_in_section(context, "comment") { if immediate_in_section(context, "comment") {
@@ -34,8 +34,7 @@ pub fn comment<'b, 'g, 'r, 's>(
"Cannot nest objects of the same element".into(), "Cannot nest objects of the same element".into(),
)))); ))));
} }
let parser_context = ContextElement::Context("comment"); let parser_context = context.with_additional_node(ContextElement::Context("comment"));
let parser_context = context.with_additional_node(&parser_context);
let comment_line_matcher = parser_with_context!(comment_line)(&parser_context); let comment_line_matcher = parser_with_context!(comment_line)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, _first_line) = comment_line_matcher(input)?; let (remaining, _first_line) = comment_line_matcher(input)?;
@@ -52,8 +51,8 @@ pub fn comment<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn comment_line<'b, 'g, 'r, 's>( fn comment_line<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
@@ -70,10 +69,8 @@ fn comment_line<'b, 'g, 'r, 's>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::context::Context; use crate::parser::parser_context::ContextTree;
use crate::context::ContextElement; use crate::parser::parser_with_context::parser_with_context;
use crate::context::GlobalSettings;
use crate::context::List;
#[test] #[test]
fn require_space_after_hash() { fn require_space_after_hash() {
@@ -82,9 +79,7 @@ mod tests {
#not a comment #not a comment
# Comment again", # Comment again",
); );
let global_settings = GlobalSettings::default(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let comment_matcher = parser_with_context!(comment)(&initial_context); let comment_matcher = parser_with_context!(comment)(&initial_context);
let (remaining, first_comment) = comment_matcher(input).expect("Parse first comment"); let (remaining, first_comment) = comment_matcher(input).expect("Parse first comment");
assert_eq!( assert_eq!(

View File

@@ -1,25 +1,22 @@
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::space0;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::sexp::sexp; use super::sexp::sexp;
use crate::context::RefContext; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::DiarySexp; use crate::parser::DiarySexp;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn diary_sexp<'b, 'g, 'r, 's>( pub fn diary_sexp<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, DiarySexp<'s>> { ) -> Res<OrgSource<'s>, DiarySexp<'s>> {
start_of_line(input)?; start_of_line(input)?;
@@ -27,11 +24,6 @@ pub fn diary_sexp<'b, 'g, 'r, 's>(
let (remaining, _clock) = tag("%%")(remaining)?; let (remaining, _clock) = tag("%%")(remaining)?;
let (remaining, _gap_whitespace) = space0(remaining)?; let (remaining, _gap_whitespace) = space0(remaining)?;
let (remaining, _sexp) = recognize(sexp)(remaining)?; let (remaining, _sexp) = recognize(sexp)(remaining)?;
let (remaining, _trailing_comment) = opt(tuple((
space0,
tag(";"),
many_till(anychar, alt((line_ending, eof))),
)))(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, _trailing_whitespace) =
recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?; recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;

View File

@@ -1,10 +1,7 @@
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::all_consuming;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::not; use nom::combinator::not;
@@ -15,129 +12,95 @@ 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::in_buffer_settings::apply_in_buffer_settings; use super::element::Element;
use super::in_buffer_settings::scan_for_in_buffer_settings; use super::object::Object;
use super::org_source::convert_error;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::parser_with_context::parser_with_context;
use super::source::Source;
use super::token::AllTokensIterator; use super::token::AllTokensIterator;
use super::token::Token; use super::token::Token;
use super::util::exit_matcher_parser; use super::util::exit_matcher_parser;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::start_of_line; use super::util::start_of_line;
use crate::context::parser_with_context; use super::Context;
use crate::context::Context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::comment::comment; use crate::parser::comment::comment;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::org_source::convert_error; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::planning::planning; use crate::parser::planning::planning;
use crate::parser::property_drawer::property_drawer; use crate::parser::property_drawer::property_drawer;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting; use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::types::Document;
use crate::types::DocumentElement;
use crate::types::Element;
use crate::types::Heading;
use crate::types::Object;
use crate::types::Section;
use crate::types::TodoKeywordType;
/// Parse a full org-mode document. #[derive(Debug)]
/// pub struct Document<'s> {
/// This is the main entry point for Organic. It will parse the full contents of the input string as an org-mode document. pub source: &'s str,
#[allow(dead_code)] pub zeroth_section: Option<Section<'s>>,
pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, String> { pub children: Vec<Heading<'s>>,
parse_with_settings(input, &GlobalSettings::default())
} }
/// Parse a full org-mode document with starting settings. #[derive(Debug)]
/// pub struct Heading<'s> {
/// This is the secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document starting with the settings you supplied. pub source: &'s str,
/// pub stars: usize,
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO". pub title: Vec<Object<'s>>,
#[allow(dead_code)] pub children: Vec<DocumentElement<'s>>,
pub fn parse_with_settings<'g, 's>(
input: &'s str,
global_settings: &'g GlobalSettings<'g, 's>,
) -> Result<Document<'s>, String> {
let initial_context = ContextElement::document_context();
let initial_context = Context::new(global_settings, List::new(&initial_context));
let wrapped_input = OrgSource::new(input);
let ret =
all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input)
.map_err(|err| err.to_string())
.map(|(_remaining, parsed_document)| parsed_document);
ret
} }
/// Parse a full org-mode document. #[derive(Debug)]
/// pub struct Section<'s> {
/// Use this entry point when you want to have direct control over the starting context or if you want to use this integrated with other nom parsers. For general-purpose usage, the `parse` and `parse_with_settings` functions are a lot simpler. pub source: &'s str,
/// pub children: Vec<Element<'s>>,
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO". }
#[allow(dead_code)]
pub fn document<'b, 'g, 'r, 's>( #[derive(Debug)]
context: RefContext<'b, 'g, 'r, 's>, pub enum DocumentElement<'s> {
input: &'s str, Heading(Heading<'s>),
) -> Res<&'s str, Document<'s>> { Section(Section<'s>),
let (remaining, doc) = document_org_source(context, input.into()).map_err(convert_error)?; }
Ok((Into::<&str>::into(remaining), doc))
impl<'s> Source<'s> for Document<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for DocumentElement<'s> {
fn get_source(&'s self) -> &'s str {
match self {
DocumentElement::Heading(obj) => obj.source,
DocumentElement::Section(obj) => obj.source,
}
}
}
impl<'s> Source<'s> for Section<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for Heading<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
#[allow(dead_code)] #[allow(dead_code)]
fn document_org_source<'b, 'g, 'r, 's>( pub fn document(input: &str) -> Res<&str, Document> {
context: RefContext<'b, 'g, 'r, 's>, let initial_context: ContextTree<'_, '_> = ContextTree::new();
input: OrgSource<'s>, let wrapped_input = OrgSource::new(input);
) -> Res<OrgSource<'s>, Document<'s>> { let (remaining, document) = _document(&initial_context, wrapped_input)
let mut final_settings = Vec::new(); .map(|(rem, out)| (Into::<&str>::into(rem), out))
let (_, document_settings) = scan_for_in_buffer_settings(input)?; .map_err(convert_error)?;
let setup_files: Vec<String> = document_settings
.iter()
.filter(|kw| kw.key.eq_ignore_ascii_case("setupfile"))
.map(|kw| kw.value)
.map(|setup_file| {
context
.get_global_settings()
.file_access
.read_file(setup_file)
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))
})
.collect::<Result<Vec<_>, _>>()?;
for setup_file in setup_files.iter().map(String::as_str) {
let (_, setup_file_settings) =
scan_for_in_buffer_settings(setup_file.into()).map_err(|_err| {
nom::Err::Error(CustomError::MyError(MyError(
"TODO: make this take an owned string so I can dump err.to_string() into it."
.into(),
)))
})?;
final_settings.extend(setup_file_settings);
}
final_settings.extend(document_settings);
let new_settings = apply_in_buffer_settings(final_settings, context.get_global_settings())
.map_err(|_err| {
nom::Err::Error(CustomError::MyError(MyError(
"TODO: make this take an owned string so I can dump err.to_string() into it."
.into(),
)))
})?;
let new_context = context.with_global_settings(&new_settings);
let context = &new_context;
let (remaining, document) =
_document(context, input).map(|(rem, out)| (Into::<&str>::into(rem), out))?;
{ {
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets. // If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
let all_radio_targets: Vec<&Vec<Object<'_>>> = document let all_radio_targets: Vec<&Vec<Object<'_>>> = document
@@ -153,11 +116,11 @@ fn document_org_source<'b, 'g, 'r, 's>(
.map(|rt| &rt.children) .map(|rt| &rt.children)
.collect(); .collect();
if !all_radio_targets.is_empty() { if !all_radio_targets.is_empty() {
let mut new_global_settings = context.get_global_settings().clone(); let initial_context = initial_context
new_global_settings.radio_targets = all_radio_targets; .with_additional_node(ContextElement::RadioTarget(all_radio_targets));
let parser_context = context.with_global_settings(&new_global_settings); let (remaining, document) = _document(&initial_context, wrapped_input)
let (remaining, document) = _document(&parser_context, input) .map(|(rem, out)| (Into::<&str>::into(rem), out))
.map(|(rem, out)| (Into::<&str>::into(rem), out))?; .map_err(convert_error)?;
return Ok((remaining.into(), document)); return Ok((remaining.into(), document));
} }
} }
@@ -165,12 +128,12 @@ fn document_org_source<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _document<'b, 'g, 'r, 's>( fn _document<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Document<'s>> { ) -> Res<OrgSource<'s>, Document<'s>> {
let zeroth_section_matcher = parser_with_context!(zeroth_section)(context); let zeroth_section_matcher = parser_with_context!(zeroth_section)(context);
let heading_matcher = parser_with_context!(heading(0))(context); let heading_matcher = parser_with_context!(heading)(context);
let (remaining, _blank_lines) = many0(blank_line)(input)?; let (remaining, _blank_lines) = many0(blank_line)(input)?;
let (remaining, zeroth_section) = opt(zeroth_section_matcher)(remaining)?; let (remaining, zeroth_section) = opt(zeroth_section_matcher)(remaining)?;
let (remaining, children) = many0(heading_matcher)(remaining)?; let (remaining, children) = many0(heading_matcher)(remaining)?;
@@ -186,25 +149,20 @@ fn _document<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn zeroth_section<'b, 'g, 'r, 's>( fn zeroth_section<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> { ) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser // TODO: The zeroth section is specialized so it probably needs its own parser
let contexts = [ let parser_context = context
ContextElement::ConsumeTrailingWhitespace(true), .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
ContextElement::Context("section"), .with_additional_node(ContextElement::Context("section"))
ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document, class: ExitClass::Document,
exit_matcher: &section_end, exit_matcher: &section_end,
}), }));
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let without_consuming_whitespace_context = ContextElement::ConsumeTrailingWhitespace(false);
let without_consuming_whitespace_context = let without_consuming_whitespace_context =
parser_context.with_additional_node(&without_consuming_whitespace_context); parser_context.with_additional_node(ContextElement::ConsumeTrailingWhitespace(false));
let element_matcher = parser_with_context!(element(true))(&parser_context); let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
@@ -245,22 +203,18 @@ fn zeroth_section<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section<'b, 'g, 'r, 's>( fn section<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
mut input: OrgSource<'s>, mut input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> { ) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser // TODO: The zeroth section is specialized so it probably needs its own parser
let contexts = [ let parser_context = context
ContextElement::ConsumeTrailingWhitespace(true), .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
ContextElement::Context("section"), .with_additional_node(ContextElement::Context("section"))
ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document, class: ExitClass::Document,
exit_matcher: &section_end, exit_matcher: &section_end,
}), }));
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let element_matcher = parser_with_context!(element(true))(&parser_context); let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (mut remaining, (planning_element, property_drawer_element)) = tuple(( let (mut remaining, (planning_element, property_drawer_element)) = tuple((
@@ -299,196 +253,70 @@ fn section<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section_end<'b, 'g, 'r, 's>( fn section_end<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(detect_headline)(input) let headline_matcher = parser_with_context!(headline)(context);
} recognize(headline_matcher)(input)
const fn heading(
parent_stars: usize,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Heading<'s>> {
move |context, input: OrgSource<'_>| _heading(context, input, parent_stars)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _heading<'b, 'g, 'r, 's>( fn heading<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
parent_stars: usize,
) -> 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, parent_stars)?;
let section_matcher = parser_with_context!(section)(context); let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading(star_count))(context); let heading_matcher = parser_with_context!(heading)(context);
let (remaining, maybe_section) = let (remaining, children) = many0(alt((
opt(map(section_matcher, DocumentElement::Section))(remaining)?; map(
let (remaining, mut children) = verify(heading_matcher, |h| h.stars > star_count),
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?; DocumentElement::Heading,
if let Some(section) = maybe_section { ),
children.insert(0, section); map(section_matcher, DocumentElement::Section),
} )))(remaining)?;
let remaining = if children.is_empty() {
// Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?;
remain
} else {
remaining
};
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Heading { Heading {
source: source.into(), source: source.into(),
stars: star_count, stars: star_count,
todo_keyword: maybe_todo_keyword.map(|((todo_keyword_type, todo_keyword), _ws)| {
(todo_keyword_type, Into::<&str>::into(todo_keyword))
}),
title, title,
tags: heading_tags,
children, children,
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { fn headline<'r, 's>(
tuple((start_of_line, many1(tag("*")), space1))(input)?; context: Context<'r, 's>,
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
parent_stars: usize, ) -> Res<OrgSource<'s>, (usize, OrgSource<'s>, Vec<Object<'s>>)> {
) -> Res< let parser_context =
OrgSource<'s>, context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
( class: ExitClass::Document,
usize, exit_matcher: &headline_end,
OrgSource<'s>, }));
Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>, let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
Vec<Object<'s>>,
Vec<&'s str>,
),
> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &headline_title_end,
});
let parser_context = context.with_additional_node(&parser_context);
let ( 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,
verify(many1_count(tag("*")), |star_count| { many1_count(tag("*")),
*star_count > parent_stars
}),
space1, space1,
opt(tuple(( many1(standard_set_object_matcher),
parser_with_context!(heading_keyword)(&parser_context),
space1,
))),
many1(parser_with_context!(standard_set_object)(&parser_context)),
opt(tuple((space0, tags))),
space0,
alt((line_ending, eof)), 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<'b, 'g, 'r, 's>( fn headline_end<'r, 's>(
_context: RefContext<'b, 'g, '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<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (TodoKeywordType, OrgSource<'s>)> {
let global_settings = context.get_global_settings();
if global_settings.in_progress_todo_keywords.is_empty()
&& global_settings.complete_todo_keywords.is_empty()
{
alt((
map(tag("TODO"), |capture| (TodoKeywordType::Todo, capture)),
map(tag("DONE"), |capture| (TodoKeywordType::Done, capture)),
))(input)
} else {
for todo_keyword in global_settings
.in_progress_todo_keywords
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Todo, ent)));
}
Err(_) => {}
}
}
for todo_keyword in global_settings
.complete_todo_keywords
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Done, ent)));
}
Err(_) => {}
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoTodoKeyword".into(),
))))
}
} }
impl<'s> Document<'s> { impl<'s> Document<'s> {

View File

@@ -11,29 +11,29 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use crate::context::parser_with_context; use super::Context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::SetSource;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
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;
use crate::parser::util::immediate_in_section; use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS; use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::types::Drawer; use crate::parser::Drawer;
use crate::types::Element; use crate::parser::Element;
use crate::types::Paragraph; use crate::parser::Paragraph;
use crate::types::SetSource;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn drawer<'b, 'g, 'r, 's>( pub fn drawer<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Drawer<'s>> { ) -> Res<OrgSource<'s>, Drawer<'s>> {
if immediate_in_section(context, "drawer") { if immediate_in_section(context, "drawer") {
@@ -50,17 +50,13 @@ pub fn drawer<'b, 'g, 'r, 's>(
recognize(tuple((space0, line_ending))), recognize(tuple((space0, line_ending))),
))(remaining)?; ))(remaining)?;
let contexts = [ let parser_context = context
ContextElement::ConsumeTrailingWhitespace(true), .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
ContextElement::Context("drawer"), .with_additional_node(ContextElement::Context("drawer"))
ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Alpha,
exit_matcher: &drawer_end, exit_matcher: &drawer_end,
}), }));
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let element_matcher = parser_with_context!(element(true))(&parser_context); let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
@@ -102,8 +98,8 @@ fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn drawer_end<'b, 'g, 'r, 's>( fn drawer_end<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;

View File

@@ -12,28 +12,28 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use crate::context::parser_with_context; use super::Context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::greater_element::DynamicBlock;
use crate::parser::lesser_element::Paragraph;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::SetSource;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
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;
use crate::parser::util::immediate_in_section; use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::DynamicBlock; use crate::parser::Element;
use crate::types::Element;
use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn dynamic_block<'b, 'g, 'r, 's>( pub fn dynamic_block<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, DynamicBlock<'s>> { ) -> Res<OrgSource<'s>, DynamicBlock<'s>> {
// TODO: Do I need to differentiate between different dynamic block types. // TODO: Do I need to differentiate between different dynamic block types.
@@ -50,17 +50,13 @@ pub fn dynamic_block<'b, 'g, 'r, 's>(
opt(tuple((space1, parameters))), opt(tuple((space1, parameters))),
line_ending, line_ending,
))(remaining)?; ))(remaining)?;
let contexts = [ let parser_context = context
ContextElement::ConsumeTrailingWhitespace(true), .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
ContextElement::Context("dynamic block"), .with_additional_node(ContextElement::Context("dynamic block"))
ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Alpha,
exit_matcher: &dynamic_block_end, exit_matcher: &dynamic_block_end,
}), }));
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -110,8 +106,8 @@ fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dynamic_block_end<'b, 'g, 'r, 's>( fn dynamic_block_end<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;

View File

@@ -18,9 +18,9 @@ use super::lesser_element::Paragraph;
use super::lesser_element::Planning; use super::lesser_element::Planning;
use super::lesser_element::SrcBlock; use super::lesser_element::SrcBlock;
use super::lesser_element::VerseBlock; use super::lesser_element::VerseBlock;
use super::source::SetSource;
use super::source::Source;
use super::Drawer; use super::Drawer;
use super::SetSource;
use super::Source;
#[derive(Debug)] #[derive(Debug)]
pub enum Element<'s> { pub enum Element<'s> {

View File

@@ -7,11 +7,11 @@ use super::comment::comment;
use super::diary_sexp::diary_sexp; use super::diary_sexp::diary_sexp;
use super::drawer::drawer; use super::drawer::drawer;
use super::dynamic_block::dynamic_block; use super::dynamic_block::dynamic_block;
use super::element::Element;
use super::fixed_width_area::fixed_width_area; use super::fixed_width_area::fixed_width_area;
use super::footnote_definition::footnote_definition; use super::footnote_definition::footnote_definition;
use super::greater_block::greater_block; use super::greater_block::greater_block;
use super::horizontal_rule::horizontal_rule; use super::horizontal_rule::horizontal_rule;
use super::keyword::affiliated_keyword;
use super::keyword::keyword; use super::keyword::keyword;
use super::latex_environment::latex_environment; use super::latex_environment::latex_environment;
use super::lesser_block::comment_block; use super::lesser_block::comment_block;
@@ -23,29 +23,25 @@ use super::org_source::OrgSource;
use super::paragraph::paragraph; use super::paragraph::paragraph;
use super::plain_list::detect_plain_list; use super::plain_list::detect_plain_list;
use super::plain_list::plain_list; use super::plain_list::plain_list;
use super::source::SetSource;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context; use super::Context;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::table::org_mode_table; use crate::parser::table::org_mode_table;
use crate::types::Element;
use crate::types::SetSource;
pub const fn element( pub const fn element(
can_be_paragraph: bool, can_be_paragraph: bool,
) -> impl for<'b, 'g, 'r, 's> Fn( ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Element<'s>> {
RefContext<'b, 'g, 'r, 's>, move |context: Context, input: OrgSource<'_>| _element(context, input, can_be_paragraph)
OrgSource<'s>,
) -> Res<OrgSource<'s>, Element<'s>> {
move |context, input: OrgSource<'_>| _element(context, input, can_be_paragraph)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _element<'b, 'g, 'r, 's>( fn _element<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<OrgSource<'s>, Element<'s>> { ) -> Res<OrgSource<'s>, Element<'s>> {
@@ -66,12 +62,10 @@ fn _element<'b, 'g, 'r, 's>(
let fixed_width_area_matcher = parser_with_context!(fixed_width_area)(context); let fixed_width_area_matcher = parser_with_context!(fixed_width_area)(context);
let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context); let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context);
let keyword_matcher = parser_with_context!(keyword)(context); let keyword_matcher = parser_with_context!(keyword)(context);
let affiliated_keyword_matcher = parser_with_context!(affiliated_keyword)(context);
let paragraph_matcher = parser_with_context!(paragraph)(context); let paragraph_matcher = parser_with_context!(paragraph)(context);
let latex_environment_matcher = parser_with_context!(latex_environment)(context); let latex_environment_matcher = parser_with_context!(latex_environment)(context);
// TODO: Affiliated keywords cannot be on comments, clocks, headings, inlinetasks, items, node properties, planning, property drawers, sections, and table rows let (remaining, mut affiliated_keywords) = many0(keyword_matcher)(input)?;
let (remaining, mut affiliated_keywords) = many0(affiliated_keyword_matcher)(input)?;
let (remaining, mut element) = match alt(( let (remaining, mut element) = match alt((
map(plain_list_matcher, Element::PlainList), map(plain_list_matcher, Element::PlainList),
map(greater_block_matcher, Element::GreaterBlock), map(greater_block_matcher, Element::GreaterBlock),
@@ -90,7 +84,6 @@ fn _element<'b, 'g, 'r, 's>(
map(fixed_width_area_matcher, Element::FixedWidthArea), map(fixed_width_area_matcher, Element::FixedWidthArea),
map(horizontal_rule_matcher, Element::HorizontalRule), map(horizontal_rule_matcher, Element::HorizontalRule),
map(latex_environment_matcher, Element::LatexEnvironment), map(latex_environment_matcher, Element::LatexEnvironment),
map(keyword_matcher, Element::Keyword),
))(remaining) ))(remaining)
{ {
the_ok @ Ok(_) => the_ok, the_ok @ Ok(_) => the_ok,
@@ -100,12 +93,12 @@ fn _element<'b, 'g, 'r, 's>(
the_ok @ Ok(_) => the_ok, the_ok @ Ok(_) => the_ok,
Err(_) => { Err(_) => {
affiliated_keywords.clear(); affiliated_keywords.clear();
map(affiliated_keyword_matcher, Element::Keyword)(input) map(keyword_matcher, Element::Keyword)(input)
} }
} }
} else { } else {
affiliated_keywords.clear(); affiliated_keywords.clear();
map(affiliated_keyword_matcher, Element::Keyword)(input) map(keyword_matcher, Element::Keyword)(input)
} }
} }
}?; }?;
@@ -121,18 +114,17 @@ fn _element<'b, 'g, 'r, 's>(
pub const fn detect_element( pub const fn detect_element(
can_be_paragraph: bool, can_be_paragraph: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()> ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
{ move |context: Context, input: OrgSource<'_>| _detect_element(context, input, can_be_paragraph)
move |context, input: OrgSource<'_>| _detect_element(context, input, can_be_paragraph)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _detect_element<'b, 'g, 'r, 's>( fn _detect_element<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
if detect_plain_list(input).is_ok() { if detect_plain_list(context, input).is_ok() {
return Ok((input, ())); return Ok((input, ()));
} }
if _element(context, input, can_be_paragraph).is_ok() { if _element(context, input, can_be_paragraph).is_ok() {

View File

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

View File

@@ -8,36 +8,33 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use super::Context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::types::ExportSnippet; use crate::parser::ExportSnippet;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn export_snippet<'b, 'g, 'r, 's>( pub fn export_snippet<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExportSnippet<'s>> { ) -> Res<OrgSource<'s>, ExportSnippet<'s>> {
let (remaining, _) = tag("@@")(input)?; let (remaining, _) = tag("@@")(input)?;
let (remaining, backend_name) = backend(context, remaining)?; let (remaining, backend_name) = backend(context, remaining)?;
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context =
class: ExitClass::Gamma, context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
exit_matcher: &export_snippet_end, class: ExitClass::Gamma,
}); exit_matcher: &export_snippet_end,
let parser_context = context.with_additional_node(&parser_context); }));
let (remaining, backend_contents) = opt(tuple(( let (remaining, backend_contents) = opt(tuple((
tag(":"), tag(":"),
parser_with_context!(contents)(&parser_context), parser_with_context!(contents)(&parser_context),
)))(remaining)?; )))(remaining)?;
let (remaining, _) = tag("@@")(remaining)?; let (remaining, _) = tag("@@")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -50,8 +47,8 @@ pub fn export_snippet<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn backend<'b, 'g, 'r, 's>( fn backend<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, backend_name) = let (remaining, backend_name) =
@@ -61,8 +58,8 @@ fn backend<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn contents<'b, 'g, 'r, 's>( fn contents<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(verify( let (remaining, source) = recognize(verify(
@@ -73,8 +70,8 @@ fn contents<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn export_snippet_end<'b, 'g, 'r, 's>( fn export_snippet_end<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag("@@")(input) tag("@@")(input)

View File

@@ -12,17 +12,17 @@ use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use crate::context::parser_with_context; use super::Context;
use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
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;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::FixedWidthArea; use crate::parser::FixedWidthArea;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn fixed_width_area<'b, 'g, 'r, 's>( pub fn fixed_width_area<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FixedWidthArea<'s>> { ) -> Res<OrgSource<'s>, FixedWidthArea<'s>> {
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context); let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
@@ -41,8 +41,8 @@ pub fn fixed_width_area<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn fixed_width_area_line<'b, 'g, 'r, 's>( fn fixed_width_area_line<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;

View File

@@ -12,26 +12,26 @@ use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::WORD_CONSTITUENT_CHARACTERS; use super::util::WORD_CONSTITUENT_CHARACTERS;
use crate::context::parser_with_context; use super::Context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::greater_element::FootnoteDefinition;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
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;
use crate::parser::util::immediate_in_section; use crate::parser::util::immediate_in_section;
use crate::parser::util::maybe_consume_trailing_whitespace; use crate::parser::util::maybe_consume_trailing_whitespace;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::FootnoteDefinition;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn footnote_definition<'b, 'g, 'r, 's>( pub fn footnote_definition<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> { ) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> {
if immediate_in_section(context, "footnote definition") { if immediate_in_section(context, "footnote definition") {
@@ -43,17 +43,13 @@ pub fn footnote_definition<'b, 'g, 'r, 's>(
// Cannot be indented. // Cannot be indented.
let (remaining, (_lead_in, lbl, _lead_out, _ws)) = let (remaining, (_lead_in, lbl, _lead_out, _ws)) =
tuple((tag_no_case("[fn:"), label, tag("]"), space0))(input)?; tuple((tag_no_case("[fn:"), label, tag("]"), space0))(input)?;
let contexts = [ let parser_context = context
ContextElement::ConsumeTrailingWhitespace(true), .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
ContextElement::Context("footnote definition"), .with_additional_node(ContextElement::Context("footnote definition"))
ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Alpha,
exit_matcher: &footnote_definition_end, exit_matcher: &footnote_definition_end,
}), }));
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
// TODO: The problem is we are not accounting for trailing whitespace like we do in section. Maybe it would be easier if we passed down whether or not to parse trailing whitespace into the element matcher similar to how tag takes in parameters. // TODO: The problem is we are not accounting for trailing whitespace like we do in section. Maybe it would be easier if we passed down whether or not to parse trailing whitespace into the element matcher similar to how tag takes in parameters.
let element_matcher = parser_with_context!(element(true))(&parser_context); let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
@@ -80,14 +76,25 @@ pub fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_definition_end<'b, 'g, 'r, 's>( fn footnote_definition_end<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let allow_nesting_context =
context.with_additional_node(ContextElement::Context("allow nesting footnotes"));
let footnote_definition_matcher = parser_with_context!(footnote_definition)(
if immediate_in_section(context, "footnote definition") {
&allow_nesting_context
} else {
context
},
);
let maybe_consume_trailing_whitespace_matcher =
parser_with_context!(maybe_consume_trailing_whitespace)(context);
let (remaining, source) = alt(( let (remaining, source) = alt((
recognize(tuple(( recognize(tuple((
parser_with_context!(maybe_consume_trailing_whitespace)(context), maybe_consume_trailing_whitespace_matcher,
detect_footnote_definition, footnote_definition_matcher,
))), ))),
recognize(tuple(( recognize(tuple((
start_of_line, start_of_line,
@@ -100,19 +107,12 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
Ok((remaining, source)) Ok((remaining, source))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
Ok((input, ()))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::context::Context; use crate::parser::parser_context::ContextTree;
use crate::context::GlobalSettings; use crate::parser::parser_with_context::parser_with_context;
use crate::context::List; use crate::parser::Source;
use crate::types::Source;
#[test] #[test]
fn two_paragraphs() { fn two_paragraphs() {
@@ -123,9 +123,7 @@ mod tests {
line footnote.", line footnote.",
); );
let global_settings = GlobalSettings::default(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context); let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_footnote_definition) = let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition"); footnote_definition_matcher(input).expect("Parse first footnote_definition");
@@ -156,9 +154,7 @@ line footnote.
not in the footnote.", not in the footnote.",
); );
let global_settings = GlobalSettings::default(); let initial_context: ContextTree<'_, '_> = ContextTree::new();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context); let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_footnote_definition) = let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition"); footnote_definition_matcher(input).expect("Parse first footnote_definition");

View File

@@ -1,30 +1,29 @@
use nom::branch::alt; 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::space0;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use super::parser_context::ContextElement;
use crate::context::parser_with_context; use super::Context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::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::FootnoteReferenceDefinition;
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;
use crate::types::FootnoteReference; use crate::parser::FootnoteReference;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn footnote_reference<'b, 'g, 'r, 's>( pub fn footnote_reference<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> { ) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
alt(( alt((
@@ -35,17 +34,23 @@ pub fn footnote_reference<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn anonymous_footnote<'b, 'g, 'r, 's>( fn anonymous_footnote<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'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 = ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::FootnoteReferenceDefinition(
class: ExitClass::Beta, FootnoteReferenceDefinition {
exit_matcher: &exit_with_depth, position: remaining,
}); depth: 0,
let parser_context = context.with_additional_node(&parser_context); },
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &footnote_definition_end,
}));
// TODO: I could insert FootnoteReferenceDefinition entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
let (remaining, (children, _exit_contents)) = verify( 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),
@@ -55,8 +60,7 @@ fn anonymous_footnote<'b, 'g, 'r, 's>(
)(remaining)?; )(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, _) = space0(remaining)?;
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -69,19 +73,25 @@ fn anonymous_footnote<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inline_footnote<'b, 'g, 'r, 's>( fn inline_footnote<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'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 (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 = ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::FootnoteReferenceDefinition(
class: ExitClass::Beta, FootnoteReferenceDefinition {
exit_matcher: &exit_with_depth, position: remaining,
}); depth: 0,
let parser_context = context.with_additional_node(&parser_context); },
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &footnote_definition_end,
}));
// TODO: I could insert FootnoteReferenceDefinition entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
let (remaining, (children, _exit_contents)) = verify( 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),
@@ -91,8 +101,7 @@ fn inline_footnote<'b, 'g, 'r, 's>(
)(remaining)?; )(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, _) = space0(remaining)?;
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -105,15 +114,14 @@ fn inline_footnote<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_reference_only<'b, 'g, 'r, 's>( fn footnote_reference_only<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, _context: Context<'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 (remaining, label_contents) = label(remaining)?; let (remaining, label_contents) = label(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, _) = space0(remaining)?;
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -125,28 +133,47 @@ fn footnote_reference_only<'b, 'g, 'r, 's>(
)) ))
} }
fn footnote_definition_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
move |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<'b, 'g, 'r, 's>( fn footnote_definition_end<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> 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

@@ -12,29 +12,28 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::in_section; use super::Context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::greater_element::GreaterBlock;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::SetSource;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
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;
use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::Element; use crate::parser::Element;
use crate::types::GreaterBlock; use crate::parser::Paragraph;
use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn greater_block<'b, 'g, 'r, 's>( pub fn greater_block<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, GreaterBlock<'s>> { ) -> Res<OrgSource<'s>, GreaterBlock<'s>> {
// TODO: Do I need to differentiate between different greater block types. // TODO: Do I need to differentiate between different greater block types.
@@ -50,29 +49,25 @@ pub fn greater_block<'b, 'g, 'r, 's>(
}), }),
))(remaining)?; ))(remaining)?;
let context_name = match Into::<&str>::into(name).to_lowercase().as_str() { let context_name = match Into::<&str>::into(name).to_lowercase().as_str() {
"center" => "center block".to_owned(), "center" => "center block",
"quote" => "quote block".to_owned(), "quote" => "quote block",
name @ _ => format!("special block {}", name), _ => "greater block",
}; };
if in_section(context, context_name.as_str()) { if immediate_in_section(context, context_name) {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"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 contexts = [ let parser_context = context
ContextElement::ConsumeTrailingWhitespace(true), .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
ContextElement::Context(context_name.as_str()), .with_additional_node(ContextElement::Context(context_name))
ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::GreaterBlock(name.into()))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Alpha,
exit_matcher: &exit_with_name, exit_matcher: &greater_block_end,
}), }));
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -99,7 +94,7 @@ pub fn greater_block<'b, 'g, '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
@@ -125,24 +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<'c>(name: &'c str) -> impl ContextMatcher + 'c {
// TODO: Can this be done without making an owned copy?
move |context, input: OrgSource<'_>| _greater_block_end(context, input, name)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _greater_block_end<'b, 'g, 'r, 's, 'c>( fn greater_block_end<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
name: &'c 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

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

View File

@@ -9,14 +9,14 @@ use nom::multi::many1_count;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use crate::context::RefContext; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::HorizontalRule; use crate::parser::HorizontalRule;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn horizontal_rule<'b, 'g, 'r, 's>( pub fn horizontal_rule<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, HorizontalRule<'s>> { ) -> Res<OrgSource<'s>, HorizontalRule<'s>> {
start_of_line(input)?; start_of_line(input)?;

View File

@@ -1,69 +0,0 @@
use nom::branch::alt;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::combinator::map;
use nom::multi::many0;
use nom::multi::many_till;
use super::keyword::filtered_keyword;
use super::keyword_todo::todo_keywords;
use super::OrgSource;
use crate::error::Res;
use crate::types::Keyword;
use crate::GlobalSettings;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn scan_for_in_buffer_settings<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> {
// TODO: Optimization idea: since this is slicing the OrgSource at each character, it might be more efficient to do a parser that uses a search function like take_until, and wrap it in a function similar to consumed but returning the input along with the normal output, then pass all of that into a verify that confirms we were at the start of a line using the input we just returned.
let keywords = many0(map(
many_till(anychar, filtered_keyword(in_buffer_settings_key)),
|(_, kw)| kw,
))(input);
keywords
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn in_buffer_settings_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((
tag_no_case("archive"),
tag_no_case("category"),
tag_no_case("columns"),
tag_no_case("filetags"),
tag_no_case("link"),
tag_no_case("priorities"),
tag_no_case("property"),
tag_no_case("seq_todo"),
tag_no_case("setupfile"),
tag_no_case("startup"),
tag_no_case("tags"),
tag_no_case("todo"),
tag_no_case("typ_todo"),
))(input)
}
pub fn apply_in_buffer_settings<'g, 's, 'sf>(
keywords: Vec<Keyword<'sf>>,
original_settings: &'g GlobalSettings<'g, 's>,
) -> Result<GlobalSettings<'g, 's>, String> {
let mut new_settings = original_settings.clone();
for kw in keywords.iter().filter(|kw| {
kw.key.eq_ignore_ascii_case("todo")
|| kw.key.eq_ignore_ascii_case("seq_todo")
|| kw.key.eq_ignore_ascii_case("typ_todo")
}) {
let (_, (in_progress_words, complete_words)) =
todo_keywords(kw.value).map_err(|err| err.to_string())?;
new_settings
.in_progress_todo_keywords
.extend(in_progress_words.into_iter().map(str::to_string));
new_settings
.complete_todo_keywords
.extend(complete_words.into_iter().map(str::to_string));
}
Ok(new_settings)
}

View File

@@ -4,30 +4,27 @@ use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use super::Context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::BabelHeaderBracket;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::types::InlineBabelCall; use crate::parser::InlineBabelCall;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn inline_babel_call<'b, 'g, 'r, 's>( pub fn inline_babel_call<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, InlineBabelCall<'s>> { ) -> Res<OrgSource<'s>, InlineBabelCall<'s>> {
let (remaining, _) = tag_no_case("call_")(input)?; let (remaining, _) = tag_no_case("call_")(input)?;
@@ -35,8 +32,7 @@ pub fn inline_babel_call<'b, 'g, 'r, 's>(
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _argument) = argument(context, remaining)?; let (remaining, _argument) = argument(context, remaining)?;
let (remaining, _header2) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, _header2) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, _) = space0(remaining)?;
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -47,15 +43,15 @@ pub fn inline_babel_call<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'b, 'g, 'r, 's>( fn name<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context =
class: ExitClass::Gamma, context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
exit_matcher: &name_end, class: ExitClass::Gamma,
}); exit_matcher: &name_end,
let parser_context = context.with_additional_node(&parser_context); }));
let (remaining, name) = recognize(many_till( let (remaining, name) = recognize(many_till(
verify(anychar, |c| !(c.is_whitespace() || "[]()".contains(*c))), verify(anychar, |c| !(c.is_whitespace() || "[]()".contains(*c))),
parser_with_context!(exit_matcher_parser)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context),
@@ -64,26 +60,29 @@ fn name<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name_end<'b, 'g, 'r, 's>( fn name_end<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(one_of("[("))(input) recognize(one_of("[("))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn header<'b, 'g, 'r, 's>( fn header<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'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 = ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket {
class: ExitClass::Gamma, position: remaining,
exit_matcher: &exit_with_depth, depth: 0,
}); }))
let parser_context = context.with_additional_node(&parser_context); .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &header_end,
}));
let (remaining, name) = recognize(many_till( let (remaining, name) = recognize(many_till(
anychar, anychar,
@@ -93,43 +92,48 @@ fn header<'b, 'g, 'r, 's>(
Ok((remaining, name)) Ok((remaining, name))
} }
fn header_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
move |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<'b, 'g, 'r, 's>( fn header_end<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> 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 {
} '(' => {
if current_depth < 0 { current_depth += 1;
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header. }
unreachable!("Exceeded header bracket depth.") ')' if current_depth == 0 => {
panic!("Exceeded inline babel call header bracket depth.")
}
')' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
} }
alt((tag("]"), line_ending))(input) alt((tag("]"), line_ending))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn argument<'b, 'g, 'r, 's>( fn argument<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'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 = ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket {
class: ExitClass::Gamma, position: remaining,
exit_matcher: &exit_with_depth, depth: 0,
}); }))
let parser_context = context.with_additional_node(&parser_context); .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &argument_end,
}));
let (remaining, name) = recognize(many_till( let (remaining, name) = recognize(many_till(
anychar, anychar,
@@ -139,26 +143,39 @@ fn argument<'b, 'g, 'r, 's>(
Ok((remaining, name)) Ok((remaining, name))
} }
fn argument_end(starting_parenthesis_depth: BracketDepth) -> impl ContextMatcher {
move |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<'b, 'g, 'r, 's>( fn argument_end<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_parenthesis_depth: BracketDepth,
) -> 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 {
} '[' => {
if current_depth < 0 { current_depth += 1;
// This shouldn't be possible because if depth is 0 then a closing parenthesis should end the argument. }
unreachable!("Exceeded argument parenthesis depth.") ']' if current_depth == 0 => {
panic!("Exceeded inline babel call argument bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
} }
alt((tag(")"), line_ending))(input) alt((tag(")"), line_ending))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r BabelHeaderBracket<'s>> {
for node in context.iter() {
match node.get_data() {
ContextElement::BabelHeaderBracket(depth) => return Some(depth),
_ => {}
}
}
None
}

View File

@@ -1,9 +1,9 @@
use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -11,33 +11,29 @@ use nom::multi::many_till;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
use tracing::span; use tracing::span;
use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use super::Context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
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::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_context::InlineSourceBlockBracket;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::types::InlineSourceBlock; use crate::parser::InlineSourceBlock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn inline_source_block<'b, 'g, 'r, 's>( pub fn inline_source_block<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, InlineSourceBlock<'s>> { ) -> Res<OrgSource<'s>, InlineSourceBlock<'s>> {
let (remaining, _) = tag_no_case("src_")(input)?; let (remaining, _) = tag_no_case("src_")(input)?;
let (remaining, _) = lang(context, remaining)?; let (remaining, _) = lang(context, remaining)?;
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _body) = body(context, remaining)?; let (remaining, _body) = body(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, _) = space0(remaining)?;
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -48,15 +44,15 @@ pub fn inline_source_block<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn lang<'b, 'g, 'r, 's>( fn lang<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context =
class: ExitClass::Beta, context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
exit_matcher: &lang_end, class: ExitClass::Beta,
}); exit_matcher: &lang_end,
let parser_context = context.with_additional_node(&parser_context); }));
let (remaining, lang) = recognize(many_till( let (remaining, lang) = recognize(many_till(
verify(anychar, |c| !(c.is_whitespace() || "[{".contains(*c))), verify(anychar, |c| !(c.is_whitespace() || "[{".contains(*c))),
parser_with_context!(exit_matcher_parser)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context),
@@ -65,26 +61,31 @@ fn lang<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn lang_end<'b, 'g, 'r, 's>( fn lang_end<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(one_of("[{"))(input) recognize(one_of("[{"))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn header<'b, 'g, 'r, 's>( fn header<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'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 = ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::InlineSourceBlockBracket(
class: ExitClass::Beta, InlineSourceBlockBracket {
exit_matcher: &exit_with_depth, position: remaining,
}); depth: 0,
let parser_context = context.with_additional_node(&parser_context); },
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &header_end,
}));
let (remaining, header_contents) = recognize(many_till( let (remaining, header_contents) = recognize(many_till(
anychar, anychar,
@@ -94,43 +95,57 @@ fn header<'b, 'g, 'r, 's>(
Ok((remaining, header_contents)) Ok((remaining, header_contents))
} }
fn header_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
move |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<'b, 'g, 'r, 's>( fn header_end<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> 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 source block 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 source block header bracket depth.")
}
']' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
} }
if current_depth < 0 { if current_depth == 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header. let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
unreachable!("Exceeded header bracket depth.") if close_bracket.is_ok() {
return close_bracket;
}
} }
alt((tag("]"), line_ending))(input)
line_ending(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn body<'b, 'g, 'r, 's>( fn body<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'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 = ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::InlineSourceBlockBracket(
class: ExitClass::Beta, InlineSourceBlockBracket {
exit_matcher: &exit_with_depth, position: remaining,
}); depth: 0,
let parser_context = context.with_additional_node(&parser_context); },
))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &body_end,
}));
let (remaining, body_contents) = recognize(many_till( let (remaining, body_contents) = recognize(many_till(
anychar, anychar,
@@ -150,26 +165,60 @@ fn body<'b, 'g, 'r, 's>(
Ok((remaining, body_contents)) Ok((remaining, body_contents))
} }
fn body_end(starting_brace_depth: BracketDepth) -> impl ContextMatcher { #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
move |context, input: OrgSource<'_>| _body_end(context, input, starting_brace_depth) fn body_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside an inline source block body.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'{' => {
current_depth += 1;
}
'}' if current_depth == 0 => {
panic!("Exceeded inline source block body bracket depth.")
}
'}' if current_depth > 0 => {
current_depth -= 1;
}
_ => {}
}
}
{
#[cfg(feature = "tracing")]
let span = span!(
tracing::Level::DEBUG,
"inside end body",
remaining = Into::<&str>::into(input),
current_depth = current_depth
);
#[cfg(feature = "tracing")]
let _enter = span.enter();
if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("}")(input);
if close_bracket.is_ok() {
return close_bracket;
}
}
}
line_ending(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _body_end<'b, 'g, 'r, 's>( pub fn get_bracket_depth<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, ) -> Option<&'r InlineSourceBlockBracket<'s>> {
starting_brace_depth: BracketDepth, 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 { None
// 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)
} }

View File

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

View File

@@ -1,94 +0,0 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::take_till;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::eof;
use nom::combinator::opt;
use nom::combinator::verify;
use nom::multi::separated_list0;
use nom::sequence::tuple;
use crate::error::Res;
// ref https://orgmode.org/manual/Per_002dfile-keywords.html
// ref https://orgmode.org/manual/Workflow-states.html
// Case is significant.
/// Parses the text in the value of a #+TODO keyword.
///
/// Example input: "foo bar baz | lorem ipsum"
pub fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, Vec<&'s str>)> {
let (remaining, mut before_pipe_words) = separated_list0(space1, todo_keyword_word)(input)?;
let (remaining, after_pipe_words) = opt(tuple((
tuple((space0, tag("|"), space0)),
separated_list0(space1, todo_keyword_word),
)))(remaining)?;
let (remaining, _eol) = alt((line_ending, eof))(remaining)?;
if let Some((_pipe, after_pipe_words)) = after_pipe_words {
Ok((remaining, (before_pipe_words, after_pipe_words)))
} else if !before_pipe_words.is_empty() {
// If there was no pipe, then the last word becomes a completion state instead.
let mut after_pipe_words = Vec::with_capacity(1);
after_pipe_words.push(
before_pipe_words
.pop()
.expect("If-statement proves this is Some."),
);
Ok((remaining, (before_pipe_words, after_pipe_words)))
} else {
// No words founds
Ok((remaining, (Vec::new(), Vec::new())))
}
}
fn todo_keyword_word<'s>(input: &'s str) -> Res<&'s str, &'s str> {
verify(take_till(|c| " \t\r\n|".contains(c)), |result: &str| {
!result.is_empty()
})(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn before_and_after() -> Result<(), Box<dyn std::error::Error>> {
let input = "foo bar baz | lorem ipsum";
let (remaining, (before_pipe_words, after_pipe_words)) = todo_keywords(input)?;
assert_eq!(remaining, "");
assert_eq!(before_pipe_words, vec!["foo", "bar", "baz"]);
assert_eq!(after_pipe_words, vec!["lorem", "ipsum"]);
Ok(())
}
#[test]
fn no_pipe() -> Result<(), Box<dyn std::error::Error>> {
let input = "foo bar baz";
let (remaining, (before_pipe_words, after_pipe_words)) = todo_keywords(input)?;
assert_eq!(remaining, "");
assert_eq!(before_pipe_words, vec!["foo", "bar"]);
assert_eq!(after_pipe_words, vec!["baz"]);
Ok(())
}
#[test]
fn early_pipe() -> Result<(), Box<dyn std::error::Error>> {
let input = "| foo bar baz";
let (remaining, (before_pipe_words, after_pipe_words)) = todo_keywords(input)?;
assert_eq!(remaining, "");
assert_eq!(before_pipe_words, Vec::<&str>::new());
assert_eq!(after_pipe_words, vec!["foo", "bar", "baz"]);
Ok(())
}
#[test]
fn late_pipe() -> Result<(), Box<dyn std::error::Error>> {
let input = "foo bar baz |";
let (remaining, (before_pipe_words, after_pipe_words)) = todo_keywords(input)?;
assert_eq!(remaining, "");
assert_eq!(before_pipe_words, vec!["foo", "bar", "baz"]);
assert_eq!(after_pipe_words, Vec::<&str>::new());
Ok(())
}
}

View File

@@ -13,20 +13,19 @@ use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_consumed; use super::util::get_consumed;
use crate::context::parser_with_context; use super::Context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::LatexEnvironment; use crate::parser::LatexEnvironment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn latex_environment<'b, 'g, 'r, 's>( pub fn latex_environment<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, LatexEnvironment<'s>> { ) -> Res<OrgSource<'s>, LatexEnvironment<'s>> {
start_of_line(input)?; start_of_line(input)?;
@@ -40,13 +39,13 @@ pub fn latex_environment<'b, 'g, 'r, 's>(
))(remaining)?; ))(remaining)?;
let latex_environment_end_specialized = latex_environment_end(name.into()); let latex_environment_end_specialized = latex_environment_end(name.into());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context =
class: ExitClass::Beta, context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
exit_matcher: &latex_environment_end_specialized, class: ExitClass::Beta,
}); exit_matcher: &latex_environment_end_specialized,
let parser_context = context.with_additional_node(&parser_context); }));
let (remaining, _contents) = contents(&latex_environment_end_specialized)(context, remaining)?; let (remaining, _contents) = contents(&latex_environment_end_specialized, context, remaining)?;
let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?; let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -63,42 +62,44 @@ fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
take_while1(|c: char| c.is_alphanumeric() || c == '*')(input) take_while1(|c: char| c.is_alphanumeric() || c == '*')(input)
} }
fn contents<F: ContextMatcher>(end_matcher: F) -> impl ContextMatcher {
move |context, input| _contents(&end_matcher, context, input)
}
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(end_matcher)) tracing::instrument(ret, level = "debug", skip(end_matcher))
)] )]
fn _contents<'b, 'g, 'r, 's, F: ContextMatcher>( pub fn contents<
'r,
's,
F: Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>,
>(
end_matcher: F, end_matcher: F,
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(many_till( let (remaining, source) = recognize(many_till(
anychar, anychar,
peek(alt(( peek(alt((
parser_with_context!(exit_matcher_parser)(context), parser_with_context!(exit_matcher_parser)(context),
parser_with_context!(&end_matcher)(context), parser_with_context!(end_matcher)(context),
))), ))),
))(input)?; ))(input)?;
Ok((remaining, source)) Ok((remaining, source))
} }
fn latex_environment_end(current_name: &str) -> impl ContextMatcher { fn latex_environment_end(
current_name: &str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_name_lower = current_name.to_lowercase(); let current_name_lower = current_name.to_lowercase();
move |context, input: OrgSource<'_>| { move |context: Context, input: OrgSource<'_>| {
_latex_environment_end(context, input, current_name_lower.as_str()) _latex_environment_end(context, input, current_name_lower.as_str())
} }
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _latex_environment_end<'b, 'g, 'r, 's, 'c>( fn _latex_environment_end<'r, 's, 'x>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
current_name_lower: &'c str, current_name_lower: &'x str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;

View File

@@ -5,6 +5,7 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::none_of; use nom::character::complete::none_of;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -13,19 +14,18 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use super::Context;
use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::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;
use crate::types::LatexFragment; use crate::parser::LatexFragment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn latex_fragment<'b, 'g, 'r, 's>( pub fn latex_fragment<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, LatexFragment<'s>> { ) -> Res<OrgSource<'s>, LatexFragment<'s>> {
let (remaining, _) = alt(( let (remaining, _) = alt((
@@ -36,8 +36,7 @@ pub fn latex_fragment<'b, 'g, 'r, 's>(
parser_with_context!(dollar_char_fragment)(context), parser_with_context!(dollar_char_fragment)(context),
parser_with_context!(bordered_dollar_fragment)(context), parser_with_context!(bordered_dollar_fragment)(context),
))(input)?; ))(input)?;
let (remaining, _trailing_whitespace) = let (remaining, _) = space0(remaining)?;
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -48,8 +47,8 @@ pub fn latex_fragment<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn raw_latex_fragment<'b, 'g, 'r, 's>( fn raw_latex_fragment<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("\\")(input)?; let (remaining, _) = tag("\\")(input)?;
@@ -61,16 +60,16 @@ fn raw_latex_fragment<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'b, 'g, 'r, 's>( fn name<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
alpha1(input) alpha1(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn brackets<'b, 'g, 'r, 's>( fn brackets<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, body) = alt(( let (remaining, body) = alt((
@@ -101,8 +100,8 @@ fn brackets<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn escaped_parenthesis_fragment<'b, 'g, 'r, 's>( fn escaped_parenthesis_fragment<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("\\(")(input)?; let (remaining, _) = tag("\\(")(input)?;
@@ -120,8 +119,8 @@ fn escaped_parenthesis_fragment<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn escaped_bracket_fragment<'b, 'g, 'r, 's>( fn escaped_bracket_fragment<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("\\[")(input)?; let (remaining, _) = tag("\\[")(input)?;
@@ -139,8 +138,8 @@ fn escaped_bracket_fragment<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn double_dollar_fragment<'b, 'g, 'r, 's>( fn double_dollar_fragment<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: The documentation on the dollar sign versions is incomplete. Test to figure out what the real requirements are. For example, can this span more than 3 lines and can this contain a single $ since its terminated by $$? // TODO: The documentation on the dollar sign versions is incomplete. Test to figure out what the real requirements are. For example, can this span more than 3 lines and can this contain a single $ since its terminated by $$?
@@ -159,8 +158,8 @@ fn double_dollar_fragment<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dollar_char_fragment<'b, 'g, 'r, 's>( fn dollar_char_fragment<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (_, _) = pre(context, input)?; let (_, _) = pre(context, input)?;
@@ -174,10 +173,7 @@ fn dollar_char_fragment<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn pre<'b, 'g, 'r, 's>( pub fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let preceding_character = input.get_preceding_character(); let preceding_character = input.get_preceding_character();
if let Some('$') = preceding_character { if let Some('$') = preceding_character {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
@@ -188,10 +184,7 @@ pub fn pre<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn post<'b, 'g, 'r, 's>( pub fn post<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
// TODO: What about eof? Test to find out. // TODO: What about eof? Test to find out.
// TODO: Figure out which punctuation characters should be included. // TODO: Figure out which punctuation characters should be included.
@@ -200,8 +193,8 @@ pub fn post<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn bordered_dollar_fragment<'b, 'g, 'r, 's>( fn bordered_dollar_fragment<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (_, _) = pre(context, input)?; let (_, _) = pre(context, input)?;
@@ -209,13 +202,17 @@ fn bordered_dollar_fragment<'b, 'g, '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
anychar, let (remaining, _) = verify(
peek(alt(( recognize(many_till(
parser_with_context!(exit_matcher_parser)(context), anychar,
tag("$"), peek(alt((
))), parser_with_context!(exit_matcher_parser)(context),
))(remaining)?; tag("$"),
))),
)),
|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)?;
@@ -226,16 +223,16 @@ fn bordered_dollar_fragment<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn open_border<'b, 'g, 'r, 's>( pub fn open_border<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(verify(none_of(".,;$"), |c| !c.is_whitespace()))(input) recognize(verify(none_of(".,;$"), |c| !c.is_whitespace()))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn close_border<'b, 'g, 'r, 's>( pub fn close_border<'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
let preceding_character = input.get_preceding_character(); let preceding_character = input.get_preceding_character();

View File

@@ -12,47 +12,42 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use crate::context::parser_with_context; use super::Context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::lesser_element::CommentBlock;
use crate::parser::lesser_element::ExampleBlock;
use crate::parser::lesser_element::ExportBlock;
use crate::parser::lesser_element::SrcBlock;
use crate::parser::lesser_element::VerseBlock;
use crate::parser::object::Object;
use crate::parser::object::PlainText;
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::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
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;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::util::text_until_exit; use crate::parser::util::text_until_exit;
use crate::types::CommentBlock;
use crate::types::ExampleBlock;
use crate::types::ExportBlock;
use crate::types::Object;
use crate::types::PlainText;
use crate::types::SrcBlock;
use crate::types::VerseBlock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn verse_block<'b, 'g, 'r, 's>( pub fn verse_block<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, VerseBlock<'s>> { ) -> Res<OrgSource<'s>, VerseBlock<'s>> {
let (remaining, name) = lesser_block_begin("verse")(context, input)?; let (remaining, name) = lesser_block_begin("verse")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = line_ending(remaining)?;
let lesser_block_end_specialized = lesser_block_end("verse"); let lesser_block_end_specialized = lesser_block_end("verse");
let contexts = [ let parser_context = context
ContextElement::ConsumeTrailingWhitespace(true), .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
ContextElement::Context("lesser block"), .with_additional_node(ContextElement::Context("lesser block"))
ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Beta,
exit_matcher: &lesser_block_end_specialized, exit_matcher: &lesser_block_end_specialized,
}), }));
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -89,25 +84,21 @@ pub fn verse_block<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn comment_block<'b, 'g, 'r, 's>( pub fn comment_block<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, CommentBlock<'s>> { ) -> Res<OrgSource<'s>, CommentBlock<'s>> {
let (remaining, name) = lesser_block_begin("comment")(context, input)?; let (remaining, name) = lesser_block_begin("comment")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = line_ending(remaining)?;
let lesser_block_end_specialized = lesser_block_end("comment"); let lesser_block_end_specialized = lesser_block_end("comment");
let contexts = [ let parser_context = context
ContextElement::ConsumeTrailingWhitespace(true), .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
ContextElement::Context("lesser block"), .with_additional_node(ContextElement::Context("lesser block"))
ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Beta,
exit_matcher: &lesser_block_end_specialized, exit_matcher: &lesser_block_end_specialized,
}), }));
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -129,25 +120,21 @@ pub fn comment_block<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn example_block<'b, 'g, 'r, 's>( pub fn example_block<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExampleBlock<'s>> { ) -> Res<OrgSource<'s>, ExampleBlock<'s>> {
let (remaining, _name) = lesser_block_begin("example")(context, input)?; let (remaining, _name) = lesser_block_begin("example")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = line_ending(remaining)?;
let lesser_block_end_specialized = lesser_block_end("example"); let lesser_block_end_specialized = lesser_block_end("example");
let contexts = [ let parser_context = context
ContextElement::ConsumeTrailingWhitespace(true), .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
ContextElement::Context("lesser block"), .with_additional_node(ContextElement::Context("lesser block"))
ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Beta,
exit_matcher: &lesser_block_end_specialized, exit_matcher: &lesser_block_end_specialized,
}), }));
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -169,8 +156,8 @@ pub fn example_block<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn export_block<'b, 'g, 'r, 's>( pub fn export_block<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExportBlock<'s>> { ) -> Res<OrgSource<'s>, ExportBlock<'s>> {
let (remaining, name) = lesser_block_begin("export")(context, input)?; let (remaining, name) = lesser_block_begin("export")(context, input)?;
@@ -178,17 +165,13 @@ pub fn export_block<'b, 'g, 'r, 's>(
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = line_ending(remaining)?;
let lesser_block_end_specialized = lesser_block_end("export"); let lesser_block_end_specialized = lesser_block_end("export");
let contexts = [ let parser_context = context
ContextElement::ConsumeTrailingWhitespace(true), .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
ContextElement::Context("lesser block"), .with_additional_node(ContextElement::Context("lesser block"))
ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Beta,
exit_matcher: &lesser_block_end_specialized, exit_matcher: &lesser_block_end_specialized,
}), }));
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -210,8 +193,8 @@ pub fn export_block<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn src_block<'b, 'g, 'r, 's>( pub fn src_block<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, SrcBlock<'s>> { ) -> Res<OrgSource<'s>, SrcBlock<'s>> {
let (remaining, name) = lesser_block_begin("src")(context, input)?; let (remaining, name) = lesser_block_begin("src")(context, input)?;
@@ -219,17 +202,13 @@ pub fn src_block<'b, 'g, 'r, 's>(
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = line_ending(remaining)?;
let lesser_block_end_specialized = lesser_block_end("src"); let lesser_block_end_specialized = lesser_block_end("src");
let contexts = [ let parser_context = context
ContextElement::ConsumeTrailingWhitespace(true), .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
ContextElement::Context("lesser block"), .with_additional_node(ContextElement::Context("lesser block"))
ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Alpha,
exit_matcher: &lesser_block_end_specialized, exit_matcher: &lesser_block_end_specialized,
}), }));
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -260,18 +239,20 @@ fn data<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not("\r\n")(input) is_not("\r\n")(input)
} }
fn lesser_block_end(current_name: &str) -> impl ContextMatcher { fn lesser_block_end(
current_name: &str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_name_lower = current_name.to_lowercase(); let current_name_lower = current_name.to_lowercase();
move |context, input: OrgSource<'_>| { move |context: Context, input: OrgSource<'_>| {
_lesser_block_end(context, input, current_name_lower.as_str()) _lesser_block_end(context, input, current_name_lower.as_str())
} }
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _lesser_block_end<'b, 'g, 'r, 's, 'c>( fn _lesser_block_end<'r, 's, 'x>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
current_name_lower: &'c str, current_name_lower: &'x str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
@@ -287,16 +268,18 @@ fn _lesser_block_end<'b, 'g, 'r, 's, 'c>(
/// Parser for the beginning of a lesser block /// Parser for the beginning of a lesser block
/// ///
/// current_name MUST be lowercase. We do not do the conversion ourselves because it is not allowed in a const fn. /// current_name MUST be lowercase. We do not do the conversion ourselves because it is not allowed in a const fn.
const fn lesser_block_begin<'c>(current_name: &'c str) -> impl ContextMatcher + 'c { const fn lesser_block_begin(
current_name: &'static str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: Since this is a const fn, is there ANY way to "generate" functions at compile time? // TODO: Since this is a const fn, is there ANY way to "generate" functions at compile time?
move |context, input: OrgSource<'_>| _lesser_block_begin(context, input, current_name) move |context: Context, input: OrgSource<'_>| _lesser_block_begin(context, input, current_name)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _lesser_block_begin<'b, 'g, 'r, 's, 'c>( fn _lesser_block_begin<'r, 's, 'x>(
_context: RefContext<'b, 'g, 'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
current_name_lower: &'c str, current_name_lower: &'x str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;

View File

@@ -1,6 +1,6 @@
use super::object::Object; use super::object::Object;
use super::source::Source;
use super::PlainText; use super::PlainText;
use super::Source;
#[derive(Debug)] #[derive(Debug)]
pub struct Paragraph<'s> { pub struct Paragraph<'s> {
@@ -87,8 +87,6 @@ pub struct HorizontalRule<'s> {
#[derive(Debug)] #[derive(Debug)]
pub struct Keyword<'s> { pub struct Keyword<'s> {
pub source: &'s str, pub source: &'s str,
pub key: &'s str,
pub value: &'s str,
} }
#[derive(Debug)] #[derive(Debug)]

View File

@@ -5,16 +5,16 @@ use nom::combinator::recognize;
use nom::multi::many0; use nom::multi::many0;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use crate::context::RefContext; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::types::LineBreak; use crate::parser::LineBreak;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn line_break<'b, 'g, 'r, 's>( pub fn line_break<'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, LineBreak<'s>> { ) -> Res<OrgSource<'s>, LineBreak<'s>> {
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
@@ -31,10 +31,7 @@ pub fn line_break<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn pre<'b, 'g, 'r, 's>( fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let preceding_character = input.get_preceding_character(); let preceding_character = input.get_preceding_character();
match preceding_character { match preceding_character {
// If None, we are at the start of the file // If None, we are at the start of the file

183
src/parser/list.rs Normal file
View File

@@ -0,0 +1,183 @@
use std::fmt::Debug;
use std::rc::Rc;
#[derive(Debug)]
pub struct List<T> {
head: Option<Rc<Node<T>>>,
}
impl<T> Clone for List<T> {
fn clone(&self) -> Self {
List {
head: self.head.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct Node<T> {
data: T,
parent: Option<Rc<Node<T>>>,
}
impl<T> Node<T> {
pub fn get_data(&self) -> &T {
&self.data
}
}
impl<T> List<T> {
pub fn new() -> Self {
List { head: None }
}
pub fn branch_from(trunk: &Rc<Node<T>>) -> Self {
List {
head: Some(trunk.clone()),
}
}
pub fn push_front(&self, data: T) -> List<T> {
List {
head: Some(Rc::new(Node {
data: data,
parent: self.head.clone(),
})),
}
}
pub fn pop_front(&mut self) -> (Option<T>, List<T>) {
match self.head.take() {
None => (None, List::new()),
Some(popped_node) => {
let extracted_node = match Rc::try_unwrap(popped_node) {
Ok(node) => node,
Err(_) => panic!("try_unwrap failed on Rc in pop_front on List."),
};
(
Some(extracted_node.data),
List {
head: extracted_node.parent,
},
)
}
}
}
#[allow(dead_code)]
pub fn without_front(&self) -> List<T> {
List {
head: self.head.as_ref().map(|node| node.parent.clone()).flatten(),
}
}
#[allow(dead_code)]
pub fn get_data(&self) -> Option<&T> {
self.head.as_ref().map(|rc_node| &rc_node.data)
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.head.is_none()
}
pub fn ptr_eq(&self, other: &List<T>) -> bool {
match (self.head.as_ref(), other.head.as_ref()) {
(None, None) => true,
(None, Some(_)) | (Some(_), None) => false,
(Some(me), Some(them)) => Rc::ptr_eq(me, them),
}
}
pub fn iter(&self) -> impl Iterator<Item = &Rc<Node<T>>> {
NodeIter {
position: &self.head,
}
}
#[allow(dead_code)]
pub fn iter_until<'a>(&'a self, other: &'a List<T>) -> impl Iterator<Item = &Rc<Node<T>>> {
NodeIterUntil {
position: &self.head,
stop: &other.head,
}
}
#[allow(dead_code)]
pub fn into_iter_until<'a>(self, other: &'a List<T>) -> impl Iterator<Item = T> + 'a {
NodeIntoIterUntil {
position: self,
stop: &other,
}
}
}
pub struct NodeIter<'a, T> {
position: &'a Option<Rc<Node<T>>>,
}
impl<'a, T> Iterator for NodeIter<'a, T> {
type Item = &'a Rc<Node<T>>;
fn next(&mut self) -> Option<Self::Item> {
let (return_value, next_position) = match &self.position {
None => return None,
Some(rc_node) => {
let next_position = &rc_node.parent;
let return_value = rc_node;
(return_value, next_position)
}
};
self.position = next_position;
Some(return_value)
}
}
pub struct NodeIterUntil<'a, T> {
position: &'a Option<Rc<Node<T>>>,
stop: &'a Option<Rc<Node<T>>>,
}
impl<'a, T> Iterator for NodeIterUntil<'a, T> {
type Item = &'a Rc<Node<T>>;
fn next(&mut self) -> Option<Self::Item> {
match (self.position, self.stop) {
(_, None) => {}
(None, _) => {}
(Some(this_rc), Some(stop_rc)) => {
if Rc::ptr_eq(this_rc, stop_rc) {
return None;
}
}
};
let (return_value, next_position) = match &self.position {
None => return None,
Some(rc_node) => {
let next_position = &rc_node.parent;
let return_value = rc_node;
(return_value, next_position)
}
};
self.position = next_position;
Some(return_value)
}
}
pub struct NodeIntoIterUntil<'a, T> {
position: List<T>,
stop: &'a List<T>,
}
impl<'a, T> Iterator for NodeIntoIterUntil<'a, T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if self.position.ptr_eq(self.stop) {
return None;
}
let (popped_element, new_position) = self.position.pop_front();
self.position = new_position;
popped_element
}
}

View File

@@ -7,27 +7,33 @@ mod diary_sexp;
mod document; mod document;
mod drawer; mod drawer;
mod dynamic_block; mod dynamic_block;
mod element;
mod element_parser; mod element_parser;
mod entity; mod entity;
mod exiting;
mod export_snippet; mod export_snippet;
mod fixed_width_area; mod fixed_width_area;
mod footnote_definition; mod footnote_definition;
mod footnote_reference; mod footnote_reference;
mod greater_block; mod greater_block;
mod greater_element;
mod horizontal_rule; mod horizontal_rule;
mod in_buffer_settings;
mod inline_babel_call; mod inline_babel_call;
mod inline_source_block; mod inline_source_block;
mod keyword; mod keyword;
mod keyword_todo;
mod latex_environment; mod latex_environment;
mod latex_fragment; mod latex_fragment;
mod lesser_block; mod lesser_block;
mod lesser_element;
mod line_break; mod line_break;
mod list;
mod object;
mod object_parser; mod object_parser;
mod org_macro; mod org_macro;
mod org_source; mod org_source;
mod paragraph; mod paragraph;
mod parser_context;
mod parser_with_context;
mod plain_link; mod plain_link;
mod plain_list; mod plain_list;
mod plain_text; mod plain_text;
@@ -36,6 +42,7 @@ mod property_drawer;
mod radio_link; mod radio_link;
mod regular_link; mod regular_link;
pub mod sexp; pub mod sexp;
mod source;
mod statistics_cookie; mod statistics_cookie;
mod subscript_and_superscript; mod subscript_and_superscript;
mod table; mod table;
@@ -45,6 +52,62 @@ mod timestamp;
mod token; mod token;
mod util; mod util;
pub use document::document; pub use document::document;
pub use document::parse; pub use document::Document;
pub use document::parse_with_settings; pub use document::DocumentElement;
pub use org_source::OrgSource; pub use document::Heading;
pub use document::Section;
pub use element::Element;
pub use greater_element::Drawer;
pub use greater_element::DynamicBlock;
pub use greater_element::FootnoteDefinition;
pub use greater_element::GreaterBlock;
pub use greater_element::PlainList;
pub use greater_element::PlainListItem;
pub use greater_element::PropertyDrawer;
pub use greater_element::Table;
pub use greater_element::TableRow;
pub use lesser_element::Clock;
pub use lesser_element::Comment;
pub use lesser_element::CommentBlock;
pub use lesser_element::DiarySexp;
pub use lesser_element::ExampleBlock;
pub use lesser_element::ExportBlock;
pub use lesser_element::FixedWidthArea;
pub use lesser_element::HorizontalRule;
pub use lesser_element::Keyword;
pub use lesser_element::LatexEnvironment;
pub use lesser_element::Paragraph;
pub use lesser_element::Planning;
pub use lesser_element::SrcBlock;
pub use lesser_element::TableCell;
pub use lesser_element::VerseBlock;
pub use object::AngleLink;
pub use object::Bold;
pub use object::Citation;
pub use object::CitationReference;
pub use object::Code;
pub use object::Entity;
pub use object::ExportSnippet;
pub use object::FootnoteReference;
pub use object::InlineBabelCall;
pub use object::InlineSourceBlock;
pub use object::Italic;
pub use object::LatexFragment;
pub use object::LineBreak;
pub use object::Object;
pub use object::OrgMacro;
pub use object::PlainLink;
pub use object::PlainText;
pub use object::RadioLink;
pub use object::RadioTarget;
pub use object::RegularLink;
pub use object::StatisticsCookie;
pub use object::StrikeThrough;
pub use object::Subscript;
pub use object::Superscript;
pub use object::Target;
pub use object::Timestamp;
pub use object::Underline;
pub use object::Verbatim;
pub use source::Source;
type Context<'r, 's> = &'r parser_context::ContextTree<'r, 's>;

View File

@@ -1,4 +1,4 @@
use super::Source; use super::source::Source;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Object<'s> { pub enum Object<'s> {
@@ -374,9 +374,3 @@ impl<'s> Source<'s> for Timestamp<'s> {
self.source self.source
} }
} }
impl<'s> Source<'s> for PlainText<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}

Some files were not shown because too many files have changed in this diff Show More