• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2024 Huawei Device Co., Ltd.
2# Licensed under the Apache License, Version 2.0 (the "License");
3# you may not use this file except in compliance with the License.
4# You may obtain a copy of the License at
5#
6# http://www.apache.org/licenses/LICENSE-2.0
7#
8# Unless required by applicable law or agreed to in writing, software
9# distributed under the License is distributed on an "AS IS" BASIS,
10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14module ESChecker
15    module Types
16        def self.value_is_number(r)
17            r.kind_of?(Float) ||
18                r.kind_of?(Integer) ||
19                r.kind_of?(Hash) && r["__kind"] == "num" ||
20                ["#__NaN", "#__NegInf", "#__Inf", "#__NaN"].include?(r) ||
21                r.kind_of?(String) && /^Double.bitCastFromLong.*/ =~ r
22        end
23
24        def self.value_is_string(r)
25            return r.kind_of?(String) && !UNMANGLE.has_key?(r) ||
26                r.kind_of?(Hash) && r["__kind"] == "str"
27        end
28
29        def self.dump(t, buf: "")
30            case t[:kind]
31            when :array
32                buf << "Array<"
33                dump t[:el], buf: buf
34                buf << ">"
35            when :tuple
36                buf << "["
37                t[:els].each_with_index { |t, i|
38                    if i != 0
39                        buf << ", "
40                    end
41                    dump t, buf: buf
42                }
43                buf << "]"
44            when :trivial
45                buf << t[:str]
46            else
47                raise "unknown type kind #{t.kind}"
48            end
49            buf
50        end
51
52        def self.detect(rb_obj, dflt: nil)
53            if !dflt.nil?
54                return ESChecker::Types::Parser.new(dflt).run
55            end
56            if [true, false].include? rb_obj
57                return { :kind => :trivial, :str => "boolean" }
58            end
59            if value_is_number(rb_obj)
60                return { :kind => :trivial, :str => "number" }
61            end
62            if value_is_string rb_obj
63                return { :kind => :trivial, :str => "string" }
64            end
65            if rb_obj.kind_of?(Hash) && rb_obj["__kind"] == "bigint"
66                return { :kind => :trivial, :str => "bigint" }
67            end
68            if rb_obj.kind_of?(Array)
69                if rb_obj.size == 0
70                    return { :kind => :array, :el => { :kind => :trivial, :str => "Object|null|undefined" } }
71                end
72                typs = rb_obj.map { |r| detect r }
73                if typs.all? { |t| t == typs[0] }
74                    return { :kind => :array, :el => typs[0] }
75                end
76            end
77            if rb_obj == "#__undefined" || rb_obj == "#__null"
78                return { :kind => :trivial, :str => "NullishType" }
79            end
80            raise "can't detect type for #{rb_obj}"
81        end
82
83        class Parser
84            def initialize(str)
85                @str = str
86                @chrs = str.chars.reverse
87            end
88
89            def error(msg: nil)
90                raise "can't parse type from '#{@str}' : tail is '#{@chrs.reverse[..100].join}' ; #{msg}"
91            end
92
93            def skip_ws
94                while @chrs.size != 0 && /\s+/ =~ @chrs[-1]
95                    @chrs.pop
96                end
97            end
98            def fetch_expected(s)
99                if @chrs[-1] != s
100                    error(msg: "expected #{s}")
101                end
102                @chrs.pop
103            end
104            def starts_with(s)
105                if s.size > @chrs.size
106                    return false
107                end
108                s.each_char.with_index { |c, i|
109                    if c != @chrs[-1 - i]
110                        return false
111                    end
112                }
113                return true
114            end
115
116            def run
117                ret = step
118                skip_ws
119                if @chrs.size != 0
120                    error(msg: "unparsed tail")
121                end
122                return ret
123            end
124
125            def parse_tuple_els
126                skip_ws
127                els = []
128                while @chrs.size != 0 && @chrs[-1] != ']'
129                    els.push(step)
130                    skip_ws
131                    if @chrs[-1] == ','
132                        @chrs.pop
133                    else
134                        break
135                    end
136                    skip_ws
137                end
138                els
139            end
140
141            def parse_trivial
142                stck = []
143                res = String.new
144                while @chrs.size > 0
145                    if @chrs[-1] == '<' || @chrs[-1] == '['
146                        stck.push @chrs[-1]
147                        res << @chrs.pop
148                    elsif @chrs[-1] == '>' || @chrs[-1] == ']'
149                        if stck.size == 0
150                            break
151                        end
152                        res << @chrs.pop
153                        stck.pop
154                    elsif stck.size == 0 && @chrs[-1] == ','
155                        break
156                    else
157                        res << @chrs.pop
158                    end
159                end
160                res
161            end
162
163            def step
164                skip_ws
165                if starts_with "Array<"
166                    @chrs.pop("Array<".size)
167                    el = step
168                    skip_ws
169                    fetch_expected '>'
170                    return { :kind => :array, :el => el }
171                elsif starts_with "Iterable<"
172                    @chrs.pop("Iterable<".size)
173                    el = step
174                    skip_ws
175                    fetch_expected '>'
176                    return { :kind => :array, :el => el }
177                elsif starts_with '['
178                    @chrs.pop
179                    els = parse_tuple_els
180                    fetch_expected ']'
181                    return { :kind => :tuple, :els => els }
182                else
183                    res = parse_trivial
184                    return { :kind => :trivial, :str => res }
185                end
186            end
187        end
188    end
189end
190