SourceForge.JP: Open Source Software

Login Create Account Help [auto][en][zh][de][fr][ko][es][pt]
Search

Browse CVS Repository

View of /rdleaves/RDLeaves/lib/rdleaves/rdsyntax.rb

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.27 - (download) (annotate)
Sun Jul 2 13:19:27 2006 UTC (3 years, 4 months ago) by zunda
Branch: MAIN
CVS Tags: RENEW_070204_bp, HEAD
Branch point for: RENEW_070204
Changes since 1.26: +11 -1 lines
* Sun Jul  2 2006 zunda <zunda at freeshell.org>
- lib/rdleaves/rdsyntax.rb: modified INLINE_END to work with ')))'
- lib/rdleaves/rdsyntax.rb: added syntax for inline plugin - ((( ~ )))

#	rdsyntax.rb: RD syntax accoding to
# http://www2.pos.to/~tosh/ruby/rdtool/ja/doc/rd-draft.html
#
# Copyright:: Copyright (C) 2005 zunda <zunda at freeshell.org>
# License:: GPL
#

module RDsyntax

	# an element, i.e. a block or an inline, in RD
	class Element
		# First Line
		attr_reader :start_line
		# parent RDpage
		attr_reader :parent
		# content of element as an array of Element
		attr_reader :elements
		# name tag
		attr_reader :tag

		def initialize( parent, line )
			@parent = parent
			@elements = Array.new
			@start_line = line
			@tag = nil
		end

		def append( element )
			@elements.push( element )
		end

		def get_tag
			@elements.each do |e|
				e.get_tag
			end
		end

		def parse_inline?; true; end
	end

	class BlockElement < Element
		def only_block?; true; end

	private
		def self::_match?( head, regex )
			/\A#{regex}\Z/ =~ head
		end
	end

	class RootElement < BlockElement
		def self::match?( head, base, env_block ); false; end
		def accept?( line ); true; end
		def wrapwith; nil; end
	end

	class ParagraphElement < RootElement
		def only_block?; false; end
		def accept?( line )
			@start_line.dangling?( line ) and \
			line.tokens[1].empty? and \
			not line.tokens[3].strip.empty?
		end
	end

	class WhiteLineElement < RootElement
		def only_block?; false; end
		def accept?( line )
			@start_line.same_level?( line ) and \
			line.tokens[1].empty? and \
			line.tokens[3].strip.empty?
		end
	end

	module BlockElements
		@@heads = nil

		def self::blocktype( line, cur_block )
			BlockElements.constants.map{ |c| BlockElements::const_get( c ) }.each do |b|
				return b if b::match?( line, cur_block )
			end
			return nil
		end

		def self::heads
			unless @@heads
				@@heads = BlockElements.constants.map{ |c| BlockElements::const_get( c )::head }.reject{ |s| not s }.uniq.join( '|' )
			end
			@@heads
		end

		class HeadElement < BlockElement
			def self::head; '={1,4}|\+{1,2}'; end
			def self::match?( line, env_block )
				RootElement === env_block and \
				line.tokens[0].empty? and \
				self::_match?( line.tokens[1], self::head )
			end
			def accept?( line )
				@start_line.dangling?( line ) and \
				not @start_line.indented?( line ) and \
				not BlockElements::blocktype( line, self )
			end
			def wrapwith; nil; end
		end

		class IncludeElement < BlockElement
			def only_block?; false; end
			def self::head; '<<<'; end
			def self::match?( line, env_block )
				RootElement === env_block and \
				line.tokens[0].empty? and \
				self::_match?( line.tokens[1], self::head )
			end
			def accept?( line ); false; end
			def wrapwith; nil; end
		end

		class VerbBlockElement < BlockElement
			def only_block?; false; end
			def parse_inline?; false; end
			def self::head; nil; end
			def self::match?( line, env_block ); false; end
			def accept?( line ); @start_line.same_verb_block?( line ); end
			def wrapwith; nil; end
		end

		class ItemListElement < BlockElement
			def self::head; nil; end
			def self::match?( line, env_block ); false; end
			def accept?( line )
				@start_line.tokens[0] == line.tokens[0] and \
				ItemListItemElement::match?( line, self )
			end
			def wrapwith; nil; end
		end

		class ItemListItemElement < BlockElement
			def self::head; '\*'; end
			def self::match?( line, env_block )
				self::_match?( line.tokens[1], self::head )
			end
			def accept?( line ); @start_line.dangling?( line ); end
			def wrapwith; ItemListElement; end
		end

		class EnumListElement < BlockElement
			def self::head; nil; end
			def self::match?( line, env_block ); false; end
			def accept?( line )
				@start_line.tokens[0] == line.tokens[0] and \
				EnumListItemElement::match?( line, self )
			end
			def wrapwith; nil; end
		end

		class EnumListItemElement < BlockElement
			def self::head; '\(\d+\)'; end
			def self::match?( line, env_block )
				self::_match?( line.tokens[1], self::head )
			end
			def accept?( line ); @start_line.dangling?( line ); end
			def wrapwith; EnumListElement; end
		end

		class DescListElement < BlockElement
			def self::head; nil; end
			def self::match?( line, env_block ); nil; end
			def accept?( line )
				# DT
				(@start_line.tokens[0] == line.tokens[0] and \
					DescTermElement::match?( line, self )) or \
				# DD
				@start_line.dangling?( line )
			end
			def wrapwith; nil; end
		end

		class DescTermElement < BlockElement
			def only_block?; false; end
			def self::head; ':'; end
			def self::match?( line, env_block )
				self::_match?( line.tokens[1], self::head )
			end
			def accept?( line ); false; end
			def wrapwith; DescListElement; end
		end

		class DescDefElement < BlockElement
			def self::head; nil; end
			def self::match?( line, env_block )
				DescListElement == env_block and line.tokens[1].empty?
			end
			def accept?( line ); @start_line.same_level?( line ); end
			def wrapwith; DescListElement; end
		end

		class MethodListElement < BlockElement
			def self::head; nil; end
			def self::match?( line, env_block ); false; end
			def accept?( line )
				@start_line.tokens[0] == line.tokens[0] and \
				@start_line.tokens[1] == line.tokens[1]
			end
			def wrapwith; nil; end
		end

		class MethodTermElement < BlockElement
			def self::head; '\-\-\-'; end
			def self::match?( line, env_block )
				self::_match?( line.tokens[1], self::head )
			end
			def accept?( line ); false; end
			def wrapwith; MethodListElement; end
		end

		class MethodDefElement < BlockElement
			def self::head; nil; end
			def self::match?( line, env_block )
				MethodListElement == env_block and line.tokens[1].empty?
			end
			def accept?( line ); @start_line.same_level?( line ); end
			def wrapwith; MethodListElement; end
		end

	end

	class InlineElement < Element

	private
		def self::_match?( head, char )
			/\A\(\(#{Regexp.quote( char )}\Z/ =~ head
		end

		def _endwith?( tail, char )
			/\A#{Regexp.quote( char )}\)\)\Z/ =~ tail
		end
	end

	INLINE_BEGIN = '\(\(.'
	INLINE_END = '.\)\)(?!\))'

	module InlineElements

		def self::inlinetype( head )
			InlineElements.constants.map{ |c| InlineElements::const_get( c ) }.each do |i|
				return i if i::match?( head )
			end
			return nil
		end

		class EmElement < InlineElement
			def self::match?( head )
				self::_match?( head, '*' )
			end
			def endwith?( tail )
				_endwith?( tail, '*' )
			end
		end

		class CodeElement < InlineElement
			def self::match?( head )
				self::_match?( head, '{' )
			end
			def endwith?( tail )
				_endwith?( tail, '}' )
			end
			def parse_inline?; false; end
		end

		class VarElement < InlineElement
			def self::match?( head )
				self::_match?( head, '|' )
			end
			def endwith?( tail )
				_endwith?( tail, '|' )
			end
			def parse_inline?; false; end
		end

		class KeyboardElement < InlineElement
			def self::match?( head )
				self::_match?( head, '%' )
			end
			def endwith?( tail )
				_endwith?( tail, '%' )
			end
			def parse_inline?; false; end
		end

		class TermElement < InlineElement
			def self::match?( head )
				self::_match?( head, ':' )
			end
			def endwith?( tail )
				_endwith?( tail, ':' )
			end
		end

		class IdElement < InlineElement
			def self::match?( head )
				self::_match?( head, '<' )
			end
			def endwith?( tail )
				_endwith?( tail, '>' )
			end
			def parse_inline?; false; end
		end

		class FootnoteElement < InlineElement
			def self::match?( head )
				self::_match?( head, '-' )
			end
			def endwith?( tail )
				_endwith?( tail, '-' )
			end
			def parse_inline?; false; end
		end

		class VerbInlineElement < InlineElement
			def self::match?( head )
				self::_match?( head, "'" )
			end
			def endwith?( tail )
				_endwith?( tail, "'" )
			end
			def parse_inline?; false; end
		end

		class PluginInlineElement < InlineElement
			def self::match?( head )
				self::_match?( head, '(' )
			end
			def endwith?( tail )
				_endwith?( tail, ')' )
			end
			def parse_inline?; false; end
		end

	end

	# a line in RD
	class Line
		# line number
		attr_reader :linenumber
		# raw line
		attr_reader :line
		# path
		attr_reader :path

		def initialize( line, linenumber = nil, path = nil )
			@line = line
			@linenumber = linenumber
			@path = path
			@tokens = nil
		end

		def get_tag
		end

		# returns Array of 0:base indent, 1:head charactors, 2:space, and 3:body
		def tokens
			unless @tokens then
				@tokens = /\A([ \t]*)(#{BlockElements::heads})?([ \t]*)(.*)\Z/m.match( @line ).to_a.map{ |x| x ? x : '' }[1..-1]
			end
			@tokens
		end

		# true if line is in the same level as self
		def same_level?( line )
			tokens[0] == line.tokens[0]
		end

		# true if line in the same verbatim block
		def same_verb_block?( line )
			return false if tokens[0].size > line.tokens[0].size
			tokens[0] == line.tokens[0][0...tokens[0].size]
		end

		# true if line is a continuation of self
		def dangling?( line )
			t = line.tokens
			[
				tokens[0] + "\t",
				tokens[0] + ' '*tokens[1].size + tokens[2],
			].each do |next_indent|
				return true if next_indent == t[0]
			end
			false
		end

		# true if line is indented compared to self
		def indented?( line )
			t = line.tokens
			[
				tokens[0] + "\t",
				tokens[0] + ' '*tokens[1].size + tokens[2],
			].each do |next_indent|
				return true if next_indent == t[0][0...(next_indent.size)] and /[ \t]+\Z/ =~ t[0][next_indent.size..-1]
			end
			false
		end

		# true if line is a whiteline compared to self
		def whiteline?( line )
			t = line.tokens
			tokens[0] == t[0] and t[1].empty?
		end

		def inspect
			"<#{self.class} #{@line.inspect}>"
		end
	end

	# lines in RD
	class Page
		class PageParserError < StandardError; end

		# Array of Line
		attr_reader :content
		# parent RDpage
		attr_reader :parent
		# parsed tree
		attr_reader :root
		# HTML
		attr_reader :html
		# name tags
		attr_reader :tags

		def initialize( parent, content )
			@parent = parent
			@content = content
			@root = nil
			@tags = Hash.new
			@html = nil
		end

		# make a tree of Element
		def parse
			@root = RootElement.new( self, Line.new( '' ) )
			block_stack = [ @root ]
			inline_stack = []
			content = @content
			while not content.empty? do

				# current line
				line = content.shift

				# current block
				begin
					cur_block = block_stack[-1]
					unless cur_block then
						raise PageParserError, "#{line.path}:#{line.linenumber}: empty stack"
					end
				end while( (not cur_block.accept?( line )) and block_stack.pop )

				# block type of current line
				block_class = BlockElements::blocktype( line, cur_block )

				# Some blocks does not have a header
				if BlockElements::DescListElement === cur_block and (\
						not block_class or \
						( not BlockElements::DescDefElement == block_class and \
						not BlockElements::DescTermElement == block_class ) \
				) then
					wrapper_block = BlockElements::DescDefElement.new( self, line )
					cur_block.append( wrapper_block )
					cur_block = wrapper_block
					block_stack.push( cur_block )
					block_class = BlockElements::blocktype( line, cur_block )
				end
				if not block_class and \
						not BlockElements::VerbBlockElement === cur_block and \
						cur_block.start_line.indented?( line ) then
					block_class = BlockElements::VerbBlockElement
				end

				# create a new block if needed
				if block_class then
					new_block = block_class.new( self, line )

					if new_block.wrapwith and not new_block.wrapwith === cur_block then
						wrapper_block = new_block.wrapwith.new( self, line )
						cur_block.append( wrapper_block )
						cur_block = wrapper_block
						block_stack.push( cur_block )
					end

					cur_block.append( new_block )
					cur_block = new_block
					block_stack.push( cur_block )
				end

				# create Paragraph or WhiteLine if needed
				if cur_block.only_block? then
					if not line.tokens[3].strip.empty? then # paragraph
						wrapper_block = ParagraphElement.new( self, line )
					else
						wrapper_block = WhiteLineElement.new( self, line )
					end
					cur_block.append( wrapper_block )
					cur_block = wrapper_block
					block_stack.push( cur_block )
				end

				# put the content into the block
				if cur_block.parse_inline? then
					inline_stack.clear
					inline_stack.push( cur_block )
					begin
						line.line.split( /(#{INLINE_BEGIN}|#{INLINE_END})/ ).map{ |str| Line.new( str, line.linenumber, line.path ) }.each do |line|
							next if line.line.empty?
							i = InlineElements::inlinetype( line.line )
							if i and inline_stack[-1].parse_inline? then	# beginning of an inline
								e = i.new( self, line )
								inline_stack[-1].append( e )
								inline_stack.push( e )
							elsif	inline_stack[-1] and \
									InlineElement === inline_stack[-1] and \
									inline_stack[-1].endwith?( line.line ) then	# endding of an inline
								inline_stack.pop
							else	# usual text
								inline_stack[-1].append( line )
							end
						end
					end while( (inline_stack.size > 1) and ( line = content.shift ) )
					if inline_stack.size > 1 then
						top = inline_stack[-1]
						raise PageParserError, "Inline starting with `#{top.start_line.line}' in #{top.start_line.path}:#{top.start_line.linenumber} does not end"
					end
				else
					cur_block.append( line )
				end

			end
			self
		end

	end

end

Back to SourceForge.jp
  SourceForge.jp (Powered by ViewVC)
Powered by ViewVC 1.0.5
ViewVC Help
©OSDN Corporation