%% name = NoraMark::Parser %% { PARAMETER_END = /[,)]/ } %% ast-location = ::NoraMark %% text = ast Text(content, line_no) %% code_inline = ast CodeInline(ids, classes, params, named_params, content, line_no) %% paragraph = ast Paragraph(ids, classes, params, named_params, raw_content, line_no) %% paragraph_group = ast ParagraphGroup(ids, classes, params,named_params, raw_content, line_no) %% br = ast Breakline(line_no) %% block = ast Block(name, ids, classes, params, named_params, raw_content, line_no) %% newpage = ast Newpage(ids, classes, params, named_params, raw_content, line_no) %% inline = ast Inline(name, ids, classes, params, named_params, raw_content, line_no) %% ul_item = ast UlItem(ids, classes, params, named_params, raw_content, line_no) %% unordered_list = ast UnorderedList(ids, classes, params, named_params, raw_content, line_no) %% ol_item = ast OlItem(ids, classes, params, named_params, raw_content, line_no) %% ordered_list = ast OrderedList(ids, classes, params,named_params, raw_content, line_no) %% dl_item = ast DLItem(ids, classes, params, named_params, raw_content, line_no) %% definition_list = ast DefinitionList(ids, classes, params, named_params, raw_content, line_no) %% preformatted_block = ast PreformattedBlock(name, ids, classes, params, named_params, codelanguage, content, line_no) %% frontmatter = ast Frontmatter(content, line_no) %% h_section = ast HeadedSection(level, heading, params, named_params, raw_content, line_no) %% page = ast Page(raw_content, line_no) %% root = ast Root(raw_content) # line number ln = { current_line } # literals BOM = /\uFEFF/ Eof = !. Space = ' ' | '\t' - = Space* EofComment = - "//" (!Eof .)* Comment = - "//" (!Nl .)* Nl EmptyLine* EmptyLine = /^/ - (Nl | Comment | EofComment) Nl = /\r?\n/ Le = Nl | Eof Word = < /[\w]/ ( '-' | /[\w]/ )* > { text } Num = < [0-9]+ > { text.to_i } #common syntax ClassName = '.' Word:classname { classname } ClassNames = (ClassName)*:classnames { classnames } IdName = '#' Word:idname { idname } IdNames = (IdName)*:idnames { idnames } CommandName = Word:name IdNames?:idnames ClassNames?:classes ln:ln { {name: name, ids: idnames, classes: classes, ln:ln } } ParameterNormal = DocumentContentExcept(PARAMETER_END):content { content } ParameterQuoted = '"' DocumentContentExcept('"'):content '"' - &/[,)]/ { content } ParameterSingleQuoted = "'" DocumentContentExcept("'"):content "'" - &/[,)]/ { content } Parameter = (ParameterQuoted | ParameterSingleQuoted | ParameterNormal ):value { value } NParameterNormal = < /[^,\]]/* > { text } NParameterQuoted = '"' < /[^"]/* > '"' - &/[,\]]/ { text } NParameterSingleQuoted = "'" < /[^']/* > "'" - &/[,\]]/ { text } NParameter = (NParameterQuoted | NParameterSingleQuoted | NParameterNormal ):value { value } Parameters = Parameter:parameter ',' - Parameters:parameters { [ parameter ] + parameters } | Parameter:parameter { [ parameter ] } NamedParameter = Word:key - ':' - NParameter:parameter { { key.to_sym => parameter } } NamedParameters = NamedParameter:parameter - ',' - NamedParameters:parameters { parameter.merge parameters } | NamedParameter:parameter { parameter } Command = CommandName:cn ('(' - Parameters:args - ')')? ('[' - NamedParameters:named_args - ']')? { cn.merge({ args: args || [] , named_args: named_args || {}}) } # paragraph ImplicitParagraph = - !ParagraphDelimiter Comment* DocumentLine:content ln:ln Comment* EofComment? ~paragraph([],[], [], {}, content, ln) # explicit paragraph ExplicitParagraphCommand = Command:c &{ c[:name] == 'p' } ExplicitParagraph = - ExplicitParagraphCommand:c ':' - DocumentContent?:content Le EmptyLine* ~paragraph(c[:ids], c[:classes], c[:args], c[:named_args], content, c[:ln]) Paragraph = ExplicitParagraph | ImplicitParagraph # paragraph_group ParagraphGroup = ln:ln Paragraph+:p EmptyLine* ~paragraph_group([],[],[],{},p, ln) # explicit block BlockHead = Command:command - '{' - Nl EmptyLine* { command } BlockEnd = - '}' - Le EmptyLine* BlockBody = (!BlockEnd Block)+:body { body } ExplicitBlock = - BlockHead:c - BlockBody:content - BlockEnd ~block(c[:name], c[:ids], c[:classes], c[:args], c[:named_args], content, c[:ln]) # preformatted block PreformattedCommand = Command:command &{ ['pre', 'code'].include? command[:name] } PreformattedCommandHeadSimple = PreformattedCommand:command - '{' - Nl { command } PreformattedCommandHeadComplex = PreformattedCommand:command - '{//' Word?:codelanguage - Nl { command.merge({codelanguage: codelanguage}) } PreformattedFence = - "```" Word?:codelanguage ('(' - Parameters:args - ')')? ('[' - NamedParameters:named_args - ']')? - Nl { {codelanguage: codelanguage, args: args || [], named_args: named_args || {} } } PreformattedCommandHead = PreformattedCommandHeadComplex | PreformattedCommandHeadSimple | PreformattedFence PreformatEndSimple = - '}' - Le EmptyLine* PreformatEndComplex = - '//}' - Le EmptyLine* PreformattedBlockSimple = - PreformattedCommandHeadSimple:c (!PreformatEndSimple (CharString Nl))+:content PreformatEndSimple ~preformatted_block(c[:name], c[:ids], c[:classes], c[:args], c[:named_args], c[:codelanguage], content, c[:ln]) PreformattedBlockComplex = - PreformattedCommandHeadComplex:c (!PreformatEndComplex (CharString Nl))+:content PreformatEndComplex ~preformatted_block(c[:name], c[:ids], c[:classes], c[:args], c[:named_args], c[:codelanguage], content, c[:ln]) PreformattedBlockFence = - ln:ln PreformattedFence:c (!"```" CharString Nl)+:content - "```" - Le EmptyLine* ~preformatted_block('code', [], [], c[:args], c[:named_args], c[:codelanguage], content, ln) PreformattedBlock = PreformattedBlockComplex | PreformattedBlockSimple | PreformattedBlockFence # inline command Inline = EscapedChar | ImgInline | MediaInline | CodeInline | CommonInline | FenceInline CommonInline = '[' Command:c '{' - DocumentContentExcept('}'):content '}' ']' ~inline(c[:name], c[:ids], c[:classes], c[:args], c[:named_args], content, c[:ln]) ImgCommand = Command:c &{ c[:name] == 'img' && c[:args].size == 2} ImgInline = '[' ImgCommand:c ']' ~inline(c[:name], c[:ids], c[:classes], c[:args], c[:named_args], nil, c[:ln]) MediaCommand = Command:c &{ (c[:name] == 'video' || c[:name] == 'audio') && c[:args].size > 0} MediaInline = '[' MediaCommand:c ('{' - DocumentContentExcept('}'):content '}')? ']' ~inline(c[:name], c[:ids], c[:classes], c[:args], c[:named_args], content, c[:ln]) FenceInline = '`' ln:ln CharStringExcept('`'):content '`' ~code_inline([], [], [], {}, content, ln) CodeCommand = Command:c &{ c[:name] == 'code' } CodeInline = '[' CodeCommand:c '{' - CharStringExcept('}'):content '}' ']' ~code_inline(c[:ids], c[:classes], c[:args], c[:named_args], content, c[:ln]) EscapedChar = "\\" < /[`]/ > ln:ln ~text(text, ln) # special line commands CommandNameForSpecialLineCommand = NewpageCommand | ExplicitParagraphCommand # newpage NewpageCommand = Command:command &{ command[:name] == 'newpage' } Newpage = - NewpageCommand:c ':' - DocumentContent?:content - Nl ~newpage(c[:ids],c[:classes],c[:args], c[:named_args], content, c[:ln]) # unordered list UnorderedStart(n) = /^/ - < (/\*+/) > &{ text.length == n && text.size < 5 } UnorderedList(n) = ln:ln UnorderedItem(n)+:items ~unordered_list([],[],[],{}, items, ln) UnorderedItem(n) = UnorderedStart(n) ln:ln - UnorderedItemBody(n):content ~ul_item([], [], [], {}, content, ln) UnorderedItemBody(n) = DocumentContent:content Nl - &UnorderedStart(n + 1) ln:ln UnorderedList(n + 1):sub_ul { content << sub_ul } | DocumentContent:content Le { content } # ordered list OrderedList = ln:ln OrderedItem+:items ~ordered_list([],[],[], {}, items, ln) OrderedItem = - ln:ln Num '.' - DocumentContent:content Le ~ol_item([], [], [], {}, content, ln) # definition list DefinitionList = ln:ln DefinitionItem+:items ~definition_list([], [], [], {}, items, ln) DefinitionItem = - ln:ln - ';:' - DocumentContentExcept(':'):term ':' - DocumentContent:definition Le ~dl_item([], [], [term], {}, definition, ln) # long definition list LongDefinitionList = ln:ln LongDefinitionItem+:items ~definition_list([], [], [], {}, items, ln) LongDefinitionItem = - ln:ln ';:' - DocumentContentExcept('{'):term '{' - Nl - BlockBody:definition - BlockEnd ~dl_item([], [], [term], {}, definition, ln) ItemsList = (UnorderedList(1) | OrderedList | DefinitionList | LongDefinitionList) # generic line command LineCommand = - !CommandNameForSpecialLineCommand Command:c ':' - DocumentContent?:content - Le EmptyLine* ~block(c[:name], c[:ids], c[:classes], c[:args], c[:named_args], content, c[:ln]) # blocks LineBlock = ItemsList | LineCommand Block = (PreformattedBlock | HeadedSection | LineBlock | ExplicitBlock | ParagraphGroup ):block EmptyLine* {block} BlockDelimiter = BlockHead | BlockEnd ParagraphDelimiter = BlockDelimiter | PreformattedCommandHead | LineBlock | Newpage | HeadedStart # markdown-style headings HStartMark(n) = - < '#'+ > &{ text.length == n } HMarkupTerminator(n) = - < '#'+ > - CharString:cs Le &{ text.length <= n && !cs.strip.end_with?('{') } | - Eof HStartCommand(n) = - HStartMark(n) ('(' - Parameters:args - ')')? ('[' - NamedParameters:named_args - ']')? { { args: args || [] , named_args: named_args || {}} } HStart(n) = - HStartCommand(n):hsc ln:ln - DocumentContent:s Le { hsc.merge({ level: n, heading: s, ln: ln}) } HSection(n) = HStart(n):h EmptyLine* (!HMarkupTerminator(n) Block)*:content EmptyLine* ~h_section(h[:level], h[:heading], h[:args], h[:named_args], content, h[:ln]) BlockHStart(n) = - HStartCommand(n):hsc ln:ln - DocumentContentExcept('{'):s - '{' - Nl { hsc.merge({ level: n, heading: s, ln: ln}) } ExplicitHSection(n) = BlockHStart(n):h EmptyLine* - BlockBody:content - BlockEnd ~h_section(h[:level], h[:heading], h[:args], h[:named_args], content, h[:ln]) HeadedStart = HStart(6) | HStart(5) | HStart(4) | HStart(3) | HStart(2) | HStart(1) | BlockHStart(6) | BlockHStart(5) | BlockHStart(4) | BlockHStart(3) | BlockHStart(2) | BlockHStart(1) HeadedSection = ExplicitHSection(6) | ExplicitHSection(5) | ExplicitHSection(4) | ExplicitHSection(3) | ExplicitHSection(2) | ExplicitHSection(1) | HSection(6) | HSection(5) | HSection(4) | HSection(3) | HSection(2) | HSection(1) # frontmatter FrontmatterSeparator = - '---' - Nl Frontmatter = FrontmatterSeparator ln:ln (!FrontmatterSeparator ( CharString Nl))+:yaml FrontmatterSeparator EmptyLine* ~frontmatter(yaml, ln) # texts Char = < /[[:print:]]/ > { text } CharString = < Char* > { text } CharExcept(e) = Char:c &{ e.is_a?(Regexp) ? e !~ c : e != c } CharStringExcept(e) = < CharExcept(e)+ > { text } DocumentTextExcept(e) = < (!Inline CharExcept(e))+ > ln:ln ~text(text, ln) DocumentContentExcept(e) = (Inline | DocumentTextExcept(e))+:content { content } DocumentText = < (!Inline Char)+ > ln:ln ~text(text, ln) DocumentContent = (Inline | DocumentText)+:content { content } DocumentLine = DocumentContent:content Le { content } #page Page = Frontmatter?:frontmatter - (!Newpage Block)*:blocks EmptyLine* ~page(([frontmatter] + blocks).select{ |x| !x.nil?}, 1) Pages = Page:page Newpage:newpage Pages:pages { pages.unshift(page) } | Page:page { [ page ] } #root root = BOM? EmptyLine* Pages:pages EofComment? Eof ~root(pages)