class CTioga2::Commands::InterpreterString::LexicalAnalyzer

A small lexical parser. It's job is to carry out the function of InterpreterString.parse_until_unquoted.

Attributes

current_string[RW]

The current object on the way of being parsed

io[RW]

The io device with which the parser is interacting.

parsed[RW]

The current string result, as described in InterpreterString

state[RW]

The state of the parser:

  • :start -> first starting elements

  • :top -> toplevel

  • :single -> in a single quoted string

  • :double -> in a double quoted string

  • :dollar -> last element was a dollar at top-level

  • :dq_dollar -> last element was an unescaped dollar within a double quoted string

  • :escape -> last element was an unescaped escape char within a double-quoted string.

  • :var -> in a $(variable)

  • :dq_var -> in a $(variable) within a double-quoted string

term[RW]

The terminating element

Public Class Methods

new(io, term) click to toggle source

Initializes the parser.

# File lib/ctioga2/commands/strings.rb, line 73
def initialize(io, term)
  @io = io
  @term = term
end

Public Instance Methods

parse(eoerror = true) click to toggle source

Parse the string from the io object

# File lib/ctioga2/commands/strings.rb, line 79
def parse(eoerror = true)
  @state = :start
  @parsed = []
  @current_string = ''
  
  i = -1
  while(1)
    c = @io.getc
    if ! c              # EOF
      if eoerror
        raise UnterminatedString, "EOF reached before the end of this string"
      else
        push_current_element
        return @parsed
      end
    end
    # Convert the integer to a string.
    ch = c.chr
    i += 1
    if (@state == :start || @state == :top) and
        (term.include?(ch)) # Finished
      push_current_element
      @io.ungetc(c)     # We push back the last char.
      return @parsed
    end

    # puts "#{@state.inspect} -- #{ch}"

    # We skip white space at the beginning of the string.
    if @state == :start
      # Skip white space
      if ! (ch =~ /\s/)
        @state = :top
      end
    end

    case @state
    when :escape
      # Evaluating escape chars
      @current_string += eval("\"\\#{ch}\"")
      @state = :double
    when :dollar, :dq_dollar
      @state = (@state == :dollar ? :top : :double)
      if ch == '('      # Beginning of a variable within a
        # quoted string
        push_current_element
        @state = (@state == :top ? :var : :dq_var)
      else
        @current_string += "$#{ch}"
      end
    when :single        # The simplest string
      if ch == "'"      # End of string
        push_current_element
        @state = :top
      else
        @current_string += ch
      end
    when :var, :dq_var
      if ch == ")"
        push_current_element
        @state = (@state == :var ? :top : :double)
      elsif ch =~ /\s/
        # We don't have a variable, but a function...
        @accessory = InterpreterString.parse_until_unquoted(@io, ")", true)
        ch = @io.getc   # Slurp the closing )
        ns = (@state == :var ? :top : :double)
        @state = (:var ? :funcall : :dq_funcall)
        push_current_element
        @state = ns
        ## @todo Optional: instead of having a space, use a ,
        ## or . or # to signify different separators ? (but
        ## quoting makes this more-or-less unnecessary, hey ?)
      else
        @current_string += ch
      end
    when :top
      if ch == "'"      # We start a single-quoted string
        push_current_element
        @state = :single
      elsif ch == '$'   # Dollar state
        @state = :dollar
      elsif ch == '"'
        push_current_element
        @state = :double
      elsif ch == '#'   # A comment: we read until end-of-line
        @io.gets        # and ignore the results
      else
        @current_string += ch
      end
    when :double
      if ch == '"'      # (necessarily unquoted)
        push_current_element
        @state = :top
      elsif ch == '$'
        @state = :dq_dollar
      elsif ch == "\\"
        @state = :escape
      else
        @current_string += ch
      end
    end
  end
  
end
push_current_element() click to toggle source

Pushes the element currently being parsed unto the result

# File lib/ctioga2/commands/strings.rb, line 186
def push_current_element
  if @current_string.size == 0
    return
  end
  case @state
  when :top
    # We push an unquoted string
    @parsed << [:unquoted, @current_string]
  when :single, :double
    @parsed << [:quoted, @current_string]
  when :var
    @parsed << [:unquoted_variable, @current_string]
  when :dq_var
    @parsed << [:quoted_variable, @current_string]
  when :funcall
    @parsed << [:unquoted_funcall, @current_string, @accessory]
  when :dq_funcall
    @parsed << [:quoted_funcall, @current_string, @accessory]
  when :dollar
    @parsed << [:unquoted, @current_string + '$']
  when :dq_dollar
    @parsed << [:quoted, @current_string + '$']
  when :escape
    @parsed << [:quoted, @current_string + "\\" ]
  when :start
    # Empty string at the beginning. Nothing interesting, move
    # along !
  else
    raise "Fatal bug of the lexical analyzer here : unkown state"
  end
  # Flush current string
  @current_string = ""
end