File: parser.rb

package info (click to toggle)
ruby-kdl 1.0.3-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 480 kB
  • sloc: ruby: 6,667; yacc: 72; sh: 5; makefile: 4
file content (151 lines) | stat: -rw-r--r-- 4,414 bytes parent folder | download | duplicates (2)
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
require_relative '../hostname/validator'

module KDL
  module Types
    class Email < Value
      class Parser
        def initialize(string, idn: false)
          @string = string
          @idn = idn
          @tokenizer = Tokenizer.new(string, idn: idn)
        end

        def parse
          local = ''
          unicode_domain = nil
          domain = nil
          context = :start

          loop do
            type, value = @tokenizer.next_token

            case type
            when :part
              case context
              when :start, :after_dot
                local += value
                context = :after_part
              else
                raise ArgumentError, "invalid email #{@string} (unexpected part #{value} at #{context})"
              end
            when :dot
              case context
              when :after_part
                local += value
                context = :after_dot
              else
                raise ArgumentError, "invalid email #{@string} (unexpected dot at #{context})"
              end
            when :at
              case context
              when :after_part
                context = :after_at
              end
            when :domain
              case context
              when :after_at
                validator = (@idn ? IDNHostname : Hostname)::Validator.new(value)
                raise ArgumentError, "invalid hostname #{value}" unless validator.valid?

                unicode_domain = validator.unicode
                domain = validator.ascii
                context = :after_domain
              else
                raise ArgumentError, "invalid email #{@string} (unexpected domain at #{context})"
              end
            when :end
              case context
              when :after_domain
                if local.size > 64
                  raise ArgumentError, "invalid email #{@string} (local part length #{local.size} exceeds maximaum of 64)"
                end

                return [local, domain, unicode_domain]
              else
                raise ArgumentError, "invalid email #{@string} (unexpected end at #{context})"
              end
            end
          end
        end
      end

      class Tokenizer
        LOCAL_PART_ASCII = %r{[a-zA-Z0-9!#$%&'*+\-/=?^_`{|}~]}.freeze
        LOCAL_PART_IDN = /[^\x00-\x1f\s".@]/.freeze

        def initialize(string, idn: false)
          @string = string
          @idn = idn
          @index = 0
          @after_at = false
        end

        def next_token
          if @after_at
            if @index < @string.size
              domain_start = @index
              @index = @string.size
              return [:domain, @string[domain_start..-1]]
            else
              return [:end, nil]
            end
          end
          @context = nil
          @buffer = ''
          loop do
            c = @string[@index]
            return [:end, nil] if c.nil?

            case @context
            when nil
              case c
              when '.'
                @index += 1
                return [:dot, '.']
              when '@'
                @after_at = true
                @index += 1
                return [:at, '@']
              when '"'
                @context = :quote
                @index += 1
              when local_part_chars
                @context = :part
                @buffer += c
                @index += 1
              else
                raise ArgumentError, "invalid email #{@string} (unexpected #{c})"
              end
            when :part
              case c
              when local_part_chars
                @buffer += c
                @index += 1
              when '.', '@'
                return [:part, @buffer]
              else
                raise ArgumentError, "invalid email #{@string} (unexpected #{c})"
              end
            when :quote
              case c
              when '"'
                n = @string[@index + 1]
                raise ArgumentError, "invalid email #{@string} (unexpected #{c})" unless n == '.' || n == '@'

                @index += 1
                return [:part, @buffer]
              else
                @buffer += c
                @index += 1
              end
            end
          end
        end

        def local_part_chars
          @idn ? LOCAL_PART_IDN : LOCAL_PART_ASCII
        end
      end
    end
  end
end