Class: Coffeetags::Parser

Inherits:
Object show all
Defined in:
lib/CoffeeTags/parser.rb

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Coffeetags::Parser) initialize(source, include_vars = false)

Creates a new parser

Parameters:

  • source (String)

    source of the CoffeeScript file

  • include_vars (Bool) (defaults to: false)

    include objects in generated tree (default false)



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/CoffeeTags/parser.rb', line 9

def initialize source, include_vars = false
  @include_vars = include_vars
  @source = source

  @fake_parent = 'window'

  # tree maps the ... tree :-)
  @tree = []

  # regexes
  @block = /^\s*(if|unless|switch|loop|do)/
  @class_regex = /\s*class\s*(?:@)?([\w\.]*)/
  @proto_meths = /^\s*([A-Za-z]*)::([@a-zA-Z0-9_]*)/
  @var_regex = /([@a-zA-Z0-9_]*)\s*[=:]{1}\s*$/
  @token_regex = /([@a-zA-Z0-9_]*)\s*[:=]{1}/
  @iterator_regex = /^\s*for\s*([a-zA-Z0-9_]*)\s*/
  @comment_regex = /^\s*#/
  @start_block_comment_regex = /^\s*###/
  @end_block_comment_regex = /^.*###/
  @oneline_block_comment_regex = /^\s*###.*###/
  @comment_lines = mark_commented_lines
end

Instance Attribute Details

- (Object) tree (readonly)

Returns the value of attribute tree



3
4
5
# File 'lib/CoffeeTags/parser.rb', line 3

def tree
  @tree
end

Instance Method Details

- (Object) execute!

Note:

this method mutates @tree instance variable of Coffeetags::Parser instance

Parse the source and create a tags tree



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/CoffeeTags/parser.rb', line 113

def execute!
  line_n = 0
  level = 0
  @source.each_line do |line|
    line_n += 1
    # indentify scopes
    level = line_level line

    # ignore comments!
    next if @comment_lines.include? line_n

    # extract elements for given line
    [
      @class_regex,
      @proto_meths,
      @var_regex
    ].each do |regex|
      mt = item_for_regex line, regex, level
      @tree << mt unless mt.nil?
    end


    # does this line contain a block?
    mt = item_for_regex line, @block, level, :kind => 'b'
    @tree << mt unless mt.nil?


    # instance variable or iterator (for/in)?
    token = line.match(@token_regex )
    token ||=  line.match(@iterator_regex)

    # we have found something!
    if not token.nil?
      o = {
        :name => token[1],
        :level => level,
        :parent => '',
        :source => line.chomp,
        :line => line_n
      }

      # remove edge cases for now
      # - if a line containes a line like:  element.getElement('type=[checkbox]').lol()
      is_in_string = line =~ /.*['"].*#{token[1]}.*=.*["'].*/

      # - scope access and comparison in if x == 'lol'
      is_in_comparison = line =~ /::|==/

      # - objects with blank parent (parser bug?)
      has_blank_parent = o[:parent] =~ /\.$/

      # - multiple consecutive assignments
      is_previous_not_the_same = !(@tree.last and @tree.last[:name] == o[:name] and  @tree.last[:level] == o[:level])

      if is_in_string.nil? and is_in_comparison.nil? and has_blank_parent.nil? and is_previous_not_the_same
        o[:kind]   =  line =~ /[:=]{1}.*[-=]\>/ ? 'f' : 'o'
        o[:parent] =  scope_path o
        o[:parent] = @fake_parent if o[:parent].empty?

        @tree << o if o[:kind] == 'f'
        @tree << o if o[:kind] == 'o' and @include_vars
      end
    end
    # get rid of duplicate entries
    @tree.uniq!
  end
  self # chain!
end

- (Object) item_for_regex(line, regex, level, additional_fields = {})

Helper function for generating parse tree elements for given line and regular expression

Parameters:

  • line (String)

    source line currently being parsed

  • regex (RegExp)

    regular expression for matching a syntax element

  • level (Integer)

    current indentation/line level

  • additional_fields (Hash, {}) (defaults to: {})

    additional fields which need to be added to generated element



100
101
102
103
104
105
106
107
108
# File 'lib/CoffeeTags/parser.rb', line 100

def item_for_regex line,  regex, level, additional_fields={}
  if item = line.match(regex)
    entry_for_item = {
      :name => item[1],
      :level => level
    }
    entry_for_item.merge(additional_fields)
  end
end

- (Integer) line_level(line)

Detect current line level based on indentation very useful in parsing, since CoffeeScript's syntax depends on whitespace

Parameters:

  • line (String)

    currently parsed line

Returns:

  • (Integer)


60
61
62
# File 'lib/CoffeeTags/parser.rb', line 60

def line_level line
  line.match(/^[ \t]*/)[0].gsub("\t", " ").split('').length
end

- (Object) mark_commented_lines

Mark line numbers as commented out either by single line comment (#) or block comments (###~###)



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/CoffeeTags/parser.rb', line 35

def mark_commented_lines
  [].tap do |reg|
    in_block_comment = false
    line_no = 0
    start_block = 0
    end_block = 0
    @source.each_line do |line|
      line_no = line_no+1

      start_block = line_no if !in_block_comment and line =~ @start_block_comment_regex
      end_block = line_no if start_block < line_no and line =~ @end_block_comment_regex
      end_block = line_no if line =~ @oneline_block_comment_regex

      in_block_comment = end_block < start_block

      reg << line_no if in_block_comment or end_block == line_no or line =~ @comment_regex
    end
  end
end

- (Object) scope_path(_el = nil, _tree = nil)

Generate current scope path, for example:

e  ->
  f ->
    z ->

Scope path for function z would be: window.e.f

Parameters:

  • _el (Hash, nil) (defaults to: nil)

    element of a prase tree (last one for given tree is used by default)

  • _tree (Array, nil) (defaults to: nil)

    parse tree (or currently built)



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/CoffeeTags/parser.rb', line 73

def scope_path _el = nil, _tree = nil
  bf = []
  tree = (_tree || @tree)
  element = (_el || tree.last)
  idx = tree.index(element) || -1

  current_level = element[:level]
  tree[0..idx].reverse.each_with_index do |item, index|
    # uhmmmmmm
    if item[:level] != current_level and item[:level] < current_level and item[:line] !~  @block
      bf << item[:name] unless item[:kind] == 'b'
      current_level = item[:level]
    end
  end
  bf.uniq.reverse.join('.')
end