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)

    of the CoffeeScript file

  • include (Bool)

    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
# 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*#/
  @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

Parse the @source and create a tags tree Coffeetags::Parser instance returns self, so it can be chained



106
107
108
109
110
111
112
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
# File 'lib/CoffeeTags/parser.rb', line 106

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!
    self # chain!
  end
end

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

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

Parameters:

  • source (String)

    line currently being parsed

  • regular (RegExp)

    expression for matching a syntax element

  • current (Integer)

    indentation/line level

  • additional (Hash)

    fields which need to be added to generated element



92
93
94
95
96
97
98
99
100
# File 'lib/CoffeeTags/parser.rb', line 92

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:

  • currently (String)

    parsed line

Returns:

  • (Integer)


52
53
54
# File 'lib/CoffeeTags/parser.rb', line 52

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 (###~###)



34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/CoffeeTags/parser.rb', line 34

def mark_commented_lines
  [].tap do |reg|
    in_block_comment = false
    @source.each_with_index do |line, index|
      line_no = index+1

      in_block_comment =  !(in_block_comment) if line =~ @block_comment_regex
      reg << line_no if in_block_comment
      reg << line_no if line =~ @comment_regex and not in_block_comment
    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:

  • element (Hash)

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

  • parse (Array)

    tree (or currently built)



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/CoffeeTags/parser.rb', line 65

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