Compare commits
111 Commits
e4c6ca2880
...
v0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06b83f9156 | ||
|
|
ef31900b51 | ||
|
|
8a221e0e0d | ||
|
|
f359676e28 | ||
|
|
7d7446d843 | ||
|
|
17e523b74c | ||
|
|
ece8fcd0c4 | ||
|
|
1a5b7ca30c | ||
|
|
b5a029e2bf | ||
|
|
c5f81298ba | ||
|
|
529418a9d1 | ||
|
|
d4a3628481 | ||
|
|
70f2747291 | ||
|
|
49d5a4e4b5 | ||
|
|
fa5fc41121 | ||
|
|
73e15286dc | ||
|
|
efa56a6bef | ||
|
|
3b11d8fb61 | ||
|
|
63fcad2ac6 | ||
|
|
23d587c699 | ||
|
|
f717d5e7df | ||
|
|
6d4379d029 | ||
|
|
8c00ee24ba | ||
|
|
4a565601c1 | ||
|
|
993c73dc9f | ||
|
|
7d73ac4bf6 | ||
|
|
b8b2e33137 | ||
|
|
c73e26e2d6 | ||
|
|
abb0aeacaf | ||
|
|
cb8775f2f4 | ||
|
|
caa6c41798 | ||
|
|
e54218c0d7 | ||
|
|
d60cad07e0 | ||
|
|
537fc00fd3 | ||
|
|
5c1d913c99 | ||
|
|
a1f3e9ea47 | ||
|
|
4d114206ef | ||
|
|
6b82214ec3 | ||
|
|
fb83e8d453 | ||
|
|
80039aa605 | ||
|
|
0b41e12424 | ||
|
|
e8979513aa | ||
|
|
e0d2bb8213 | ||
|
|
b323a407c4 | ||
|
|
45b01012b3 | ||
|
|
2773b35438 | ||
|
|
eef2944307 | ||
|
|
1e2ea17a9c | ||
|
|
7ce9dafd96 | ||
|
|
d678391789 | ||
|
|
640a9375bc | ||
|
|
1a8bf01fba | ||
|
|
4ad297f58a | ||
|
|
6b47a6c6c3 | ||
|
|
e24b413cd0 | ||
|
|
339ff5cd26 | ||
|
|
d5c611674e | ||
|
|
4e791b175e | ||
|
|
7611deb1ff | ||
|
|
b850f59640 | ||
|
|
a36a820e84 | ||
|
|
6822069c2f | ||
|
|
5fb66a586d | ||
|
|
9c2eb3b122 | ||
|
|
c1a99a03f8 | ||
|
|
8cdca061f8 | ||
|
|
d3c265415c | ||
|
|
95e033a99b | ||
|
|
1fb8ce9af6 | ||
|
|
eb03342506 | ||
|
|
8be47c551d | ||
|
|
3328c94a21 | ||
|
|
5e73ca74d5 | ||
|
|
0d6f6288c9 | ||
|
|
a817eefce7 | ||
|
|
4f5c40cd4b | ||
|
|
faf81d0143 | ||
|
|
c79b8c7833 | ||
|
|
5ad8fdf4b2 | ||
|
|
3ab0dd4531 | ||
|
|
11e76814f4 | ||
|
|
d46b3b9a58 | ||
|
|
754e9ff7d7 | ||
|
|
21f46d09e6 | ||
|
|
2966b91a09 | ||
|
|
a596dcff39 | ||
|
|
322a10dc38 | ||
|
|
87c378bc4e | ||
|
|
b6e268bdbb | ||
|
|
bab9232ebc | ||
|
|
c9ee61eae9 | ||
|
|
0cd398c30c | ||
|
|
bf8bd5bcbd | ||
|
|
b3c638428b | ||
|
|
db0ea7394e | ||
|
|
97f956d0bf | ||
|
|
167ffa650c | ||
|
|
27d863b875 | ||
|
|
e608b73d1a | ||
|
|
b27f911ff3 | ||
|
|
08e6efe5f5 | ||
|
|
0e73b83bf3 | ||
|
|
793e560bd5 | ||
|
|
0073af19e2 | ||
|
|
76187a0cb9 | ||
|
|
688779ba40 | ||
|
|
bd04451d58 | ||
|
|
ef2c351696 | ||
|
|
cdd3517655 | ||
|
|
7ca8beac5a | ||
|
|
4ba1e63dde |
@@ -196,7 +196,7 @@ spec:
|
|||||||
subPath: rust-source
|
subPath: rust-source
|
||||||
- name: cargo-cache
|
- name: cargo-cache
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: cargo-cache
|
claimName: organic-cargo-cache-test
|
||||||
- name: docker-credentials
|
- name: docker-credentials
|
||||||
secret:
|
secret:
|
||||||
secretName: harbor-plain
|
secretName: harbor-plain
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ 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
|
- name: rustfmt-command
|
||||||
type: array
|
type: array
|
||||||
description: Command to run.
|
description: Command to run rustfmt.
|
||||||
default: []
|
default: []
|
||||||
- name: args
|
- name: rustfmt-args
|
||||||
type: array
|
type: array
|
||||||
description: Arguments passed to command.
|
description: Arguments passed to rustfmt.
|
||||||
default: []
|
default: []
|
||||||
- name: GIT_USER_NAME
|
- name: GIT_USER_NAME
|
||||||
description: The username for git
|
description: The username for git
|
||||||
@@ -119,7 +119,7 @@ spec:
|
|||||||
runAfter:
|
runAfter:
|
||||||
- fetch-repository
|
- fetch-repository
|
||||||
- detect-tag
|
- detect-tag
|
||||||
- name: run-image
|
- name: rustfmt
|
||||||
taskRef:
|
taskRef:
|
||||||
name: run-docker-image
|
name: run-docker-image
|
||||||
workspaces:
|
workspaces:
|
||||||
@@ -129,9 +129,26 @@ spec:
|
|||||||
- build-image
|
- build-image
|
||||||
params:
|
params:
|
||||||
- name: command
|
- name: command
|
||||||
value: ["$(params.command[*])"]
|
value: ["$(params.rustfmt-command[*])"]
|
||||||
- name: args
|
- name: args
|
||||||
value: ["$(params.args[*])"]
|
value: ["$(params.rustfmt-args[*])"]
|
||||||
|
- name: docker-image
|
||||||
|
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||||
|
- name: cargo-fix
|
||||||
|
taskRef:
|
||||||
|
name: run-docker-image
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
workspace: git-source
|
||||||
|
- name: cargo-cache
|
||||||
|
workspace: cargo-cache
|
||||||
|
runAfter:
|
||||||
|
- rustfmt
|
||||||
|
params:
|
||||||
|
- name: command
|
||||||
|
value: ["cargo", "fix"]
|
||||||
|
- name: args
|
||||||
|
value: ["--allow-dirty"]
|
||||||
- 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: commit-changes
|
- name: commit-changes
|
||||||
@@ -148,7 +165,7 @@ spec:
|
|||||||
git config --global --add safe.directory /workspace/source
|
git config --global --add safe.directory /workspace/source
|
||||||
git_status=$(git status --porcelain)
|
git_status=$(git status --porcelain)
|
||||||
if [ -n "$git_status" ]; then
|
if [ -n "$git_status" ]; then
|
||||||
git commit -a -m "CI: format rust code."
|
git commit -a -m "CI: autofix rust code."
|
||||||
git push origin HEAD:$(params.PULL_BASE_REF)
|
git push origin HEAD:$(params.PULL_BASE_REF)
|
||||||
else
|
else
|
||||||
echo "No changes to commit."
|
echo "No changes to commit."
|
||||||
@@ -157,7 +174,7 @@ spec:
|
|||||||
- name: source
|
- name: source
|
||||||
workspace: git-source
|
workspace: git-source
|
||||||
runAfter:
|
runAfter:
|
||||||
- run-image
|
- cargo-fix
|
||||||
finally:
|
finally:
|
||||||
- name: report-success
|
- name: report-success
|
||||||
when:
|
when:
|
||||||
@@ -217,6 +234,9 @@ spec:
|
|||||||
requests:
|
requests:
|
||||||
storage: 10Gi
|
storage: 10Gi
|
||||||
subPath: rust-source
|
subPath: rust-source
|
||||||
|
- name: cargo-cache
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: organic-cargo-cache-fmt
|
||||||
- name: docker-credentials
|
- name: docker-credentials
|
||||||
secret:
|
secret:
|
||||||
secretName: harbor-plain
|
secretName: harbor-plain
|
||||||
|
|||||||
@@ -2,31 +2,6 @@ apiVersion: config.lighthouse.jenkins-x.io/v1alpha1
|
|||||||
kind: TriggerConfig
|
kind: TriggerConfig
|
||||||
spec:
|
spec:
|
||||||
postsubmits:
|
postsubmits:
|
||||||
- name: semver
|
|
||||||
agent: tekton-pipeline
|
|
||||||
branches:
|
|
||||||
- ^main$
|
|
||||||
- ^master$
|
|
||||||
max_concurrency: 1
|
|
||||||
# Override https-based url from lighthouse events.
|
|
||||||
clone_uri: "git@code.fizz.buzz:talexander/organic.git"
|
|
||||||
pipeline_run_spec:
|
|
||||||
serviceAccountName: build-bot
|
|
||||||
pipelineRef:
|
|
||||||
name: semver
|
|
||||||
namespace: lighthouse
|
|
||||||
workspaces:
|
|
||||||
- name: git-source
|
|
||||||
volumeClaimTemplate:
|
|
||||||
spec:
|
|
||||||
storageClassName: "nfs-client"
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 10Gi
|
|
||||||
subPath: organic-source
|
|
||||||
params: []
|
|
||||||
- name: rustfmt
|
- name: rustfmt
|
||||||
source: "pipeline-rustfmt.yaml"
|
source: "pipeline-rustfmt.yaml"
|
||||||
# Override https-based url from lighthouse events.
|
# Override https-based url from lighthouse events.
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "organic"
|
name = "organic"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
authors = ["Tom Alexander <tom@fizz.buzz>"]
|
||||||
|
description = "An org-mode parser."
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "0BSD"
|
license = "0BSD"
|
||||||
|
repository = "https://code.fizz.buzz/talexander/organic"
|
||||||
|
readme = "README.org"
|
||||||
|
keywords = ["emacs", "org-mode"]
|
||||||
|
categories = ["template-engine"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "organic"
|
name = "organic"
|
||||||
|
|||||||
10
README.org
Normal file
10
README.org
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#+options: toc:nil
|
||||||
|
#+title: Organic - Free Range Org-Mode
|
||||||
|
|
||||||
|
Organic is an emacs-less implementation of an [[https://orgmode.org/][org-mode]] parser.
|
||||||
|
|
||||||
|
* Project Status
|
||||||
|
This project is a personal learning project to grow my experience in [[https://www.rust-lang.org/][rust]]. It is under development and at this time I would not recommend anyone use this code. The goal is to turn this into a project others can use, at which point more information will appear in this README.
|
||||||
|
|
||||||
|
* License
|
||||||
|
This project is released under the public-domain-equivalent [[https://www.tldrlegal.com/license/bsd-0-clause-license][0BSD license]]. This license puts no restrictions on the use of this code (you do not even have to include the copyright notice or license text when using it). HOWEVER, this project has a couple permissively licensed dependencies which do require their copyright notices and/or license texts to be included. I am not a lawyer and this is not legal advice but it is my layperson's understanding that if you distribute a binary with this library linked in, you will need to abide by their terms since their code will also be linked in your binary. I try to keep the dependencies to a minimum and the most restrictive dependency I will ever include is a permissively licensed one.
|
||||||
8
build.rs
8
build.rs
@@ -73,14 +73,8 @@ fn is_expect_fail(name: &str) -> Option<&str> {
|
|||||||
match name {
|
match name {
|
||||||
"drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
|
"drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
|
||||||
"element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
|
"element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
|
||||||
"element_container_priority_drawer_greater_block" => Some("Need to implement subscript."),
|
|
||||||
"element_container_priority_dynamic_block_greater_block" => Some("Need to implement subscript."),
|
|
||||||
"element_container_priority_footnote_definition_greater_block" => Some("Need to implement subscript."),
|
|
||||||
"element_container_priority_greater_block_greater_block" => Some("Need to implement subscript."),
|
|
||||||
"element_container_priority_section_greater_block" => Some("Need to implement subscript."),
|
|
||||||
"paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."),
|
"paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."),
|
||||||
"radio_link_before_and_after" => Some("Matching the contents of radio targets not yet implemented."),
|
"export_snippet_paragraph_break_precedent" => Some("Emacs 28 has broken behavior so the tests in the CI fail."),
|
||||||
"radio_link_simple" => Some("Matching the contents of radio targets not yet implemented."),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
FROM rustlang/rust:nightly-alpine3.17
|
FROM rustlang/rust:nightly-alpine3.17
|
||||||
|
|
||||||
|
RUN apk add --no-cache musl-dev
|
||||||
RUN rustup component add rustfmt
|
RUN rustup component add rustfmt
|
||||||
|
|
||||||
ENTRYPOINT ["cargo", "fmt"]
|
ENTRYPOINT ["cargo", "fmt"]
|
||||||
|
|||||||
5
org_mode_samples/citation/simple.org
Normal file
5
org_mode_samples/citation/simple.org
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[cite:@foo]
|
||||||
|
|
||||||
|
[cite/a/b-_/foo:globalprefix;keyprefix @foo keysuffix;globalsuffix]
|
||||||
|
|
||||||
|
text before [cite:@bar] text after
|
||||||
3
org_mode_samples/entity/simple.org
Normal file
3
org_mode_samples/entity/simple.org
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
foo \Delta bar
|
||||||
|
foo \pibar
|
||||||
|
foo \pi{}bar
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
@@latex:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\documentclass[margin,11pt]{res} % default is 10 pt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@@
|
||||||
4
org_mode_samples/export_snippet/simple.org
Normal file
4
org_mode_samples/export_snippet/simple.org
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
@@html:<b>@@This text is only bold for the html exporter@@html:</b>@@
|
||||||
|
@@latex:
|
||||||
|
\documentclass[margin,11pt]{res} % default is 10 pt
|
||||||
|
@@
|
||||||
8
org_mode_samples/footnote_reference/simple.org
Normal file
8
org_mode_samples/footnote_reference/simple.org
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[fn:1] This is a regular footnote definition because it is on an unindented line while lacking a definition inside the brackets.
|
||||||
|
|
||||||
|
|
||||||
|
[fn:1] This is a footnote reference because the line is indented
|
||||||
|
|
||||||
|
[fn:2:This is a footnote reference since it has the definition inside the brackets. This style is referred to as an "inline footnote".]
|
||||||
|
|
||||||
|
[fn::This is a footnote reference since it has the definition inside the brackets. This style is referred to as an "anonymous footnote".]
|
||||||
4
org_mode_samples/inline_babel_call/simple.org
Normal file
4
org_mode_samples/inline_babel_call/simple.org
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
call_foo(arguments)
|
||||||
|
call_bar[header](arguments)
|
||||||
|
call_baz(arguments)[header]
|
||||||
|
call_lorem[header](arguments)[another header]
|
||||||
2
org_mode_samples/inline_source_block/simple.org
Normal file
2
org_mode_samples/inline_source_block/simple.org
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
before src_foo{ipsum} after
|
||||||
|
src_bar[lorem]{ipsum}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
\begin{itemize}
|
||||||
|
\item foo \sqrt{x}
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item bar \sqrt{y}
|
||||||
|
\end{itemize} % Need text on this line to prevent it from becoming a LaTeX environment org-mode element
|
||||||
1
org_mode_samples/latex_fragment/math_mode.org
Normal file
1
org_mode_samples/latex_fragment/math_mode.org
Normal file
@@ -0,0 +1 @@
|
|||||||
|
tex can have math between dollar signs like $x^2=y$ and $$ x=+\sqrt{y} $$ but also braces and brackets like \( x=2 \) and \[ x=-\sqrt{2} \]
|
||||||
4
org_mode_samples/latex_fragment/simple.org
Normal file
4
org_mode_samples/latex_fragment/simple.org
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
\begin{itemize}
|
||||||
|
% this would be a LaTeX comment if this was a LaTeX document
|
||||||
|
\item Heres some math \sqrt{y}
|
||||||
|
\end{itemize} % Need text on this line to prevent it from becoming a LaTeX environment org-mode element
|
||||||
6
org_mode_samples/line_break/simple.org
Normal file
6
org_mode_samples/line_break/simple.org
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
foo\\
|
||||||
|
bar
|
||||||
|
|
||||||
|
|
||||||
|
lorem\\\
|
||||||
|
ipsum
|
||||||
3
org_mode_samples/statistics_cookie/simple.org
Normal file
3
org_mode_samples/statistics_cookie/simple.org
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[70%]
|
||||||
|
[3/5]
|
||||||
|
[-3/5]
|
||||||
7
org_mode_samples/subscript_and_superscript/simple.org
Normal file
7
org_mode_samples/subscript_and_superscript/simple.org
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
foo^*
|
||||||
|
bar_*
|
||||||
|
baz^{hello *world*}
|
||||||
|
lorem_{}
|
||||||
|
ipsum^+,\.a5
|
||||||
|
dolar_,\.a5
|
||||||
|
text before foo_7 text afterwards
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
_{foo}
|
||||||
3
org_mode_samples/target/simple.org
Normal file
3
org_mode_samples/target/simple.org
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
foo <<bar>> baz
|
||||||
|
|
||||||
|
lorem << ipsum >> dolar
|
||||||
14
org_mode_samples/timestamp/simple.org
Normal file
14
org_mode_samples/timestamp/simple.org
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# diary
|
||||||
|
<%%(foo bar baz)>
|
||||||
|
# active
|
||||||
|
<1970-01-01 Thu 8:15rest +1w -1d>
|
||||||
|
# inactive
|
||||||
|
[1970-01-01 Thu 8:15rest +1w -1d]
|
||||||
|
# active date range
|
||||||
|
<1970-01-01 Thu 8:15rest +1w -1d>--<1970-01-01 Thu 8:15rest +1w -1d>
|
||||||
|
# active time range
|
||||||
|
<1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d>
|
||||||
|
# inactive date range
|
||||||
|
[1970-01-01 Thu 8:15rest +1w -1d]--[1970-01-01 Thu 8:15rest +1w -1d]
|
||||||
|
# inactive time range
|
||||||
|
[1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d]
|
||||||
@@ -3,6 +3,8 @@ use super::util::assert_name;
|
|||||||
use crate::parser::sexp::Token;
|
use crate::parser::sexp::Token;
|
||||||
use crate::parser::AngleLink;
|
use crate::parser::AngleLink;
|
||||||
use crate::parser::Bold;
|
use crate::parser::Bold;
|
||||||
|
use crate::parser::Citation;
|
||||||
|
use crate::parser::CitationReference;
|
||||||
use crate::parser::Clock;
|
use crate::parser::Clock;
|
||||||
use crate::parser::Code;
|
use crate::parser::Code;
|
||||||
use crate::parser::Comment;
|
use crate::parser::Comment;
|
||||||
@@ -13,16 +15,23 @@ use crate::parser::DocumentElement;
|
|||||||
use crate::parser::Drawer;
|
use crate::parser::Drawer;
|
||||||
use crate::parser::DynamicBlock;
|
use crate::parser::DynamicBlock;
|
||||||
use crate::parser::Element;
|
use crate::parser::Element;
|
||||||
|
use crate::parser::Entity;
|
||||||
use crate::parser::ExampleBlock;
|
use crate::parser::ExampleBlock;
|
||||||
use crate::parser::ExportBlock;
|
use crate::parser::ExportBlock;
|
||||||
|
use crate::parser::ExportSnippet;
|
||||||
use crate::parser::FixedWidthArea;
|
use crate::parser::FixedWidthArea;
|
||||||
use crate::parser::FootnoteDefinition;
|
use crate::parser::FootnoteDefinition;
|
||||||
|
use crate::parser::FootnoteReference;
|
||||||
use crate::parser::GreaterBlock;
|
use crate::parser::GreaterBlock;
|
||||||
use crate::parser::Heading;
|
use crate::parser::Heading;
|
||||||
use crate::parser::HorizontalRule;
|
use crate::parser::HorizontalRule;
|
||||||
|
use crate::parser::InlineBabelCall;
|
||||||
|
use crate::parser::InlineSourceBlock;
|
||||||
use crate::parser::Italic;
|
use crate::parser::Italic;
|
||||||
use crate::parser::Keyword;
|
use crate::parser::Keyword;
|
||||||
use crate::parser::LatexEnvironment;
|
use crate::parser::LatexEnvironment;
|
||||||
|
use crate::parser::LatexFragment;
|
||||||
|
use crate::parser::LineBreak;
|
||||||
use crate::parser::Object;
|
use crate::parser::Object;
|
||||||
use crate::parser::OrgMacro;
|
use crate::parser::OrgMacro;
|
||||||
use crate::parser::Paragraph;
|
use crate::parser::Paragraph;
|
||||||
@@ -37,10 +46,15 @@ use crate::parser::RadioTarget;
|
|||||||
use crate::parser::RegularLink;
|
use crate::parser::RegularLink;
|
||||||
use crate::parser::Section;
|
use crate::parser::Section;
|
||||||
use crate::parser::SrcBlock;
|
use crate::parser::SrcBlock;
|
||||||
|
use crate::parser::StatisticsCookie;
|
||||||
use crate::parser::StrikeThrough;
|
use crate::parser::StrikeThrough;
|
||||||
|
use crate::parser::Subscript;
|
||||||
|
use crate::parser::Superscript;
|
||||||
use crate::parser::Table;
|
use crate::parser::Table;
|
||||||
use crate::parser::TableCell;
|
use crate::parser::TableCell;
|
||||||
use crate::parser::TableRow;
|
use crate::parser::TableRow;
|
||||||
|
use crate::parser::Target;
|
||||||
|
use crate::parser::Timestamp;
|
||||||
use crate::parser::Underline;
|
use crate::parser::Underline;
|
||||||
use crate::parser::Verbatim;
|
use crate::parser::Verbatim;
|
||||||
use crate::parser::VerseBlock;
|
use crate::parser::VerseBlock;
|
||||||
@@ -154,6 +168,20 @@ fn compare_object<'s>(
|
|||||||
Object::PlainLink(obj) => compare_plain_link(source, emacs, obj),
|
Object::PlainLink(obj) => compare_plain_link(source, emacs, obj),
|
||||||
Object::AngleLink(obj) => compare_angle_link(source, emacs, obj),
|
Object::AngleLink(obj) => compare_angle_link(source, emacs, obj),
|
||||||
Object::OrgMacro(obj) => compare_org_macro(source, emacs, obj),
|
Object::OrgMacro(obj) => compare_org_macro(source, emacs, obj),
|
||||||
|
Object::Entity(obj) => compare_entity(source, emacs, obj),
|
||||||
|
Object::LatexFragment(obj) => compare_latex_fragment(source, emacs, obj),
|
||||||
|
Object::ExportSnippet(obj) => compare_export_snippet(source, emacs, obj),
|
||||||
|
Object::FootnoteReference(obj) => compare_footnote_reference(source, emacs, obj),
|
||||||
|
Object::Citation(obj) => compare_citation(source, emacs, obj),
|
||||||
|
Object::CitationReference(obj) => compare_citation_reference(source, emacs, obj),
|
||||||
|
Object::InlineBabelCall(obj) => compare_inline_babel_call(source, emacs, obj),
|
||||||
|
Object::InlineSourceBlock(obj) => compare_inline_source_block(source, emacs, obj),
|
||||||
|
Object::LineBreak(obj) => compare_line_break(source, emacs, obj),
|
||||||
|
Object::Target(obj) => compare_target(source, emacs, obj),
|
||||||
|
Object::StatisticsCookie(obj) => compare_statistics_cookie(source, emacs, obj),
|
||||||
|
Object::Subscript(obj) => compare_subscript(source, emacs, obj),
|
||||||
|
Object::Superscript(obj) => compare_superscript(source, emacs, obj),
|
||||||
|
Object::Timestamp(obj) => compare_timestamp(source, emacs, obj),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1238,3 +1266,325 @@ fn compare_org_macro<'s>(
|
|||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compare_entity<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s Entity<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "entity";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_latex_fragment<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s LatexFragment<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "latex-fragment";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_export_snippet<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s ExportSnippet<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "export-snippet";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_footnote_reference<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s FootnoteReference<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "footnote-reference";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_citation<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s Citation<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "citation";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_citation_reference<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s CitationReference<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "citation-reference";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_inline_babel_call<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s InlineBabelCall<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "inline-babel-call";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_inline_source_block<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s InlineSourceBlock<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "inline-src-block";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_line_break<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s LineBreak<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "line-break";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_target<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s Target<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "target";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_statistics_cookie<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s StatisticsCookie<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "statistics-cookie";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_subscript<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s Subscript<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "subscript";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_superscript<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s Superscript<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "superscript";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_timestamp<'s>(
|
||||||
|
source: &'s str,
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
rust: &'s Timestamp<'s>,
|
||||||
|
) -> Result<DiffResult, Box<dyn std::error::Error>> {
|
||||||
|
let mut this_status = DiffStatus::Good;
|
||||||
|
let emacs_name = "timestamp";
|
||||||
|
if assert_name(emacs, emacs_name).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assert_bounds(source, emacs, rust).is_err() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiffResult {
|
||||||
|
status: this_status,
|
||||||
|
name: emacs_name.to_owned(),
|
||||||
|
message: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
221
src/parser/citation.rs
Normal file
221
src/parser/citation.rs
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::bytes::complete::tag_no_case;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
|
use nom::character::complete::space0;
|
||||||
|
use nom::combinator::opt;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many1;
|
||||||
|
use nom::multi::many_till;
|
||||||
|
use nom::multi::separated_list1;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::Res;
|
||||||
|
use crate::parser::citation_reference::citation_reference;
|
||||||
|
use crate::parser::citation_reference::citation_reference_key;
|
||||||
|
use crate::parser::citation_reference::get_bracket_depth;
|
||||||
|
use crate::parser::exiting::ExitClass;
|
||||||
|
use crate::parser::object::Citation;
|
||||||
|
use crate::parser::object_parser::standard_set_object;
|
||||||
|
use crate::parser::parser_context::CitationBracket;
|
||||||
|
use crate::parser::parser_context::ContextElement;
|
||||||
|
use crate::parser::parser_context::ExitMatcherNode;
|
||||||
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
|
use crate::parser::util::exit_matcher_parser;
|
||||||
|
use crate::parser::util::get_consumed;
|
||||||
|
use crate::parser::Object;
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn citation<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, 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.
|
||||||
|
let (remaining, _) = tag_no_case("[cite")(input)?;
|
||||||
|
let (remaining, _) = opt(citestyle)(remaining)?;
|
||||||
|
let (remaining, _) = tag(":")(remaining)?;
|
||||||
|
let (remaining, _prefix) = opt(parser_with_context!(global_prefix)(context))(remaining)?;
|
||||||
|
let (remaining, _references) =
|
||||||
|
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
|
||||||
|
let (remaining, _suffix) = opt(tuple((
|
||||||
|
tag(";"),
|
||||||
|
parser_with_context!(global_suffix)(context),
|
||||||
|
)))(remaining)?;
|
||||||
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, Citation { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn citestyle<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, _) = tuple((tag("/"), style))(input)?;
|
||||||
|
let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn style<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
recognize(many1(verify(anychar, |c| {
|
||||||
|
c.is_alphanumeric() || "_-".contains(*c)
|
||||||
|
})))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn variant<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
recognize(many1(verify(anychar, |c| {
|
||||||
|
c.is_alphanumeric() || "_-/".contains(*c)
|
||||||
|
})))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn global_prefix<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, Vec<Object<'s>>> {
|
||||||
|
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
||||||
|
let parser_context = context
|
||||||
|
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
||||||
|
position: input,
|
||||||
|
depth: 0,
|
||||||
|
}))
|
||||||
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &global_prefix_end,
|
||||||
|
}));
|
||||||
|
let (remaining, (children, _exit_contents)) = verify(
|
||||||
|
many_till(
|
||||||
|
parser_with_context!(standard_set_object)(&parser_context),
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
),
|
||||||
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
|
)(input)?;
|
||||||
|
let (remaining, _) = tag(";")(remaining)?;
|
||||||
|
Ok((remaining, children))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn global_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let context_depth = get_bracket_depth(context)
|
||||||
|
.expect("This function should only be called from inside a citation.");
|
||||||
|
let text_since_context_entry = get_consumed(context_depth.position, input);
|
||||||
|
let mut current_depth = context_depth.depth;
|
||||||
|
for c in text_since_context_entry.chars() {
|
||||||
|
match c {
|
||||||
|
'[' => {
|
||||||
|
current_depth += 1;
|
||||||
|
}
|
||||||
|
']' if current_depth == 0 => {
|
||||||
|
panic!("Exceeded citation global prefix bracket depth.")
|
||||||
|
}
|
||||||
|
']' if current_depth > 0 => {
|
||||||
|
current_depth -= 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if current_depth == 0 {
|
||||||
|
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
|
||||||
|
if close_bracket.is_ok() {
|
||||||
|
return close_bracket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alt((
|
||||||
|
tag(";"),
|
||||||
|
recognize(parser_with_context!(citation_reference_key)(context)),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn global_suffix<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, Vec<Object<'s>>> {
|
||||||
|
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
||||||
|
let parser_context = context
|
||||||
|
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
||||||
|
position: input,
|
||||||
|
depth: 0,
|
||||||
|
}))
|
||||||
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &global_suffix_end,
|
||||||
|
}));
|
||||||
|
let (remaining, (children, _exit_contents)) = verify(
|
||||||
|
many_till(
|
||||||
|
parser_with_context!(standard_set_object)(&parser_context),
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
),
|
||||||
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
|
)(input)?;
|
||||||
|
Ok((remaining, children))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn global_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let context_depth = get_bracket_depth(context)
|
||||||
|
.expect("This function should only be called from inside a citation.");
|
||||||
|
let text_since_context_entry = get_consumed(context_depth.position, input);
|
||||||
|
let mut current_depth = context_depth.depth;
|
||||||
|
for c in text_since_context_entry.chars() {
|
||||||
|
match c {
|
||||||
|
'[' => {
|
||||||
|
current_depth += 1;
|
||||||
|
}
|
||||||
|
']' if current_depth == 0 => {
|
||||||
|
panic!("Exceeded citation global suffix bracket depth.")
|
||||||
|
}
|
||||||
|
']' if current_depth > 0 => {
|
||||||
|
current_depth -= 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if current_depth == 0 {
|
||||||
|
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
|
||||||
|
if close_bracket.is_ok() {
|
||||||
|
return close_bracket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alt((
|
||||||
|
tag(";"),
|
||||||
|
recognize(parser_with_context!(citation_reference_key)(context)),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::parser::element_parser::element;
|
||||||
|
use crate::parser::parser_context::ContextElement;
|
||||||
|
use crate::parser::parser_context::ContextTree;
|
||||||
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
|
use crate::parser::source::Source;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn citation_simple() {
|
||||||
|
let input = "[cite:@foo]";
|
||||||
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
|
let document_context =
|
||||||
|
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
||||||
|
let paragraph_matcher = parser_with_context!(element(true))(&document_context);
|
||||||
|
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
||||||
|
let first_paragraph = match first_paragraph {
|
||||||
|
crate::parser::Element::Paragraph(paragraph) => paragraph,
|
||||||
|
_ => panic!("Should be a paragraph!"),
|
||||||
|
};
|
||||||
|
assert_eq!(remaining, "");
|
||||||
|
assert_eq!(first_paragraph.get_source(), "[cite:@foo]");
|
||||||
|
assert_eq!(first_paragraph.children.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
first_paragraph
|
||||||
|
.children
|
||||||
|
.get(0)
|
||||||
|
.expect("Len already asserted to be 1"),
|
||||||
|
&Object::Citation(Citation {
|
||||||
|
source: "[cite:@foo]"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
175
src/parser/citation_reference.rs
Normal file
175
src/parser/citation_reference.rs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
|
use nom::combinator::not;
|
||||||
|
use nom::combinator::opt;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many1;
|
||||||
|
use nom::multi::many_till;
|
||||||
|
use nom::sequence::preceded;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::Res;
|
||||||
|
use crate::parser::exiting::ExitClass;
|
||||||
|
use crate::parser::object::CitationReference;
|
||||||
|
use crate::parser::object_parser::minimal_set_object;
|
||||||
|
use crate::parser::parser_context::CitationBracket;
|
||||||
|
use crate::parser::parser_context::ContextElement;
|
||||||
|
use crate::parser::parser_context::ExitMatcherNode;
|
||||||
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
|
use crate::parser::util::exit_matcher_parser;
|
||||||
|
use crate::parser::util::get_consumed;
|
||||||
|
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
|
||||||
|
use crate::parser::Object;
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn citation_reference<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, CitationReference<'s>> {
|
||||||
|
let (remaining, _prefix) = opt(parser_with_context!(key_prefix)(context))(input)?;
|
||||||
|
let (remaining, _key) = parser_with_context!(citation_reference_key)(context)(remaining)?;
|
||||||
|
let (remaining, _suffix) = opt(parser_with_context!(key_suffix)(context))(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
|
Ok((remaining, CitationReference { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn citation_reference_key<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, source) = recognize(tuple((
|
||||||
|
tag("@"),
|
||||||
|
many1(verify(
|
||||||
|
preceded(
|
||||||
|
not(parser_with_context!(exit_matcher_parser)(context)),
|
||||||
|
anychar,
|
||||||
|
),
|
||||||
|
|c| {
|
||||||
|
WORD_CONSTITUENT_CHARACTERS.contains(*c) || "-.:?~`'/*@+|(){}<>&_^$#%~".contains(*c)
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
)))(input)?;
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn key_prefix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Vec<Object<'s>>> {
|
||||||
|
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
||||||
|
let parser_context = context
|
||||||
|
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
||||||
|
position: input,
|
||||||
|
depth: 0,
|
||||||
|
}))
|
||||||
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &key_prefix_end,
|
||||||
|
}));
|
||||||
|
let (remaining, (children, _exit_contents)) = verify(
|
||||||
|
many_till(
|
||||||
|
parser_with_context!(minimal_set_object)(&parser_context),
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
),
|
||||||
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
|
)(input)?;
|
||||||
|
Ok((remaining, children))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn key_suffix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Vec<Object<'s>>> {
|
||||||
|
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
||||||
|
let parser_context = context
|
||||||
|
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
||||||
|
position: input,
|
||||||
|
depth: 0,
|
||||||
|
}))
|
||||||
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &key_suffix_end,
|
||||||
|
}));
|
||||||
|
let (remaining, (children, _exit_contents)) = verify(
|
||||||
|
many_till(
|
||||||
|
parser_with_context!(minimal_set_object)(&parser_context),
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
),
|
||||||
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
|
)(input)?;
|
||||||
|
Ok((remaining, children))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r CitationBracket<'s>> {
|
||||||
|
for node in context.iter() {
|
||||||
|
match node.get_data() {
|
||||||
|
ContextElement::CitationBracket(depth) => return Some(depth),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn key_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let context_depth = get_bracket_depth(context)
|
||||||
|
.expect("This function should only be called from inside a citation reference.");
|
||||||
|
let text_since_context_entry = get_consumed(context_depth.position, input);
|
||||||
|
let mut current_depth = context_depth.depth;
|
||||||
|
for c in text_since_context_entry.chars() {
|
||||||
|
match c {
|
||||||
|
'[' => {
|
||||||
|
current_depth += 1;
|
||||||
|
}
|
||||||
|
']' if current_depth == 0 => {
|
||||||
|
panic!("Exceeded citation reference key prefix bracket depth.")
|
||||||
|
}
|
||||||
|
']' if current_depth > 0 => {
|
||||||
|
current_depth -= 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if current_depth == 0 {
|
||||||
|
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
|
||||||
|
if close_bracket.is_ok() {
|
||||||
|
return close_bracket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alt((
|
||||||
|
tag(";"),
|
||||||
|
recognize(parser_with_context!(citation_reference_key)(context)),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn key_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let context_depth = get_bracket_depth(context)
|
||||||
|
.expect("This function should only be called from inside a citation reference.");
|
||||||
|
let text_since_context_entry = get_consumed(context_depth.position, input);
|
||||||
|
let mut current_depth = context_depth.depth;
|
||||||
|
for c in text_since_context_entry.chars() {
|
||||||
|
match c {
|
||||||
|
'[' => {
|
||||||
|
current_depth += 1;
|
||||||
|
}
|
||||||
|
']' if current_depth == 0 => {
|
||||||
|
panic!("Exceeded citation reference key prefix bracket depth.")
|
||||||
|
}
|
||||||
|
']' if current_depth > 0 => {
|
||||||
|
current_depth -= 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if current_depth == 0 {
|
||||||
|
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
|
||||||
|
if close_bracket.is_ok() {
|
||||||
|
return close_bracket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag(";")(input)
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ use super::element::Element;
|
|||||||
use super::object::Object;
|
use super::object::Object;
|
||||||
use super::parser_with_context::parser_with_context;
|
use super::parser_with_context::parser_with_context;
|
||||||
use super::source::Source;
|
use super::source::Source;
|
||||||
|
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;
|
||||||
@@ -96,8 +97,35 @@ pub fn document(input: &str) -> Res<&str, Document> {
|
|||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let document_context =
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
||||||
let zeroth_section_matcher = parser_with_context!(zeroth_section)(&document_context);
|
let (remaining, document) = _document(&document_context, input)?;
|
||||||
let heading_matcher = parser_with_context!(heading)(&document_context);
|
{
|
||||||
|
// 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
|
||||||
|
.iter_tokens()
|
||||||
|
.filter_map(|tkn| match tkn {
|
||||||
|
Token::Object(obj) => Some(obj),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.filter_map(|obj| match obj {
|
||||||
|
Object::RadioTarget(rt) => Some(rt),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.map(|rt| &rt.children)
|
||||||
|
.collect();
|
||||||
|
if !all_radio_targets.is_empty() {
|
||||||
|
let document_context = document_context
|
||||||
|
.with_additional_node(ContextElement::RadioTarget(all_radio_targets));
|
||||||
|
let (remaining, document) = _document(&document_context, input)?;
|
||||||
|
return Ok((remaining, document));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((remaining, document))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn _document<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Document<'s>> {
|
||||||
|
let zeroth_section_matcher = parser_with_context!(zeroth_section)(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)?;
|
||||||
@@ -259,28 +287,6 @@ fn headline_end<'r, 's>(_context: Context<'r, 's>, input: &'s str) -> Res<&'s st
|
|||||||
|
|
||||||
impl<'s> Document<'s> {
|
impl<'s> Document<'s> {
|
||||||
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
|
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
|
||||||
self.zeroth_section
|
AllTokensIterator::new(Token::Document(self))
|
||||||
.iter()
|
|
||||||
.map(Token::Section)
|
|
||||||
.chain(self.children.iter().map(Token::Heading))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'s> Heading<'s> {
|
|
||||||
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
|
|
||||||
self.title.iter().map(Token::Object).chain(self.children.iter().map(
|
|
||||||
|de| {
|
|
||||||
match de {
|
|
||||||
DocumentElement::Heading(obj) => Token::Heading(obj),
|
|
||||||
DocumentElement::Section(obj) => Token::Section(obj),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'s> Section<'s> {
|
|
||||||
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
|
|
||||||
self.children.iter().map(Token::Element)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
50
src/parser/entity.rs
Normal file
50
src/parser/entity.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::bytes::complete::tag_no_case;
|
||||||
|
use nom::character::complete::satisfy;
|
||||||
|
use nom::character::complete::space0;
|
||||||
|
use nom::combinator::eof;
|
||||||
|
use nom::combinator::peek;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
use crate::error::Res;
|
||||||
|
use crate::parser::object::Entity;
|
||||||
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
|
use crate::parser::util::get_consumed;
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn entity<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Entity<'s>> {
|
||||||
|
let (remaining, _) = tag("\\")(input)?;
|
||||||
|
let (remaining, entity_name) = name(context, remaining)?;
|
||||||
|
let (remaining, _) = alt((
|
||||||
|
tag("{}"),
|
||||||
|
peek(recognize(parser_with_context!(entity_end)(context))),
|
||||||
|
))(remaining)?;
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Entity {
|
||||||
|
source,
|
||||||
|
entity_name,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
// TODO: This should be defined by org-entities and optionally org-entities-user
|
||||||
|
|
||||||
|
// TODO: Add the rest of the entities, this is a very incomplete list
|
||||||
|
let (remaining, proto) = alt((alt((tag_no_case("delta"), tag_no_case("pi"))),))(input)?;
|
||||||
|
Ok((remaining, proto))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn entity_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
||||||
|
let (remaining, _) = alt((eof, recognize(satisfy(|c| !c.is_alphabetic()))))(input)?;
|
||||||
|
|
||||||
|
Ok((remaining, ()))
|
||||||
|
}
|
||||||
61
src/parser/export_snippet.rs
Normal file
61
src/parser/export_snippet.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
|
use nom::combinator::opt;
|
||||||
|
use nom::combinator::peek;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many1;
|
||||||
|
use nom::multi::many_till;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
use crate::error::Res;
|
||||||
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
|
use crate::parser::util::exit_matcher_parser;
|
||||||
|
use crate::parser::util::get_consumed;
|
||||||
|
use crate::parser::ExportSnippet;
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn export_snippet<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, ExportSnippet<'s>> {
|
||||||
|
let (remaining, _) = tag("@@")(input)?;
|
||||||
|
let (remaining, backend_name) = backend(context, remaining)?;
|
||||||
|
let (remaining, backend_contents) =
|
||||||
|
opt(tuple((tag(":"), parser_with_context!(contents)(context))))(remaining)?;
|
||||||
|
let (remaining, _) = tag("@@")(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
ExportSnippet {
|
||||||
|
source,
|
||||||
|
backend: backend_name,
|
||||||
|
contents: backend_contents.map(|(_colon, backend_contents)| backend_contents),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn backend<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, backend_name) =
|
||||||
|
recognize(many1(verify(anychar, |c| c.is_alphanumeric() || *c == '-')))(input)?;
|
||||||
|
|
||||||
|
Ok((remaining, backend_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn contents<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, source) = recognize(verify(
|
||||||
|
many_till(
|
||||||
|
anychar,
|
||||||
|
peek(alt((
|
||||||
|
parser_with_context!(exit_matcher_parser)(context),
|
||||||
|
tag("@@"),
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
|
))(input)?;
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
@@ -67,7 +67,7 @@ pub fn footnote_definition<'r, 's>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(ret, level = "debug")]
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
fn label<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
pub fn label<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
alt((
|
alt((
|
||||||
digit1,
|
digit1,
|
||||||
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)),
|
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)),
|
||||||
|
|||||||
184
src/parser/footnote_reference.rs
Normal file
184
src/parser/footnote_reference.rs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::bytes::complete::tag_no_case;
|
||||||
|
use nom::character::complete::space0;
|
||||||
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many_till;
|
||||||
|
|
||||||
|
use super::parser_context::ContextElement;
|
||||||
|
use super::Context;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::MyError;
|
||||||
|
use crate::error::Res;
|
||||||
|
use crate::parser::exiting::ExitClass;
|
||||||
|
use crate::parser::footnote_definition::label;
|
||||||
|
use crate::parser::object_parser::standard_set_object;
|
||||||
|
use crate::parser::parser_context::ExitMatcherNode;
|
||||||
|
use crate::parser::parser_context::FootnoteReferenceDefinition;
|
||||||
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
|
use crate::parser::util::exit_matcher_parser;
|
||||||
|
use crate::parser::util::get_consumed;
|
||||||
|
use crate::parser::FootnoteReference;
|
||||||
|
use crate::parser::Object;
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn footnote_reference<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, FootnoteReference<'s>> {
|
||||||
|
alt((
|
||||||
|
parser_with_context!(anonymous_footnote)(context),
|
||||||
|
parser_with_context!(footnote_reference_only)(context),
|
||||||
|
parser_with_context!(inline_footnote)(context),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn anonymous_footnote<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, FootnoteReference<'s>> {
|
||||||
|
let (remaining, _) = tag_no_case("[fn::")(input)?;
|
||||||
|
let parser_context = context
|
||||||
|
.with_additional_node(ContextElement::FootnoteReferenceDefinition(
|
||||||
|
FootnoteReferenceDefinition {
|
||||||
|
position: remaining,
|
||||||
|
depth: 0,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
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(
|
||||||
|
many_till(
|
||||||
|
parser_with_context!(standard_set_object)(&parser_context),
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
),
|
||||||
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
|
)(remaining)?;
|
||||||
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
FootnoteReference {
|
||||||
|
source,
|
||||||
|
label: None,
|
||||||
|
definition: children,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn inline_footnote<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, FootnoteReference<'s>> {
|
||||||
|
let (remaining, _) = tag_no_case("[fn:")(input)?;
|
||||||
|
let (remaining, label_contents) = label(remaining)?;
|
||||||
|
let (remaining, _) = tag(":")(remaining)?;
|
||||||
|
let parser_context = context
|
||||||
|
.with_additional_node(ContextElement::FootnoteReferenceDefinition(
|
||||||
|
FootnoteReferenceDefinition {
|
||||||
|
position: remaining,
|
||||||
|
depth: 0,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
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(
|
||||||
|
many_till(
|
||||||
|
parser_with_context!(standard_set_object)(&parser_context),
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
),
|
||||||
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
|
)(remaining)?;
|
||||||
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
FootnoteReference {
|
||||||
|
source,
|
||||||
|
label: Some(label_contents),
|
||||||
|
definition: children,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn footnote_reference_only<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, FootnoteReference<'s>> {
|
||||||
|
let (remaining, _) = tag_no_case("[fn:")(input)?;
|
||||||
|
let (remaining, label_contents) = label(remaining)?;
|
||||||
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
FootnoteReference {
|
||||||
|
source,
|
||||||
|
label: Some(label_contents),
|
||||||
|
definition: Vec::with_capacity(0),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn definition<'s>(input: &'s str) -> Res<&'s str, Vec<Object<'s>>> {
|
||||||
|
Ok((input, vec![]))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn footnote_definition_end<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, &'s str> {
|
||||||
|
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 text_since_context_entry.chars() {
|
||||||
|
match c {
|
||||||
|
'[' => {
|
||||||
|
current_depth += 1;
|
||||||
|
}
|
||||||
|
']' if current_depth == 0 => {
|
||||||
|
panic!("Exceeded footnote reference definition bracket depth.")
|
||||||
|
}
|
||||||
|
']' if current_depth > 0 => {
|
||||||
|
current_depth -= 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if current_depth > 0 {
|
||||||
|
// Its impossible for the next character to end the footnote reference definition if we're any amount of brackets deep
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"NoFootnoteReferenceDefinitionEnd",
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
tag("]")(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
}
|
||||||
157
src/parser/inline_babel_call.rs
Normal file
157
src/parser/inline_babel_call.rs
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::bytes::complete::tag_no_case;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
|
use nom::character::complete::line_ending;
|
||||||
|
use nom::character::complete::one_of;
|
||||||
|
use nom::character::complete::space0;
|
||||||
|
use nom::combinator::opt;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many_till;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
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::get_consumed;
|
||||||
|
use crate::parser::InlineBabelCall;
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn inline_babel_call<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, InlineBabelCall<'s>> {
|
||||||
|
let (remaining, _) = tag_no_case("call_")(input)?;
|
||||||
|
let (remaining, _name) = name(context, remaining)?;
|
||||||
|
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
|
||||||
|
let (remaining, _argument) = argument(context, remaining)?;
|
||||||
|
let (remaining, _header2) = opt(parser_with_context!(header)(context))(remaining)?;
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, InlineBabelCall { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let parser_context =
|
||||||
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &name_end,
|
||||||
|
}));
|
||||||
|
let (remaining, name) = recognize(many_till(
|
||||||
|
verify(anychar, |c| !(c.is_whitespace() || "[]()".contains(*c))),
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
))(input)?;
|
||||||
|
Ok((remaining, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn name_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
recognize(one_of("[("))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn header<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, _) = tag("[")(input)?;
|
||||||
|
|
||||||
|
let parser_context = context
|
||||||
|
.with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket {
|
||||||
|
position: remaining,
|
||||||
|
depth: 0,
|
||||||
|
}))
|
||||||
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &header_end,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let (remaining, name) = recognize(many_till(
|
||||||
|
anychar,
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
))(remaining)?;
|
||||||
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
|
Ok((remaining, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn header_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let context_depth = get_bracket_depth(context)
|
||||||
|
.expect("This function should only be called from inside an inline babel call header.");
|
||||||
|
let text_since_context_entry = get_consumed(context_depth.position, input);
|
||||||
|
let mut current_depth = context_depth.depth;
|
||||||
|
for c in text_since_context_entry.chars() {
|
||||||
|
match c {
|
||||||
|
'(' => {
|
||||||
|
current_depth += 1;
|
||||||
|
}
|
||||||
|
')' if current_depth == 0 => {
|
||||||
|
panic!("Exceeded inline babel call header bracket depth.")
|
||||||
|
}
|
||||||
|
')' if current_depth > 0 => {
|
||||||
|
current_depth -= 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alt((tag("]"), line_ending))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn argument<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, _) = tag("(")(input)?;
|
||||||
|
|
||||||
|
let parser_context = context
|
||||||
|
.with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket {
|
||||||
|
position: remaining,
|
||||||
|
depth: 0,
|
||||||
|
}))
|
||||||
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &argument_end,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let (remaining, name) = recognize(many_till(
|
||||||
|
anychar,
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
))(remaining)?;
|
||||||
|
let (remaining, _) = tag(")")(remaining)?;
|
||||||
|
Ok((remaining, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn argument_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let context_depth = get_bracket_depth(context)
|
||||||
|
.expect("This function should only be called from inside an inline babel call argument.");
|
||||||
|
let text_since_context_entry = get_consumed(context_depth.position, input);
|
||||||
|
let mut current_depth = context_depth.depth;
|
||||||
|
for c in text_since_context_entry.chars() {
|
||||||
|
match c {
|
||||||
|
'[' => {
|
||||||
|
current_depth += 1;
|
||||||
|
}
|
||||||
|
']' if current_depth == 0 => {
|
||||||
|
panic!("Exceeded inline babel call argument bracket depth.")
|
||||||
|
}
|
||||||
|
']' if current_depth > 0 => {
|
||||||
|
current_depth -= 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alt((tag(")"), line_ending))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
}
|
||||||
195
src/parser/inline_source_block.rs
Normal file
195
src/parser/inline_source_block.rs
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::bytes::complete::tag_no_case;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
|
use nom::character::complete::line_ending;
|
||||||
|
use nom::character::complete::one_of;
|
||||||
|
use nom::character::complete::space0;
|
||||||
|
use nom::combinator::opt;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many_till;
|
||||||
|
use tracing::span;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::Res;
|
||||||
|
use crate::parser::exiting::ExitClass;
|
||||||
|
use crate::parser::parser_context::ContextElement;
|
||||||
|
use crate::parser::parser_context::ExitMatcherNode;
|
||||||
|
use crate::parser::parser_context::InlineSourceBlockBracket;
|
||||||
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
|
use crate::parser::util::exit_matcher_parser;
|
||||||
|
use crate::parser::util::get_consumed;
|
||||||
|
use crate::parser::InlineSourceBlock;
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn inline_source_block<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, InlineSourceBlock<'s>> {
|
||||||
|
let (remaining, _) = tag_no_case("src_")(input)?;
|
||||||
|
let (remaining, _) = lang(context, remaining)?;
|
||||||
|
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
|
||||||
|
let (remaining, _body) = body(context, remaining)?;
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, InlineSourceBlock { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn lang<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let parser_context =
|
||||||
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &lang_end,
|
||||||
|
}));
|
||||||
|
let (remaining, lang) = recognize(many_till(
|
||||||
|
verify(anychar, |c| !(c.is_whitespace() || "[{".contains(*c))),
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
))(input)?;
|
||||||
|
Ok((remaining, lang))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn lang_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
recognize(one_of("[{"))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn header<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, _) = tag("[")(input)?;
|
||||||
|
|
||||||
|
let parser_context = context
|
||||||
|
.with_additional_node(ContextElement::InlineSourceBlockBracket(
|
||||||
|
InlineSourceBlockBracket {
|
||||||
|
position: remaining,
|
||||||
|
depth: 0,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &header_end,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let (remaining, header_contents) = recognize(many_till(
|
||||||
|
anychar,
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
))(remaining)?;
|
||||||
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
|
Ok((remaining, header_contents))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn header_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let context_depth = get_bracket_depth(context)
|
||||||
|
.expect("This function should only be called from inside an inline source block header.");
|
||||||
|
let text_since_context_entry = get_consumed(context_depth.position, input);
|
||||||
|
let mut current_depth = context_depth.depth;
|
||||||
|
for c in text_since_context_entry.chars() {
|
||||||
|
match c {
|
||||||
|
'[' => {
|
||||||
|
current_depth += 1;
|
||||||
|
}
|
||||||
|
']' if current_depth == 0 => {
|
||||||
|
panic!("Exceeded inline source block header bracket depth.")
|
||||||
|
}
|
||||||
|
']' if current_depth > 0 => {
|
||||||
|
current_depth -= 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if current_depth == 0 {
|
||||||
|
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
|
||||||
|
if close_bracket.is_ok() {
|
||||||
|
return close_bracket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line_ending(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn body<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, _) = tag("{")(input)?;
|
||||||
|
|
||||||
|
let parser_context = context
|
||||||
|
.with_additional_node(ContextElement::InlineSourceBlockBracket(
|
||||||
|
InlineSourceBlockBracket {
|
||||||
|
position: remaining,
|
||||||
|
depth: 0,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &body_end,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let (remaining, body_contents) = recognize(many_till(
|
||||||
|
anychar,
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
))(remaining)?;
|
||||||
|
let (remaining, _) = {
|
||||||
|
let span = span!(
|
||||||
|
tracing::Level::DEBUG,
|
||||||
|
"outside end body",
|
||||||
|
remaining = remaining
|
||||||
|
);
|
||||||
|
let _enter = span.enter();
|
||||||
|
tag("}")(remaining)?
|
||||||
|
};
|
||||||
|
Ok((remaining, body_contents))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn body_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let span = span!(
|
||||||
|
tracing::Level::DEBUG,
|
||||||
|
"inside end body",
|
||||||
|
remaining = input,
|
||||||
|
current_depth = current_depth
|
||||||
|
);
|
||||||
|
let _enter = span.enter();
|
||||||
|
|
||||||
|
if current_depth == 0 {
|
||||||
|
let close_bracket = tag::<&str, &str, CustomError<&str>>("}")(input);
|
||||||
|
if close_bracket.is_ok() {
|
||||||
|
return close_bracket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line_ending(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn get_bracket_depth<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
) -> Option<&'r InlineSourceBlockBracket<'s>> {
|
||||||
|
for node in context.iter() {
|
||||||
|
match node.get_data() {
|
||||||
|
ContextElement::InlineSourceBlockBracket(depth) => return Some(depth),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
@@ -2,10 +2,13 @@ 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::bytes::complete::take_while1;
|
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::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::map;
|
use nom::combinator::peek;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::util::get_consumed;
|
use super::util::get_consumed;
|
||||||
@@ -15,7 +18,7 @@ use crate::parser::exiting::ExitClass;
|
|||||||
use crate::parser::parser_context::ContextElement;
|
use crate::parser::parser_context::ContextElement;
|
||||||
use crate::parser::parser_context::ExitMatcherNode;
|
use crate::parser::parser_context::ExitMatcherNode;
|
||||||
use crate::parser::parser_with_context::parser_with_context;
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
use crate::parser::plain_text::plain_text;
|
use crate::parser::util::exit_matcher_parser;
|
||||||
use crate::parser::util::start_of_line;
|
use crate::parser::util::start_of_line;
|
||||||
use crate::parser::LatexEnvironment;
|
use crate::parser::LatexEnvironment;
|
||||||
|
|
||||||
@@ -41,9 +44,7 @@ pub fn latex_environment<'r, 's>(
|
|||||||
exit_matcher: &latex_environment_end_specialized,
|
exit_matcher: &latex_environment_end_specialized,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let (remaining, _contents) = map(parser_with_context!(plain_text)(&parser_context), |obj| {
|
let (remaining, _contents) = contents(&latex_environment_end_specialized, context, remaining)?;
|
||||||
obj.source
|
|
||||||
})(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);
|
||||||
@@ -55,6 +56,23 @@ fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
|||||||
take_while1(|c: char| c.is_alphanumeric() || c == '*')(input)
|
take_while1(|c: char| c.is_alphanumeric() || c == '*')(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug", skip(end_matcher))]
|
||||||
|
pub fn contents<'r, 's, F: Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str>>(
|
||||||
|
end_matcher: F,
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, source) = recognize(many_till(
|
||||||
|
anychar,
|
||||||
|
peek(alt((
|
||||||
|
parser_with_context!(exit_matcher_parser)(context),
|
||||||
|
parser_with_context!(end_matcher)(context),
|
||||||
|
))),
|
||||||
|
))(input)?;
|
||||||
|
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
fn latex_environment_end(
|
fn latex_environment_end(
|
||||||
current_name: &str,
|
current_name: &str,
|
||||||
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str> {
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
|||||||
230
src/parser/latex_fragment.rs
Normal file
230
src/parser/latex_fragment.rs
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::alpha1;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
|
use nom::character::complete::line_ending;
|
||||||
|
use nom::character::complete::none_of;
|
||||||
|
use nom::character::complete::one_of;
|
||||||
|
use nom::character::complete::space0;
|
||||||
|
use nom::combinator::peek;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many0;
|
||||||
|
use nom::multi::many_till;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::MyError;
|
||||||
|
use crate::error::Res;
|
||||||
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
|
use crate::parser::util::exit_matcher_parser;
|
||||||
|
use crate::parser::util::get_consumed;
|
||||||
|
use crate::parser::util::get_one_before;
|
||||||
|
use crate::parser::LatexFragment;
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn latex_fragment<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, LatexFragment<'s>> {
|
||||||
|
let (remaining, _) = alt((
|
||||||
|
parser_with_context!(raw_latex_fragment)(context),
|
||||||
|
parser_with_context!(escaped_parenthesis_fragment)(context),
|
||||||
|
parser_with_context!(escaped_bracket_fragment)(context),
|
||||||
|
parser_with_context!(double_dollar_fragment)(context),
|
||||||
|
parser_with_context!(dollar_char_fragment)(context),
|
||||||
|
parser_with_context!(bordered_dollar_fragment)(context),
|
||||||
|
))(input)?;
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, LatexFragment { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn raw_latex_fragment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, _) = tag("\\")(input)?;
|
||||||
|
let (remaining, _) = name(context, remaining)?;
|
||||||
|
let (remaining, _) = many0(parser_with_context!(brackets)(context))(remaining)?;
|
||||||
|
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
alpha1(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn brackets<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, body) = alt((
|
||||||
|
recognize(tuple((
|
||||||
|
tag("["),
|
||||||
|
many_till(
|
||||||
|
anychar,
|
||||||
|
peek(alt((
|
||||||
|
parser_with_context!(exit_matcher_parser)(context),
|
||||||
|
alt((recognize(one_of("{}[]")), line_ending)),
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
tag("]"),
|
||||||
|
))),
|
||||||
|
recognize(tuple((
|
||||||
|
tag("{"),
|
||||||
|
many_till(
|
||||||
|
anychar,
|
||||||
|
peek(alt((
|
||||||
|
parser_with_context!(exit_matcher_parser)(context),
|
||||||
|
alt((recognize(one_of("{}")), line_ending)),
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
tag("}"),
|
||||||
|
))),
|
||||||
|
))(input)?;
|
||||||
|
Ok((remaining, body))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn escaped_parenthesis_fragment<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, _) = tag("\\(")(input)?;
|
||||||
|
let (remaining, _) = recognize(many_till(
|
||||||
|
anychar,
|
||||||
|
peek(alt((
|
||||||
|
parser_with_context!(exit_matcher_parser)(context),
|
||||||
|
tag("\\)"),
|
||||||
|
))),
|
||||||
|
))(remaining)?;
|
||||||
|
let (remaining, _) = tag("\\)")(remaining)?;
|
||||||
|
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn escaped_bracket_fragment<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, _) = tag("\\[")(input)?;
|
||||||
|
let (remaining, _) = recognize(many_till(
|
||||||
|
anychar,
|
||||||
|
peek(alt((
|
||||||
|
parser_with_context!(exit_matcher_parser)(context),
|
||||||
|
tag("\\]"),
|
||||||
|
))),
|
||||||
|
))(remaining)?;
|
||||||
|
let (remaining, _) = tag("\\]")(remaining)?;
|
||||||
|
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn double_dollar_fragment<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, &'s str> {
|
||||||
|
// 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 $$?
|
||||||
|
let (remaining, _) = tag("$$")(input)?;
|
||||||
|
let (remaining, _) = recognize(many_till(
|
||||||
|
anychar,
|
||||||
|
peek(alt((
|
||||||
|
parser_with_context!(exit_matcher_parser)(context),
|
||||||
|
tag("$"),
|
||||||
|
))),
|
||||||
|
))(remaining)?;
|
||||||
|
let (remaining, _) = tag("$$")(remaining)?;
|
||||||
|
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn dollar_char_fragment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (_, _) = pre(context, input)?;
|
||||||
|
let (remaining, _) = tag("$")(input)?;
|
||||||
|
let (remaining, _) = verify(none_of(".,?;\""), |c| !c.is_whitespace())(remaining)?;
|
||||||
|
let (remaining, _) = tag("$")(remaining)?;
|
||||||
|
let (_, _) = peek(parser_with_context!(post)(context))(remaining)?;
|
||||||
|
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
||||||
|
let document_root = context.get_document_root().unwrap();
|
||||||
|
let preceding_character = get_one_before(document_root, input)
|
||||||
|
.map(|slice| slice.chars().next())
|
||||||
|
.flatten();
|
||||||
|
if let Some('$') = preceding_character {
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"Not a valid pre character for dollar char fragment.",
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
Ok((input, ()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn post<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
||||||
|
// TODO: What about eof? Test to find out.
|
||||||
|
|
||||||
|
// TODO: Figure out which punctuation characters should be included.
|
||||||
|
let (remaining, _) = alt((recognize(one_of(" \t-.,;:!?'\"")), line_ending))(input)?;
|
||||||
|
Ok((remaining, ()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn bordered_dollar_fragment<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, &'s str> {
|
||||||
|
let (_, _) = pre(context, input)?;
|
||||||
|
let (remaining, _) = tag("$")(input)?;
|
||||||
|
// TODO: I'm assuming I should be peeking at the borders but the documentation is not clear. Test to figure out.
|
||||||
|
let (_, _) = peek(parser_with_context!(open_border)(context))(remaining)?;
|
||||||
|
|
||||||
|
// TODO: As an optimization it would be nice to exit early upon hitting the 3rd line break
|
||||||
|
let (remaining, _) = verify(
|
||||||
|
recognize(many_till(
|
||||||
|
anychar,
|
||||||
|
peek(alt((
|
||||||
|
parser_with_context!(exit_matcher_parser)(context),
|
||||||
|
tag("$"),
|
||||||
|
))),
|
||||||
|
)),
|
||||||
|
|body: &str| body.lines().take(4).count() <= 3,
|
||||||
|
)(remaining)?;
|
||||||
|
|
||||||
|
let (_, _) = peek(parser_with_context!(close_border)(context))(remaining)?;
|
||||||
|
let (remaining, _) = tag("$")(remaining)?;
|
||||||
|
let (_, _) = peek(parser_with_context!(post)(context))(remaining)?;
|
||||||
|
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn open_border<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
recognize(verify(none_of(".,;$"), |c| !c.is_whitespace()))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn close_border<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
||||||
|
let document_root = context.get_document_root().unwrap();
|
||||||
|
let preceding_character = get_one_before(document_root, input)
|
||||||
|
.map(|slice| slice.chars().next())
|
||||||
|
.flatten();
|
||||||
|
match preceding_character {
|
||||||
|
Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())),
|
||||||
|
_ => {
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"Not a valid pre character for dollar char fragment.",
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/parser/line_break.rs
Normal file
59
src/parser/line_break.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::line_ending;
|
||||||
|
use nom::character::complete::one_of;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
use nom::multi::many0;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::MyError;
|
||||||
|
use crate::error::Res;
|
||||||
|
use crate::parser::util::get_consumed;
|
||||||
|
use crate::parser::util::get_current_line_before_position;
|
||||||
|
use crate::parser::util::get_one_before;
|
||||||
|
use crate::parser::LineBreak;
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn line_break<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, LineBreak<'s>> {
|
||||||
|
let (remaining, _) = pre(context, input)?;
|
||||||
|
let (remaining, _) = tag(r#"\\"#)(remaining)?;
|
||||||
|
let (remaining, _) = recognize(many0(one_of(" \t")))(remaining)?;
|
||||||
|
let (remaining, _) = line_ending(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, LineBreak { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
||||||
|
let document_root = context.get_document_root().unwrap();
|
||||||
|
let preceding_character = get_one_before(document_root, input)
|
||||||
|
.map(|slice| slice.chars().next())
|
||||||
|
.flatten();
|
||||||
|
match preceding_character {
|
||||||
|
// If None, we are at the start of the file
|
||||||
|
None | Some('\\') => {
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"Not a valid pre character for line break.",
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
let current_line = get_current_line_before_position(document_root, input);
|
||||||
|
match current_line {
|
||||||
|
Some(line) => {
|
||||||
|
let is_non_empty_line = line.chars().any(|c| !c.is_whitespace());
|
||||||
|
if !is_non_empty_line {
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"Not a valid pre line for line break.",
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"No preceding line for line break.",
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((input, ()))
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
mod angle_link;
|
mod angle_link;
|
||||||
|
mod citation;
|
||||||
|
mod citation_reference;
|
||||||
mod clock;
|
mod clock;
|
||||||
mod comment;
|
mod comment;
|
||||||
mod diary_sexp;
|
mod diary_sexp;
|
||||||
@@ -7,16 +9,23 @@ mod drawer;
|
|||||||
mod dynamic_block;
|
mod dynamic_block;
|
||||||
mod element;
|
mod element;
|
||||||
mod element_parser;
|
mod element_parser;
|
||||||
|
mod entity;
|
||||||
mod exiting;
|
mod exiting;
|
||||||
|
mod export_snippet;
|
||||||
mod fixed_width_area;
|
mod fixed_width_area;
|
||||||
mod footnote_definition;
|
mod footnote_definition;
|
||||||
|
mod footnote_reference;
|
||||||
mod greater_block;
|
mod greater_block;
|
||||||
mod greater_element;
|
mod greater_element;
|
||||||
mod horizontal_rule;
|
mod horizontal_rule;
|
||||||
|
mod inline_babel_call;
|
||||||
|
mod inline_source_block;
|
||||||
mod keyword;
|
mod keyword;
|
||||||
mod latex_environment;
|
mod latex_environment;
|
||||||
|
mod latex_fragment;
|
||||||
mod lesser_block;
|
mod lesser_block;
|
||||||
mod lesser_element;
|
mod lesser_element;
|
||||||
|
mod line_break;
|
||||||
mod list;
|
mod list;
|
||||||
mod object;
|
mod object;
|
||||||
mod object_parser;
|
mod object_parser;
|
||||||
@@ -33,8 +42,12 @@ mod radio_link;
|
|||||||
mod regular_link;
|
mod regular_link;
|
||||||
pub mod sexp;
|
pub mod sexp;
|
||||||
mod source;
|
mod source;
|
||||||
|
mod statistics_cookie;
|
||||||
|
mod subscript_and_superscript;
|
||||||
mod table;
|
mod table;
|
||||||
|
mod target;
|
||||||
mod text_markup;
|
mod text_markup;
|
||||||
|
mod timestamp;
|
||||||
mod token;
|
mod token;
|
||||||
mod util;
|
mod util;
|
||||||
pub use document::document;
|
pub use document::document;
|
||||||
@@ -69,8 +82,17 @@ pub use lesser_element::TableCell;
|
|||||||
pub use lesser_element::VerseBlock;
|
pub use lesser_element::VerseBlock;
|
||||||
pub use object::AngleLink;
|
pub use object::AngleLink;
|
||||||
pub use object::Bold;
|
pub use object::Bold;
|
||||||
|
pub use object::Citation;
|
||||||
|
pub use object::CitationReference;
|
||||||
pub use object::Code;
|
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::Italic;
|
||||||
|
pub use object::LatexFragment;
|
||||||
|
pub use object::LineBreak;
|
||||||
pub use object::Object;
|
pub use object::Object;
|
||||||
pub use object::OrgMacro;
|
pub use object::OrgMacro;
|
||||||
pub use object::PlainLink;
|
pub use object::PlainLink;
|
||||||
@@ -78,7 +100,12 @@ pub use object::PlainText;
|
|||||||
pub use object::RadioLink;
|
pub use object::RadioLink;
|
||||||
pub use object::RadioTarget;
|
pub use object::RadioTarget;
|
||||||
pub use object::RegularLink;
|
pub use object::RegularLink;
|
||||||
|
pub use object::StatisticsCookie;
|
||||||
pub use object::StrikeThrough;
|
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::Underline;
|
||||||
pub use object::Verbatim;
|
pub use object::Verbatim;
|
||||||
pub use source::Source;
|
pub use source::Source;
|
||||||
|
|||||||
@@ -15,6 +15,20 @@ pub enum Object<'s> {
|
|||||||
PlainLink(PlainLink<'s>),
|
PlainLink(PlainLink<'s>),
|
||||||
AngleLink(AngleLink<'s>),
|
AngleLink(AngleLink<'s>),
|
||||||
OrgMacro(OrgMacro<'s>),
|
OrgMacro(OrgMacro<'s>),
|
||||||
|
Entity(Entity<'s>),
|
||||||
|
LatexFragment(LatexFragment<'s>),
|
||||||
|
ExportSnippet(ExportSnippet<'s>),
|
||||||
|
FootnoteReference(FootnoteReference<'s>),
|
||||||
|
Citation(Citation<'s>),
|
||||||
|
CitationReference(CitationReference<'s>),
|
||||||
|
InlineBabelCall(InlineBabelCall<'s>),
|
||||||
|
InlineSourceBlock(InlineSourceBlock<'s>),
|
||||||
|
LineBreak(LineBreak<'s>),
|
||||||
|
Target(Target<'s>),
|
||||||
|
StatisticsCookie(StatisticsCookie<'s>),
|
||||||
|
Subscript(Subscript<'s>),
|
||||||
|
Superscript(Superscript<'s>),
|
||||||
|
Timestamp(Timestamp<'s>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@@ -96,6 +110,81 @@ pub struct OrgMacro<'s> {
|
|||||||
pub macro_args: Vec<&'s str>,
|
pub macro_args: Vec<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Entity<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
pub entity_name: &'s str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct LatexFragment<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct ExportSnippet<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
pub backend: &'s str,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct FootnoteReference<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
pub label: Option<&'s str>,
|
||||||
|
pub definition: Vec<Object<'s>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Citation<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct CitationReference<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct InlineBabelCall<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct InlineSourceBlock<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct LineBreak<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Target<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct StatisticsCookie<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Subscript<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Superscript<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Timestamp<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for Object<'s> {
|
impl<'s> Source<'s> for Object<'s> {
|
||||||
fn get_source(&'s self) -> &'s str {
|
fn get_source(&'s self) -> &'s str {
|
||||||
match self {
|
match self {
|
||||||
@@ -112,6 +201,20 @@ impl<'s> Source<'s> for Object<'s> {
|
|||||||
Object::PlainLink(obj) => obj.source,
|
Object::PlainLink(obj) => obj.source,
|
||||||
Object::AngleLink(obj) => obj.source,
|
Object::AngleLink(obj) => obj.source,
|
||||||
Object::OrgMacro(obj) => obj.source,
|
Object::OrgMacro(obj) => obj.source,
|
||||||
|
Object::Entity(obj) => obj.source,
|
||||||
|
Object::LatexFragment(obj) => obj.source,
|
||||||
|
Object::ExportSnippet(obj) => obj.source,
|
||||||
|
Object::FootnoteReference(obj) => obj.source,
|
||||||
|
Object::Citation(obj) => obj.source,
|
||||||
|
Object::CitationReference(obj) => obj.source,
|
||||||
|
Object::InlineBabelCall(obj) => obj.source,
|
||||||
|
Object::InlineSourceBlock(obj) => obj.source,
|
||||||
|
Object::LineBreak(obj) => obj.source,
|
||||||
|
Object::Target(obj) => obj.source,
|
||||||
|
Object::Timestamp(obj) => obj.source,
|
||||||
|
Object::StatisticsCookie(obj) => obj.source,
|
||||||
|
Object::Subscript(obj) => obj.source,
|
||||||
|
Object::Superscript(obj) => obj.source,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,3 +290,87 @@ impl<'s> Source<'s> for OrgMacro<'s> {
|
|||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for Entity<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for LatexFragment<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for ExportSnippet<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for FootnoteReference<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for Citation<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for CitationReference<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for InlineBabelCall<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for InlineSourceBlock<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for LineBreak<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for Target<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for StatisticsCookie<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for Subscript<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for Superscript<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Source<'s> for Timestamp<'s> {
|
||||||
|
fn get_source(&'s self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,22 +8,68 @@ use super::regular_link::regular_link;
|
|||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::angle_link::angle_link;
|
use crate::parser::angle_link::angle_link;
|
||||||
|
use crate::parser::citation::citation;
|
||||||
|
use crate::parser::entity::entity;
|
||||||
|
use crate::parser::export_snippet::export_snippet;
|
||||||
|
use crate::parser::footnote_reference::footnote_reference;
|
||||||
|
use crate::parser::inline_babel_call::inline_babel_call;
|
||||||
|
use crate::parser::inline_source_block::inline_source_block;
|
||||||
|
use crate::parser::latex_fragment::latex_fragment;
|
||||||
|
use crate::parser::line_break::line_break;
|
||||||
use crate::parser::object::Object;
|
use crate::parser::object::Object;
|
||||||
use crate::parser::org_macro::org_macro;
|
use crate::parser::org_macro::org_macro;
|
||||||
use crate::parser::plain_link::plain_link;
|
use crate::parser::plain_link::plain_link;
|
||||||
use crate::parser::radio_link::radio_link;
|
use crate::parser::radio_link::radio_link;
|
||||||
use crate::parser::radio_link::radio_target;
|
use crate::parser::radio_link::radio_target;
|
||||||
|
use crate::parser::statistics_cookie::statistics_cookie;
|
||||||
|
use crate::parser::subscript_and_superscript::subscript;
|
||||||
|
use crate::parser::subscript_and_superscript::superscript;
|
||||||
|
use crate::parser::target::target;
|
||||||
use crate::parser::text_markup::text_markup;
|
use crate::parser::text_markup::text_markup;
|
||||||
|
use crate::parser::timestamp::timestamp;
|
||||||
|
|
||||||
#[tracing::instrument(ret, level = "debug")]
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
pub fn standard_set_object<'r, 's>(
|
pub fn standard_set_object<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: &'s str,
|
||||||
) -> Res<&'s str, Object<'s>> {
|
) -> Res<&'s str, Object<'s>> {
|
||||||
// TODO: add entities, LaTeX fragments, export snippets, footnote references, citations (NOT citation references), inline babel calls, inline source blocks, line breaks, links, macros, targets and radio targets, statistics cookies, subscript and superscript, timestamps, and text markup.
|
|
||||||
not(|i| context.check_exit_matcher(i))(input)?;
|
not(|i| context.check_exit_matcher(i))(input)?;
|
||||||
|
|
||||||
alt((
|
alt((
|
||||||
|
map(parser_with_context!(timestamp)(context), Object::Timestamp),
|
||||||
|
map(parser_with_context!(subscript)(context), Object::Subscript),
|
||||||
|
map(
|
||||||
|
parser_with_context!(superscript)(context),
|
||||||
|
Object::Superscript,
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
parser_with_context!(statistics_cookie)(context),
|
||||||
|
Object::StatisticsCookie,
|
||||||
|
),
|
||||||
|
map(parser_with_context!(target)(context), Object::Target),
|
||||||
|
map(parser_with_context!(line_break)(context), Object::LineBreak),
|
||||||
|
map(
|
||||||
|
parser_with_context!(inline_source_block)(context),
|
||||||
|
Object::InlineSourceBlock,
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
parser_with_context!(inline_babel_call)(context),
|
||||||
|
Object::InlineBabelCall,
|
||||||
|
),
|
||||||
|
map(parser_with_context!(citation)(context), Object::Citation),
|
||||||
|
map(
|
||||||
|
parser_with_context!(footnote_reference)(context),
|
||||||
|
Object::FootnoteReference,
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
parser_with_context!(export_snippet)(context),
|
||||||
|
Object::ExportSnippet,
|
||||||
|
),
|
||||||
|
map(parser_with_context!(entity)(context), Object::Entity),
|
||||||
|
map(
|
||||||
|
parser_with_context!(latex_fragment)(context),
|
||||||
|
Object::LatexFragment,
|
||||||
|
),
|
||||||
map(parser_with_context!(radio_link)(context), Object::RadioLink),
|
map(parser_with_context!(radio_link)(context), Object::RadioLink),
|
||||||
map(
|
map(
|
||||||
parser_with_context!(radio_target)(context),
|
parser_with_context!(radio_target)(context),
|
||||||
@@ -46,10 +92,19 @@ pub fn minimal_set_object<'r, 's>(
|
|||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: &'s str,
|
||||||
) -> Res<&'s str, Object<'s>> {
|
) -> Res<&'s str, Object<'s>> {
|
||||||
// TODO: add entities, LaTeX fragments, superscripts and subscripts
|
|
||||||
not(|i| context.check_exit_matcher(i))(input)?;
|
not(|i| context.check_exit_matcher(i))(input)?;
|
||||||
|
|
||||||
alt((
|
alt((
|
||||||
|
map(parser_with_context!(subscript)(context), Object::Subscript),
|
||||||
|
map(
|
||||||
|
parser_with_context!(superscript)(context),
|
||||||
|
Object::Superscript,
|
||||||
|
),
|
||||||
|
map(parser_with_context!(entity)(context), Object::Entity),
|
||||||
|
map(
|
||||||
|
parser_with_context!(latex_fragment)(context),
|
||||||
|
Object::LatexFragment,
|
||||||
|
),
|
||||||
parser_with_context!(text_markup)(context),
|
parser_with_context!(text_markup)(context),
|
||||||
map(parser_with_context!(plain_text)(context), Object::PlainText),
|
map(parser_with_context!(plain_text)(context), Object::PlainText),
|
||||||
))(input)
|
))(input)
|
||||||
@@ -62,6 +117,40 @@ pub fn any_object_except_plain_text<'r, 's>(
|
|||||||
) -> Res<&'s str, Object<'s>> {
|
) -> Res<&'s str, Object<'s>> {
|
||||||
// Used for exit matchers so this does not check exit matcher condition.
|
// Used for exit matchers so this does not check exit matcher condition.
|
||||||
alt((
|
alt((
|
||||||
|
map(parser_with_context!(timestamp)(context), Object::Timestamp),
|
||||||
|
map(parser_with_context!(subscript)(context), Object::Subscript),
|
||||||
|
map(
|
||||||
|
parser_with_context!(superscript)(context),
|
||||||
|
Object::Superscript,
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
parser_with_context!(statistics_cookie)(context),
|
||||||
|
Object::StatisticsCookie,
|
||||||
|
),
|
||||||
|
map(parser_with_context!(target)(context), Object::Target),
|
||||||
|
map(parser_with_context!(line_break)(context), Object::LineBreak),
|
||||||
|
map(
|
||||||
|
parser_with_context!(inline_source_block)(context),
|
||||||
|
Object::InlineSourceBlock,
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
parser_with_context!(inline_babel_call)(context),
|
||||||
|
Object::InlineBabelCall,
|
||||||
|
),
|
||||||
|
map(parser_with_context!(citation)(context), Object::Citation),
|
||||||
|
map(
|
||||||
|
parser_with_context!(footnote_reference)(context),
|
||||||
|
Object::FootnoteReference,
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
parser_with_context!(export_snippet)(context),
|
||||||
|
Object::ExportSnippet,
|
||||||
|
),
|
||||||
|
map(parser_with_context!(entity)(context), Object::Entity),
|
||||||
|
map(
|
||||||
|
parser_with_context!(latex_fragment)(context),
|
||||||
|
Object::LatexFragment,
|
||||||
|
),
|
||||||
map(parser_with_context!(radio_link)(context), Object::RadioLink),
|
map(parser_with_context!(radio_link)(context), Object::RadioLink),
|
||||||
map(
|
map(
|
||||||
parser_with_context!(radio_target)(context),
|
parser_with_context!(radio_target)(context),
|
||||||
@@ -83,6 +172,25 @@ pub fn regular_link_description_object_set<'r, 's>(
|
|||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: &'s str,
|
||||||
) -> Res<&'s str, Object<'s>> {
|
) -> Res<&'s str, Object<'s>> {
|
||||||
// TODO: minimal set of objects as well as export snippets, inline babel calls, inline source blocks, macros, and statistics cookies. It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
|
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
|
||||||
alt((parser_with_context!(minimal_set_object)(context),))(input)
|
alt((
|
||||||
|
map(
|
||||||
|
parser_with_context!(export_snippet)(context),
|
||||||
|
Object::ExportSnippet,
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
parser_with_context!(statistics_cookie)(context),
|
||||||
|
Object::StatisticsCookie,
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
parser_with_context!(inline_source_block)(context),
|
||||||
|
Object::InlineSourceBlock,
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
parser_with_context!(inline_babel_call)(context),
|
||||||
|
Object::InlineBabelCall,
|
||||||
|
),
|
||||||
|
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
|
||||||
|
parser_with_context!(minimal_set_object)(context),
|
||||||
|
))(input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,72 @@ pub enum ContextElement<'r, 's> {
|
|||||||
/// If any are found, this will force a 2nd parse through the
|
/// If any are found, this will force a 2nd parse through the
|
||||||
/// org-mode document since text needs to be re-parsed to look for
|
/// org-mode document since text needs to be re-parsed to look for
|
||||||
/// radio links matching the contents of radio targets.
|
/// radio links matching the contents of radio targets.
|
||||||
RadioTarget(Vec<Vec<Object<'s>>>),
|
RadioTarget(Vec<&'r Vec<Object<'s>>>),
|
||||||
|
|
||||||
|
/// Stores the current bracket depth inside a footnote reference's definition.
|
||||||
|
///
|
||||||
|
/// The definition inside a footnote reference must have balanced
|
||||||
|
/// brackets [] inside the definition, so this stores the amount
|
||||||
|
/// of opening brackets subtracted by the amount of closing
|
||||||
|
/// brackets within the definition must equal zero.
|
||||||
|
///
|
||||||
|
/// A reference to the position in the string is also included so
|
||||||
|
/// unbalanced brackets can be detected in the middle of an
|
||||||
|
/// object.
|
||||||
|
FootnoteReferenceDefinition(FootnoteReferenceDefinition<'s>),
|
||||||
|
|
||||||
|
/// Stores the current bracket depth inside a citation.
|
||||||
|
///
|
||||||
|
/// The global prefix, global suffix, key prefix, and key suffix
|
||||||
|
/// inside a footnote reference must have balanced brackets []
|
||||||
|
/// inside the definition, so this stores the amount of opening
|
||||||
|
/// brackets subtracted by the amount of closing brackets within
|
||||||
|
/// the definition must equal zero. None of the prefixes or
|
||||||
|
/// suffixes can be nested inside each other so we can use a
|
||||||
|
/// single type for this without conflict.
|
||||||
|
///
|
||||||
|
/// A reference to the position in the string is also included so
|
||||||
|
/// unbalanced brackets can be detected in the middle of an
|
||||||
|
/// object.
|
||||||
|
CitationBracket(CitationBracket<'s>),
|
||||||
|
|
||||||
|
/// Stores the current bracket or parenthesis depth inside an inline babel call.
|
||||||
|
///
|
||||||
|
/// Inside an inline babel call the headers must have balanced
|
||||||
|
/// parentheses () and the arguments must have balanced brackets
|
||||||
|
/// [], so this stores the amount of opening brackets subtracted
|
||||||
|
/// by the amount of closing brackets within the definition must
|
||||||
|
/// equal zero.
|
||||||
|
///
|
||||||
|
/// A reference to the position in the string is also included so
|
||||||
|
/// unbalanced brackets can be detected in the middle of an
|
||||||
|
/// object.
|
||||||
|
BabelHeaderBracket(BabelHeaderBracket<'s>),
|
||||||
|
|
||||||
|
/// Stores the current bracket or parenthesis depth inside an inline babel call.
|
||||||
|
///
|
||||||
|
/// Inside an inline babel call the headers must have balanced
|
||||||
|
/// parentheses () and the arguments must have balanced brackets
|
||||||
|
/// [], so this stores the amount of opening brackets subtracted
|
||||||
|
/// by the amount of closing brackets within the definition must
|
||||||
|
/// equal zero.
|
||||||
|
///
|
||||||
|
/// A reference to the position in the string is also included so
|
||||||
|
/// unbalanced brackets can be detected in the middle of an
|
||||||
|
/// object.
|
||||||
|
InlineSourceBlockBracket(InlineSourceBlockBracket<'s>),
|
||||||
|
|
||||||
|
/// Stores the current bracket or parenthesis depth inside a
|
||||||
|
/// superscript or superscript.
|
||||||
|
///
|
||||||
|
/// Inside the braces of a subscript or superscript there must be
|
||||||
|
/// balanced braces {}, so this stores the amount of opening
|
||||||
|
/// braces subtracted by the amount of closing braces within the
|
||||||
|
/// definition must equal zero.
|
||||||
|
///
|
||||||
|
/// A reference to the position in the string is also included so
|
||||||
|
/// unbalanced braces can be detected in the middle of an object.
|
||||||
|
SubscriptSuperscriptBrace(SubscriptSuperscriptBrace<'s>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExitMatcherNode<'r> {
|
pub struct ExitMatcherNode<'r> {
|
||||||
@@ -148,6 +213,36 @@ pub struct ExitMatcherNode<'r> {
|
|||||||
pub class: ExitClass,
|
pub class: ExitClass,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FootnoteReferenceDefinition<'s> {
|
||||||
|
pub position: &'s str,
|
||||||
|
pub depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CitationBracket<'s> {
|
||||||
|
pub position: &'s str,
|
||||||
|
pub depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BabelHeaderBracket<'s> {
|
||||||
|
pub position: &'s str,
|
||||||
|
pub depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InlineSourceBlockBracket<'s> {
|
||||||
|
pub position: &'s str,
|
||||||
|
pub depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SubscriptSuperscriptBrace<'s> {
|
||||||
|
pub position: &'s str,
|
||||||
|
pub depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
|
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let mut formatter = f.debug_struct("ExitMatcherNode");
|
let mut formatter = f.debug_struct("ExitMatcherNode");
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ pub fn radio_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
for radio_target in radio_targets {
|
for radio_target in radio_targets {
|
||||||
let rematched_target = rematch_target(context, radio_target, input);
|
let rematched_target = rematch_target(context, radio_target, input);
|
||||||
if let Ok((remaining, rematched_target)) = rematched_target {
|
if let Ok((remaining, rematched_target)) = rematched_target {
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
return Ok((
|
return Ok((
|
||||||
remaining,
|
remaining,
|
||||||
@@ -58,6 +59,11 @@ pub fn rematch_target<'x, 'r, 's>(
|
|||||||
for original_object in target {
|
for original_object in target {
|
||||||
match original_object {
|
match original_object {
|
||||||
// TODO: The rest of the minimal set of objects.
|
// TODO: The rest of the minimal set of objects.
|
||||||
|
Object::Bold(bold) => {
|
||||||
|
let (new_remaining, new_match) = bold.rematch_object(context, remaining)?;
|
||||||
|
remaining = new_remaining;
|
||||||
|
new_matches.push(new_match);
|
||||||
|
}
|
||||||
Object::PlainText(plaintext) => {
|
Object::PlainText(plaintext) => {
|
||||||
let (new_remaining, new_match) = plaintext.rematch_object(context, remaining)?;
|
let (new_remaining, new_match) = plaintext.rematch_object(context, remaining)?;
|
||||||
remaining = new_remaining;
|
remaining = new_remaining;
|
||||||
@@ -120,17 +126,17 @@ mod tests {
|
|||||||
use crate::parser::parser_context::ContextTree;
|
use crate::parser::parser_context::ContextTree;
|
||||||
use crate::parser::parser_with_context::parser_with_context;
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
use crate::parser::source::Source;
|
use crate::parser::source::Source;
|
||||||
|
use crate::parser::Bold;
|
||||||
use crate::parser::PlainText;
|
use crate::parser::PlainText;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_text_radio_target() {
|
fn plain_text_radio_target() {
|
||||||
let input = "foo bar baz";
|
let input = "foo bar baz";
|
||||||
|
let radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })];
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context = initial_context
|
let document_context = initial_context
|
||||||
.with_additional_node(ContextElement::DocumentRoot(input))
|
.with_additional_node(ContextElement::DocumentRoot(input))
|
||||||
.with_additional_node(ContextElement::RadioTarget(vec![vec![Object::PlainText(
|
.with_additional_node(ContextElement::RadioTarget(vec![&radio_target_match]));
|
||||||
PlainText { source: "bar" },
|
|
||||||
)]]));
|
|
||||||
let paragraph_matcher = parser_with_context!(element(true))(&document_context);
|
let paragraph_matcher = parser_with_context!(element(true))(&document_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 {
|
||||||
@@ -151,4 +157,39 @@ mod tests {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bold_radio_target() {
|
||||||
|
let input = "foo *bar* baz";
|
||||||
|
let radio_target_match = vec![Object::Bold(Bold {
|
||||||
|
source: "*bar*",
|
||||||
|
children: vec![Object::PlainText(PlainText { source: "bar" })],
|
||||||
|
})];
|
||||||
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
|
let document_context = initial_context
|
||||||
|
.with_additional_node(ContextElement::DocumentRoot(input))
|
||||||
|
.with_additional_node(ContextElement::RadioTarget(vec![&radio_target_match]));
|
||||||
|
let paragraph_matcher = parser_with_context!(element(true))(&document_context);
|
||||||
|
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
||||||
|
let first_paragraph = match first_paragraph {
|
||||||
|
crate::parser::Element::Paragraph(paragraph) => paragraph,
|
||||||
|
_ => panic!("Should be a paragraph!"),
|
||||||
|
};
|
||||||
|
assert_eq!(remaining, "");
|
||||||
|
assert_eq!(first_paragraph.get_source(), "foo *bar* baz");
|
||||||
|
assert_eq!(first_paragraph.children.len(), 3);
|
||||||
|
assert_eq!(
|
||||||
|
first_paragraph
|
||||||
|
.children
|
||||||
|
.get(1)
|
||||||
|
.expect("Len already asserted to be 3"),
|
||||||
|
&Object::RadioLink(RadioLink {
|
||||||
|
source: "*bar* ",
|
||||||
|
children: vec![Object::Bold(Bold {
|
||||||
|
source: "*bar* ",
|
||||||
|
children: vec![Object::PlainText(PlainText { source: "bar" })]
|
||||||
|
})]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
|||||||
_ => false,
|
_ => false,
|
||||||
}),
|
}),
|
||||||
'\\',
|
'\\',
|
||||||
one_of(r#""n"#),
|
one_of(r#""n\\"#),
|
||||||
)(remaining)?;
|
)(remaining)?;
|
||||||
let (remaining, _) = tag(r#"""#)(remaining)?;
|
let (remaining, _) = tag(r#"""#)(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
@@ -298,4 +298,27 @@ mod tests {
|
|||||||
r#"baz"#
|
r#"baz"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_containing_escaped_characters() {
|
||||||
|
let input = r#" (foo "\\( x=2 \\)" bar) "#;
|
||||||
|
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
|
||||||
|
assert_eq!(remaining, "");
|
||||||
|
assert!(match parsed {
|
||||||
|
Token::Atom(_) => false,
|
||||||
|
Token::List(_) => true,
|
||||||
|
Token::TextWithProperties(_) => false,
|
||||||
|
});
|
||||||
|
let children = match parsed {
|
||||||
|
Token::List(children) => children,
|
||||||
|
_ => panic!("Should be a list."),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
match children.get(1) {
|
||||||
|
Some(Token::Atom(body)) => *body,
|
||||||
|
_ => panic!("First child should be an atom."),
|
||||||
|
},
|
||||||
|
r#""\\( x=2 \\)""#
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
48
src/parser/statistics_cookie.rs
Normal file
48
src/parser/statistics_cookie.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::space0;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
use crate::error::Res;
|
||||||
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
|
use crate::parser::StatisticsCookie;
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn statistics_cookie<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, StatisticsCookie<'s>> {
|
||||||
|
alt((
|
||||||
|
parser_with_context!(percent_statistics_cookie)(context),
|
||||||
|
parser_with_context!(fraction_statistics_cookie)(context),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn percent_statistics_cookie<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, StatisticsCookie<'s>> {
|
||||||
|
let (remaining, source) =
|
||||||
|
recognize(tuple((tag("["), nom::character::complete::u64, tag("%]"))))(input)?;
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
Ok((remaining, StatisticsCookie { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn fraction_statistics_cookie<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, StatisticsCookie<'s>> {
|
||||||
|
let (remaining, source) = recognize(tuple((
|
||||||
|
tag("["),
|
||||||
|
nom::character::complete::u64,
|
||||||
|
tag("/"),
|
||||||
|
nom::character::complete::u64,
|
||||||
|
tag("]"),
|
||||||
|
)))(input)?;
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
Ok((remaining, StatisticsCookie { source }))
|
||||||
|
}
|
||||||
204
src/parser/subscript_and_superscript.rs
Normal file
204
src/parser/subscript_and_superscript.rs
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
|
use nom::character::complete::one_of;
|
||||||
|
use nom::character::complete::space0;
|
||||||
|
use nom::combinator::map;
|
||||||
|
use nom::combinator::not;
|
||||||
|
use nom::combinator::opt;
|
||||||
|
use nom::combinator::peek;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many_till;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
use super::Object;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::MyError;
|
||||||
|
use crate::error::Res;
|
||||||
|
use crate::parser::exiting::ExitClass;
|
||||||
|
use crate::parser::object_parser::standard_set_object;
|
||||||
|
use crate::parser::parser_context::ContextElement;
|
||||||
|
use crate::parser::parser_context::ExitMatcherNode;
|
||||||
|
use crate::parser::parser_context::SubscriptSuperscriptBrace;
|
||||||
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
|
use crate::parser::util::exit_matcher_parser;
|
||||||
|
use crate::parser::util::get_consumed;
|
||||||
|
use crate::parser::util::get_one_before;
|
||||||
|
use crate::parser::Subscript;
|
||||||
|
use crate::parser::Superscript;
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn subscript<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Subscript<'s>> {
|
||||||
|
// We check for the underscore first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
|
||||||
|
let (remaining, _) = tag("_")(input)?;
|
||||||
|
pre(context, input)?;
|
||||||
|
let (remaining, _body) = script_body(context, remaining)?;
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, Subscript { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn superscript<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, Superscript<'s>> {
|
||||||
|
// We check for the circumflex first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
|
||||||
|
let (remaining, _) = tag("^")(input)?;
|
||||||
|
pre(context, input)?;
|
||||||
|
let (remaining, _body) = script_body(context, remaining)?;
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, Superscript { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
||||||
|
let document_root = context.get_document_root().unwrap();
|
||||||
|
let preceding_character = get_one_before(document_root, input)
|
||||||
|
.map(|slice| slice.chars().next())
|
||||||
|
.flatten();
|
||||||
|
match preceding_character {
|
||||||
|
Some(c) if !c.is_whitespace() => {}
|
||||||
|
_ => {
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"Must be preceded by a non-whitespace character.",
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((input, ()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ScriptBody<'s> {
|
||||||
|
Braceless(&'s str),
|
||||||
|
WithBraces(Vec<Object<'s>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn script_body<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ScriptBody<'s>> {
|
||||||
|
alt((
|
||||||
|
map(parser_with_context!(script_asterisk)(context), |body| {
|
||||||
|
ScriptBody::Braceless(body)
|
||||||
|
}),
|
||||||
|
map(parser_with_context!(script_alphanum)(context), |body| {
|
||||||
|
ScriptBody::Braceless(body)
|
||||||
|
}),
|
||||||
|
map(parser_with_context!(script_with_braces)(context), |body| {
|
||||||
|
ScriptBody::WithBraces(body)
|
||||||
|
}),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn script_asterisk<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
tag("*")(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn script_alphanum<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, _sign) = opt(recognize(one_of("+-")))(input)?;
|
||||||
|
let (remaining, _script) = many_till(
|
||||||
|
parser_with_context!(script_alphanum_character)(context),
|
||||||
|
parser_with_context!(end_script_alphanum_character)(context),
|
||||||
|
)(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn script_alphanum_character<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, &'s str> {
|
||||||
|
recognize(verify(anychar, |c| {
|
||||||
|
c.is_alphanumeric() || r#",.\"#.contains(*c)
|
||||||
|
}))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn end_script_alphanum_character<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?;
|
||||||
|
peek(not(parser_with_context!(script_alphanum_character)(
|
||||||
|
context,
|
||||||
|
)))(remaining)?;
|
||||||
|
Ok((remaining, final_char))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn script_with_braces<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, Vec<Object<'s>>> {
|
||||||
|
let (remaining, _) = tag("{")(input)?;
|
||||||
|
let parser_context = context
|
||||||
|
.with_additional_node(ContextElement::SubscriptSuperscriptBrace(
|
||||||
|
SubscriptSuperscriptBrace {
|
||||||
|
position: remaining,
|
||||||
|
depth: 0,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &script_with_braces_end,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let (remaining, (children, _exit_contents)) = many_till(
|
||||||
|
parser_with_context!(standard_set_object)(&parser_context),
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
)(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, _) = tag("}")(remaining)?;
|
||||||
|
Ok((remaining, children))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn script_with_braces_end<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, &'s str> {
|
||||||
|
let context_depth = get_bracket_depth(context)
|
||||||
|
.expect("This function should only be called from inside a subscript or superscript.");
|
||||||
|
let text_since_context_entry = get_consumed(context_depth.position, input);
|
||||||
|
let mut current_depth = context_depth.depth;
|
||||||
|
for c in text_since_context_entry.chars() {
|
||||||
|
match c {
|
||||||
|
'{' => {
|
||||||
|
current_depth += 1;
|
||||||
|
}
|
||||||
|
'}' if current_depth == 0 => {
|
||||||
|
panic!("Exceeded subscript or superscript brace depth.")
|
||||||
|
}
|
||||||
|
'}' if current_depth > 0 => {
|
||||||
|
current_depth -= 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if current_depth == 0 {
|
||||||
|
let close_bracket = tag::<&str, &str, CustomError<&str>>("}")(input);
|
||||||
|
if close_bracket.is_ok() {
|
||||||
|
return close_bracket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"Not a valid end for subscript or superscript.",
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn get_bracket_depth<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
) -> Option<&'r SubscriptSuperscriptBrace<'s>> {
|
||||||
|
for node in context.iter() {
|
||||||
|
match node.get_data() {
|
||||||
|
ContextElement::SubscriptSuperscriptBrace(depth) => return Some(depth),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
60
src/parser/target.rs
Normal file
60
src/parser/target.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
|
use nom::character::complete::one_of;
|
||||||
|
use nom::character::complete::space0;
|
||||||
|
use nom::combinator::peek;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many_till;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::MyError;
|
||||||
|
use crate::error::Res;
|
||||||
|
use crate::parser::exiting::ExitClass;
|
||||||
|
use crate::parser::parser_context::ContextElement;
|
||||||
|
use crate::parser::parser_context::ExitMatcherNode;
|
||||||
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
|
use crate::parser::util::exit_matcher_parser;
|
||||||
|
use crate::parser::util::get_consumed;
|
||||||
|
use crate::parser::util::get_one_before;
|
||||||
|
use crate::parser::Target;
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn target<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Target<'s>> {
|
||||||
|
let (remaining, _) = tag("<<")(input)?;
|
||||||
|
let (remaining, _) = peek(verify(anychar, |c| {
|
||||||
|
!c.is_whitespace() && !"<>\n".contains(*c)
|
||||||
|
}))(remaining)?;
|
||||||
|
|
||||||
|
let parser_context =
|
||||||
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &target_end,
|
||||||
|
}));
|
||||||
|
let (remaining, _body) = recognize(many_till(
|
||||||
|
anychar,
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
))(remaining)?;
|
||||||
|
|
||||||
|
let document_root = context.get_document_root().unwrap();
|
||||||
|
let preceding_character = get_one_before(document_root, remaining)
|
||||||
|
.map(|slice| slice.chars().next())
|
||||||
|
.flatten()
|
||||||
|
.expect("We cannot be at the start of the file because we are inside a target.");
|
||||||
|
if preceding_character.is_whitespace() {
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"Targets cannot end with whitespace.",
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
let (remaining, _) = tag(">>")(remaining)?;
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
|
Ok((remaining, Target { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn target_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
recognize(one_of("<>\n"))(input)
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ use nom::combinator::recognize;
|
|||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::terminated;
|
use nom::sequence::terminated;
|
||||||
use nom::sequence::tuple;
|
|
||||||
use tracing::span;
|
use tracing::span;
|
||||||
|
|
||||||
use super::radio_link::RematchObject;
|
use super::radio_link::RematchObject;
|
||||||
@@ -205,7 +204,7 @@ pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()>
|
|||||||
match preceding_character {
|
match preceding_character {
|
||||||
// If None, we are at the start of the file which is technically the beginning of a line.
|
// If None, we are at the start of the file which is technically the beginning of a line.
|
||||||
None | Some('\r') | Some('\n') | Some(' ') | Some('\t') | Some('-') | Some('(')
|
None | Some('\r') | Some('\n') | Some(' ') | Some('\t') | Some('-') | Some('(')
|
||||||
| Some('{') | Some('\'') | Some('"') => {}
|
| Some('{') | Some('\'') | Some('"') | Some('<') => {}
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
// Not at start of line, cannot be a heading
|
// Not at start of line, cannot be a heading
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
@@ -218,7 +217,7 @@ pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()>
|
|||||||
|
|
||||||
#[tracing::instrument(ret, level = "debug")]
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
pub fn post<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
pub fn post<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
||||||
let (remaining, _) = alt((recognize(one_of(" \r\n\t-.,;:!?')}[\"")), line_ending))(input)?;
|
let (remaining, _) = alt((recognize(one_of(" \r\n\t-.,;:!?')}[\">")), line_ending))(input)?;
|
||||||
Ok((remaining, ()))
|
Ok((remaining, ()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,5 +290,5 @@ fn _rematch_text_markup_object<'r, 's, 'x>(
|
|||||||
|
|
||||||
let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
|
let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
|
||||||
let (remaining, _trailing_whitespace) = space0(remaining)?;
|
let (remaining, _trailing_whitespace) = space0(remaining)?;
|
||||||
Ok((remaining, Vec::new()))
|
Ok((remaining, children))
|
||||||
}
|
}
|
||||||
|
|||||||
357
src/parser/timestamp.rs
Normal file
357
src/parser/timestamp.rs
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
|
use nom::character::complete::digit1;
|
||||||
|
use nom::character::complete::one_of;
|
||||||
|
use nom::character::complete::space0;
|
||||||
|
use nom::character::complete::space1;
|
||||||
|
use nom::combinator::opt;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many_till;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
use crate::error::Res;
|
||||||
|
use crate::parser::exiting::ExitClass;
|
||||||
|
use crate::parser::parser_context::ContextElement;
|
||||||
|
use crate::parser::parser_context::ContextTree;
|
||||||
|
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::get_consumed;
|
||||||
|
use crate::parser::Timestamp;
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
pub fn timestamp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Timestamp<'s>> {
|
||||||
|
// TODO: This would be more efficient if we didn't throw away the parse result of the first half of an active/inactive date range timestamp if the parse fails (as in, the first thing active_date_range_timestamp parses is a active_timestamp but then we throw that away if it doesn't turn out to be a full active_date_range_timestamp despite the active_timestamp parse being completely valid). I am going with the simplest/cleanest approach for the first implementation.
|
||||||
|
alt((
|
||||||
|
// Order matters here. If its a date range, we need to parse the entire date range instead of just the first timestamp. If its a time range, we need to make sure thats parsed as a time range instead of as the "rest" portion of a single timestamp.
|
||||||
|
parser_with_context!(diary_timestamp)(context),
|
||||||
|
parser_with_context!(active_time_range_timestamp)(context),
|
||||||
|
parser_with_context!(inactive_time_range_timestamp)(context),
|
||||||
|
parser_with_context!(active_date_range_timestamp)(context),
|
||||||
|
parser_with_context!(inactive_date_range_timestamp)(context),
|
||||||
|
parser_with_context!(active_timestamp)(context),
|
||||||
|
parser_with_context!(inactive_timestamp)(context),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn diary_timestamp<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, Timestamp<'s>> {
|
||||||
|
let (remaining, _) = tag("<%%(")(input)?;
|
||||||
|
let (remaining, _body) = sexp(context, remaining)?;
|
||||||
|
let (remaining, _) = tag(")>")(remaining)?;
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
|
Ok((remaining, Timestamp { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn sexp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let parser_context =
|
||||||
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &sexp_end,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let (remaining, body) = recognize(verify(
|
||||||
|
many_till(
|
||||||
|
anychar,
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
),
|
||||||
|
|(body, _end_contents)| !body.is_empty(),
|
||||||
|
))(input)?;
|
||||||
|
|
||||||
|
Ok((remaining, body))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn sexp_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
alt((tag(")>"), recognize(one_of(">\n"))))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn active_timestamp<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, Timestamp<'s>> {
|
||||||
|
let (remaining, _) = tag("<")(input)?;
|
||||||
|
let (remaining, _date) = date(context, remaining)?;
|
||||||
|
let time_context =
|
||||||
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &active_time_rest_end,
|
||||||
|
}));
|
||||||
|
let (remaining, _time) =
|
||||||
|
opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?;
|
||||||
|
let (remaining, _repeater) =
|
||||||
|
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
||||||
|
let (remaining, _warning_delay) = opt(tuple((
|
||||||
|
space1,
|
||||||
|
parser_with_context!(warning_delay)(context),
|
||||||
|
)))(remaining)?;
|
||||||
|
let (remaining, _) = tag(">")(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
|
Ok((remaining, Timestamp { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn inactive_timestamp<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, Timestamp<'s>> {
|
||||||
|
let (remaining, _) = tag("[")(input)?;
|
||||||
|
let (remaining, _date) = date(context, remaining)?;
|
||||||
|
let time_context =
|
||||||
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &inactive_time_rest_end,
|
||||||
|
}));
|
||||||
|
let (remaining, _time) =
|
||||||
|
opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?;
|
||||||
|
let (remaining, _repeater) =
|
||||||
|
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
||||||
|
let (remaining, _warning_delay) = opt(tuple((
|
||||||
|
space1,
|
||||||
|
parser_with_context!(warning_delay)(context),
|
||||||
|
)))(remaining)?;
|
||||||
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
|
Ok((remaining, Timestamp { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn active_date_range_timestamp<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, Timestamp<'s>> {
|
||||||
|
let (remaining, _first_timestamp) = active_timestamp(context, input)?;
|
||||||
|
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
||||||
|
let (remaining, _separator) = tag("--")(remaining)?;
|
||||||
|
let (remaining, _second_timestamp) = active_timestamp(context, remaining)?;
|
||||||
|
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
|
Ok((remaining, Timestamp { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn active_time_range_timestamp<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, Timestamp<'s>> {
|
||||||
|
let (remaining, _) = tag("<")(input)?;
|
||||||
|
let (remaining, _date) = date(context, remaining)?;
|
||||||
|
let time_context =
|
||||||
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &active_time_rest_end,
|
||||||
|
}));
|
||||||
|
let first_time_context =
|
||||||
|
time_context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &time_range_rest_end,
|
||||||
|
}));
|
||||||
|
let (remaining, _first_time) =
|
||||||
|
tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?;
|
||||||
|
let (remaining, _) = tag("-")(remaining)?;
|
||||||
|
let (remaining, _second_time) = parser_with_context!(time)(&time_context)(remaining)?;
|
||||||
|
let (remaining, _repeater) =
|
||||||
|
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
||||||
|
let (remaining, _warning_delay) = opt(tuple((
|
||||||
|
space1,
|
||||||
|
parser_with_context!(warning_delay)(context),
|
||||||
|
)))(remaining)?;
|
||||||
|
let (remaining, _) = tag(">")(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
|
Ok((remaining, Timestamp { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn inactive_date_range_timestamp<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, Timestamp<'s>> {
|
||||||
|
let (remaining, _first_timestamp) = inactive_timestamp(context, input)?;
|
||||||
|
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
||||||
|
let (remaining, _separator) = tag("--")(remaining)?;
|
||||||
|
let (remaining, _second_timestamp) = inactive_timestamp(context, remaining)?;
|
||||||
|
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
|
Ok((remaining, Timestamp { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn inactive_time_range_timestamp<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, Timestamp<'s>> {
|
||||||
|
let (remaining, _) = tag("[")(input)?;
|
||||||
|
let (remaining, _date) = date(context, remaining)?;
|
||||||
|
let time_context =
|
||||||
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &inactive_time_rest_end,
|
||||||
|
}));
|
||||||
|
let first_time_context =
|
||||||
|
time_context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &time_range_rest_end,
|
||||||
|
}));
|
||||||
|
let (remaining, _first_time) =
|
||||||
|
tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?;
|
||||||
|
let (remaining, _) = tag("-")(remaining)?;
|
||||||
|
let (remaining, _second_time) = parser_with_context!(time)(&time_context)(remaining)?;
|
||||||
|
let (remaining, _repeater) =
|
||||||
|
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
||||||
|
let (remaining, _warning_delay) = opt(tuple((
|
||||||
|
space1,
|
||||||
|
parser_with_context!(warning_delay)(context),
|
||||||
|
)))(remaining)?;
|
||||||
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
|
Ok((remaining, Timestamp { source }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn date<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, _year) = verify(digit1, |year: &str| year.len() == 4)(input)?;
|
||||||
|
let (remaining, _) = tag("-")(remaining)?;
|
||||||
|
let (remaining, _month) = verify(digit1, |month: &str| month.len() == 2)(remaining)?;
|
||||||
|
let (remaining, _) = tag("-")(remaining)?;
|
||||||
|
let (remaining, _day_of_month) =
|
||||||
|
verify(digit1, |day_of_month: &str| day_of_month.len() == 2)(remaining)?;
|
||||||
|
let (remaining, _dayname) =
|
||||||
|
opt(tuple((space1, parser_with_context!(dayname)(context))))(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn dayname<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let parser_context =
|
||||||
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &dayname_end,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let (remaining, body) = recognize(verify(
|
||||||
|
many_till(
|
||||||
|
anychar,
|
||||||
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
|
),
|
||||||
|
|(body, _end_contents)| !body.is_empty(),
|
||||||
|
))(input)?;
|
||||||
|
|
||||||
|
Ok((remaining, body))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn dayname_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
recognize(verify(anychar, |c| {
|
||||||
|
c.is_whitespace() || "+-]>0123456789\n".contains(*c)
|
||||||
|
}))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn time<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, _hour) =
|
||||||
|
verify(digit1, |hour: &str| hour.len() >= 1 && hour.len() <= 2)(input)?;
|
||||||
|
let (remaining, _) = tag(":")(remaining)?;
|
||||||
|
let (remaining, _minute) = verify(digit1, |minute: &str| minute.len() == 2)(remaining)?;
|
||||||
|
let (remaining, _time_rest) = opt(parser_with_context!(time_rest)(context))(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn time_rest<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
let (remaining, body) = recognize(verify(
|
||||||
|
many_till(anychar, parser_with_context!(exit_matcher_parser)(context)),
|
||||||
|
|(body, _end_contents)| !body.is_empty(),
|
||||||
|
))(input)?;
|
||||||
|
|
||||||
|
Ok((remaining, body))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn active_time_rest_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
alt((
|
||||||
|
recognize(verify(anychar, |c| ">\n".contains(*c))),
|
||||||
|
recognize(tuple((space1, parser_with_context!(repeater)(context)))),
|
||||||
|
recognize(tuple((
|
||||||
|
space1,
|
||||||
|
parser_with_context!(warning_delay)(context),
|
||||||
|
))),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn inactive_time_rest_end<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: &'s str,
|
||||||
|
) -> Res<&'s str, &'s str> {
|
||||||
|
alt((
|
||||||
|
recognize(verify(anychar, |c| "]\n".contains(*c))),
|
||||||
|
recognize(tuple((space1, parser_with_context!(repeater)(context)))),
|
||||||
|
recognize(tuple((
|
||||||
|
space1,
|
||||||
|
parser_with_context!(warning_delay)(context),
|
||||||
|
))),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn time_range_rest_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
// We pop off the most recent context element to get a context tree with just the active/inactive_time_rest_end exit matcher (removing this function from the exit matcher chain) because the 2nd time in the range does not end when a "-TIME" pattern is found.
|
||||||
|
let parent_node = context.iter().next().expect("Two context elements are added to the tree when adding this exit matcher, so it should be impossible for this to return None.");
|
||||||
|
let parent_tree = ContextTree::branch_from(parent_node);
|
||||||
|
let exit_contents =
|
||||||
|
recognize(tuple((tag("-"), parser_with_context!(time)(&parent_tree))))(input);
|
||||||
|
exit_contents
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn repeater<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
// + for cumulative type
|
||||||
|
// ++ for catch-up type
|
||||||
|
// .+ for restart type
|
||||||
|
let (remaining, _mark) = alt((tag("++"), tag("+"), tag(".+")))(input)?;
|
||||||
|
let (remaining, _value) = digit1(remaining)?;
|
||||||
|
// h = hour, d = day, w = week, m = month, y = year
|
||||||
|
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, level = "debug")]
|
||||||
|
fn warning_delay<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
||||||
|
// - for all type
|
||||||
|
// -- for first type
|
||||||
|
let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?;
|
||||||
|
let (remaining, _value) = digit1(remaining)?;
|
||||||
|
// h = hour, d = day, w = week, m = month, y = year
|
||||||
|
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
@@ -1,12 +1,129 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use super::Document;
|
use super::Document;
|
||||||
use super::Element;
|
use super::Element;
|
||||||
use super::Heading;
|
use super::Heading;
|
||||||
use super::Object;
|
use super::Object;
|
||||||
|
use super::PlainListItem;
|
||||||
use super::Section;
|
use super::Section;
|
||||||
|
use super::TableCell;
|
||||||
|
use super::TableRow;
|
||||||
|
use crate::parser::DocumentElement;
|
||||||
|
|
||||||
pub enum Token<'r, 's> {
|
pub enum Token<'r, 's> {
|
||||||
|
Document(&'r Document<'s>),
|
||||||
Heading(&'r Heading<'s>),
|
Heading(&'r Heading<'s>),
|
||||||
Section(&'r Section<'s>),
|
Section(&'r Section<'s>),
|
||||||
Object(&'r Object<'s>),
|
Object(&'r Object<'s>),
|
||||||
Element(&'r Element<'s>),
|
Element(&'r Element<'s>),
|
||||||
|
PlainListItem(&'r PlainListItem<'s>),
|
||||||
|
TableRow(&'r TableRow<'s>),
|
||||||
|
TableCell(&'r TableCell<'s>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> Token<'r, 's> {
|
||||||
|
pub fn iter_tokens(&self) -> Box<dyn Iterator<Item = Token<'r, 's>> + '_> {
|
||||||
|
match self {
|
||||||
|
Token::Document(document) => Box::new(
|
||||||
|
document
|
||||||
|
.zeroth_section
|
||||||
|
.iter()
|
||||||
|
.map(Token::Section)
|
||||||
|
.chain(document.children.iter().map(Token::Heading)),
|
||||||
|
),
|
||||||
|
Token::Heading(heading) => Box::new(heading.title.iter().map(Token::Object).chain(
|
||||||
|
heading.children.iter().map(|de| match de {
|
||||||
|
DocumentElement::Heading(ref obj) => Token::Heading(obj),
|
||||||
|
DocumentElement::Section(ref obj) => Token::Section(obj),
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
Token::Section(section) => Box::new(section.children.iter().map(Token::Element)),
|
||||||
|
Token::Object(obj) => match obj {
|
||||||
|
Object::Bold(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||||
|
Object::Italic(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||||
|
Object::Underline(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||||
|
Object::StrikeThrough(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||||
|
Object::Code(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::Verbatim(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::PlainText(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::RegularLink(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::RadioLink(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||||
|
Object::RadioTarget(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||||
|
Object::PlainLink(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::AngleLink(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::OrgMacro(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::Entity(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::LatexFragment(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::ExportSnippet(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::FootnoteReference(inner) => {
|
||||||
|
Box::new(inner.definition.iter().map(Token::Object))
|
||||||
|
}
|
||||||
|
Object::Citation(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
|
||||||
|
Object::CitationReference(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
|
||||||
|
Object::InlineBabelCall(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::InlineSourceBlock(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::LineBreak(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::Target(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::StatisticsCookie(_) => Box::new(std::iter::empty()),
|
||||||
|
Object::Subscript(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
|
||||||
|
Object::Superscript(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
|
||||||
|
Object::Timestamp(_) => Box::new(std::iter::empty()),
|
||||||
|
},
|
||||||
|
Token::Element(elem) => match elem {
|
||||||
|
Element::Paragraph(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||||
|
Element::PlainList(inner) => {
|
||||||
|
Box::new(inner.children.iter().map(Token::PlainListItem))
|
||||||
|
}
|
||||||
|
Element::GreaterBlock(inner) => Box::new(inner.children.iter().map(Token::Element)),
|
||||||
|
Element::DynamicBlock(inner) => Box::new(inner.children.iter().map(Token::Element)),
|
||||||
|
Element::FootnoteDefinition(inner) => {
|
||||||
|
Box::new(inner.children.iter().map(Token::Element))
|
||||||
|
}
|
||||||
|
Element::Comment(_) => Box::new(std::iter::empty()),
|
||||||
|
Element::Drawer(inner) => Box::new(inner.children.iter().map(Token::Element)),
|
||||||
|
Element::PropertyDrawer(_) => Box::new(std::iter::empty()),
|
||||||
|
Element::Table(inner) => Box::new(inner.children.iter().map(Token::TableRow)),
|
||||||
|
Element::VerseBlock(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||||
|
Element::CommentBlock(_) => Box::new(std::iter::empty()),
|
||||||
|
Element::ExampleBlock(_) => Box::new(std::iter::empty()),
|
||||||
|
Element::ExportBlock(_) => Box::new(std::iter::empty()),
|
||||||
|
Element::SrcBlock(_) => Box::new(std::iter::empty()),
|
||||||
|
Element::Clock(_) => Box::new(std::iter::empty()),
|
||||||
|
Element::DiarySexp(_) => Box::new(std::iter::empty()),
|
||||||
|
Element::Planning(_) => Box::new(std::iter::empty()),
|
||||||
|
Element::FixedWidthArea(_) => Box::new(std::iter::empty()),
|
||||||
|
Element::HorizontalRule(_) => Box::new(std::iter::empty()),
|
||||||
|
Element::Keyword(_) => Box::new(std::iter::empty()),
|
||||||
|
Element::LatexEnvironment(_) => Box::new(std::iter::empty()),
|
||||||
|
},
|
||||||
|
Token::PlainListItem(elem) => Box::new(elem.children.iter().map(Token::Element)),
|
||||||
|
Token::TableRow(elem) => Box::new(elem.children.iter().map(Token::TableCell)),
|
||||||
|
Token::TableCell(elem) => Box::new(elem.children.iter().map(Token::Object)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AllTokensIterator<'r, 's> {
|
||||||
|
queued_tokens: VecDeque<Token<'r, 's>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> AllTokensIterator<'r, 's> {
|
||||||
|
pub fn new(tkn: Token<'r, 's>) -> Self {
|
||||||
|
let mut queued_tokens = VecDeque::new();
|
||||||
|
queued_tokens.push_back(tkn);
|
||||||
|
AllTokensIterator { queued_tokens }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> Iterator for AllTokensIterator<'r, 's> {
|
||||||
|
type Item = Token<'r, 's>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let next_token = match self.queued_tokens.pop_front() {
|
||||||
|
Some(tkn) => tkn,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
self.queued_tokens.extend(next_token.iter_tokens());
|
||||||
|
Some(next_token)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,32 @@ pub fn get_one_before<'s>(document: &'s str, current_position: &'s str) -> Optio
|
|||||||
Some(&document[previous_character_offset..offset])
|
Some(&document[previous_character_offset..offset])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the line current_position is on up until current_position
|
||||||
|
pub fn get_current_line_before_position<'s>(
|
||||||
|
document: &'s str,
|
||||||
|
current_position: &'s str,
|
||||||
|
) -> Option<&'s str> {
|
||||||
|
assert!(is_slice_of(document, current_position));
|
||||||
|
if document.as_ptr() as usize == current_position.as_ptr() as usize {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let offset = current_position.as_ptr() as usize - document.as_ptr() as usize;
|
||||||
|
let mut previous_character_offset = offset;
|
||||||
|
loop {
|
||||||
|
let new_offset = document.floor_char_boundary(previous_character_offset - 1);
|
||||||
|
let new_line = &document[new_offset..offset];
|
||||||
|
let leading_char = new_line
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.expect("Impossible to not have at least 1 character to read.");
|
||||||
|
if "\r\n".contains(leading_char) || new_offset == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
previous_character_offset = new_offset;
|
||||||
|
}
|
||||||
|
Some(&document[previous_character_offset..offset])
|
||||||
|
}
|
||||||
|
|
||||||
/// 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 {
|
||||||
let parent_start = parent.as_ptr() as usize;
|
let parent_start = parent.as_ptr() as usize;
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
foo *bar /baz *lorem* ipsum/ dolar* alpha
|
foo bar baz
|
||||||
|
|
||||||
foo *bar /baz _lorem_ ipsum/ dolar* alpha
|
lorem ipsum
|
||||||
|
|||||||
Reference in New Issue
Block a user