# Copyright (C) garin <garin54@gmail.com> 2011 # See the included file COPYING for details. class BlockParser
token HEADER WHITELINE HEADLINE PLAIN DESCLINE_TITLE DESCLINE PREFORMAT QUOTE INDENT DEDENT ITEMLIST ITEMLISTCONTINUE NUMLIST TABLELINE preclow nonassoc DUMMY prechigh options no_result_var rule document : blocks{ val[0].compact } blocks : block { val } | blocks block { [val[0], val[1]].flatten } block : header | paragraph { val[0] } | preformat_block | quote_block | itemlist_blocks { ItemList.new(val[0].flatten) } | numlist_blocks { NumList.new(val[0].flatten) } | desc_block | table_block | headline | WHITELINE { WhiteLine.new } # ----- header header : HEADER { name, val = val[0].split(":",2) if name.nil? or val.nil? else @metadata.update({name.strip.to_sym => val.strip }) end nil } # ----- headline headline : HEADLINE { # val[0] is like [level, title, index] title = val[0][1] level = val[0][0] if level == 1 @metadata[:subject] ||= title else @head_index.update(level) end @index[:head] ||= [] @index[:head] << {:title => title, :level => level, :index => @head_index.to_s} HeadLine.new([level, title, @index[:head].size, @head_index.to_s]) } # ----- paragraph paragraph : plain_texts { Paragraph.new @inline_parser.parse(val, @no) } plain_texts : PLAIN { val[0] } | plain_texts PLAIN { val[0] + val[1] } # ----- desc desc_block : DESCLINE_TITLE desclines { if val[1].nil? lines = [Plain.new("")] else lines = @inline_parser.parse(val[1], @no) end Desc.new([val[0], lines]) } desclines : DESCLINE { val[0] } | desclines DESCLINE { val[0] + val[1] } | # ----- preformat preformat_block : preformats { pr = val[0].strip ; Preformat.new([pr]) unless pr.empty? } preformats : PREFORMAT { val[0] } | preformats PREFORMAT { val[0] + val[1] } # ----- preformat end # ----- quote quote_block : quotes { qu = val[0].strip ; Quote.new(@inline_parser.parse(qu, @no)) unless qu.empty? } quotes : QUOTE { val[0] } | quotes QUOTE { val[0] + val[1] } # ----- quote end # ----- itemlist itemlist_blocks : itemlist_block { val[0] } | itemlist_blocks itemlist_block { val[0] << val[1] } itemlist_block : itemlists { val[0] } | itemlist_indent_blocks { val[0] } itemlist_indent_blocks : INDENT itemlist_blocks DEDENT { val } itemlists : itemlistitems {[PlainTextBlock.new(@inline_parser.parse(val[0], @no))]} | itemlists itemlistitems { val[0] << PlainTextBlock.new(@inline_parser.parse(val[1],@no)) } itemlistitems : ITEMLIST { val[0] } | ITEMLIST itemlist_continues { val[0] + val[1] } itemlist_continues : ITEMLISTCONTINUE { "\n" + val[0] } | itemlist_continues ITEMLISTCONTINUE { val[0] + "\n" + val[1] } # ----- itemlist end # ----- numlist numlist_blocks : numlist_block { val[0] } | numlist_blocks numlist_block { val[0] << val[1] } numlist_block : numlists { val[0] } | numlist_indent_blocks { val[0] } numlist_indent_blocks : INDENT numlist_blocks DEDENT { val } numlists : NUMLIST { [PlainTextBlock.new(@inline_parser.parse(val[0],@no))] } | numlists NUMLIST { val[0] << PlainTextBlock.new(@inline_parser.parse(val[1],@no)) } # ----- numlist end # ----- tableblock table_block : tablelines { Table.new(val[0]) } tablelines : TABLELINE { val } | tablelines TABLELINE { val[0] << val[1] } # ----- tableblock end
end # end of rule
—- inner include ParserUtility
class Line
def initialize(line) @content = line
# @indent = get_line_indent(line)
# @type = nil end attr_reader :indent, :no attr_accessor :type, :content alias indent_size indent def get_line_indent return 0 if @content.nil? @content =~ /(\s*)/ $1.size end alias indent get_line_indent
end
def initialize(options = {})
@options = options @inline_parser = InlineParser.new(options) @metadata = {} @inline_index = @inline_parser.index @index = {} @head_index = HeadIndex.new
end attr_reader :metadata, :inline_index, :index
def parse(src)
@no = 0 # srcが空の時の対応 src = ["# No Document"] if src.size == 0 # srcをerbで処理 if @options[:erb] src = ERB.new(src.join,4).result(binding.taint).split("\n").map {|s| "#{s}\n"} end # 部分テンプレート(partial) src = insert_partial(src) @src = Array(src) @line = Line.new("") @line_pre = @line.dup @indent_stack = [] @current_indent = 0 @current_type = :header @yydebug = true @view_token_type = false do_parse
end
def on_error(token_id, value, stack)
lineno = @src[0..@no].to_s.split("\n").size raise Racc::ParseError, "mokblockpaser: line #{lineno}: syntax error on #{value.inspect}"
end
def next_token
@line_pre = @line.dup @line = Line.new(@src[@no])
# puts “line: #{@line.content}” if @view_token_type
case @line.content when nil @line.content = "" if_current_indent_equal("") do puts "b: false: #{@line.content}" if @view_token_type [false, false] end when /^$/ @line.content = "" if_current_indent_equal("") do if @current_type == :preformat puts "b: :PREFORMAT: #{@line.content}" if @view_token_type [:PREFORMAT, "\n"] elsif @current_type == :quote puts "b: :QUOTE: #{@line.content}" if @view_token_type [:QUOTE, "\n"] elsif @current_type == :descline puts "b: DESCLINE: #{@line.content}" if @view_token_type [:DESCLINE, " "] else puts "b: WHITELINE: #{@line.content}" if @view_token_type @current_type = :whiteline [:WHITELINE, :WHITELINE] end end when /^\#(.*)/ # comment line @no += 1 if @current_type == :header puts "b: HEADER: #{@line.content}" if @view_token_type [:HEADER, $1.strip] else puts "b: COMMENT(noop): #{@line.content}" if @view_token_type next_token end when /^(={1,4})(?!=)\s*(?=\S)/, /^(\+{1,2})(?!\+)\s*(?=\S)/ rest = $' # ' rest.strip! mark = $1 # if_current_indent_equal("") do if_current_indent_equal(@line.indent) do @current_type = :headline puts "b: HEADLINE: #{@line.content}" if @view_token_type [:HEADLINE, [mark_to_level(mark), rest]] end when /^\s\s+(.*)/ # type == preformat puts "b: 2 WHITE SPACE(#{@current_type}) : #{@line.content}" if @view_token_type case @current_type when :itemlist if @line.content =~ /^(\s*)(\*)(\s+)(.*)/ line = $4.strip if line.empty? @no += 1 next_token else if_current_indent_equal(@line.indent) do puts "b: ITEMLIST: [#{line}]" if @view_token_type @current_type = :itemlist [:ITEMLIST, line] end end else line = @line.content.strip if line.empty? @no += 1 next_token else puts "b: ITEMLISTCONTINUE: [#{line.empty?}] --" if @view_token_type @no += 1 @current_type = :itemlist [:ITEMLISTCONTINUE, line] end end when :numlist @line.content =~ /^(\s*)(\(\d+\))(\s+)(.*)/ if $4.nil? @line.content =~ /^(\s*)(\d\.)(\s+)(.*)/ end line = $4 line ||= @line.content.strip if line.empty? @no += 1 next_token else if_current_indent_equal(@line.indent) do puts "b: NUMLIST: [#{line}]" if @view_token_type @current_type = :numlist [:NUMLIST, line] end end else @no += 1 if @current_type == :descline @current_type = :descline puts "b: DESCLINE: #{@line.content}" if @view_token_type [:DESCLINE, $1 + "\n"] else @current_type = :preformat puts "b: PREFORMAT: #{$1}" if @view_token_type [:PREFORMAT, @line.content.sub(" ","")] end end when /^>\s(.*)/ # type == quote puts "b: 2 WHITE SPACE(#{@current_type}) : #{@line.content}" if @view_token_type @current_type = :quote puts "b: QUOTE: #{$1}" if @view_token_type if_current_indent_equal(@line.indent) do [:QUOTE, @line.content.sub("> ","")] end when /^(\:)(.*)/ # type = desclist if_current_indent_equal(@line.indent) do @current_type = :descline puts "b: DESCLINE_TILTE: #{$2.strip}" if @view_token_type [:DESCLINE_TITLE, $2.strip] end when /^(\s*)(\*)(\s+)(.*)/ # type = itemlist if_current_indent_equal(@line.indent) do puts "b: ITEMLIST: #{@line.content}" if @view_token_type @current_type = :itemlist [:ITEMLIST, $4] end when /^(\s*)(\(\d+\))(\s+)(.*)/ if_current_indent_equal(@line.indent) do puts "b: NUMLIST: #{@line.content}" if @view_token_type @current_type = :numlist [:NUMLIST, $4] end when /^(\s*)(\d+\.)(\s+)(.*)/ # type = numlist if_current_indent_equal(@line.indent) do puts "b: NUMLIST: #{@line.content}" if @view_token_type @current_type = :numlist [:NUMLIST, $4] end when /^\|-.*/ # type = table head # テーブル内であればテーブルヘッダとして無視、そうでなければ普通のPLAINとして扱う if @current_type == :table @no += 1 next_token else @current_type = :plain if_current_indent_equal(@line.indent) do puts "b: PLAIN: #{@line.content}" if @view_token_type [:PLAIN, @line.content] end end when /^\|.*/ # type = table @no += 1 @current_type = :table lines = @line.content.chomp.split("|") lines.shift [:TABLELINE, lines] when /(.*)/ # type == plain @current_type = :plain if_current_indent_equal(@line.indent) do puts "b: PLAIN: #{@line.content}" if @view_token_type [:PLAIN, @line.content] end else puts "raise : #{@line}" end
end
def if_current_indent_equal(ident)
indent_space = 2 puts "current: #{@current_indent}, line: #{@line.indent}, stack #{@indent_stack.size}:" if @view_token_type indent_sabun = @current_indent - @line.indent if indent_sabun >= -1 and indent_sabun <= 1 @no += 1 yield elsif @current_indent < @line.indent ((@line.indent - @current_indent) / indent_space).times do @indent_stack.push("") end @current_indent = @line.indent puts "b: INDENT" if @view_token_type [:INDENT, :INDENT] else @indent_stack.pop @current_indent = @line.indent if @line.indent == @indent_stack.size * indent_space puts "b: DEDENT" if @view_token_type [:DEDENT, :DEDENT] end
end
def insert_partial(src)
src.map do |line| if line =~ /^\(\(\!(.*?)\!\)\)/ file = File.join(File.dirname($1), "_#{File.basename($1)}.mok") file_path = file file_path = File.join(File.dirname(@options[:src_file]),file) if @options[:src_file] unless @options[:partial_directory].nil? file_path = File.join(@options[:partial_directory],file) unless @options[:partial_directory].empty? end if File.exist?(file_path) File.open(file_path).readlines else ["((*Warning*)): [partial] #{file} is not found\n"] end else line end end.flatten
end
—- header require “parserutility” require “mokinlineparser.tab” require “mokelement” require “erb”
module Mok
—- footer
if __FILE__ == $0 mok = BlockParser.new src = $stdin.readlines nodes = mok.parse(src) puts "----- index -----" mok.index.each do |key,val| puts key val.each do |v| p v end end puts "----- info -----" p mok.info puts "----- output -----" nodes.each do |n| puts n.apply end end
end # end of module Mok