xref: /freebsd/contrib/lyaml/lib/lyaml/implicit.lua (revision 2bc180ef045e5911cce0cea1c2a139cffd2b577a)
1-- LYAML parse implicit type tokens.
2-- Written by Gary V. Vaughan, 2015
3--
4-- Copyright(C) 2015-2022 Gary V. Vaughan
5--
6-- Permission is hereby granted, free of charge, to any person obtaining
7-- a copy of this software and associated documentation files(the
8-- "Software"), to deal in the Software without restriction, including
9-- without limitation the rights to use, copy, modify, merge, publish,
10-- distribute, sublicense, and/or sell copies of the Software, and to
11-- permit persons to whom the Software is furnished to do so, subject to
12-- the following conditions:
13--
14-- The above copyright notice and this permission notice shall be
15-- included in all copies or substantial portions of the Software.
16--
17-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25--- @module lyaml.implicit
26
27
28local NULL = require 'lyaml.functional'.NULL
29local find = string.find
30local floor = math.floor
31local gsub = string.gsub
32local sub = string.sub
33
34local tointeger = (function(f)
35   if not tointeger then
36      -- No host tointeger implementation, use our own.
37      return function(x)
38         if type(x) == 'number' and x - floor(x) == 0.0 then
39            return x
40         end
41      end
42
43   elseif f '1' ~= nil then
44      -- Don't perform implicit string-to-number conversion!
45      return function(x)
46         if type(x) == 'number' then
47            return tointeger(x)
48         end
49      end
50   end
51
52   -- Host tointeger is good!
53   return f
54end)(math.tointeger)
55
56
57local function int(x)
58   local r = tonumber(x)
59   if r ~= nil then
60      return tointeger(r)
61   end
62end
63
64
65local is_null = {['']=true, ['~']=true, null=true, Null=true, NULL=true}
66
67
68--- Parse a null token to a null value.
69-- @param value token
70-- @return[1] lyaml.null, for an empty string or literal ~
71-- @return[2] nil otherwise, nil
72-- @usage maybe_null = implicit.null(token)
73local function null(value)
74   if is_null[value] then
75      return NULL
76   end
77end
78
79
80local to_bool = {
81   ['true']  = true,  True  = true,  TRUE  = true,
82   ['false'] = false, False = false, FALSE = false,
83   yes = true,  Yes = true,  YES = true,
84   no  = false, No  = false, NO  = false,
85   on  = true,  On  = true,  ON  = true,
86   off = false, Off = false, OFF = false,
87}
88
89
90--- Parse a boolean token to the equivalent value.
91-- Treats capilalized, lower and upper-cased variants of true/false,
92-- yes/no or on/off tokens as boolean `true` and `false` values.
93-- @param value token
94-- @treturn[1] bool if a valid boolean token was recognized
95-- @treturn[2] nil otherwise, nil
96-- @usage maybe_bool = implicit.bool(token)
97local function bool(value)
98   return to_bool[value]
99end
100
101
102--- Parse a binary token, such as '0b1010\_0111\_0100\_1010\_1110'.
103-- @tparam string value token
104-- @treturn[1] int integer equivalent, if a valid token was recognized
105-- @treturn[2] nil otherwise, nil
106-- @usage maybe_int = implicit.binary(value)
107local function binary(value)
108   local r
109   gsub(value, '^([+-]?)0b_*([01][01_]+)$', function(sign, rest)
110      r = 0
111      gsub(rest, '_*(.)', function(digit)
112         r = r * 2 + int(digit)
113      end)
114      if sign == '-' then
115         r = r * -1
116      end
117   end)
118   return r
119end
120
121
122--- Parse an octal token, such as '012345'.
123-- @tparam string value token
124-- @treturn[1] int integer equivalent, if a valid token was recognized
125-- @treturn[2] nil otherwise, nil
126-- @usage maybe_int = implicit.octal(value)
127local function octal(value)
128   local r
129   gsub(value, '^([+-]?)0_*([0-7][0-7_]*)$', function(sign, rest)
130      r = 0
131      gsub(rest, '_*(.)', function(digit)
132         r = r * 8 + int(digit)
133      end)
134      if sign == '-' then
135         r = r * -1
136      end
137   end)
138   return r
139end
140
141
142--- Parse a decimal token, such as '0' or '12345'.
143-- @tparam string value token
144-- @treturn[1] int integer equivalent, if a valid token was recognized
145-- @treturn[2] nil otherwise, nil
146-- @usage maybe_int = implicit.decimal(value)
147local function decimal(value)
148   local r
149   gsub(value, '^([+-]?)_*([0-9][0-9_]*)$', function(sign, rest)
150      rest = gsub(rest, '_', '')
151      if rest == '0' or #rest > 1 or sub(rest, 1, 1) ~= '0' then
152         r = int(rest)
153         if sign == '-' then
154            r = r * -1
155         end
156      end
157   end)
158   return r
159end
160
161
162--- Parse a hexadecimal token, such as '0xdeadbeef'.
163-- @tparam string value token
164-- @treturn[1] int integer equivalent, if a valid token was recognized
165-- @treturn[2] nil otherwise, nil
166-- @usage maybe_int = implicit.hexadecimal(value)
167local function hexadecimal(value)
168   local r
169   gsub(value, '^([+-]?)(0x_*[0-9a-fA-F][0-9a-fA-F_]*)$', function(sign, rest)
170      rest = gsub(rest, '_', '')
171      r = int(rest)
172      if sign == '-' then
173         r = r * -1
174      end
175   end)
176   return r
177end
178
179
180--- Parse a sexagesimal token, such as '190:20:30'.
181-- Useful for times and angles.
182-- @tparam string value token
183-- @treturn[1] int integer equivalent, if a valid token was recognized
184-- @treturn[2] nil otherwise, nil
185-- @usage maybe_int = implicit.sexagesimal(value)
186local function sexagesimal(value)
187   local r
188   gsub(value, '^([+-]?)([0-9]+:[0-5]?[0-9][:0-9]*)$', function(sign, rest)
189      r = 0
190      gsub(rest, '([0-9]+):?', function(digit)
191         r = r * 60 + int(digit)
192      end)
193      if sign == '-' then
194         r = r * -1
195      end
196   end)
197   return r
198end
199
200
201local isnan = {['.nan']=true, ['.NaN']=true, ['.NAN']=true}
202
203
204--- Parse a `nan` token.
205-- @tparam string value token
206-- @treturn[1] nan not-a-number, if a valid token was recognized
207-- @treturn[2] nil otherwise, nil
208-- @usage maybe_nan = implicit.nan(value)
209local function nan(value)
210   if isnan[value] then
211      return 0/0
212   end
213end
214
215
216local isinf = {
217   ['.inf']  = math.huge,  ['.Inf']  = math.huge,  ['.INF']  = math.huge,
218   ['+.inf'] = math.huge,  ['+.Inf'] = math.huge,  ['+.INF'] = math.huge,
219   ['-.inf'] = -math.huge, ['-.Inf'] = -math.huge, ['-.INF'] = -math.huge,
220}
221
222
223--- Parse a signed `inf` token.
224-- @tparam string value token
225-- @treturn[1] number plus/minus-infinity, if a valid token was recognized
226-- @treturn[2] nil otherwise, nil
227-- @usage maybe_inf = implicit.inf(value)
228local function inf(value)
229   return isinf[value]
230end
231
232
233--- Parse a floating point number token, such as '1e-3' or '-0.12'.
234-- @tparam string value token
235-- @treturn[1] number float equivalent, if a valid token was recognized
236-- @treturn[2] nil otherwise, nil
237-- @usage maybe_float = implicit.float(value)
238local function float(value)
239   local r = tonumber((gsub(value, '_', '')))
240   if r and find(value, '[%.eE]') then
241      return r
242   end
243end
244
245
246--- Parse a sexagesimal float, such as '190:20:30.15'.
247-- Useful for times and angles.
248-- @tparam string value token
249-- @treturn[1] number float equivalent, if a valid token was recognized
250-- @treturn[2] nil otherwise, nil
251-- @usage maybe_float = implicit.sexfloat(value)
252local function sexfloat(value)
253   local r
254   gsub(value, '^([+-]?)([0-9]+:[0-5]?[0-9][:0-9]*)(%.[0-9]+)$',
255      function(sign, rest, float)
256         r = 0
257         gsub(rest, '([0-9]+):?', function(digit)
258            r = r * 60 + int(digit)
259         end)
260         r = r + tonumber(float)
261         if sign == '-' then
262            r = r * -1
263         end
264      end
265   )
266   return r
267end
268
269
270--- @export
271return {
272   binary = binary,
273   decimal = decimal,
274   float = float,
275   hexadecimal = hexadecimal,
276   inf = inf,
277   nan = nan,
278   null = null,
279   octal = octal,
280   sexagesimal = sexagesimal,
281   sexfloat = sexfloat,
282   bool = bool,
283}
284