summaryrefslogtreecommitdiff
path: root/etc/todo/www.demiurgo.org/darcs/coderay/lib/coderay/scanners/javascript.rb
blob: da670844454035da85a810426cc2049938215aa0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
module CodeRay
module Scanners

  # Basic Javascript scanner
  class Javascript < Scanner

    include Streamable

    register_for :javascript

    helper :patterns

    DEFAULT_OPTIONS = {
    }

  private
    def scan_tokens tokens, options
      first_bake = saved_tokens = nil
      last_token_dot = false
      last_state = nil
      state = :initial
      depth = nil
      inline_block_stack = []

      patterns = Patterns  # avoid constant lookup

      until eos?
        match = nil
        kind = nil

        if state.instance_of? patterns::StringState
# {{{
          match = scan_until(state.pattern) || scan_until(/\z/)
          tokens << [match, :content] unless match.empty?
          break if eos?

          case match = getch

          when state.delim
            if state.paren
              state.paren_depth -= 1
              if state.paren_depth > 0
                tokens << [match, :nesting_delimiter]
                next
              end
            end
            tokens << [match, :delimiter]
            tokens << [:close, state.type]
            state = state.next_state

          when '\\'
            if state.interpreted
              if esc = scan(/ #{patterns::ESCAPE} /ox)
                tokens << [match + esc, :char]
              else
                tokens << [match, :error]
              end
            else
              case m = getch
              when state.delim, '\\'
                tokens << [match + m, :char]
              when nil
                tokens << [match, :error]
              else
                tokens << [match + m, :content]
              end
            end

          when '#'
            case peek(1)[0]
            when ?{
              inline_block_stack << [state, depth]
              state = :initial
              depth = 1
              tokens << [:open, :inline]
              tokens << [match + getch, :delimiter]
            when ?$, ?@
              tokens << [match, :escape]
              last_state = state  # scan one token as normal code, then return here
              state = :initial
            else
              raise_inspect 'else-case # reached; #%p not handled' % peek(1), tokens
            end

          when state.paren
            state.paren_depth += 1
            tokens << [match, :nesting_delimiter]

          else
            raise_inspect 'else-case " reached; %p not handled, state = %p' % [match, state], tokens

          end
          next
# }}}
        else
# {{{
          if match = scan(/ [ \t\f]+ | \\? \n | \# .* /x) 
            case m = match[0]
            when ?\s, ?\t, ?\f
              match << scan(/\s*/) unless eos?
              kind = :space
            when ?\n, ?\\
              kind = :space
              match << scan(/\s*/) unless eos?
            when ?#, ?=, ?_
              kind = :comment
            else
              raise_inspect 'else-case _ reached, because case %p was not handled' % [matched[0].chr], tokens
            end
            tokens << [match, kind]
            next

          elsif state == :initial

            # IDENTS #
            if match = scan(/#{patterns::METHOD_NAME}/o)
              kind = last_token_dot ? :ident :
                                      patterns::IDENT_KIND[match]

            # OPERATORS #
            elsif (not last_token_dot and match = scan(/ ==?=? | \.\.?\.? | [\(\)\[\]\{\}] | :: | , /x)) or
              (last_token_dot and match = scan(/#{patterns::METHOD_NAME_OPERATOR}/o))
              last_token_dot = :set if match == '.' or match == '::'
              kind = :operator
              unless inline_block_stack.empty?
                case match
                when '{'
                  depth += 1
                when '}'
                  depth -= 1
                  if depth == 0  # closing brace of inline block reached
                    state, depth = inline_block_stack.pop
                    tokens << [match, :delimiter]
                    kind = :inline
                    match = :close
                  end
                end
              end

            elsif match = scan(/ ['"] /mx)
              tokens << [:open, :string]
              kind = :delimiter
              state = patterns::StringState.new :string, match == '"', match  # important for streaming

            elsif match = scan(/#{patterns::NUMERIC}/o)
              kind = if self[1] then :float else :integer end

            elsif match = scan(/ \+\+ | -- | << | >> /x)
              kind = :operator

            elsif match = scan(/ [-+!~^]=? | [*|&]{1,2}=? | >>? /x)
              kind = :operator

            elsif match = scan(/ [\/%]=? | <(?:<|=>?)? | [?:;] /x)
              kind = :operator

            else
              kind = :error
              match = getch

            end

          end
# }}}

          last_token_dot = last_token_dot == :set

          if $DEBUG and not kind
            raise_inspect 'Error token %p in line %d' %
              [[match, kind], line], tokens, state
          end
          raise_inspect 'Empty token', tokens unless match

          tokens << [match, kind]

          if last_state
            state = last_state
            last_state = nil
          end
        end
      end

      inline_block_stack << [state] if state.is_a? patterns::StringState
      until inline_block_stack.empty?
        this_block = inline_block_stack.pop
        tokens << [:close, :inline] if this_block.size > 1
        state = this_block.first
        tokens << [:close, state.type]
      end

      tokens
    end

  end

end
end

# vim:fdm=marker