57 Commits

Author SHA1 Message Date
Tom Alexander
06b83f9156 Merge branch 'publish_crate'
Some checks failed
rustfmt Build rustfmt has failed
rust-test Build rust-test has succeeded
2023-07-27 21:06:05 -04:00
Tom Alexander
ef31900b51 Remove semver pipeline.
Since I'm publishing this to crates.io it doesn't make sense to have a separate version string from the one in Cargo.toml.
2023-07-27 21:05:27 -04:00
Tom Alexander
8a221e0e0d Add metadata for publishing crate. 2023-07-27 21:05:27 -04:00
Tom Alexander
f359676e28 Separate out the persistent volumes for cargo cache for tekton pipelines.
All checks were successful
semver Build semver has succeeded
rust-test Build rust-test has succeeded
rustfmt Build rustfmt has succeeded
Both pipelines running simultaneously is causing non-deterministic failures.
2023-07-27 20:46:18 -04:00
Tom Alexander
7d7446d843 Merge branch 'timestamp'
Some checks failed
semver Build semver has succeeded
rustfmt Build rustfmt has failed
rust-test Build rust-test has succeeded
2023-07-27 20:39:51 -04:00
Tom Alexander
17e523b74c Do not consume space in time rest when leading into a repeater or delay. 2023-07-27 20:39:13 -04:00
Tom Alexander
ece8fcd0c4 Implement parser for active/inactive timestamp time ranges. 2023-07-27 20:26:56 -04:00
Tom Alexander
1a5b7ca30c Implement parser for active/inactive timestamp date ranges. 2023-07-27 19:59:36 -04:00
Tom Alexander
b5a029e2bf Implement parser for inactive timestamps. 2023-07-27 19:54:33 -04:00
Tom Alexander
c5f81298ba Switch to passing in the exit matcher from a higher context to reduce the permutations of functions. 2023-07-27 19:52:35 -04:00
Tom Alexander
529418a9d1 Implement parser for active timestamps. 2023-07-27 19:45:57 -04:00
Tom Alexander
d4a3628481 Implement parser for date. 2023-07-27 19:20:28 -04:00
Tom Alexander
70f2747291 Implement parser for diary timestamps. 2023-07-27 18:59:32 -04:00
Tom Alexander
49d5a4e4b5 Call out to the parsers for the various timestamp types.
The parsers for those types remain unimplemented.
2023-07-24 17:54:49 -04:00
Tom Alexander
fa5fc41121 Create structure for timestamps. 2023-07-24 17:34:07 -04:00
Tom Alexander
73e15286dc Add test cases. 2023-07-24 17:15:27 -04:00
fluxcdbot
efa56a6bef CI: autofix rust code.
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-24 16:57:08 -04:00
Tom Alexander
3b11d8fb61 Merge branch 'subscript_superscript'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-24 16:39:35 -04:00
Tom Alexander
63fcad2ac6 Enable tests that needed subscript/superscript implemented. 2023-07-24 16:32:56 -04:00
Tom Alexander
23d587c699 Implement parser for subscript/superscript with braces. 2023-07-24 16:29:31 -04:00
Tom Alexander
f717d5e7df Implement parser for braceless subscript/superscript. 2023-07-24 15:41:14 -04:00
Tom Alexander
6d4379d029 Add a License section to the README. 2023-07-24 14:44:27 -04:00
Tom Alexander
8c00ee24ba Add a test to prove that subscript/superscript cannot start without a leading character even though its at the start of the file. 2023-07-24 14:33:04 -04:00
Tom Alexander
4a565601c1 Add test cases. 2023-07-24 14:29:20 -04:00
Tom Alexander
993c73dc9f Create structure for subscript and superscript. 2023-07-24 14:23:36 -04:00
Tom Alexander
7d73ac4bf6 Merge branch 'statistics_cookie'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-22 02:12:30 -04:00
Tom Alexander
b8b2e33137 Implement the statistics cookie parser. 2023-07-22 02:12:21 -04:00
Tom Alexander
c73e26e2d6 Create structure for statistics cookies. 2023-07-22 02:12:20 -04:00
fluxcdbot
abb0aeacaf CI: autofix rust code.
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-22 05:43:17 +00:00
Tom Alexander
cb8775f2f4 Merge branch 'target'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-22 01:40:44 -04:00
Tom Alexander
caa6c41798 Add a test case. 2023-07-22 01:40:27 -04:00
Tom Alexander
e54218c0d7 Implement the target parser. 2023-07-22 01:36:00 -04:00
Tom Alexander
d60cad07e0 Create structure for targets. 2023-07-22 01:15:04 -04:00
Tom Alexander
537fc00fd3 Merge branch 'line_break'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-22 00:44:03 -04:00
Tom Alexander
5c1d913c99 Check that the preceding line for a line break is non-empty. 2023-07-22 00:43:02 -04:00
Tom Alexander
a1f3e9ea47 Implement line break parser. 2023-07-22 00:03:21 -04:00
Tom Alexander
4d114206ef Create structure for line breaks. 2023-07-21 23:48:37 -04:00
Tom Alexander
6b82214ec3 Add test case. 2023-07-21 23:41:29 -04:00
Tom Alexander
fb83e8d453 Merge branch 'inline_source_block'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-21 23:21:22 -04:00
Tom Alexander
80039aa605 Fix bracket counting in inline babel calls. 2023-07-21 23:20:10 -04:00
Tom Alexander
0b41e12424 Fix counting brackets in inline source block. 2023-07-21 23:14:52 -04:00
Tom Alexander
e8979513aa Implement parser for inline source blocks. 2023-07-21 22:51:19 -04:00
Tom Alexander
e0d2bb8213 Create structure for inline source blocks. 2023-07-21 22:29:04 -04:00
Tom Alexander
b323a407c4 Merge branch 'inline_babel'
All checks were successful
semver Build semver has succeeded
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
2023-07-21 21:40:18 -04:00
Tom Alexander
45b01012b3 Implement the parser for inline babel calls. 2023-07-21 21:39:53 -04:00
Tom Alexander
2773b35438 Add test case. 2023-07-21 19:53:18 -04:00
Tom Alexander
eef2944307 Create structure for inline babel calls. 2023-07-21 19:53:02 -04:00
Tom Alexander
1e2ea17a9c Merge branch 'citation'
Some checks failed
semver Build semver has succeeded
rustfmt Build rustfmt has failed
rust-test Build rust-test has failed
2023-07-21 18:52:33 -04:00
Tom Alexander
7ce9dafd96 Fix parsing citations inside paragraphs. 2023-07-21 18:52:02 -04:00
Tom Alexander
d678391789 Fix parsing semicolons. 2023-07-21 18:42:22 -04:00
Tom Alexander
640a9375bc Add a testcase populating all the optional fields for citations. 2023-07-21 18:28:16 -04:00
Tom Alexander
1a8bf01fba Fix simple citations by making prefixes and suffixes optional. 2023-07-21 18:19:39 -04:00
Tom Alexander
4ad297f58a Add a test for a simple citation. 2023-07-21 18:00:19 -04:00
Tom Alexander
6b47a6c6c3 Initial implementation for citations.
This implementation definitely has bugs and is completely untested at this point. I'm just committing the initial "assume everything works" version before I did into debugging and fixing.
2023-07-21 17:52:18 -04:00
Tom Alexander
e24b413cd0 Finish first implementation of citation reference. 2023-07-21 16:38:49 -04:00
Tom Alexander
339ff5cd26 Implement key parser and begin key_suffix parser. 2023-07-20 01:13:49 -04:00
Tom Alexander
d5c611674e Create structure for citations. 2023-07-20 00:38:16 -04:00
32 changed files with 2125 additions and 38 deletions

View File

@@ -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

View File

@@ -236,7 +236,7 @@ spec:
subPath: rust-source subPath: rust-source
- name: cargo-cache - name: cargo-cache
persistentVolumeClaim: persistentVolumeClaim:
claimName: cargo-cache claimName: organic-cargo-cache-fmt
- name: docker-credentials - name: docker-credentials
secret: secret:
secretName: harbor-plain secretName: harbor-plain

View File

@@ -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.

View File

@@ -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"

View File

@@ -5,3 +5,6 @@ Organic is an emacs-less implementation of an [[https://orgmode.org/][org-mode]]
* Project Status * 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. 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.

View File

@@ -73,11 +73,6 @@ 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."),
"export_snippet_paragraph_break_precedent" => Some("Emacs 28 has broken behavior so the tests in the CI fail."), "export_snippet_paragraph_break_precedent" => Some("Emacs 28 has broken behavior so the tests in the CI fail."),
_ => None, _ => None,

View File

@@ -0,0 +1,5 @@
[cite:@foo]
[cite/a/b-_/foo:globalprefix;keyprefix @foo keysuffix;globalsuffix]
text before [cite:@bar] text after

View File

@@ -0,0 +1,4 @@
call_foo(arguments)
call_bar[header](arguments)
call_baz(arguments)[header]
call_lorem[header](arguments)[another header]

View File

@@ -0,0 +1,2 @@
before src_foo{ipsum} after
src_bar[lorem]{ipsum}

View File

@@ -0,0 +1,6 @@
foo\\
bar
lorem\\\
ipsum

View File

@@ -0,0 +1,3 @@
[70%]
[3/5]
[-3/5]

View File

@@ -0,0 +1,7 @@
foo^*
bar_*
baz^{hello *world*}
lorem_{}
ipsum^+,\.a5
dolar_,\.a5
text before foo_7 text afterwards

View File

@@ -0,0 +1 @@
_{foo}

View File

@@ -0,0 +1,3 @@
foo <<bar>> baz
lorem << ipsum >> dolar

View 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]

View File

@@ -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;
@@ -23,10 +25,13 @@ 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::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;
@@ -41,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;
@@ -162,6 +172,16 @@ fn compare_object<'s>(
Object::LatexFragment(obj) => compare_latex_fragment(source, emacs, obj), Object::LatexFragment(obj) => compare_latex_fragment(source, emacs, obj),
Object::ExportSnippet(obj) => compare_export_snippet(source, emacs, obj), Object::ExportSnippet(obj) => compare_export_snippet(source, emacs, obj),
Object::FootnoteReference(obj) => compare_footnote_reference(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),
} }
} }
@@ -1338,3 +1358,233 @@ fn compare_footnote_reference<'s>(
children: Vec::new(), 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
View 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]"
})
);
}
}

View 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)
}

View 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
}

View 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
}

59
src/parser/line_break.rs Normal file
View 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, ()))
}

View File

@@ -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;
@@ -16,11 +18,14 @@ 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 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;
@@ -37,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;
@@ -73,12 +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::Entity;
pub use object::ExportSnippet; pub use object::ExportSnippet;
pub use object::FootnoteReference; 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::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;
@@ -86,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;

View File

@@ -19,6 +19,16 @@ pub enum Object<'s> {
LatexFragment(LatexFragment<'s>), LatexFragment(LatexFragment<'s>),
ExportSnippet(ExportSnippet<'s>), ExportSnippet(ExportSnippet<'s>),
FootnoteReference(FootnoteReference<'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)]
@@ -125,6 +135,56 @@ pub struct FootnoteReference<'s> {
pub definition: Vec<Object<'s>>, 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 {
@@ -145,6 +205,16 @@ impl<'s> Source<'s> for Object<'s> {
Object::LatexFragment(obj) => obj.source, Object::LatexFragment(obj) => obj.source,
Object::ExportSnippet(obj) => obj.source, Object::ExportSnippet(obj) => obj.source,
Object::FootnoteReference(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,
} }
} }
} }
@@ -244,3 +314,63 @@ impl<'s> Source<'s> for FootnoteReference<'s> {
self.source 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
}
}

View File

@@ -8,26 +8,55 @@ 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::entity::entity;
use crate::parser::export_snippet::export_snippet; use crate::parser::export_snippet::export_snippet;
use crate::parser::footnote_reference::footnote_reference; 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::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: 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( map(
parser_with_context!(footnote_reference)(context), parser_with_context!(footnote_reference)(context),
Object::FootnoteReference, Object::FootnoteReference,
@@ -63,10 +92,14 @@ 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: 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!(entity)(context), Object::Entity),
map( map(
parser_with_context!(latex_fragment)(context), parser_with_context!(latex_fragment)(context),
@@ -84,6 +117,27 @@ 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( map(
parser_with_context!(footnote_reference)(context), parser_with_context!(footnote_reference)(context),
Object::FootnoteReference, Object::FootnoteReference,
@@ -118,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)
} }

View File

@@ -147,12 +147,65 @@ pub enum ContextElement<'r, 's> {
/// The definition inside a footnote reference must have balanced /// The definition inside a footnote reference must have balanced
/// brackets [] inside the definition, so this stores the amount /// brackets [] inside the definition, so this stores the amount
/// of opening brackets subtracted by the amount of closing /// of opening brackets subtracted by the amount of closing
/// brackets within the definition. /// brackets within the definition must equal zero.
/// ///
/// A reference to the position in the string is also included so /// A reference to the position in the string is also included so
/// unbalanced brackets can be detected in the middle of an /// unbalanced brackets can be detected in the middle of an
/// object. /// object.
FootnoteReferenceDefinition(FootnoteReferenceDefinition<'s>), 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> {
@@ -166,6 +219,30 @@ pub struct FootnoteReferenceDefinition<'s> {
pub depth: usize, 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");

View 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 }))
}

View 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
View 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)
}

357
src/parser/timestamp.rs Normal file
View 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))
}

View File

@@ -58,6 +58,16 @@ impl<'r, 's> Token<'r, 's> {
Object::FootnoteReference(inner) => { Object::FootnoteReference(inner) => {
Box::new(inner.definition.iter().map(Token::Object)) 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 { Token::Element(elem) => match elem {
Element::Paragraph(inner) => Box::new(inner.children.iter().map(Token::Object)), Element::Paragraph(inner) => Box::new(inner.children.iter().map(Token::Object)),

View File

@@ -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;

View File

@@ -1 +1,3 @@
[fn:2:This is a footnote reference since it has the definition inside the brackets. This style is referred to as an "inline footnote".] foo bar baz
lorem ipsum