summaryrefslogtreecommitdiff
path: root/lib/coderay/scanners/tcl.rb
blob: 7ae6d7c905ca53e439f05615c431c086b2c5c948 (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
module CodeRay
  module Scanners

    # by Eric Thomas
    class Tcl < Scanner

      register_for :tcl

      # Taken from http://www.xilinx.com/itp/xilinx10/isehelp/ite_r_tcl_reserved_words.htm
      RESERVED_WORDS = %w(after append array auto_execok auto_import auto_load
                          auto_load_index auto_qualify binary bgerrro break
                          catch cd clock close concat continue dde default
                          else elseif encoding eof error eval exec exit expr
                          fblocked fconfigure fcopy file fileevent flush for
                          foreach format gets glob global history if incr
                          interp join lappend lindex list llength load lrange
                          lrange lreplace lsearch lsort namespace open package
                          pid pkg_mkIndex proc puts pwd read regexp regsub
                          rename resource return scan seek set socket source
                          split string subst switch tclLog tell time trace
                          unknown unset update uplevel upvar vwait while)

      PREDEFINED_TYPES = %w(variable)

      # should we include 0 or 1?
      PREDEFINED_CONSTANTS = %w(true false yes no on off 1 0)

      IDENT_KIND = WordList.new(:ident).
        add(RESERVED_WORDS, :reserved).
        add(PREDEFINED_TYPES, :pre_type).
        add(PREDEFINED_CONSTANTS, :pre_constant)

      ESCAPE = / [rbfntv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x
      UNICODE_ESCAPE =  / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x

      def scan_tokens tokens, options
        state = :initial

        until eos?
          kind = nil
          match = nil

          case state
          when :initial

            if scan(/ \s+ | \\\n /x)
              kind = :space

            elsif scan(/#.*/)
               kind = :comment

            elsif scan(/ [-+*\/=<>?:;,!&^|()\[\]{}~%]+ | \.(?!\d) /x)
              kind = :operator

            elsif scan(/\$\S+/)
              kind = :variable

            elsif match = scan(/ [A-Za-z_][A-Za-z_0-9]* /x)
              kind = IDENT_KIND[match]
              if kind == :ident and check(/:(?!:)/)
                match << scan(/:/)
                kind = :label
              end

            elsif match = scan(/L?"/)
              tokens << [:open, :string]
              if match[0] == ?L
                tokens << ['L', :modifier]
                match = '"'
              end
              state = :string
              kind = :delimiter

            elsif scan(/ L?' (?: [^\'\n\\] | \\ #{ESCAPE} )? '? /ox)
              kind = :char

            elsif scan(/0[xX][0-9A-Fa-f]+/)
              kind = :hex

            elsif scan(/(?:0[0-7]+)(?![89.eEfF])/)
              kind = :oct

            elsif scan(/(?:\d+)(?![.eEfF])/)
              kind = :integer

            elsif scan(/\d[fF]?|\d*\.\d+(?:[eE][+-]?\d+)?[fF]?|\d+[eE][+-]?\d+[fF]?/)
              kind = :float

            else
              getch
              kind = :error

            end

          when :string
            if scan(/[^\\\n"]+/)
              kind = :content
            elsif scan(/"/)
              tokens << ['"', :delimiter]
              tokens << [:close, :string]
              state = :initial
              next
            elsif scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox)
              kind = :char
            elsif scan(/ \\ | $ /x)
              tokens << [:close, :string]
              kind = :error
              state = :initial
            else
              raise_inspect "else case \" reached; %p not handled." % peek(1), tokens
            end

          else
            raise_inspect 'Unknown state', tokens

          end

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

          tokens << [match, kind]

        end

        if state == :string
          tokens << [:close, :string]
        end

        tokens
      end
    end
  end
end