class Kwalify::PlainYamlParser

base class of yaml parser

ex.

str = ARGF.read()
parser = Kwalify::PlainYamlParser.new(str)
doc = parser.parse()
p doc

Public Class Methods

new(yaml_str) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 36
def initialize(yaml_str)
  @lines = yaml_str.to_a()
  @line  = nil
  @linenum = 0
  @anchors = {}
  @aliases = {}
end

Public Instance Methods

has_next?() click to toggle source
# File lib/kwalify/yaml-parser.rb, line 55
def has_next?
  return @end_flag != 'EOF'
end
parse() click to toggle source
# File lib/kwalify/yaml-parser.rb, line 45
def parse()
  data = parse_child(0)
  if data.nil? && @end_flag == '---'
    data = parse_child(0)
  end
  resolve_aliases(data) unless @aliases.empty?
  return data
end
parse_all() click to toggle source
# File lib/kwalify/yaml-parser.rb, line 60
def parse_all
  list = []
  while has_next()
    doc = parse()
    list << doc
  end
  return list
end

Protected Instance Methods

add_to_map(map, key, value, linenum) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 89
def add_to_map(map, key, value, linenum)
  map[key] = value
end
add_to_seq(seq, value, linenum) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 77
def add_to_seq(seq, value, linenum)
  seq << value
end
create_mapping(linenum=nil) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 85
def create_mapping(linenum=nil)
  return {}
end
create_scalar(value, linenum=nil) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 107
def create_scalar(value, linenum=nil)
  return value
end
create_sequence(linenum=nil) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 73
def create_sequence(linenum=nil)
  return []
end
current_line() click to toggle source
# File lib/kwalify/yaml-parser.rb, line 112
def current_line
  return @line
end
current_linenum() click to toggle source
# File lib/kwalify/yaml-parser.rb, line 116
def current_linenum
  return @linenum
end
merge_map(map, map2, linenum) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 101
def merge_map(map, map2, linenum)
  map2.each do |key, val|
    map[key] = value unless map.key?(key)
  end
end
set_default(map, value, linenum) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 97
def set_default(map, value, linenum)
  map.value = value
end
set_map_with(map, key, value, linenum) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 93
def set_map_with(map, key, value, linenum)
  map[key] = value
end
set_seq_at(seq, i, value, linenum) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 81
def set_seq_at(seq, i, value, linenum)
  seq[i] = value
end

Private Instance Methods

_getchar() click to toggle source
# File lib/kwalify/yaml-parser.rb, line 149
def _getchar
  @index += 1
  ch = @sbuf[@index]
  while ch.nil?
    break if (line = getline()).nil?
    reset_sbuf(line)
    @index += 1
    ch = @sbuf[@index]
  end
  return ch
end
_getline() click to toggle source
# File lib/kwalify/yaml-parser.rb, line 130
def _getline
  @line = @lines[@linenum]
  @linenum += 1
  case @line
  when nil             ; @end_flag = 'EOF'
  when /^\.\.\.$/      ; @end_flag = '...'; @line = nil
  when /^---(\s+.*)?$/ ; @end_flag = '---'; @line = nil
  else                 ; @end_flag = nil
  end
  return @line
end
assert(bool_expr) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 720
def assert(bool_expr)
  raise "*** assertion error" unless bool_expr
end
current_char() click to toggle source
# File lib/kwalify/yaml-parser.rb, line 173
def current_char
  return @sbuf[@index]
end
getchar() click to toggle source
# File lib/kwalify/yaml-parser.rb, line 161
def getchar
  ch = _getchar()
  ch = _getchar() while ch && white?(ch)
  return ch
end
getchar_or_nl() click to toggle source
# File lib/kwalify/yaml-parser.rb, line 167
def getchar_or_nl
  ch = _getchar()
  ch = _getchar() while ch && white?(ch) && ch != ?\n
  return ch
end
getlabel() click to toggle source
# File lib/kwalify/yaml-parser.rb, line 177
def getlabel
  if @sbuf[@index..-1] =~ /\A\w[-\w]*/
    label = $&
    @index += label.length
  else
    label = nil
  end
  return label
end
getline() click to toggle source
# File lib/kwalify/yaml-parser.rb, line 124
def getline
  line = _getline()
  line = _getline() while line && line =~ /^\s*($|\#)/
    return line
end
parse_alias(column, value) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 478
def parse_alias(column, value)
  assert value =~ /^\*([-\w]+)(( *)(.*))?$/
  label  = $1
  space  = $3
  value2 = $4
  if value2 && !value2.empty? && value2[0] != ?\#
    #* key=:alias_extradata  msg="alias cannot take any data."
    raise syntax_error(:alias_extradata)
  end
  data = @anchors[label]
  unless data
    data = register_alias(label)
    #raise syntax_error("anchor '#{label}' not found (cannot refer to backward or child anchor).")
  end
  getline()
  return data
end
parse_anchor(column, value) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 451
def parse_anchor(column, value)
  assert value =~ /^\&([-\w]+)(( *)(.*))?$/
  label  = $1
  space  = $3
  value2 = $4
  if value2 && !value2.empty?
    #column2 = column + 1 + label.length + space.length
    #data = parse_value(column2, value2)
    value_start_column = column + 1 + label.length + space.length
    data = parse_value(column, value2, value_start_column)
  else
    #column2 = column + 1
    #data = parse_child(column2)
    data = parse_child(column)
  end
  register_anchor(label, data)
  return data
end
parse_block_text(column, value) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 546
def parse_block_text(column, value)
  assert value =~ /^[>|\|]/
  value =~ /^([>|\|])([-+]?)(\d+)?\s*(.*)$/
  char = $1
  indicator = $2
  sep = char == "|" ? "\n" : " "
  margin = $3 && !$3.empty? ? $3.to_i : nil
  #text = $4.empty? ? '' :  $4 + sep
  text = $4
  s = ''
  empty = ''
  min_indent = -1
  while line = _getline()
    line =~ /^( *)(.*)/
    indent = $1.length
    if $2.empty?
      empty << "\n"
    elsif indent < column
      break
    else
      min_indent = indent if min_indent < 0 || min_indent > indent
      s << empty << line
      empty = ''
    end
  end
  s << empty if indicator == '+' && char != '>'
  s[-1] = "" if indicator == '-'
  min_indent = column + margin - 1 if margin
  if min_indent > 0
    sp = ' ' * min_indent
    s.gsub!(/^#{sp}/, '')
  end
  if char == '>'
    s.gsub!(/([^\n])\n([^\n])/, '\1 \2')
    s.gsub!(/\n(\n+)/, '\1')
    s << empty if indicator == '+'
  end
  getline() if current_line() =~ /^\s*\#/
    return create_scalar(text + s)
end
parse_child(column) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 199
def parse_child(column)
  line = getline()
  return create_scalar(nil) if !line
  line =~ /^( *)(.*)/
  indent = $1.length
  return create_scalar(nil) if indent < column
  value = $2
  return parse_value(column, value, indent)
end
parse_flow(depth) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 267
def parse_flow(depth)
  ch = current_char()
  #ch = getchar()
  if ch.nil?
    #* key=:flow_eof  msg="found EOF when parsing flow style."
    rase syntax_error(:flow_eof)
  end
  ## alias
  if ch == ?*
    _getchar()
    label = getlabel()
    unless label
      #* key=:flow_alias_label  msg="alias name expected."
      rase syntax_error(:flow_alias_label)
    end
    data = @anchors[label]
    unless data
      data = register_alias(label)
      #raise syntax_error("anchor '#{label}' not found (cannot refer to backward or child anchor).")
    end
    return data
  end
  ## anchor
  label = nil
  if ch == ?&
    _getchar()
    label = getlabel()
    unless label
      #* key=:flow_anchor_label  msg="anchor name expected."
      rase syntax_error(:flow_anchor_label)
    end
    ch = current_char()
    ch = getchar() if white?(ch)
  end
  ## flow data
  if ch == ?[
    data = parse_flow_seq(depth)
  elsif ch == ?{
    data = parse_flow_map(depth)
  else
    data = parse_flow_scalar(depth)
  end
  ## register anchor
  register_anchor(label, data) if label
  return data
end
parse_flow_map(depth) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 346
def parse_flow_map(depth)
  assert current_char() == ?{          #}
  map = create_mapping()  # {}
  ch = getchar()
  if ch != ?}
    linenum = current_linenum()
    key, value = parse_flow_map_item(depth + 1)
    #map[key] = value
    add_to_map(map, key, value, linenum)
    while (ch = current_char()) == ?,
      ch = getchar()
      if ch == ?}
        #* key=:flow_mapnoitem  msg="mapping item required (or last comma is extra)."
        raise syntax_error(:flow_mapnoitem)
      end
      #break if ch == ?}
      linenum = current_linenum()
      key, value = parse_flow_map_item(depth + 1)
      #map[key] = value
      add_to_map(map, key, value, linenum)
    end
  end
  unless current_char() == ?}
    #* key=:flow_mapnotclosed  msg="flow style mapping requires '}'."
    raise syntax_error(:flow_mapnotclosed)
  end
  getchar() if depth > 0
  return map
end
parse_flow_map_item(depth) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 376
def parse_flow_map_item(depth)
  key = parse_flow(depth)
  unless (ch = current_char()) == ?:
    $stderr.puts "*** debug: key=#{key.inspect}"
    s = ch ? "'#{ch.chr}'" : "EOF"
    #* key=:flow_nocolon  msg="':' expected but got %s."
    raise syntax_error(:flow_nocolon, [s])
  end
  getchar()
  value = parse_flow(depth)
  return key, value
end
parse_flow_scalar(depth) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 389
def parse_flow_scalar(depth)
  case ch = current_char()
  when ?", ?'         #"
    endch = ch
    s = ''
    while (ch = _getchar()) != nil && ch != endch
      if ch == ?\             ch = _getchar()
        if ch.nil?
          #* key=:flow_str_notclosed  msg="%s: string not closed."
          raise syntax_error(:flow_str_notclosed, endch == ?" ? "'\"'" : '"\"')
        end
        if endch == ?"
          case ch
          when ?\\ ;  s << "\\"
          when ?"  ;  s << "\""
          when ?n  ;  s << "\n"
          when ?r  ;  s << "\r"
          when ?t  ;  s << "\t"
          when ?b  ;  s << "\b"
          else     ;  s << "\\" << ch.chr
          end
        elsif endch == ?'
          case ch
          when ?\\ ;  s << '\'
          when ?'  ;  s << '\'
          else     ;  s << '\' << ch.chr
          end
        end
      else
        s << ch.chr
      end
    end
    getchar()
    scalar = s
  else
    s = ch.chr
    while (ch = _getchar()) != nil && ch != ?: && ch != ?, && ch != ?] && ch != ?}
      s << ch.chr
    end
    scalar = to_scalar(s.strip)
  end
  return create_scalar(scalar)
end
parse_flow_seq(depth) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 314
def parse_flow_seq(depth)
  assert current_char() == ?[
  seq = create_sequence()  # []
  ch = getchar()
  if ch != ?}
    linenum = current_linenum()
    #seq << parse_flow_seq_item(depth + 1)
    add_to_seq(seq, parse_flow_seq_item(depth + 1), linenum)
    while (ch = current_char()) == ?,
      ch = getchar()
      if ch == ?]
        #* key=:flow_noseqitem  msg="sequence item required (or last comma is extra)."
        raise syntax_error(:flow_noseqitem)
      end
      #break if ch == ?]
      linenum = current_linenum()
      #seq << parse_flow_seq_item(depth + 1)
      add_to_seq(seq, parse_flow_seq_item(depth + 1), linenum)
    end
  end
  unless current_char() == ?]
    #* key=:flow_seqnotclosed  msg="flow style sequence requires ']'."
    raise syntax_error(:flow_seqnotclosed)
  end
  getchar() if depth > 0
  return seq
end
parse_flow_seq_item(depth) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 342
def parse_flow_seq_item(depth)
  return parse_flow(depth)
end
parse_flowstyle(column, value) click to toggle source

flowstyle ::= flow_seq | flow_map | flow_scalar | alias

flow_seq ::= '[' [ flow_seq_item { ',' sp flow_seq_item } ] ']' flow_seq_item ::= flowstyle

flow_map ::= '{' [ flow_map_item { ',' sp flow_map_item } ] '}' flow_map_item ::= flowstyle ':' sp flowstyle

flow_scalar ::= string | number | boolean | symbol | date

# File lib/kwalify/yaml-parser.rb, line 252
def parse_flowstyle(column, value)
  reset_sbuf(value)
  getchar()
  data = parse_flow(0)
  ch = current_char
  assert ch == ?] || ch == ?}
  ch = getchar_or_nl()
  unless ch == ?\n || ch == ?# || ch.nil?
    #* key=:flow_hastail  msg="flow style sequence is closed but got '%s'."
    raise syntax_error(:flow_hastail, [ch.chr])
  end
  getline() if !ch.nil?
  return data
end
parse_mapping(column, value) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 625
def parse_mapping(column, value)
  #assert value =~ /^(:?["']?[-.\w]+["']? *):(( +)(.*))?$/         #'
  assert value =~ /^((?::?[-.\w]+\*?|'.*?'|".*?"|=|<<) *):(( +)(.*))?$/
  map = create_mapping()  # {}
  while true
    #unless value =~ /^(:?["']?[-.\w]+["']? *):(( +)(.*))?$/      #'
    unless value =~ /^((?::?[-.\w]+\*?|'.*?'|".*?"|=|<<) *):(( +)(.*))?$/
      #* key=:mapping_noitem  msg="mapping item is expected."
      raise syntax_error(:mapping_noitem)
    end
    v = $1.strip
    key = to_scalar(v)
    value2 = $4
    column2 = column + 1
    linenum = current_linenum()
    #
    if !value2 || value2.empty?
      elem = parse_child(column2)
    else
      value_start_column = column2 + $1.length + $3.length
      elem = parse_value(column2, value2, value_start_column)
    end
    case v
    when '='
      set_default(map, elem, linenum)
    when '<<'
      merge_map(map, elem, linenum)
    else
      add_to_map(map, key, elem, linenum)    # map[key] = elem
    end
    #
    line = current_line()
    break unless line
    line =~ /^( *)(.*)/
    indent = $1.length
    if    indent < column
      break
    elsif indent > column
      #* key=:mapping_badindent  msg="illegal indent of mapping."
      raise syntax_error(:mapping_badindent)
    end
    value = $2
  end
  return map
end
parse_scalar(indent, value) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 672
def parse_scalar(indent, value)
  data = create_scalar(to_scalar(value))
  getline()
  return data
end
parse_sequence(column, value) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 588
def parse_sequence(column, value)
  assert value =~ /^-(( +)(.*))?$/
  seq = create_sequence()  # []
  while true
    unless value =~ /^-(( +)(.*))?$/
      #* key=:sequence_noitem  msg="sequence item is expected."
      raise syntax_error(:sequence_noitem)
    end
    value2 = $3
    space  = $2
    column2 = column + 1
    linenum = current_linenum()
    #
    if !value2 || value2.empty?
      elem = parse_child(column2)
    else
      value_start_column = column2 + space.length
      elem = parse_value(column2, value2, value_start_column)
    end
    add_to_seq(seq, elem, linenum)    #seq << elem
    #
    line = current_line()
    break unless line
    line =~ /^( *)(.*)/
    indent = $1.length
    if    indent < column
      break
    elsif indent > column
      #* key=:sequence_badindent  msg="illegal indent of sequence."
      raise syntax_error(:sequence_badindent)
    end
    value = $2
  end
  return seq
end
parse_tag(column, value) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 435
def parse_tag(column, value)
  assert value =~ /^!\S+/
  value =~ /^!(\S+)((\s+)(.*))?$/
  tag    = $1
  space  = $3
  value2 = $4
  if value2 && !value2.empty?
    value_start_column = column + 1 + tag.length + space.length
    data = parse_value(column, value2, value_start_column)
  else
    data = parse_child(column)
  end
  return data
end
parse_value(column, value, value_start_column) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 210
def parse_value(column, value, value_start_column)
  case value
  when /^-( |$)/
    data = parse_sequence(value_start_column, value)
  when /^(:?:?[-.\w]+\*?|'.*?'|".*?"|=|<<) *:( |$)/
    #when /^:?["']?[-.\w]+["']? *:( |$)/                    #'
    data = parse_mapping(value_start_column, value)
  when /^\[/, /^\{/
    data = parse_flowstyle(column, value)
  when /^\&[-\w]+( |$)/
    data = parse_anchor(column, value)
  when /^\*[-\w]+( |$)/
    data = parse_alias(column, value)
  when /^[|>]/
    data = parse_block_text(column, value)
  when /^!/
    data = parse_tag(column, value)
  when /^\#/
      data = parse_child(column)
  else
    data = parse_scalar(column, value)
  end
  return data
end
register_alias(label) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 496
def register_alias(label)
  @aliases[label] ||= 0
  @aliases[label] += 1
  return Alias.new(label, @linenum)
end
register_anchor(label, data) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 470
def register_anchor(label, data)
  if @anchors[label]
    #* key=:anchor_duplicated  msg="anchor '%s' is already used."
    raise syntax_error(:anchor_duplicated, [label])
  end
  @anchors[label] = data
end
reset_sbuf(str) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 143
def reset_sbuf(str)
  @sbuf = str[-1] == ?\n ? str : str + "\n"
  @index = -1
end
resolve_aliases(data) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 503
def resolve_aliases(data)
  @resolved ||= {}
  return if @resolved[data.__id__]
  @resolved[data.__id__] = data
  case data
  when Array
    seq = data
    seq.each_with_index do |val, i|
      if val.is_a?(Alias)
        anchor = val
        if @anchors.key?(anchor.label)
          #seq[i] = @anchors[anchor.label]
          set_seq_at(seq, i, @anchors[anchor.label], anchor.linenum)
        else
          #* key=:anchor_notfound  msg="anchor '%s' not found"
          raise syntax_error(:anchor_notfound, [anchor.label], val.linenum)
        end
      elsif val.is_a?(Array) || val.is_a?(Hash)
        resolve_aliases(val)
      end
    end
  when Hash
    map = data
    map.each do |key, val|
      if val.is_a?(Alias)
        if @anchors.key?(val.label)
          anchor = val
          #map[key] = @anchors[anchor.label]
          set_map_with(map, key, @anchors[anchor.label], anchor.linenum)
        else
          ## :anchor_notfound is already defined on above
          raise syntax_error(:anchor_notfound, [val.label], val.linenum)
        end
      elsif val.is_a?(Array) || val.is_a?(Hash)
        resolve_aliases(val)
      end
    end
  else
    assert !data.is_a?(Alias)
  end
end
syntax_error(error_symbol, arg=nil, linenum=@linenum) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 193
def syntax_error(error_symbol, arg=nil, linenum=@linenum)
  msg = Kwalify.msg(error_symbol)
  msg = msg % arg.to_a unless arg.nil?
  return Kwalify::YamlSyntaxError.new(msg, linenum, error_symbol)
end
to_scalar(str) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 679
def to_scalar(str)
  case str
  when /^"(.*)"([ \t]*\#.*$)?/    ; return $1
  when /^'(.*)'([ \t]*\#.*$)?/    ; return $1
  when /^(.*\S)[ \t]*\#/          ; str = $1
  end

  case str
  when /^-?\d+$/              ;  return str.to_i    # integer
  when /^-?\d+\.\d+$/         ;  return str.to_f    # float
  when "true", "yes", "on"    ;  return true        # true
  when "false", "no", "off"   ;  return false       # false
  when "null", "~"            ;  return nil         # nil
    #when /^"(.*)"$/             ;  return $1          # "string"
    #when /^'(.*)'$/             ;  return $1          # 'string'
  when /^:(\w+)$/             ;  return $1.intern   # :symbol
  when /^(\d\d\d\d)-(\d\d)-(\d\d)$/                 # date
    year, month, day = $1.to_i, $2.to_i, $3.to_i
    return Date.new(year, month, day)
  when /^(\d\d\d\d)-(\d\d)-(\d\d)(?:[Tt]|[ \t]+)(\d\d?):(\d\d):(\d\d)(\.\d*)?(?:Z|[ \t]*([-+]\d\d?)(?::(\d\d))?)?$/
    year, mon, mday, hour, min, sec, usec, tzone_h, tzone_m = $1, $2, $3, $4, $5, $6, $7, $8, $9
    #Time.utc(sec, min, hour, mday, mon, year, wday, yday, isdst, zone)
    #t = Time.utc(sec, min, hour, mday, mon, year, nil, nil, nil, nil)
    #Time.utc(year[, mon[, day[, hour[, min[, sec[, usec]]]]]])
    time = Time.utc(year, mon, day, hour, min, sec, usec)
    if tzone_h
      diff_sec = tzone_h.to_i * 60 * 60
      if tzone_m
        if diff_sec > 0 ; diff_sec += tzone_m.to_i * 60
        else            ; diff_sec -= tzone_m.to_i * 60
        end
      end
      p diff_sec
      time -= diff_sec
    end
    return time
  end
  return str
end
white?(ch) click to toggle source
# File lib/kwalify/yaml-parser.rb, line 235
def white?(ch)
  return ch == ?\  || ch == ?\t || ch == ?\n || ch == ?\r
end