2023-04-21 20:57:49 +00:00
use nom ::branch ::alt ;
use nom ::bytes ::complete ::is_not ;
use nom ::bytes ::complete ::tag_no_case ;
use nom ::character ::complete ::line_ending ;
use nom ::character ::complete ::space0 ;
use nom ::character ::complete ::space1 ;
use nom ::combinator ::consumed ;
use nom ::combinator ::eof ;
2023-10-04 15:31:45 +00:00
use nom ::combinator ::map ;
2023-04-21 20:57:49 +00:00
use nom ::combinator ::opt ;
2023-09-06 22:25:35 +00:00
use nom ::combinator ::recognize ;
2023-04-21 20:57:49 +00:00
use nom ::combinator ::verify ;
use nom ::multi ::many_till ;
2023-10-04 15:31:45 +00:00
use nom ::multi ::separated_list1 ;
2023-04-21 20:57:49 +00:00
use nom ::sequence ::tuple ;
2023-08-23 04:30:26 +00:00
use super ::org_source ::OrgSource ;
2023-09-03 15:05:34 +00:00
use crate ::context ::parser_with_context ;
use crate ::context ::ContextElement ;
2023-09-03 16:28:45 +00:00
use crate ::context ::ContextMatcher ;
2023-09-03 15:05:34 +00:00
use crate ::context ::ExitClass ;
use crate ::context ::ExitMatcherNode ;
use crate ::context ::RefContext ;
2023-04-23 01:45:18 +00:00
use crate ::error ::Res ;
2023-04-21 20:57:49 +00:00
use crate ::parser ::object_parser ::standard_set_object ;
use crate ::parser ::util ::blank_line ;
use crate ::parser ::util ::exit_matcher_parser ;
use crate ::parser ::util ::get_consumed ;
use crate ::parser ::util ::start_of_line ;
2023-04-23 02:06:34 +00:00
use crate ::parser ::util ::text_until_exit ;
2023-09-03 15:05:34 +00:00
use crate ::types ::CommentBlock ;
use crate ::types ::ExampleBlock ;
use crate ::types ::ExportBlock ;
2023-10-04 15:31:45 +00:00
use crate ::types ::LineNumber ;
2023-09-03 15:05:34 +00:00
use crate ::types ::Object ;
use crate ::types ::PlainText ;
use crate ::types ::SrcBlock ;
2023-10-04 15:31:45 +00:00
use crate ::types ::SwitchNumberLines ;
2023-09-03 15:05:34 +00:00
use crate ::types ::VerseBlock ;
2023-04-21 20:31:25 +00:00
2023-08-11 00:04:59 +00:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-11 17:13:28 +00:00
pub ( crate ) fn verse_block < ' b , ' g , ' r , ' s > (
2023-09-03 19:44:18 +00:00
context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-23 04:30:26 +00:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , VerseBlock < ' s > > {
2023-04-21 22:01:06 +00:00
let ( remaining , name ) = lesser_block_begin ( " verse " ) ( context , input ) ? ;
2023-04-21 20:57:49 +00:00
let ( remaining , parameters ) = opt ( tuple ( ( space1 , data ) ) ) ( remaining ) ? ;
2023-09-06 22:25:35 +00:00
let ( remaining , _nl ) = recognize ( tuple ( ( space0 , line_ending ) ) ) ( remaining ) ? ;
2023-04-21 22:01:06 +00:00
let lesser_block_end_specialized = lesser_block_end ( " verse " ) ;
2023-09-03 15:05:34 +00:00
let contexts = [
ContextElement ::ConsumeTrailingWhitespace ( true ) ,
ContextElement ::Context ( " lesser block " ) ,
ContextElement ::ExitMatcherNode ( ExitMatcherNode {
2023-09-06 18:14:02 +00:00
class : ExitClass ::Alpha ,
2023-04-21 21:11:27 +00:00
exit_matcher : & lesser_block_end_specialized ,
2023-09-03 15:05:34 +00:00
} ) ,
] ;
2023-09-03 16:28:45 +00:00
let parser_context = context . with_additional_node ( & contexts [ 0 ] ) ;
let parser_context = parser_context . with_additional_node ( & contexts [ 1 ] ) ;
let parser_context = parser_context . with_additional_node ( & contexts [ 2 ] ) ;
2023-04-21 20:57:49 +00:00
let parameters = match parameters {
Some ( ( _ws , parameters ) ) = > Some ( parameters ) ,
None = > None ,
} ;
let object_matcher = parser_with_context! ( standard_set_object ) ( & parser_context ) ;
let exit_matcher = parser_with_context! ( exit_matcher_parser ) ( & parser_context ) ;
// Check for a completely empty block
let ( remaining , children ) = match consumed ( many_till ( blank_line , exit_matcher ) ) ( remaining ) {
2023-04-21 22:01:06 +00:00
Ok ( ( remaining , ( whitespace , ( _children , _exit_contents ) ) ) ) = > (
remaining ,
2023-08-23 04:30:26 +00:00
vec! [ Object ::PlainText ( PlainText {
source : whitespace . into ( ) ,
} ) ] ,
2023-04-21 22:01:06 +00:00
) ,
2023-04-21 20:57:49 +00:00
Err ( _ ) = > {
let ( remaining , ( children , _exit_contents ) ) =
many_till ( object_matcher , exit_matcher ) ( remaining ) ? ;
( remaining , children )
}
} ;
2023-04-21 21:11:27 +00:00
let ( remaining , _end ) = lesser_block_end_specialized ( & parser_context , remaining ) ? ;
2023-04-21 20:57:49 +00:00
let source = get_consumed ( input , remaining ) ;
Ok ( (
remaining ,
2023-04-21 21:40:49 +00:00
VerseBlock {
2023-08-23 04:30:26 +00:00
source : source . into ( ) ,
name : name . into ( ) ,
data : parameters . map ( | parameters | Into ::< & str > ::into ( parameters ) ) ,
2023-04-21 20:57:49 +00:00
children ,
} ,
) )
}
2023-08-11 00:04:59 +00:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-11 17:13:28 +00:00
pub ( crate ) fn comment_block < ' b , ' g , ' r , ' s > (
2023-09-03 19:44:18 +00:00
context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-23 04:30:26 +00:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , CommentBlock < ' s > > {
2023-04-21 21:32:53 +00:00
let ( remaining , name ) = lesser_block_begin ( " comment " ) ( context , input ) ? ;
2023-10-04 13:51:28 +00:00
let ( remaining , _parameters ) = opt ( tuple ( ( space1 , data ) ) ) ( remaining ) ? ;
2023-09-06 22:25:35 +00:00
let ( remaining , _nl ) = recognize ( tuple ( ( space0 , line_ending ) ) ) ( remaining ) ? ;
2023-04-21 21:32:53 +00:00
let lesser_block_end_specialized = lesser_block_end ( " comment " ) ;
2023-09-03 15:05:34 +00:00
let contexts = [
ContextElement ::ConsumeTrailingWhitespace ( true ) ,
ContextElement ::Context ( " lesser block " ) ,
ContextElement ::ExitMatcherNode ( ExitMatcherNode {
2023-09-06 18:14:02 +00:00
class : ExitClass ::Alpha ,
2023-04-21 21:32:53 +00:00
exit_matcher : & lesser_block_end_specialized ,
2023-09-03 15:05:34 +00:00
} ) ,
] ;
2023-09-03 16:28:45 +00:00
let parser_context = context . with_additional_node ( & contexts [ 0 ] ) ;
let parser_context = parser_context . with_additional_node ( & contexts [ 1 ] ) ;
let parser_context = parser_context . with_additional_node ( & contexts [ 2 ] ) ;
2023-04-21 21:32:53 +00:00
2023-04-23 02:06:34 +00:00
let ( remaining , contents ) = parser_with_context! ( text_until_exit ) ( & parser_context ) ( remaining ) ? ;
2023-04-21 21:32:53 +00:00
let ( remaining , _end ) = lesser_block_end_specialized ( & parser_context , remaining ) ? ;
let source = get_consumed ( input , remaining ) ;
Ok ( (
remaining ,
2023-04-21 21:40:49 +00:00
CommentBlock {
2023-08-23 04:30:26 +00:00
source : source . into ( ) ,
name : name . into ( ) ,
contents : contents . into ( ) ,
2023-04-21 21:40:49 +00:00
} ,
) )
}
2023-08-11 00:04:59 +00:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-11 17:13:28 +00:00
pub ( crate ) fn example_block < ' b , ' g , ' r , ' s > (
2023-09-03 19:44:18 +00:00
context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-23 04:30:26 +00:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , ExampleBlock < ' s > > {
2023-08-24 22:39:55 +00:00
let ( remaining , _name ) = lesser_block_begin ( " example " ) ( context , input ) ? ;
2023-10-04 15:31:45 +00:00
let ( remaining , parameters ) = opt ( tuple ( ( space1 , example_switches ) ) ) ( remaining ) ? ;
2023-09-06 22:25:35 +00:00
let ( remaining , _nl ) = recognize ( tuple ( ( space0 , line_ending ) ) ) ( remaining ) ? ;
2023-04-21 21:40:49 +00:00
let lesser_block_end_specialized = lesser_block_end ( " example " ) ;
2023-09-03 16:28:45 +00:00
let contexts = [
ContextElement ::ConsumeTrailingWhitespace ( true ) ,
ContextElement ::Context ( " lesser block " ) ,
ContextElement ::ExitMatcherNode ( ExitMatcherNode {
2023-09-06 18:14:02 +00:00
class : ExitClass ::Alpha ,
2023-04-21 21:40:49 +00:00
exit_matcher : & lesser_block_end_specialized ,
2023-09-03 16:28:45 +00:00
} ) ,
] ;
let parser_context = context . with_additional_node ( & contexts [ 0 ] ) ;
let parser_context = parser_context . with_additional_node ( & contexts [ 1 ] ) ;
let parser_context = parser_context . with_additional_node ( & contexts [ 2 ] ) ;
2023-10-04 14:31:01 +00:00
let parameters = parameters . map ( | ( _ , parameters ) | parameters ) ;
2023-04-21 21:40:49 +00:00
2023-04-23 02:06:34 +00:00
let ( remaining , contents ) = parser_with_context! ( text_until_exit ) ( & parser_context ) ( remaining ) ? ;
2023-04-21 21:40:49 +00:00
let ( remaining , _end ) = lesser_block_end_specialized ( & parser_context , remaining ) ? ;
let source = get_consumed ( input , remaining ) ;
2023-10-04 15:31:45 +00:00
let ( switches , number_lines ) = {
if let Some ( parameters ) = parameters {
( Some ( parameters . source ) , parameters . number_lines )
} else {
( None , None )
}
} ;
2023-04-21 21:40:49 +00:00
Ok ( (
remaining ,
ExampleBlock {
2023-08-23 04:30:26 +00:00
source : source . into ( ) ,
name : source . into ( ) ,
2023-10-04 15:31:45 +00:00
switches ,
number_lines ,
2023-08-23 04:30:26 +00:00
contents : contents . into ( ) ,
2023-04-21 21:40:49 +00:00
} ,
) )
}
2023-08-11 00:04:59 +00:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-11 17:13:28 +00:00
pub ( crate ) fn export_block < ' b , ' g , ' r , ' s > (
2023-09-03 19:44:18 +00:00
context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-23 04:30:26 +00:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , ExportBlock < ' s > > {
2023-04-21 21:40:49 +00:00
let ( remaining , name ) = lesser_block_begin ( " export " ) ( context , input ) ? ;
2023-04-21 22:11:26 +00:00
// https://orgmode.org/worg/org-syntax.html#Blocks claims that export blocks must have a single word for data but testing shows no data and multi-word data still parses as an export block.
2023-04-21 21:40:49 +00:00
let ( remaining , parameters ) = opt ( tuple ( ( space1 , data ) ) ) ( remaining ) ? ;
2023-09-06 22:25:35 +00:00
let ( remaining , _nl ) = recognize ( tuple ( ( space0 , line_ending ) ) ) ( remaining ) ? ;
2023-04-21 21:40:49 +00:00
let lesser_block_end_specialized = lesser_block_end ( " export " ) ;
2023-09-03 16:28:45 +00:00
let contexts = [
ContextElement ::ConsumeTrailingWhitespace ( true ) ,
ContextElement ::Context ( " lesser block " ) ,
ContextElement ::ExitMatcherNode ( ExitMatcherNode {
2023-09-06 18:14:02 +00:00
class : ExitClass ::Alpha ,
2023-04-21 21:40:49 +00:00
exit_matcher : & lesser_block_end_specialized ,
2023-09-03 16:28:45 +00:00
} ) ,
] ;
let parser_context = context . with_additional_node ( & contexts [ 0 ] ) ;
let parser_context = parser_context . with_additional_node ( & contexts [ 1 ] ) ;
let parser_context = parser_context . with_additional_node ( & contexts [ 2 ] ) ;
2023-04-21 21:40:49 +00:00
let parameters = match parameters {
Some ( ( _ws , parameters ) ) = > Some ( parameters ) ,
None = > None ,
} ;
2023-04-23 02:06:34 +00:00
let ( remaining , contents ) = parser_with_context! ( text_until_exit ) ( & parser_context ) ( remaining ) ? ;
2023-04-21 21:40:49 +00:00
let ( remaining , _end ) = lesser_block_end_specialized ( & parser_context , remaining ) ? ;
let source = get_consumed ( input , remaining ) ;
Ok ( (
remaining ,
ExportBlock {
2023-08-23 04:30:26 +00:00
source : source . into ( ) ,
name : name . into ( ) ,
data : parameters . map ( | parameters | Into ::< & str > ::into ( parameters ) ) ,
contents : contents . into ( ) ,
2023-04-21 21:40:49 +00:00
} ,
) )
}
2023-08-11 00:04:59 +00:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-11 17:13:28 +00:00
pub ( crate ) fn src_block < ' b , ' g , ' r , ' s > (
2023-09-03 19:44:18 +00:00
context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-23 04:30:26 +00:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , SrcBlock < ' s > > {
2023-04-21 21:40:49 +00:00
let ( remaining , name ) = lesser_block_begin ( " src " ) ( context , input ) ? ;
2023-04-21 22:13:23 +00:00
// https://orgmode.org/worg/org-syntax.html#Blocks claims that data is mandatory and must follow the LANGUAGE SWITCHES ARGUMENTS pattern but testing has shown that no data and incorrect data here will still parse to a src block.
2023-04-21 21:40:49 +00:00
let ( remaining , parameters ) = opt ( tuple ( ( space1 , data ) ) ) ( remaining ) ? ;
2023-09-06 22:25:35 +00:00
let ( remaining , _nl ) = recognize ( tuple ( ( space0 , line_ending ) ) ) ( remaining ) ? ;
2023-04-21 21:40:49 +00:00
let lesser_block_end_specialized = lesser_block_end ( " src " ) ;
2023-09-03 16:28:45 +00:00
let contexts = [
ContextElement ::ConsumeTrailingWhitespace ( true ) ,
ContextElement ::Context ( " lesser block " ) ,
ContextElement ::ExitMatcherNode ( ExitMatcherNode {
2023-08-14 21:28:57 +00:00
class : ExitClass ::Alpha ,
2023-04-21 21:40:49 +00:00
exit_matcher : & lesser_block_end_specialized ,
2023-09-03 16:28:45 +00:00
} ) ,
] ;
let parser_context = context . with_additional_node ( & contexts [ 0 ] ) ;
let parser_context = parser_context . with_additional_node ( & contexts [ 1 ] ) ;
let parser_context = parser_context . with_additional_node ( & contexts [ 2 ] ) ;
2023-04-21 21:40:49 +00:00
let parameters = match parameters {
Some ( ( _ws , parameters ) ) = > Some ( parameters ) ,
None = > None ,
} ;
2023-04-23 02:06:34 +00:00
let ( remaining , contents ) = parser_with_context! ( text_until_exit ) ( & parser_context ) ( remaining ) ? ;
2023-04-21 21:40:49 +00:00
let ( remaining , _end ) = lesser_block_end_specialized ( & parser_context , remaining ) ? ;
let source = get_consumed ( input , remaining ) ;
Ok ( (
remaining ,
SrcBlock {
2023-08-23 04:30:26 +00:00
source : source . into ( ) ,
name : name . into ( ) ,
2023-10-04 13:55:00 +00:00
switches : parameters . map ( | parameters | Into ::< & str > ::into ( parameters ) ) ,
2023-08-23 04:30:26 +00:00
contents : contents . into ( ) ,
2023-04-21 21:32:53 +00:00
} ,
) )
}
2023-08-11 00:04:59 +00:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-08-23 04:30:26 +00:00
fn name < ' s > ( input : OrgSource < ' s > ) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-04-21 20:57:49 +00:00
is_not ( " \t \r \n " ) ( input )
}
2023-08-11 00:04:59 +00:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-08-23 04:30:26 +00:00
fn data < ' s > ( input : OrgSource < ' s > ) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-04-21 20:57:49 +00:00
is_not ( " \r \n " ) ( input )
}
2023-09-22 04:55:10 +00:00
fn lesser_block_end < ' c > ( current_name : & ' c str ) -> impl ContextMatcher + ' c {
// Since the lesser block names are statically defined in code, we can simply assert that the name is lowercase instead of causing an allocation by converting to lowercase.
debug_assert! ( current_name = = current_name . to_lowercase ( ) ) ;
move | context , input : OrgSource < '_ > | _lesser_block_end ( context , input , current_name )
2023-04-21 20:31:25 +00:00
}
2023-04-21 21:32:53 +00:00
2023-08-14 21:18:48 +00:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-03 19:44:18 +00:00
fn _lesser_block_end < ' b , ' g , ' r , ' s , ' c > (
_context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-23 04:30:26 +00:00
input : OrgSource < ' s > ,
2023-09-03 19:44:18 +00:00
current_name_lower : & ' c str ,
2023-08-23 04:30:26 +00:00
) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-08-24 23:29:00 +00:00
start_of_line ( input ) ? ;
2023-08-14 21:18:48 +00:00
let ( remaining , _leading_whitespace ) = space0 ( input ) ? ;
2023-09-06 22:25:35 +00:00
let ( remaining , ( _begin , _name , _ws , _ending ) ) = tuple ( (
2023-08-14 21:18:48 +00:00
tag_no_case ( " #+end_ " ) ,
tag_no_case ( current_name_lower ) ,
2023-09-06 22:25:35 +00:00
space0 ,
2023-08-14 21:18:48 +00:00
alt ( ( eof , line_ending ) ) ,
) ) ( remaining ) ? ;
let source = get_consumed ( input , remaining ) ;
Ok ( ( remaining , source ) )
}
2023-08-25 00:10:43 +00:00
/// Parser for the beginning of a lesser block
///
/// current_name MUST be lowercase. We do not do the conversion ourselves because it is not allowed in a const fn.
2023-09-03 16:28:45 +00:00
const fn lesser_block_begin < ' c > ( current_name : & ' c str ) -> impl ContextMatcher + ' c {
2023-08-25 00:10:43 +00:00
// TODO: Since this is a const fn, is there ANY way to "generate" functions at compile time?
2023-09-03 15:05:34 +00:00
move | context , input : OrgSource < '_ > | _lesser_block_begin ( context , input , current_name )
2023-04-21 21:32:53 +00:00
}
2023-08-14 21:18:48 +00:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-03 19:44:18 +00:00
fn _lesser_block_begin < ' b , ' g , ' r , ' s , ' c > (
_context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-23 04:30:26 +00:00
input : OrgSource < ' s > ,
2023-09-03 19:44:18 +00:00
current_name_lower : & ' c str ,
2023-08-23 04:30:26 +00:00
) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-08-24 23:29:00 +00:00
start_of_line ( input ) ? ;
2023-08-14 21:18:48 +00:00
let ( remaining , _leading_whitespace ) = space0 ( input ) ? ;
let ( remaining , ( _begin , name ) ) = tuple ( (
tag_no_case ( " #+begin_ " ) ,
2023-08-23 04:30:26 +00:00
verify ( name , | name : & OrgSource < '_ > | {
Into ::< & str > ::into ( name ) . to_lowercase ( ) . as_str ( ) = = current_name_lower
2023-08-14 21:18:48 +00:00
} ) ,
) ) ( remaining ) ? ;
Ok ( ( remaining , name ) )
}
2023-10-04 15:31:45 +00:00
struct ExampleSwitches < ' s > {
source : & ' s str ,
number_lines : Option < SwitchNumberLines > ,
}
enum SwitchState {
Normal ,
NewLineNumber ,
ContinuedLineNumber ,
}
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
fn example_switches < ' s > ( input : OrgSource < ' s > ) -> Res < OrgSource < ' s > , ExampleSwitches < ' s > > {
let mut number_lines = None ;
let ( remaining , ( source , words ) ) = consumed ( separated_list1 (
space1 ,
map ( is_not ( " \t \r \n " ) , | val | Into ::< & str > ::into ( val ) ) ,
) ) ( input ) ? ;
let mut state = SwitchState ::Normal ;
for word in words {
state = match ( state , word ) {
( SwitchState ::Normal , " -n " ) = > SwitchState ::NewLineNumber ,
( SwitchState ::Normal , " +n " ) = > SwitchState ::ContinuedLineNumber ,
( SwitchState ::NewLineNumber , _ ) = > {
let val = word
. parse ::< LineNumber > ( )
. expect ( " TODO: I should be able to return CustomError from nom parsers. " ) ;
if val < 0 {
number_lines = Some ( SwitchNumberLines ::New ( 0 ) ) ;
} else {
// Note that this can result in a negative 1 if the val is originally 0.
number_lines = Some ( SwitchNumberLines ::New ( val - 1 ) ) ;
}
SwitchState ::Normal
}
( SwitchState ::ContinuedLineNumber , _ ) = > {
let val = word
. parse ::< LineNumber > ( )
. expect ( " TODO: I should be able to return CustomError from nom parsers. " ) ;
if val < 0 {
number_lines = Some ( SwitchNumberLines ::Continued ( 0 ) ) ;
} else {
// Note that this can result in a negative 1 if the val is originally 0.
number_lines = Some ( SwitchNumberLines ::Continued ( val - 1 ) ) ;
}
SwitchState ::Normal
}
( state @ SwitchState ::Normal , _ ) = > state ,
} ;
}
Ok ( (
remaining ,
ExampleSwitches {
source : Into ::< & str > ::into ( source ) ,
number_lines ,
} ,
) )
}