class Ruby2JS::JsxParser

Public Class Methods

new(stream) click to toggle source
# File lib/ruby2js/jsx.rb, line 13
def initialize(stream)
  @stream = stream.respond_to?(:next) ? stream : OpalEnumerator.new(stream)
  @state = :text
  @text = ''
  @result = []
  @element = ''
  @attrs = {}
  @attr_name = ''
  @value = ''
  @tag_stack = []
  @expr_nesting = 0
  @wrap_value = true
end

Public Instance Methods

parse(state = :text, wrap_value = true) click to toggle source
# File lib/ruby2js/jsx.rb, line 27
def parse(state = :text, wrap_value = true)
  @wrap_value = wrap_value
  @state = state
  backtrace = ''
  prev = nil

  loop do
    c = @stream.next

    if c == "\n"
      backtrace = ''
    else
      backtrace += c
    end

    case @state
    when :text
      if c == '<'
        @result << "_(\"#{@text.strip}\")" unless @text.strip.empty?
        if @tag_stack.empty?
          @result += self.class.new(@stream).parse(:element)
          @state = :text
          @text = ''
        else
          @state = :element
          @element = ''
          @attrs = {}
        end
      elsif c == '\\'
        @text += c + c
      elsif c == '{'
        @result << "_(\"#{@text}\")" unless @text.empty?
        @result += parse_expr
        @text = ''
      else
        @text += c unless @text.empty? and c =~ /\s/
      end

    when :element
      if c == '/'
        if @element == ''
          @state = :close
          @element = ''
        else
          @state = :void
        end
      elsif c == '>'
        @result << "_#{@element} do"
        @tag_stack << @element
        @state = :text
        @text = ''
      elsif c == ' '
        @state = :attr_name
        @attr_name = ''
        @attrs = {}
      elsif c == '-'
        @element += '_'
      elsif c =~ /^\w$/
        @element += c
      else
        raise SyntaxError.new("invalid character in element name: #{c.inspect}")
      end

    when :close
      if c == '>'
        if @element == @tag_stack.last
          @tag_stack.pop
        elsif @tag_stack.last
          raise SyntaxError.new("missing close tag for: #{@tag_stack.last.inspect}")
        else
          raise SyntaxError.new("close tag for element that is not open: #{@element}")
        end

        @result << 'end'
        return @result if @tag_stack.empty?
      elsif c =~ /^\w$/
        @element += c
      elsif c != ' '
        raise SyntaxError.new("invalid character in element: #{c.inspect}")
      end

    when :void
      if c == '>'
        if @attrs.empty?
          @result << "_#{@element}"
        else
          @result << "_#{@element}(#{@attrs.map {|name, value| "#{name}: #{value}"}.join(' ')})"
        end
        return @result if @tag_stack.empty?

        @state = :text
        @text = ''
      elsif c != ' '
        raise SyntaxError.new('invalid character in element: "/"')
      end

    when :attr_name
      if c =~ /^\w$/
        @attr_name += c
      elsif c == '='
        @state = :attr_value
        @value = ''
      elsif c == '/' and @attr_name == ''
        @state = :void
      elsif c == ' ' or c == "\n" or c == '>'
        if not @attr_name.empty?
          raise SyntaxError.new("missing \"=\" after attribute #{@attr_name.inspect} " +
            "in element #{@element.inspect}")
        elsif c == '>'
          @result << "_#{@element}(#{@attrs.map {|name, value| "#{name}: #{value}"}.join(' ')}) do"
          @tag_stack << @element
          @state = :text
          @text = ''
        end
      else
        raise SyntaxError.new("invalid character in attribute name: #{c.inspect}")
      end

    when :attr_value
      if c == '"'
        @state = :dquote
      elsif c == "'"
        @state = :squote
      elsif c == '{'
        @attrs[@attr_name] = parse_value
        @state = :attr_name
        @attr_name = ''
      else
        raise SyntaxError.new("invalid value for attribute #{@attr_name.inspect} " +
          "in element #{@element.inspect}")
      end

    when :dquote
      if c == '"'
        @attrs[@attr_name] = '"' + @value + '"'
        @state = :attr_name
        @attr_name = ''
      elsif c == "\\"
        @value += c + c
      else
        @value += c
      end

    when :squote
      if c == "'"
        @attrs[@attr_name] = "'" + @value + "'"
        @state = :attr_name
        @attr_name = ''
      elsif c == "\\"
        @value += c + c
      else
        @value += c
      end

    when :expr
      if c == "}"
        if @expr_nesting > 0
          @value += c
          @expr_nesting -= 1
        else
          @result << (@wrap_value ? "_(#{@value})" : @value)
          return @result
        end
      elsif c == '<'
        if prev =~ /[\w\)\]\}]/
          @value += c # less than
        elsif prev == ' '
          if @stream.peek =~ /[a-zA-Z]/
            @value += parse_element.join(';')
            @wrap_value = false
          else
            @value += c
          end
        else
          @value += parse_element.join(';')
          @wrap_value = false
        end
      else
        @value += c
        @state = :expr_squote if c == "'"
        @state = :expr_dquote if c == '"'
        @expr_nesting += 1 if c == '{'
      end

    when :expr_squote
      @value += c
      if c == "\\"
        @state = :expr_squote_backslash
      elsif c == "'"
        @state = :expr
      end

    when :expr_squote_backslash
      @value += c
      @state = :expr_squote

    when :expr_dquote
      @value += c
      if c == "\\"
        @state = :expr_dquote_backslash
      elsif c == '#'
        @state = :expr_dquote_hash
      elsif c == '"'
        @state = :expr
      end

    when :expr_dquote_backslash
      @value += c
      @state = :expr_dquote

    when :expr_dquote_hash
      @value += c
      @value += parse_value + '}' if c == '{'
      @state = :expr_dquote

    else
      raise RangeError.new("internal state error in JSX: #{@state.inspect}")
    end

    prev = c
  end

  unless @tag_stack.empty?
    raise SyntaxError.new("missing close tag for: #{@tag_stack.last.inspect}")
  end

  case @state
  when :text
    @result << "_(\"#{@text.strip}\")" unless @text.strip.empty?

  when :element, :attr_name, :attr_value
    raise SyntaxError.new("unclosed element #{@element.inspect}")

  when :dquote, :squote, :expr_dquote, :expr_dquote_backslash, 
    :expr_squote, :expr_squote_backslash
    raise SyntaxError.new("unclosed quote")

  when :expr
    raise SyntaxError.new("unclosed value")

  else
    raise RangeError.new("internal state error in JSX: #{@state.inspect}")
  end

  @result
rescue SyntaxError => e
  e.set_backtrace backtrace
  raise e
end

Private Instance Methods

parse_element() click to toggle source
# File lib/ruby2js/jsx.rb, line 287
def parse_element
  self.class.new(@stream).parse(:element)
end
parse_expr() click to toggle source
# File lib/ruby2js/jsx.rb, line 283
def parse_expr
  self.class.new(@stream).parse(:expr, true)
end
parse_value() click to toggle source
# File lib/ruby2js/jsx.rb, line 279
def parse_value
  self.class.new(@stream).parse(:expr, false).join(',')
end