class Campa::Reader
Reads strings or files into Campa expressions. rubocop: disable Metrics/ClassLength
Constants
- BOOLS
- BOOLS_START
- CAST_FLOAT
- CAST_INT
- SEPARATORS
Public Class Methods
new(input)
click to toggle source
Given a String, a file pointer or any #getc, #eof? it allows fetch every valid Campa form from it.
If the String is a valid file path it will be converted into a file pointer. Anything else will be converted into a StringIO
@param input [String, (getc, eof?)]
# File lib/campa/reader.rb, line 17 def initialize(input) @input = to_io_like(input) next_char end
Public Instance Methods
next()
click to toggle source
Return the next Campa form available in the underlying io like object.
@return [Object] next available Campa form
# File lib/campa/reader.rb, line 26 def next eat_separators return read if !@input.eof? return if @current_char.nil? # Exhaust the reader if @input.eof? and !@current_char.nil? read.tap { @current_char = nil } end
Private Instance Methods
boolean?()
click to toggle source
# File lib/campa/reader.rb, line 204 def boolean? return false if !BOOLS_START.include?(@current_char) @current_token = @current_char @current_token << next_char until @input.eof? || peek == " " || peek == ")" BOOLS.include? @current_token end
break?()
click to toggle source
# File lib/campa/reader.rb, line 189 def break? @current_char == "\n" end
delimiter?()
click to toggle source
# File lib/campa/reader.rb, line 193 def delimiter? @current_char == ")" end
digit?(char = nil)
click to toggle source
# File lib/campa/reader.rb, line 197 def digit?(char = nil) char ||= @current_char # TODO: should we force the encoding of source files? # (since codepoints will be different depending on encoding). !char.nil? && (char.ord >= 48 && char.ord <= 57) end
eat_separators()
click to toggle source
# File lib/campa/reader.rb, line 68 def eat_separators if @input.eof? @current_char = nil if separator? || break? return end next_char while separator? || break? end
next_char()
click to toggle source
# File lib/campa/reader.rb, line 51 def next_char if @next_char.nil? @current_char = @input.getc else @current_char = @next_char @next_char = nil end @current_char end
peek()
click to toggle source
# File lib/campa/reader.rb, line 62 def peek return @next_char if !@next_char.nil? @next_char = @input.getc end
read()
click to toggle source
rubocop: disable Metrics/MethodLength, Metrics/PerceivedComplexity rubocop: disable Style/EmptyCaseCondition, Metrics/CyclomaticComplexity
# File lib/campa/reader.rb, line 79 def read case when @current_char == "\"" read_string when digit? || @current_char == "-" && digit?(peek) read_number when @current_char == "'" read_quotation when @current_char == "(" read_list when boolean? read_boolean else read_symbol end end
read_boolean()
click to toggle source
# File lib/campa/reader.rb, line 163 def read_boolean boolean_value = @current_token @current_token = nil next_char boolean_value == "true" end
read_list()
click to toggle source
# File lib/campa/reader.rb, line 145 def read_list # eats the opening ( next_char elements = [] while !@input.eof? && !delimiter? token = self.next elements << token eat_separators if separator? end raise Error::MissingDelimiter, ")" if !delimiter? # eats the closing ) next_char List.new(*elements) end
read_number()
click to toggle source
# File lib/campa/reader.rb, line 117 def read_number number = @current_char cast = CAST_INT until @input.eof? next_char break if separator? || delimiter? cast = CAST_FLOAT if @current_char == "." number << @current_char end safe_cast(number, cast) end
read_quotation()
click to toggle source
# File lib/campa/reader.rb, line 138 def read_quotation # eats the ' char next_char List.new(SYMBOL_QUOTE, self.next) end
read_string()
click to toggle source
rubocop: enable Metrics/MethodLength, Metrics/PerceivedComplexity rubocop: enable Style/EmptyCaseCondition, Metrics/CyclomaticComplexity
# File lib/campa/reader.rb, line 98 def read_string return if @input.eof? string = "" # eats the opening " next_char while !@input.eof? && @current_char != "\"" string << @current_char next_char end raise Error::MissingDelimiter, "\"" if @current_char != "\"" # eats the closing " next_char string end
read_symbol()
click to toggle source
# File lib/campa/reader.rb, line 170 def read_symbol label = @current_token || @current_char @current_token = nil until @input.eof? next_char break if separator? || delimiter? || break? label << @current_char end # TODO: validate symbol (raise if invalid chars are present) Symbol.new(label) end
safe_cast(number, cast)
click to toggle source
# File lib/campa/reader.rb, line 132 def safe_cast(number, cast) cast.call(number) rescue ArgumentError raise Error::InvalidNumber, number end
separator?()
click to toggle source
# File lib/campa/reader.rb, line 185 def separator? SEPARATORS.include? @current_char end
to_io_like(input)
click to toggle source
# File lib/campa/reader.rb, line 43 def to_io_like(input) return input if input.respond_to?(:getc) && input.respond_to?(:eof?) return File.new(input) if File.file?(input) # TODO: check if it is "castable" first, StringIO.new(input) end