class SXP::Reader

The base class for S-expression parsers.

Attributes

input[R]

@return [Object]

options[R]

@return [Hash]

Public Class Methods

new(input, **options, &block) click to toggle source

Initializes the reader.

@param [IO, StringIO, String] input @param [Hash{Symbol => Object}] options

# File lib/sxp/reader.rb, line 84
def initialize(input, **options, &block)
  @options = options.dup

  case
    when [:getc, :ungetc, :eof?].all? { |x| input.respond_to?(x) }
      @input = input
    when input.respond_to?(:to_str)
      require 'stringio' unless defined?(StringIO)
      # NOTE: StringIO#ungetc mutates the string, so we use #dup to take a copy.
      @input = StringIO.new(input.to_str.dup)
      @input.set_encoding('UTF-8') if @input.respond_to?(:set_encoding)
    else
      raise ArgumentError, "expected an IO or String input stream, but got #{input.inspect}"
  end

  if block_given?
    case block.arity
      when 1 then block.call(self)
      else self.instance_eval(&block)
    end
  end
end
read(input, **options) click to toggle source

Reads one S-expression from the given input stream.

@param [IO, StringIO, String] input @param [Hash{Symbol => Object}] options

See {#read}

@return [Object]

# File lib/sxp/reader.rb, line 75
def self.read(input, **options)
  self.new(input, **options).read
end
read_all(input, **options) click to toggle source

Reads all S-expressions from the given input stream.

@param [IO, StringIO, String] input @param [Hash{Symbol => Object}] options

See {#read}

@return [Enumerable<Object>]

# File lib/sxp/reader.rb, line 64
def self.read_all(input, **options)
  self.new(input, **options).read_all
end
read_file(filename, **options) click to toggle source

Reads all S-expressions from a given input file.

@param [String, to_s] filename @param [Hash{Symbol => Object}] options

See {#read}

@return [Enumerable<Object>]

# File lib/sxp/reader.rb, line 53
def self.read_file(filename, **options)
  File.open(filename.to_s, 'rb') { |io| read_all(io, **options) }
end
read_url(url, **options) click to toggle source

Reads all S-expressions from a given input URL using the HTTP or FTP protocols.

@param [String, to_s] url @param [Hash{Symbol => Object}] options

See {#read}

@return [Enumerable<Object>]

# File lib/sxp/reader.rb, line 24
def self.read_url(url, **options)
  require 'open-uri'
  open(url.to_s, 'rb', nil, **options) { |io| read_all(io, **options) }
end

Public Instance Methods

each(&block) click to toggle source

@yield [object] @yieldparam [Object] object @return [Enumerator]

# File lib/sxp/reader.rb, line 117
def each(&block)
  unless block_given?
    to_enum
  else
    read_all.each(&block) # TODO: lazy reading
  end
end
read(eof: nil, eol: nil, list_term: false, **options) click to toggle source

@param [Hash{Symbol => Object}] options @option options [:throw] :eof

If `:throw`, throw `:eof` on end of file.

@option options [:throw] :eol

If `:throw`, throw `:eol` on end of line.

@option options [String] :list_term

Expected list terminator; it's an error
if another terminator is found

@return [Object]

# File lib/sxp/reader.rb, line 147
def read(eof: nil, eol: nil, list_term: false, **options)
  skip_comments
  token, value = read_token
  case token
    when :eof
      throw :eof if eof == :throw
      raise EOF, "unexpected end of input"
    when :list
      if ndx = self.class.const_get(:LPARENS).index(value)
        term = self.class.const_get(:RPARENS)[ndx]
        read_list(term)
      else
        throw :eol if eol == :throw && value == list_term
        raise Error, "unexpected list terminator: ?#{value.chr}"
      end
    else value
  end
end
Also aliased as: skip
read_all(**options) click to toggle source

@param [Hash{Symbol => Object}] options

See {#read}

@return [Array]

# File lib/sxp/reader.rb, line 129
def read_all(**options)
  list = []
  catch (:eof) do
    list << read(eof: :throw, **options) until eof?
  end
  list
end
read_atom() click to toggle source

@return [Object]

# File lib/sxp/reader.rb, line 208
def read_atom
  raise NotImplementedError.new("#{self.class}#read_atom")
end
read_character() click to toggle source

@return [String]

# File lib/sxp/reader.rb, line 220
def read_character
  raise NotImplementedError.new("#{self.class}#read_character")
end
read_files(*filenames) click to toggle source

Reads all S-expressions from the given input files.

@overload read_files(*filenames)

@param  [Enumerable<String>]     filenames

@overload read_files(*filenames, **options)

@param  [Enumerable<String>]     filenames
@param  [Hash{Symbol => Object}] options
  See {#read}

@return [Enumerable<Object>]

# File lib/sxp/reader.rb, line 41
def read_files(*filenames)
  options = filenames.last.is_a?(Hash) ? filenames.pop : {}
  filenames.map { |filename| read_file(filename, **options) }.inject { |sxps, sxp| sxps + sxp }
end
read_integer(base = 10) click to toggle source

@param [Integer] base @return [Integer]

# File lib/sxp/reader.rb, line 198
def read_integer(base = 10)
  case buffer = read_literal
    when self.class.const_get(:"INTEGER_BASE_#{base}")
      buffer.to_i(base)
    else raise Error, "illegal base-#{base} number syntax: #{buffer}"
  end
end
read_list(list_term = nil) click to toggle source

@param [String] list_term (nil)

String expected to terminate the list

@return [Array]

# File lib/sxp/reader.rb, line 172
def read_list(list_term = nil)
  list = []
  catch (:eol) do
    list << read(eol: :throw, list_term: list_term) while true
  end
  list
end
read_literal() click to toggle source

@return [String]

# File lib/sxp/reader.rb, line 226
def read_literal
  raise NotImplementedError.new("#{self.class}#read_literal")
end
read_sharp() click to toggle source

@return [Object]

# File lib/sxp/reader.rb, line 191
def read_sharp
  raise NotImplementedError.new("#{self.class}#read_sharp")
end
read_string() click to toggle source

@return [String]

# File lib/sxp/reader.rb, line 214
def read_string
  raise NotImplementedError.new("#{self.class}#read_string")
end
read_token() click to toggle source

@return [Object]

# File lib/sxp/reader.rb, line 182
def read_token
  case peek_char
    when nil    then :eof
    else [:atom, read_atom]
  end
end
skip(eof: nil, eol: nil, list_term: false, **options)
Alias for: read

Protected Instance Methods

eof?() click to toggle source

@return [Boolean]

# File lib/sxp/reader.rb, line 287
def eof?
  @input.eof?
end
peek_char() click to toggle source

@return [String]

# File lib/sxp/reader.rb, line 272
def peek_char
  char = @input.getc
  @input.ungetc(char) unless char.nil?
  char
end
read_char() click to toggle source

@return [String]

# File lib/sxp/reader.rb, line 262
def read_char
  char = @input.getc
  raise EOF, 'unexpected end of input' if char.nil?
  char
end
Also aliased as: skip_char
read_chars(count = 1) click to toggle source

@param [Integer] count @return [String]

# File lib/sxp/reader.rb, line 254
def read_chars(count = 1)
  buffer = ''
  count.times { buffer << read_char.chr }
  buffer
end
skip_char()
Alias for: read_char
skip_comments() click to toggle source

@return [void]

# File lib/sxp/reader.rb, line 234
def skip_comments
  until eof?
    case (char = peek_char).chr
      when /\s+/ then skip_char
      else break
    end
  end
end
skip_line() click to toggle source

@return [void]

# File lib/sxp/reader.rb, line 245
def skip_line
  loop do
    break if eof? || read_char.chr == $/
  end
end
unread(string) click to toggle source

Unread the string, putting back into the input stream @param [String] string

# File lib/sxp/reader.rb, line 281
def unread(string)
  string.reverse.each_char {|c| @input.ungetc(c)}
end