{-
Copyright (C) 2006-2010 John MacFarlane <jgm@berkeley.edu>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-}{- |
Module : Text.Pandoc.Readers.RST
Copyright : Copyright (C) 2006-2010 John MacFarlane
License : GNU GPL, version 2 or above
Maintainer : John MacFarlane <jgm@berkeley.edu>
Stability : alpha
Portability : portable
Conversion from reStructuredText to 'Pandoc' document.
-}moduleText.Pandoc.Readers.RST(readRST)whereimportText.Pandoc.DefinitionimportText.Pandoc.SharedimportText.Pandoc.ParsingimportText.ParserCombinators.ParsecimportControl.Monad(when,liftM)importData.List(findIndex,intercalate,transpose,sort,deleteFirstsBy)importqualifiedData.MapasMimportText.Printf(printf)importData.Maybe(catMaybes)-- | Parse reStructuredText string and return Pandoc document.readRST::ParserState-- ^ Parser state, including options for parser->String-- ^ String to parse (assuming @'\n'@ line endings)->PandocreadRSTstates=(readWithparseRST)state(s++"\n\n")---- Constants and data structure definitions---bulletListMarkers::[Char]bulletListMarkers="*+-"underlineChars::[Char]underlineChars="!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"-- treat these as potentially non-text when parsing inline:specialChars::[Char]specialChars="\\`|*_<>$:[]()-.\"'\8216\8217\8220\8221"---- parsing documents--isHeader::Int->Block->BoolisHeadern(Headerx_)=x==nisHeader__=False-- | Promote all headers in a list of blocks. (Part of-- title transformation for RST.)promoteHeaders::Int->[Block]->[Block]promoteHeadersnum((Headerleveltext):rest)=(Header(level-num)text):(promoteHeadersnumrest)promoteHeadersnum(other:rest)=other:(promoteHeadersnumrest)promoteHeaders_[]=[]-- | If list of blocks starts with a header (or a header and subheader)-- of level that are not found elsewhere, return it as a title and-- promote all the other headers. titleTransform::[Block]-- ^ list of blocks->([Block],[Inline])-- ^ modified list of blocks, titletitleTransform((Header1head1):(Header2head2):rest)|not(any(isHeader1)rest||any(isHeader2)rest)=-- both title & subtitle(promoteHeaders2rest,head1++[Str":",Space]++head2)titleTransform((Header1head1):rest)|not(any(isHeader1)rest)=-- title, no subtitle(promoteHeaders1rest,head1)titleTransformblocks=(blocks,[])parseRST::GenParserCharParserStatePandocparseRST=dooptionalblanklines-- skip blank lines at beginning of filestartPos<-getPosition-- go through once just to get list of reference keys and notes-- docMinusKeys is the raw document with blanks where the keys were...docMinusKeys<-manyTill(referenceKey<|>noteBlock<|>lineClump)eof>>=return.concatsetInputdocMinusKeyssetPositionstartPosst'<-getStateletreversedNotes=stateNotesst'updateState$\s->s{stateNotes=reversereversedNotes}-- now parse it for real...blocks<-parseBlocksletblocks'=filter(/=Null)blocksstate<-getStatelet(blocks'',title)=ifstateStandalonestatethentitleTransformblocks'else(blocks',[])letauthors=stateAuthorsstateletdate=stateDatestatelettitle'=if(nulltitle)then(stateTitlestate)elsetitlereturn$Pandoc(Metatitle'authorsdate)blocks''---- parsing blocks--parseBlocks::GenParserCharParserState[Block]parseBlocks=manyTillblockeofblock::GenParserCharParserStateBlockblock=choice[codeBlock,rawBlock,blockQuote,fieldList,imageBlock,figureBlock,customCodeBlock,mathBlock,defaultRoleBlock,unknownDirective,header,hrule,lineBlock-- must go before definitionList,table,list,lhsCodeBlock,para,plain,nullBlock]<?>"block"---- field list--rawFieldListItem::String->GenParserCharParserState(String,String)rawFieldListItemindent=try$dostringindentchar':'name<-many1$alphaNum<|>spaceCharstring": "skipSpacesfirst<-manyTillanyCharnewlinerest<-option""$try$dolookAhead(stringindent>>spaceChar)indentedBlockletraw=first++"\n"++rest++"\n"return(name,raw)fieldListItem::String->GenParserCharParserState(Maybe([Inline],[[Block]]))fieldListItemindent=try$do(name,raw)<-rawFieldListItemindentletterm=[Strname]contents<-parseFromString(manyblock)rawoptionalblanklinescase(name,contents)of("Author",x)->doupdateState$\st->st{stateAuthors=stateAuthorsst++[extractContentsx]}returnNothing("Authors",[BulletListauths])->doupdateState$\st->st{stateAuthors=mapextractContentsauths}returnNothing("Date",x)->doupdateState$\st->st{stateDate=extractContentsx}returnNothing("Title",x)->doupdateState$\st->st{stateTitle=extractContentsx}returnNothing_->return$Just(term,[contents])extractContents::[Block]->[Inline]extractContents[Plainauth]=authextractContents[Paraauth]=authextractContents_=[]fieldList::GenParserCharParserStateBlockfieldList=try$doindent<-lookAhead$manyspaceCharitems<-many1$fieldListItemindentifnullitemsthenreturnNullelsereturn$DefinitionList$catMaybesitems---- line block--lineBlockLine::GenParserCharParserState[Inline]lineBlockLine=try$dochar'|'char' '<|>lookAhead(char'\n')white<-manyspaceCharline<-many$(notFollowedBynewline>>inline)<|>(try$endline>>~char' ')optionalendlinereturn$ifnullwhitethennormalizeSpaceslineelseStrwhite:normalizeSpaceslinelineBlock::GenParserCharParserStateBlocklineBlock=try$dolines'<-many1lineBlockLineblanklinesreturn$Para(intercalate[LineBreak]lines')---- paragraph block--para::GenParserCharParserStateBlockpara=paraBeforeCodeBlock<|>paraNormal<?>"paragraph"codeBlockStart::GenParserCharstCharcodeBlockStart=string"::">>blankline>>blankline-- paragraph that ends in a :: starting a code blockparaBeforeCodeBlock::GenParserCharParserStateBlockparaBeforeCodeBlock=try$doresult<-many1(notFollowedBy'codeBlockStart>>inline)lookAhead(string"::")return$Para$iflastresult==SpacethennormalizeSpacesresultelse(normalizeSpacesresult)++[Str":"]-- regular paragraphparaNormal::GenParserCharParserStateBlockparaNormal=try$doresult<-many1inlinenewlineblanklinesreturn$Para$normalizeSpacesresultplain::GenParserCharParserStateBlockplain=many1inline>>=return.Plain.normalizeSpaces---- image block--imageBlock::GenParserCharParserStateBlockimageBlock=try$dostring".. image:: "src<-manyTillanyCharnewlinefields<-try$doindent<-lookAhead$many(oneOf" /t")many$rawFieldListItemindentoptionalblanklinescaselookup"alt"fieldsofJustalt->return$Plain[Image[Str$removeTrailingSpacealt](src,"")]Nothing->return$Plain[Image[Str"image"](src,"")]---- header blocks--header::GenParserCharParserStateBlockheader=doubleHeader<|>singleHeader<?>"header"-- a header with lines on top and bottomdoubleHeader::GenParserCharParserStateBlockdoubleHeader=try$doc<-oneOfunderlineCharsrest<-many(charc)-- the top lineletlenTop=length(c:rest)skipSpacesnewlinetxt<-many1(notFollowedByblankline>>inline)pos<-getPositionletlen=(sourceColumnpos)-1if(len>lenTop)thenfail"title longer than border"elsereturn()blankline-- spaces and newlinecountlenTop(charc)-- the bottom lineblanklines-- check to see if we've had this kind of header before. -- if so, get appropriate level. if not, add to list.state<-getStateletheaderTable=stateHeaderTablestatelet(headerTable',level)=casefindIndex(==DoubleHeaderc)headerTableofJustind->(headerTable,ind+1)Nothing->(headerTable++[DoubleHeaderc],(lengthheaderTable)+1)setState(state{stateHeaderTable=headerTable'})return$Headerlevel(normalizeSpacestxt)-- a header with line on the bottom onlysingleHeader::GenParserCharParserStateBlocksingleHeader=try$donotFollowedBy'whitespacetxt<-many1(do{notFollowedByblankline;inline})pos<-getPositionletlen=(sourceColumnpos)-1blanklinec<-oneOfunderlineCharscount(len-1)(charc)many(charc)blanklinesstate<-getStateletheaderTable=stateHeaderTablestatelet(headerTable',level)=casefindIndex(==SingleHeaderc)headerTableofJustind->(headerTable,ind+1)Nothing->(headerTable++[SingleHeaderc],(lengthheaderTable)+1)setState(state{stateHeaderTable=headerTable'})return$Headerlevel(normalizeSpacestxt)---- hrule block--hrule::GenParserCharstBlockhrule=try$dochr<-oneOfunderlineCharscount3(charchr)skipMany(charchr)blanklineblanklinesreturnHorizontalRule---- code blocks---- read a line indented by a given stringindentedLine::String->GenParserCharst[Char]indentedLineindents=try$dostringindentsmanyTillanyCharnewline-- one or more indented lines, possibly separated by blank lines.-- any amount of indentation will work.indentedBlock::GenParserCharst[Char]indentedBlock=try$doindents<-lookAhead$many1spaceCharlns<-many1$try$dob<-option""blanklinesl<-indentedLineindentsreturn(b++l)optionalblanklinesreturn$unlineslnscodeBlock::GenParserCharstBlockcodeBlock=try$docodeBlockStartresult<-indentedBlockreturn$CodeBlock("",[],[])$stripTrailingNewlinesresult-- | The 'code-block' directive (from Sphinx) that allows a language to be-- specified.customCodeBlock::GenParserCharstBlockcustomCodeBlock=try$dostring".. code-block:: "language<-manyTillanyCharnewlineblanklinesresult<-indentedBlockreturn$CodeBlock("",["sourceCode",language],[])$stripTrailingNewlinesresultfigureBlock::GenParserCharParserStateBlockfigureBlock=try$dostring".. figure::"src<-removeLeadingTrailingSpace`fmap`manyTillanyCharnewlinebody<-indentedBlockcaption<-parseFromStringextractCaptionbodyreturn$Para[Imagecaption(src,"")]extractCaption::GenParserCharParserState[Inline]extractCaption=try$domanyTillanyLineblanklinesmanyinline-- | The 'math' directive (from Sphinx) for display math.mathBlock::GenParserCharstBlockmathBlock=try$dostring".. math::"mathBlockMultiline<|>mathBlockOneLinemathBlockOneLine::GenParserCharstBlockmathBlockOneLine=try$doresult<-manyTillanyCharnewlineblanklinesreturn$Para[MathDisplayMath$removeLeadingTrailingSpaceresult]mathBlockMultiline::GenParserCharstBlockmathBlockMultiline=try$doblanklinesresult<-indentedBlock-- a single block can contain multiple equations, which need to go-- in separate Pandoc math elementsletlns=mapremoveLeadingTrailingSpace$linesresult-- drop :label, :nowrap, etc.letstartsWithColon(':':_)=TruestartsWithColon_=Falseletlns'=dropWhilestartsWithColonlnsleteqs=map(removeLeadingTrailingSpace.unlines)$filter(not.null)$splitBynulllns'return$Para$map(MathDisplayMath)eqslhsCodeBlock::GenParserCharParserStateBlocklhsCodeBlock=try$dofailUnlessLHSoptionalcodeBlockStartpos<-getPositionwhen(sourceColumnpos/=1)$fail"Not in first column"lns<-many1birdTrackLine-- if (as is normal) there is always a space after >, drop itletlns'=ifall(\ln->nullln||take1ln==" ")lnsthenmap(drop1)lnselselnsblanklinesreturn$CodeBlock("",["sourceCode","literate","haskell"],[])$intercalate"\n"lns'birdTrackLine::GenParserCharst[Char]birdTrackLine=dochar'>'manyTillanyCharnewline---- raw html/latex/etc--rawBlock::GenParserCharstBlockrawBlock=try$dostring".. raw:: "lang<-many1(letter<|>digit)blanklinesresult<-indentedBlockreturn$RawBlocklangresult---- block quotes--blockQuote::GenParserCharParserStateBlockblockQuote=doraw<-indentedBlock-- parse the extracted block, which may contain various block elements:contents<-parseFromStringparseBlocks$raw++"\n\n"return$BlockQuotecontents---- list blocks--list::GenParserCharParserStateBlocklist=choice[bulletList,orderedList,definitionList]<?>"list"definitionListItem::GenParserCharParserState([Inline],[[Block]])definitionListItem=try$do-- avoid capturing a directive or commentnotFollowedBy(try$char'.'>>char'.')term<-many1Tillinlineendlineraw<-indentedBlock-- parse the extracted block, which may contain various block elements:contents<-parseFromStringparseBlocks$raw++"\n"return(normalizeSpacesterm,[contents])definitionList::GenParserCharParserStateBlockdefinitionList=many1definitionListItem>>=return.DefinitionList-- parses bullet list start and returns its length (inc. following whitespace)bulletListStart::GenParserCharstIntbulletListStart=try$donotFollowedBy'hrule-- because hrules start out just like listsmarker<-oneOfbulletListMarkerswhite<-many1spaceCharreturn$length(marker:white)-- parses ordered list start and returns its length (inc following whitespace)orderedListStart::ListNumberStyle->ListNumberDelim->GenParserCharParserStateIntorderedListStartstyledelim=try$do(_,markerLen)<-withHorizDisplacement(orderedListMarkerstyledelim)white<-many1spaceCharreturn$markerLen+lengthwhite-- parse a line of a list itemlistLine::Int->GenParserCharParserState[Char]listLinemarkerLength=try$donotFollowedByblanklineindentWithmarkerLengthline<-manyTillanyCharnewlinereturn$line++"\n"-- indent by specified number of spaces (or equiv. tabs)indentWith::Int->GenParserCharParserState[Char]indentWithnum=dostate<-getStatelettabStop=stateTabStopstateif(num<tabStop)thencountnum(char' ')elsechoice[try(countnum(char' ')),(try(char'\t'>>count(num-tabStop)(char' ')))]-- parse raw text for one list item, excluding start marker and continuationsrawListItem::GenParserCharParserStateInt->GenParserCharParserState(Int,[Char])rawListItemstart=try$domarkerLength<-startfirstLine<-manyTillanyCharnewlinerestLines<-many(listLinemarkerLength)return(markerLength,(firstLine++"\n"++(concatrestLines)))-- continuation of a list item - indented and separated by blankline or -- (in compact lists) endline. -- Note: nested lists are parsed as continuations.listContinuation::Int->GenParserCharParserState[Char]listContinuationmarkerLength=try$doblanks<-many1blanklineresult<-many1(listLinemarkerLength)return$blanks++concatresultlistItem::GenParserCharParserStateInt->GenParserCharParserState[Block]listItemstart=try$do(markerLength,first)<-rawListItemstartrest<-many(listContinuationmarkerLength)blanks<-choice[try(manyblankline>>~lookAheadstart),many1blankline]-- whole list must end with blank.-- parsing with ListItemState forces markers at beginning of lines to-- count as list item markers, even if not separated by blank space.-- see definition of "endline"state<-getStateletoldContext=stateParserContextstatesetState$state{stateParserContext=ListItemState}-- parse the extracted block, which may itself contain block elementsparsed<-parseFromStringparseBlocks$concat(first:rest)++blanksupdateState(\st->st{stateParserContext=oldContext})returnparsedorderedList::GenParserCharParserStateBlockorderedList=try$do(start,style,delim)<-lookAhead(anyOrderedListMarker>>~spaceChar)items<-many1(listItem(orderedListStartstyledelim))letitems'=compactifyitemsreturn$OrderedList(start,style,delim)items'bulletList::GenParserCharParserStateBlockbulletList=many1(listItembulletListStart)>>=return.BulletList.compactify---- default-role block--defaultRoleBlock::GenParserCharParserStateBlockdefaultRoleBlock=try$dostring".. default-role::"-- doesn't enforce any restrictions on the role name; embedded spaces shouldn't be allowed, for onerole<-manyTillanyCharnewline>>=return.removeLeadingTrailingSpaceupdateState$\s->s{stateRstDefaultRole=ifnullrolethenstateRstDefaultRoledefaultParserStateelserole}-- skip body of the directive if it existsmany$blanklines<|>(spaceChar>>manyTillanyCharnewline)returnNull---- unknown directive (e.g. comment)--unknownDirective::GenParserCharstBlockunknownDirective=try$dostring".."notFollowedBy(noneOf" \t\n")manyTillanyCharnewlinemany$blanklines<|>(spaceChar>>manyTillanyCharnewline)returnNull------ note block---noteBlock::GenParserCharParserState[Char]noteBlock=try$dostartPos<-getPositionstring".."spaceChar>>skipManyspaceCharref<-noteMarkerfirst<-(spaceChar>>skipManyspaceChar>>anyLine)<|>(newline>>return"")blanks<-option""blanklinesrest<-option""indentedBlockendPos<-getPositionletraw=first++"\n"++blanks++rest++"\n"letnewnote=(ref,raw)st<-getStateletoldnotes=stateNotesstupdateState$\s->s{stateNotes=newnote:oldnotes}-- return blanks so line count isn't affectedreturn$replicate(sourceLineendPos-sourceLinestartPos)'\n'noteMarker::GenParserCharParserState[Char]noteMarker=dochar'['res<-many1digit<|>(try$char'#'>>liftM('#':)simpleReferenceName')<|>count1(oneOf"#*")char']'returnres---- reference key--quotedReferenceName::GenParserCharParserState[Inline]quotedReferenceName=try$dochar'`'>>notFollowedBy(char'`')-- `` means inline code!label'<-many1Tillinline(char'`')returnlabel'unquotedReferenceName::GenParserCharParserState[Inline]unquotedReferenceName=try$dolabel'<-many1Tillinline(lookAhead$char':')returnlabel'-- Simple reference names are single words consisting of alphanumerics-- plus isolated (no two adjacent) internal hyphens, underscores,-- periods, colons and plus signs; no whitespace or other characters-- are allowed.simpleReferenceName'::GenParserCharstStringsimpleReferenceName'=dox<-alphaNumxs<-many$alphaNum<|>(try$oneOf"-_:+.">>lookAheadalphaNum)return(x:xs)simpleReferenceName::GenParserCharst[Inline]simpleReferenceName=doraw<-simpleReferenceName'return[Strraw]referenceName::GenParserCharParserState[Inline]referenceName=quotedReferenceName<|>(try$simpleReferenceName>>~lookAhead(char':'))<|>unquotedReferenceNamereferenceKey::GenParserCharParserState[Char]referenceKey=dostartPos<-getPosition(key,target)<-choice[imageKey,anonymousKey,regularKey]st<-getStateletoldkeys=stateKeysstupdateState$\s->s{stateKeys=M.insertkeytargetoldkeys}optionalblanklinesendPos<-getPosition-- return enough blanks to replace keyreturn$replicate(sourceLineendPos-sourceLinestartPos)'\n'targetURI::GenParserCharst[Char]targetURI=doskipSpacesoptionalnewlinecontents<-many1(try(manyspaceChar>>newline>>many1spaceChar>>noneOf" \t\n")<|>noneOf"\n")blanklinesreturn$escapeURI$removeLeadingTrailingSpace$contentsimageKey::GenParserCharParserState(Key,Target)imageKey=try$dostring".. |"ref<-manyTillinline(char'|')skipSpacesstring"image::"src<-targetURIreturn(toKey(normalizeSpacesref),(src,""))anonymousKey::GenParserCharst(Key,Target)anonymousKey=try$dooneOfStrings[".. __:","__"]src<-targetURIpos<-getPositionreturn(toKey[Str$"_"++printf"%09d"(sourceLinepos)],(src,""))regularKey::GenParserCharParserState(Key,Target)regularKey=try$dostring".. _"ref<-referenceNamechar':'src<-targetURIreturn(toKey(normalizeSpacesref),(src,""))---- tables---- General tables TODO:-- - figure out if leading spaces are acceptable and if so, add-- support for them---- Simple tables TODO:-- - column spans-- - multiline support-- - ensure that rightmost column span does not need to reach end -- - require at least 2 columns---- Grid tables TODO:-- - column spansdashedLine::Char->GenParserCharst(Int,Int)dashedLinech=dodashes<-many1(charch)sp<-many(char' ')return(lengthdashes,length$dashes++sp)simpleDashedLines::Char->GenParserCharst[(Int,Int)]simpleDashedLinesch=try$many1(dashedLinech)-- Parse a table row separatorsimpleTableSep::Char->GenParserCharParserStateCharsimpleTableSepch=try$simpleDashedLinesch>>newline-- Parse a table footersimpleTableFooter::GenParserCharParserState[Char]simpleTableFooter=try$simpleTableSep'='>>blanklines-- Parse a raw line and split it into chunks by indices.simpleTableRawLine::[Int]->GenParserCharParserState[String]simpleTableRawLineindices=doline<-many1TillanyCharnewlinereturn(simpleTableSplitLineindicesline)-- Parse a table row and return a list of blocks (columns).simpleTableRow::[Int]->GenParserCharParserState[[Block]]simpleTableRowindices=donotFollowedBy'simpleTableFooterfirstLine<-simpleTableRawLineindicescolLines<-return[]-- TODOletcols=mapunlines.transpose$firstLine:colLinesmapM(parseFromString(manyplain))colssimpleTableSplitLine::[Int]->String->[String]simpleTableSplitLineindicesline=mapremoveLeadingTrailingSpace$tail$splitByIndices(initindices)linesimpleTableHeader::Bool-- ^ Headerless table ->GenParserCharParserState([[Block]],[Alignment],[Int])simpleTableHeaderheadless=try$dooptionalblanklinesrawContent<-ifheadlessthenreturn""elsesimpleTableSep'='>>anyLinedashes<-simpleDashedLines'='newlineletlines'=mapsnddashesletindices=scanl(+)0lines'letaligns=replicate(lengthlines')AlignDefaultletrawHeads=ifheadlessthenreplicate(lengthdashes)""elsesimpleTableSplitLineindicesrawContentheads<-mapM(parseFromString(manyplain))$mapremoveLeadingTrailingSpacerawHeadsreturn(heads,aligns,indices)-- Parse a simple table.simpleTable::Bool-- ^ Headerless table->GenParserCharParserStateBlocksimpleTableheadless=doTableca_whl<-tableWith(simpleTableHeaderheadless)simpleTableRowsepsimpleTableFooter(return[])-- Simple tables get 0s for relative column widths (i.e., use default)return$Tableca(replicate(lengtha)0)hlwheresep=return()-- optional (simpleTableSep '-')gridTable::Bool-- ^ Headerless table->GenParserCharParserStateBlockgridTable=gridTableWithblock(return[])table::GenParserCharParserStateBlocktable=gridTableFalse<|>simpleTableFalse<|>gridTableTrue<|>simpleTableTrue<?>"table"-- -- inline--inline::GenParserCharParserStateInlineinline=choice[whitespace,link,str,endline,strong,emph,code,image,superscript,subscript,math,note,smartPunctuationinline,hyphens,escapedChar,symbol]<?>"inline"hyphens::GenParserCharParserStateInlinehyphens=doresult<-many1(char'-')optionSpaceendline-- don't want to treat endline after hyphen or dash as a spacereturn$StrresultescapedChar::GenParserCharstInlineescapedChar=doc<-escapedanyCharreturn$ifc==' '-- '\ ' is null in RSTthenStr""elseStr[c]symbol::GenParserCharParserStateInlinesymbol=doresult<-oneOfspecialCharsreturn$Str[result]-- parses inline code, between codeStart and codeEndcode::GenParserCharParserStateInlinecode=try$dostring"``"result<-manyTillanyChar(try(string"``"))return$CodenullAttr$removeLeadingTrailingSpace$intercalate" "$linesresultemph::GenParserCharParserStateInlineemph=enclosed(char'*')(char'*')inline>>=return.Emph.normalizeSpacesstrong::GenParserCharParserStateInlinestrong=enclosed(string"**")(try$string"**")inline>>=return.Strong.normalizeSpaces-- Parses inline interpreted text which is required to have the given role.-- This decision is based on the role marker (if present),-- and the current default interpreted text role.interpreted::[Char]->GenParserCharParserState[Char]interpretedrole=try$dostate<-getStateifrole==stateRstDefaultRolestatethentrymarkedInterpretedText<|>unmarkedInterpretedTextelsemarkedInterpretedTextwheremarkedInterpretedText=try(roleMarker>>unmarkedInterpretedText)<|>(unmarkedInterpretedText>>=(\txt->roleMarker>>returntxt))roleMarker=string$":"++role++":"-- Note, this doesn't precisely implement the complex rule in-- http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#inline-markup-recognition-rules-- but it should be good enough for most purposesunmarkedInterpretedText=doresult<-enclosed(char'`')(char'`')anyCharreturnresultsuperscript::GenParserCharParserStateInlinesuperscript=interpreted"sup">>=\x->return(Superscript[Strx])subscript::GenParserCharParserStateInlinesubscript=interpreted"sub">>=\x->return(Subscript[Strx])math::GenParserCharParserStateInlinemath=interpreted"math">>=\x->return(MathInlineMathx)whitespace::GenParserCharParserStateInlinewhitespace=many1spaceChar>>returnSpace<?>"whitespace"str::GenParserCharParserStateInlinestr=doresult<-many1(noneOf(specialChars++"\t\n "))pos<-getPositionupdateState$\s->s{stateLastStrPos=Justpos}return$Strresult-- an endline character that can be treated as a space, not a structural breakendline::GenParserCharParserStateInlineendline=try$donewlinenotFollowedByblankline-- parse potential list-starts at beginning of line differently in a list:st<-getStateif(stateParserContextst)==ListItemStatethennotFollowedBy(anyOrderedListMarker>>spaceChar)>>notFollowedBy'bulletListStartelsereturn()returnSpace---- links--link::GenParserCharParserStateInlinelink=choice[explicitLink,referenceLink,autoLink]<?>"link"explicitLink::GenParserCharParserStateInlineexplicitLink=try$dochar'`'notFollowedBy(char'`')-- `` marks start of inline codelabel'<-manyTill(notFollowedBy(char'`')>>inline)(try(spaces>>char'<'))src<-manyTill(noneOf">\n")(char'>')skipSpacesstring"`_"return$Link(normalizeSpaceslabel')(escapeURI$removeLeadingTrailingSpacesrc,"")referenceLink::GenParserCharParserStateInlinereferenceLink=try$dolabel'<-(quotedReferenceName<|>simpleReferenceName)>>~char'_'state<-getStateletkeyTable=stateKeysstateletisAnonKeyx=casefromKeyxof[Str('_':_)]->True_->Falsekey<-option(toKeylabel')$dochar'_'letanonKeys=sort$filterisAnonKey$M.keyskeyTableifnullanonKeysthenpzeroelsereturn(headanonKeys)(src,tit)<-caselookupKeySrckeyTablekeyofNothing->fail"no corresponding key"Justtarget->returntarget-- if anonymous link, remove key so it won't be used againwhen(isAnonKeykey)$updateState$\s->s{stateKeys=M.deletekeykeyTable}return$Link(normalizeSpaceslabel')(src,tit)autoURI::GenParserCharParserStateInlineautoURI=do(orig,src)<-urireturn$Link[Strorig](src,"")autoEmail::GenParserCharParserStateInlineautoEmail=do(orig,src)<-emailAddressreturn$Link[Strorig](src,"")autoLink::GenParserCharParserStateInlineautoLink=autoURI<|>autoEmail-- For now, we assume that all substitution references are for images.image::GenParserCharParserStateInlineimage=try$dochar'|'ref<-manyTillinline(char'|')state<-getStateletkeyTable=stateKeysstate(src,tit)<-caselookupKeySrckeyTable(toKeyref)ofNothing->fail"no corresponding key"Justtarget->returntargetreturn$Image(normalizeSpacesref)(src,tit)note::GenParserCharParserStateInlinenote=try$doref<-noteMarkerchar'_'state<-getStateletnotes=stateNotesstatecaselookuprefnotesofNothing->fail"note not found"Justraw->do-- We temporarily empty the note list while parsing the note,-- so that we don't get infinite loops with notes inside notes...-- Note references inside other notes are allowed in reST, but-- not yet in this implementation.updateState$\st->st{stateNotes=[]}contents<-parseFromStringparseBlocksrawletnewnotes=if(ref=="*"||ref=="#")-- auto-numbered-- delete the note so the next auto-numbered note-- doesn't get the same contents:thendeleteFirstsBy(==)notes[(ref,raw)]elsenotesupdateState$\st->st{stateNotes=newnotes}return$Notecontents