Commit ff438d9e authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Moved all code inside JsDuck module + test case.

parent ff7e6972
Loading
Loading
Loading
Loading

duck.rb

deleted100755 → 0
+0 −166
Original line number Diff line number Diff line
#!/usr/bin/env ruby
require 'strscan'
require 'pp'

class Lexer
  def initialize(input)
    @input = StringScanner.new(input)
    tokenize
  end

  def look(*tokens)
    i = 0
    tokens.all? do |t|
      tok = @tokens[i]
      i += 1
      if t.instance_of?(Symbol) then
        tok[:type] == t
      else
        tok[:value] == t
      end
    end
  end

  def next
    @tokens.shift[:value]
  end

  def empty?
    @tokens.empty?
  end

  # Goes through the whole input and tokenizes it
  def tokenize
    @tokens = []
    while !@input.eos? do
      skip_white_and_comments
      if @input.check(/\w+/) then
        @tokens << {
          :type => :keyword,
          :value => @input.scan(/\w+/)
        }
      elsif @input.check(/\/\*\*/) then
        @tokens << {
          :type => :doc_comment,
          :value => DocComment.new(@input.scan_until(/\*\//))
        }
      elsif @input.check(/"/) then
        @tokens << {
          :type => :string,
          :value => eval(@input.scan(/"([^\\]|\\.)*"/))
        }
      elsif @input.check(/'/) then
        @tokens << {
          :type => :string,
          :value => eval(@input.scan(/'([^\\]|\\.)*'/))
        }
      elsif @input.check(/./) then
        @tokens << {
          :type => :operator,
          :value => @input.scan(/./)
        }
      end
    end
  end

  def skip_white_and_comments
    skip_white
    while multiline_comment? || line_comment? do
      if multiline_comment? then
        @input.scan_until(/\*\//)
      elsif line_comment? then
        @input.scan_until(/\n/)
      end
      skip_white
    end
  end

  def multiline_comment?
    @input.check(/\/\*[^*]/)
  end

  def line_comment?
    @input.check(/\/\//)
  end

  def skip_white
    @input.scan(/\s+/)
  end

end


class DocComment
  attr_accessor :function
  
  def initialize(input)
    @function = ""
    @doc = []
    @params = []
    @return = "void"
    parse(purify(input))
  end

  # Extracts content inside /** ... */
  def purify(input)
    result = []
    input.each_line do |line|
      line.chomp!
      if line =~ /\A\/\*\*/ || line =~ /\*\/\Z/ then
        # ignore first and last line
      elsif line =~ /\A\s*\*\s?(.*)\Z/ then
        result << $1
      else
        result << line
      end
    end
    return result.join("\n")
  end

  def parse(input)
    input.each_line do |line|
      line.chomp!
      if line =~ /\A@param\b/ then
        @params << line
      elsif line =~ /\A@return\b/ then
        @return = line
      else
        @doc << line
      end
    end
  end

  def print
    puts "function: " + @function
    puts "doc: " + @doc.join("\\n")
    puts "params: " + @params.join("\\n")
    puts "return: " + @return
  end
end



lex = Lexer.new($stdin.read)
docs = []
while !lex.empty? do
  if lex.look(:doc_comment) then
    doc = lex.next
    if lex.look("function", :keyword) then
      lex.next
      # function name(){
      doc.function = lex.next
    elsif lex.look("var", :keyword, "=", "function") then
      lex.next
      # var name = function(){
      doc.function = lex.next
    elsif lex.look(:keyword, ":", "function") || lex.look(:string, ":", "function") then
      # name: function(){
      doc.function = lex.next
    end
    docs << doc
  else
    lex.next
  end
end

docs.each {|d| d.print; puts}

jsduck.rb

0 → 100755
+179 −0
Original line number Diff line number Diff line
#!/usr/bin/env ruby
require 'strscan'
require 'pp'

module JsDuck

  class Lexer
    def initialize(input)
      @input = StringScanner.new(input)
      tokenize
    end

    def look(*tokens)
      i = 0
      tokens.all? do |t|
        tok = @tokens[i]
        i += 1
        return false if tok == nil
        if t.instance_of?(Symbol) then
          tok[:type] == t
        else
          tok[:value] == t
        end
      end
    end

    def next
      @tokens.shift[:value]
    end

    def empty?
      @tokens.empty?
    end

    # Goes through the whole input and tokenizes it
    def tokenize
      @tokens = []
      while !@input.eos? do
        skip_white_and_comments
        if @input.check(/\w+/) then
          @tokens << {
            :type => :keyword,
            :value => @input.scan(/\w+/)
          }
        elsif @input.check(/\/\*\*/) then
          @tokens << {
            :type => :doc_comment,
            :value => DocComment.new(@input.scan_until(/\*\//))
          }
        elsif @input.check(/"/) then
          @tokens << {
            :type => :string,
            :value => eval(@input.scan(/"([^\\]|\\.)*"/))
          }
        elsif @input.check(/'/) then
          @tokens << {
            :type => :string,
            :value => eval(@input.scan(/'([^\\]|\\.)*'/))
          }
        elsif @input.check(/./) then
          @tokens << {
            :type => :operator,
            :value => @input.scan(/./)
          }
        end
      end
    end

    def skip_white_and_comments
      skip_white
      while multiline_comment? || line_comment? do
        if multiline_comment? then
          @input.scan_until(/\*\/|\Z/)
        elsif line_comment? then
          @input.scan_until(/\n|\Z/)
        end
        skip_white
      end
    end

    def multiline_comment?
      @input.check(/\/\*[^*]/)
    end

    def line_comment?
      @input.check(/\/\//)
    end

    def skip_white
      @input.scan(/\s+/)
    end

  end


  class DocComment
    attr_accessor :function, :doc
    
    def initialize(input)
      @function = ""
      @doc = ""
      @params = []
      @return = "void"
      parse(purify(input))
    end

    # Extracts content inside /** ... */
    def purify(input)
      result = []
      input.each_line do |line|
        line.chomp!
        if line =~ /\A\/\*\*/ || line =~ /\*\/\Z/ then
          # ignore first and last line
        elsif line =~ /\A\s*\*\s?(.*)\Z/ then
          result << $1
        else
          result << line
        end
      end
      return result.join("\n")
    end

    def parse(input)
      doc = []
      input.each_line do |line|
        line.chomp!
        if line =~ /\A@param\b/ then
          @params << line
        elsif line =~ /\A@return\b/ then
          @return = line
        else
          doc << line
        end
      end
      @doc = doc.join("\n")
    end

    def print
      puts "function: " + @function
      puts "doc: " + @doc
      puts "params: " + @params.join("\n")
      puts "return: " + @return
    end
  end


  def JsDuck.parse(input)
    lex = Lexer.new(input)
    docs = []
    while !lex.empty? do
      if lex.look(:doc_comment) then
        doc = lex.next
        if lex.look("function", :keyword) then
          lex.next
          # function name(){
          doc.function = lex.next
        elsif lex.look("var", :keyword, "=", "function") then
          lex.next
          # var name = function(){
          doc.function = lex.next
        elsif lex.look(:keyword, ":", "function") || lex.look(:string, ":", "function") then
          # name: function(){
          doc.function = lex.next
        end
        docs << doc
      else
        lex.next
      end
    end
    return docs
  end

end


if __FILE__ == $0 then
  JsDuck.parse($stdin.read).each {|d| d.print; puts}
end

tc_jsduck.rb

0 → 100644
+23 −0
Original line number Diff line number Diff line
require "jsduck"
require "test/unit"
 
class TestJsDuck < Test::Unit::TestCase

  def test_no_doc_comments
    assert_equal([], JsDuck.parse("var foo = 8;") )
  end

  def test_singleline_comment_until_file_end
    assert_equal([], JsDuck.parse("// ") )
  end

  def test_multiline_comment_until_file_end
    assert_equal([], JsDuck.parse("/* ") )
  end

  def test_single_doc_comment
    assert_equal("Hello", JsDuck.parse("/**\nHello\n*/")[0].doc )
  end

end