@@ -223,6 +223,31 @@ struct PendingBlockDirective {
223223 }
224224}
225225
226+ struct PendingDoxygenCommand {
227+ enum CommandKind {
228+ case param( name: Substring )
229+
230+ var debugDescription : String {
231+ switch self {
232+ case . param( name: let name) :
233+ return " 'param' Argument: ' \( name) ' "
234+ }
235+ }
236+ }
237+
238+ var atLocation : SourceLocation
239+
240+ var nameLocation : SourceLocation
241+
242+ var kind : CommandKind
243+
244+ var endLocation : SourceLocation
245+
246+ mutating func addLine( _ line: TrimmedLine ) {
247+ endLocation = SourceLocation ( line: line. lineNumber ?? 0 , column: line. untrimmedText. count + 1 , source: line. source)
248+ }
249+ }
250+
226251struct TrimmedLine {
227252 /// A successful result of scanning for a prefix on a ``TrimmedLine``.
228253 struct Lex : Equatable {
@@ -459,8 +484,11 @@ private enum ParseContainer: CustomStringConvertible {
459484 /// A block directive container, which can contain other block directives or runs of lines.
460485 case blockDirective( PendingBlockDirective , [ ParseContainer ] )
461486
462- init < TrimmedLines: Sequence > ( parsingHierarchyFrom trimmedLines: TrimmedLines ) where TrimmedLines. Element == TrimmedLine {
463- self = ParseContainerStack ( parsingHierarchyFrom: trimmedLines) . top
487+ /// A Doxygen command, which can contain arbitrary markup (but not block directives).
488+ case doxygenCommand( PendingDoxygenCommand , [ TrimmedLine ] )
489+
490+ init < TrimmedLines: Sequence > ( parsingHierarchyFrom trimmedLines: TrimmedLines , options: ParseOptions ) where TrimmedLines. Element == TrimmedLine {
491+ self = ParseContainerStack ( parsingHierarchyFrom: trimmedLines, options: options) . top
464492 }
465493
466494 var children : [ ParseContainer ] {
@@ -471,6 +499,8 @@ private enum ParseContainer: CustomStringConvertible {
471499 return children
472500 case . lineRun:
473501 return [ ]
502+ case . doxygenCommand:
503+ return [ ]
474504 }
475505 }
476506
@@ -545,6 +575,15 @@ private enum ParseContainer: CustomStringConvertible {
545575 indent -= 4
546576 }
547577 print ( children: children)
578+ case . doxygenCommand( let pendingDoxygenCommand, let lines) :
579+ print ( " * Doxygen command \( pendingDoxygenCommand. kind. debugDescription) " )
580+ queueNewline ( )
581+ indent += 4
582+ for line in lines {
583+ print ( line. text. debugDescription)
584+ queueNewline ( )
585+ }
586+ indent -= 4
548587 }
549588 }
550589 }
@@ -565,6 +604,9 @@ private enum ParseContainer: CustomStringConvertible {
565604 case . blockDirective( var pendingBlockDirective, let children) :
566605 pendingBlockDirective. updateIndentation ( for: line)
567606 self = . blockDirective( pendingBlockDirective, children)
607+ case . doxygenCommand:
608+ var newParent : ParseContainer ? = nil
609+ parent? . updateIndentation ( under: & newParent, for: line)
568610 }
569611 }
570612
@@ -609,6 +651,8 @@ private enum ParseContainer: CustomStringConvertible {
609651 return parent? . indentationAdjustment ( under: nil ) ?? 0
610652 case . blockDirective( let pendingBlockDirective, _) :
611653 return pendingBlockDirective. indentationColumnCount
654+ case . doxygenCommand:
655+ return parent? . indentationAdjustment ( under: nil ) ?? 0
612656 }
613657 }
614658
@@ -677,6 +721,15 @@ private enum ParseContainer: CustomStringConvertible {
677721 } ) ,
678722 parsedRange: pendingBlockDirective. atLocation..< pendingBlockDirective. endLocation,
679723 children) ]
724+ case let . doxygenCommand( pendingDoxygenCommand, lines) :
725+ let range = pendingDoxygenCommand. atLocation..< pendingDoxygenCommand. endLocation
726+ ranges. add ( range)
727+ let children = ParseContainer . lineRun ( lines, isInCodeFence: false )
728+ . convertToRawMarkup ( ranges: & ranges, parent: self , options: options)
729+ switch pendingDoxygenCommand. kind {
730+ case . param( let name) :
731+ return [ . doxygenParam( name: String ( name) , parsedRange: range, children) ]
732+ }
680733 }
681734 }
682735}
@@ -689,8 +742,11 @@ struct ParseContainerStack {
689742 /// The stack of containers to be incrementally folded into a hierarchy.
690743 private var stack : [ ParseContainer ]
691744
692- init < TrimmedLines: Sequence > ( parsingHierarchyFrom trimmedLines: TrimmedLines ) where TrimmedLines. Element == TrimmedLine {
745+ private let options : ParseOptions
746+
747+ init < TrimmedLines: Sequence > ( parsingHierarchyFrom trimmedLines: TrimmedLines , options: ParseOptions ) where TrimmedLines. Element == TrimmedLine {
693748 self . stack = [ . root( [ ] ) ]
749+ self . options = options
694750 for line in trimmedLines {
695751 accept ( line)
696752 }
@@ -708,6 +764,20 @@ struct ParseContainerStack {
708764 } != nil
709765 }
710766
767+ private var canParseDoxygenCommand : Bool {
768+ guard options. contains ( . parseMinimalDoxygen) else { return false }
769+
770+ guard !isInBlockDirective else { return false }
771+
772+ if case . blockDirective = top {
773+ return false
774+ } else if case . lineRun( _, isInCodeFence: let codeFence) = top {
775+ return !codeFence
776+ } else {
777+ return true
778+ }
779+ }
780+
711781 private func isCodeFenceOrIndentedCodeBlock( on line: TrimmedLine ) -> Bool {
712782 // Check if this line is indented 4 or more spaces relative to the current
713783 // indentation adjustment.
@@ -760,23 +830,85 @@ struct ParseContainerStack {
760830 return pendingBlockDirective
761831 }
762832
833+ private func parseDoxygenCommandOpening( on line: TrimmedLine ) -> ( pendingCommand: PendingDoxygenCommand , remainder: TrimmedLine ) ? {
834+ guard canParseDoxygenCommand else { return nil }
835+ guard !isCodeFenceOrIndentedCodeBlock( on: line) else { return nil }
836+
837+ var remainder = line
838+ guard let at = remainder. lex ( until: { ch in
839+ switch ch {
840+ case " @ " , " \\ " :
841+ return . continue
842+ default :
843+ return . stop
844+ }
845+ } ) else { return nil }
846+ guard let name = remainder. lex ( until: { ch in
847+ if ch. isWhitespace {
848+ return . stop
849+ } else {
850+ return . continue
851+ }
852+ } ) else { return nil }
853+ remainder. lexWhitespace ( )
854+
855+ switch name. text. lowercased ( ) {
856+ case " param " :
857+ guard let paramName = remainder. lex ( until: { ch in
858+ if ch. isWhitespace {
859+ return . stop
860+ } else {
861+ return . continue
862+ }
863+ } ) else { return nil }
864+ remainder. lexWhitespace ( )
865+ var pendingCommand = PendingDoxygenCommand (
866+ atLocation: at. range!. lowerBound,
867+ nameLocation: name. range!. lowerBound,
868+ kind: . param( name: paramName. text) ,
869+ endLocation: name. range!. upperBound)
870+ pendingCommand. addLine ( remainder)
871+ return ( pendingCommand, remainder)
872+ default :
873+ return nil
874+ }
875+ }
876+
763877 /// Accept a trimmed line, opening new block directives as indicated by the source,
764878 /// closing a block directive if applicable, or adding the line to a run of lines to be parsed
765879 /// as Markdown later.
766880 private mutating func accept( _ line: TrimmedLine ) {
767- if line. isEmptyOrAllWhitespace,
768- case let . blockDirective( pendingBlockDirective, _) = top {
769- switch pendingBlockDirective. parseState {
770- case . argumentsStart,
771- . contentsStart,
772- . done:
773- closeTop ( )
881+ if line. isEmptyOrAllWhitespace {
882+ switch top {
883+ case let . blockDirective( pendingBlockDirective, _) :
884+ switch pendingBlockDirective. parseState {
885+ case . argumentsStart,
886+ . contentsStart,
887+ . done:
888+ closeTop ( )
774889
890+ default :
891+ break
892+ }
893+ case . doxygenCommand:
894+ closeTop ( )
775895 default :
776896 break
777897 }
778898 }
779899
900+ // If we can parse a Doxygen command from this line, start one and skip everything else.
901+ if let result = parseDoxygenCommandOpening ( on: line) {
902+ switch top {
903+ case . root:
904+ break
905+ default :
906+ closeTop ( )
907+ }
908+ push ( . doxygenCommand( result. pendingCommand, [ result. remainder] ) )
909+ return
910+ }
911+
780912 // If we're inside a block directive, check to see whether we need to update its
781913 // indentation calculation to account for its content.
782914 updateIndentation ( for: line)
@@ -822,7 +954,7 @@ struct ParseContainerStack {
822954 switch top {
823955 case . root:
824956 push ( . blockDirective( newBlockDirective, [ ] ) )
825- case . lineRun:
957+ case . lineRun, . doxygenCommand :
826958 closeTop ( )
827959 push ( . blockDirective( newBlockDirective, [ ] ) )
828960 case . blockDirective( let previousBlockDirective, _) :
@@ -848,11 +980,16 @@ struct ParseContainerStack {
848980 } else {
849981 switch top {
850982 case . root:
851- push ( . lineRun( [ line] , isInCodeFence: false ) )
983+ push ( . lineRun( [ line] , isInCodeFence: line . isProbablyCodeFence ) )
852984 case . lineRun( var lines, let isInCodeFence) :
853985 pop ( )
854986 lines. append ( line)
855987 push ( . lineRun( lines, isInCodeFence: isInCodeFence != line. isProbablyCodeFence) )
988+ case . doxygenCommand( var pendingDoxygenCommand, var lines) :
989+ pop ( )
990+ lines. append ( line)
991+ pendingDoxygenCommand. addLine ( line)
992+ push ( . doxygenCommand( pendingDoxygenCommand, lines) )
856993 case . blockDirective( var pendingBlockDirective, let children) :
857994 // A pending block directive can accept this line if it is in the middle of
858995 // parsing arguments text (to allow indentation to align arguments) or
@@ -923,6 +1060,8 @@ struct ParseContainerStack {
9231060 push ( . blockDirective( pendingBlockDirective, children) )
9241061 case . lineRun:
9251062 fatalError ( " Line runs cannot have children " )
1063+ case . doxygenCommand:
1064+ fatalError ( " Doxygen commands cannot have children " )
9261065 }
9271066 }
9281067
@@ -985,7 +1124,7 @@ struct BlockDirectiveParser {
9851124 // Phase 1: Categorize the lines into a hierarchy of block containers by parsing the prefix
9861125 // of the line, opening and closing block directives appropriately, and folding elements
9871126 // into a root document.
988- let rootContainer = ParseContainer ( parsingHierarchyFrom: trimmedLines)
1127+ let rootContainer = ParseContainer ( parsingHierarchyFrom: trimmedLines, options : options )
9891128
9901129 // Phase 2: Convert the hierarchy of block containers into a real ``Document``.
9911130 // This is where the CommonMark parser is called upon to parse runs of lines of content,
0 commit comments