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