Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use System.Reflection.Metadata.ILOpCode | |
| 3 | use Logging | |
| 4 | use Source.LOCATION | |
| 5 | ||
| 6 | use Semantic.Lookups.InnateSymbolLookup | |
| 7 | use Semantic.Types.Type | |
| 8 | ||
| 9 | use IR.Values | |
| 10 | ||
| 11 | // Classify a parsed integer literal: pick the target type from | |
| 12 | // any trailing type-suffix (`b`/`s`/`i`/`l`/`w`/`c`, optionally | |
| 13 | // prefixed by `u` for unsigned or `s` for sign-without-width), | |
| 14 | // strip the suffix from the value string, and range-check the | |
| 15 | // result against the chosen type. The lexer accepts arbitrarily | |
| 16 | // long digit strings and any suffix combination; this is where | |
| 17 | // suffix → type and value → fits-in-type get decided. | |
| 18 | // | |
| 19 | // Bounds errors and "character literals cannot be unsigned" | |
| 20 | // structural errors are logged via the constructor-supplied | |
| 21 | // logger. Callers receive a non-null `Literal.NUMBER` on success | |
| 22 | // and null on either kind of failure. | |
| 23 | // | |
| 24 | // The `fits` / `parse_hex` / `max_unsigned_for` helpers are | |
| 25 | // exposed for unit testing in isolation from the suffix path. | |
| 26 | class NUMERIC_LITERAL_CLASSIFIER is | |
| 27 | _logger: Logger | |
| 28 | _innate_symbol_lookup: InnateSymbolLookup | |
| 29 | ||
| 30 | init(logger: Logger, innate_symbol_lookup: InnateSymbolLookup) is | |
| 31 | super.init() | |
| 32 | ||
| 33 | _logger = logger | |
| 34 | _innate_symbol_lookup = innate_symbol_lookup | |
| 35 | si | |
| 36 | ||
| 37 | // How many trailing characters of an integer literal are its | |
| 38 | // type suffix. | |
| 39 | // | |
| 40 | // The digits are scanned maximally in the literal's own radix, | |
| 41 | // and the suffix is whatever is left. That is what keeps `b` | |
| 42 | // and `c` - hex digits as well as size selectors - reading as | |
| 43 | // digits in a hex literal, so `0x20AC` is 8364 rather than | |
| 44 | // 0x20A as a char. A backtick attaches a suffix that would | |
| 45 | // otherwise be read as a digit, and is counted as part of the | |
| 46 | // suffix so that stripping it yields the value. | |
| 47 | // | |
| 48 | // Every reader of a given token has to resolve this the same | |
| 49 | // way, so it is the one place the rule lives. | |
| 50 | suffix_length(value_string: string) -> int static is | |
| 51 | if value_string.length == 0 then | |
| 52 | return 0 | |
| 53 | fi | |
| 54 | ||
| 55 | let tick = value_string.index_of('`') | |
| 56 | ||
| 57 | if tick >= 0 then | |
| 58 | return value_string.length - tick | |
| 59 | fi | |
| 60 | ||
| 61 | // A sign belongs to the value rather than to either end of | |
| 62 | // it, and a caller may hand the text over with one still | |
| 63 | // attached. | |
| 64 | let signed = value_string[0] == '-' \/ value_string[0] == '+' | |
| 65 | let start = if signed then 1 else 0 fi | |
| 66 | ||
| 67 | let is_hex = | |
| 68 | value_string.length > start + 1 /\ | |
| 69 | value_string[start] == '0' /\ | |
| 70 | (value_string[start + 1] == 'x' \/ value_string[start + 1] == 'X') | |
| 71 | ||
| 72 | let first_digit = if is_hex then start + 2 else start fi | |
| 73 | let index mut = first_digit | |
| 74 | ||
| 75 | while index < value_string.length /\ is_digit(value_string[index], is_hex) do | |
| 76 | index = index + 1 | |
| 77 | od | |
| 78 | ||
| 79 | return value_string.length - index | |
| 80 | si | |
| 81 | ||
| 82 | // Whether `c` is a digit of the literal's radix. `_` never | |
| 83 | // reaches here - the lexer drops digit-group separators before | |
| 84 | // the token text is built. | |
| 85 | is_digit(c: char, is_hex: bool) -> bool static => | |
| 86 | (c >= '0' /\ c <= '9') \/ | |
| 87 | (is_hex /\ ((c >= 'a' /\ c <= 'f') \/ (c >= 'A' /\ c <= 'F'))) | |
| 88 | ||
| 89 | // The literal with its type suffix removed, which is the value | |
| 90 | // it denotes and the string every back end should read. | |
| 91 | strip_suffix(value_string: string) -> string static => | |
| 92 | value_string.substring(0, value_string.length - suffix_length(value_string)) | |
| 93 | ||
| 94 | // The value an integer literal denotes. Every scalar suffix | |
| 95 | // yields a `Literal.NUMBER`; `n` yields a `Literal.BIGINT`, | |
| 96 | // which has no constant to load and is built at run time. | |
| 97 | classify(location: LOCATION, value_string: string) -> Value? => | |
| 98 | if is_bigint(value_string) then | |
| 99 | classify_bigint(location, value_string) | |
| 100 | else | |
| 101 | classify_integer(location, value_string) | |
| 102 | fi | |
| 103 | ||
| 104 | // Whether the literal carries the `n` suffix. The size selector | |
| 105 | // is always the last character of the suffix, so this is the | |
| 106 | // whole of the test. | |
| 107 | is_bigint(value_string: string) -> bool static is | |
| 108 | if suffix_length(value_string) == 0 then | |
| 109 | return false | |
| 110 | fi | |
| 111 | ||
| 112 | let last = value_string[value_string.length - 1] | |
| 113 | ||
| 114 | return last == 'n' \/ last == 'N' | |
| 115 | si | |
| 116 | ||
| 117 | // The selectors of a suffixed literal, with any attaching | |
| 118 | // backtick dropped - it marks the boundary and says nothing | |
| 119 | // about the type. Empty when the backtick had nothing after it, | |
| 120 | // which the lexer has already reported. | |
| 121 | selectors(value_string: string) -> string static is | |
| 122 | let cutoff = value_string.length - suffix_length(value_string) | |
| 123 | ||
| 124 | if value_string[cutoff] == '`' then | |
| 125 | return value_string.substring(cutoff + 1, value_string.length - cutoff - 1) | |
| 126 | fi | |
| 127 | ||
| 128 | return value_string.substring(cutoff, value_string.length - cutoff) | |
| 129 | si | |
| 130 | ||
| 131 | classify_bigint(location: LOCATION, value_string: string) -> Literal.BIGINT? is | |
| 132 | let selected = selectors(value_string) | |
| 133 | ||
| 134 | if selected.length == 0 then | |
| 135 | return null | |
| 136 | fi | |
| 137 | ||
| 138 | if "uU".contains(selected[0]) then | |
| 139 | _logger.error(location, "bigint literal cannot be unsigned") | |
| 140 | return null | |
| 141 | fi | |
| 142 | ||
| 143 | let working = strip_suffix(value_string) | |
| 144 | ||
| 145 | let (ok, digits) = decimal_magnitude(working) | |
| 146 | ||
| 147 | if !ok then | |
| 148 | _logger.error(location, "invalid bigint literal {working}") | |
| 149 | return null | |
| 150 | fi | |
| 151 | ||
| 152 | let as_long: long mut = 0l | |
| 153 | let fits_long = fits_in_long(digits) /\ long.try_parse(digits, as_long ref) | |
| 154 | ||
| 155 | return Literal.BIGINT(digits, _innate_symbol_lookup.get_bigint_type(), as_long, fits_long) | |
| 156 | si | |
| 157 | ||
| 158 | // Largest magnitude a `long` holds, as the digit string | |
| 159 | // `fits_in_long` compares against. | |
| 160 | LONG_MAX_DIGITS: string static => "9223372036854775807" | |
| 161 | ||
| 162 | // A bigint literal has no width to overflow, so its magnitude is | |
| 163 | // carried as digits rather than range-checked. `long` is only | |
| 164 | // the shorter of the two ways to build one at run time, so | |
| 165 | // whether the value fits is a question about which instructions | |
| 166 | // to emit rather than about whether the literal is legal. | |
| 167 | fits_in_long(digits: string) -> bool => | |
| 168 | digits.length < LONG_MAX_DIGITS.length \/ | |
| 169 | (digits.length == LONG_MAX_DIGITS.length /\ string.compare_ordinal(digits, LONG_MAX_DIGITS) <= 0) | |
| 170 | ||
| 171 | // The literal's magnitude in decimal digits, with no leading | |
| 172 | // zeros. A decimal literal already is that, once its zeros are | |
| 173 | // dropped; a hex one is accumulated a digit at a time, because | |
| 174 | // the value has no bound to parse it into. | |
| 175 | decimal_magnitude(value_string: string) -> (ok: bool, digits: string) is | |
| 176 | let is_hex = value_string.starts_with("0x") \/ value_string.starts_with("0X") | |
| 177 | let source = if is_hex then value_string.substring(2, value_string.length - 2) else value_string fi | |
| 178 | ||
| 179 | if source.length == 0 then | |
| 180 | return (false, "0") | |
| 181 | fi | |
| 182 | ||
| 183 | // Least significant first, one decimal digit each, so a | |
| 184 | // carry appends rather than shifting the whole number. | |
| 185 | let accumulator = Collections.LIST[int]([0]) | |
| 186 | let radix = if is_hex then 16 else 10 fi | |
| 187 | ||
| 188 | for c in source do | |
| 189 | let digit: int mut | |
| 190 | ||
| 191 | if c >= '0' /\ c <= '9' then | |
| 192 | digit = cast int(c) - cast int('0') | |
| 193 | elif is_hex /\ c >= 'a' /\ c <= 'f' then | |
| 194 | digit = cast int(c) - cast int('a') + 10 | |
| 195 | elif is_hex /\ c >= 'A' /\ c <= 'F' then | |
| 196 | digit = cast int(c) - cast int('A') + 10 | |
| 197 | else | |
| 198 | return (false, "0") | |
| 199 | fi | |
| 200 | ||
| 201 | let carry mut = digit | |
| 202 | ||
| 203 | for i in 0..accumulator.count do | |
| 204 | let product = accumulator[i] * radix + carry | |
| 205 | accumulator[i] = product % 10 | |
| 206 | carry = product / 10 | |
| 207 | od | |
| 208 | ||
| 209 | while carry > 0 do | |
| 210 | accumulator.add(carry % 10) | |
| 211 | carry = carry / 10 | |
| 212 | od | |
| 213 | od | |
| 214 | ||
| 215 | let buffer = System.Text.StringBuilder(accumulator.count) | |
| 216 | let top mut = accumulator.count - 1 | |
| 217 | ||
| 218 | while top > 0 /\ accumulator[top] == 0 do | |
| 219 | top = top - 1 | |
| 220 | od | |
| 221 | ||
| 222 | for i in 0::top do | |
| 223 | buffer.append(cast char(cast int('0') + accumulator[top - i])) | |
| 224 | od | |
| 225 | ||
| 226 | return (true, buffer.to_string()) | |
| 227 | si | |
| 228 | ||
| 229 | classify_integer(location: LOCATION, value_string: string) -> Literal.NUMBER? is | |
| 230 | let type: Type? mut = null | |
| 231 | let is_wide mut = false | |
| 232 | let conv: ILOpCode? mut = null | |
| 233 | let working mut = value_string | |
| 234 | ||
| 235 | let suffix = suffix_length(working) | |
| 236 | ||
| 237 | if suffix > 0 then | |
| 238 | let cutoff = working.length - suffix | |
| 239 | ||
| 240 | // The selectors themselves, with any attaching backtick | |
| 241 | // dropped - it marks the boundary and says nothing about | |
| 242 | // the type. | |
| 243 | let selectors = | |
| 244 | if working[cutoff] == '`' then | |
| 245 | working.substring(cutoff + 1, suffix - 1) | |
| 246 | else | |
| 247 | working.substring(cutoff, suffix) | |
| 248 | fi | |
| 249 | ||
| 250 | // A backtick with nothing after it. The lexer has already | |
| 251 | // reported it; failing quietly here keeps it to one | |
| 252 | // diagnostic. | |
| 253 | if selectors.length == 0 then | |
| 254 | return null | |
| 255 | fi | |
| 256 | ||
| 257 | let last_char = selectors[selectors.length - 1] | |
| 258 | let is_unsigned = "uU".contains(selectors[0]) | |
| 259 | ||
| 260 | case last_char | |
| 261 | when 'b', 'B' then | |
| 262 | if is_unsigned then | |
| 263 | type = _innate_symbol_lookup.get_ubyte_type() | |
| 264 | else | |
| 265 | type = _innate_symbol_lookup.get_byte_type() | |
| 266 | fi | |
| 267 | ||
| 268 | when 'c', 'C' then | |
| 269 | if is_unsigned then | |
| 270 | _logger.error(location, "numeric character literal cannot be unsigned") | |
| 271 | else | |
| 272 | type = _innate_symbol_lookup.get_char_type() | |
| 273 | fi | |
| 274 | ||
| 275 | when 's', 'S' then | |
| 276 | if is_unsigned then | |
| 277 | type = _innate_symbol_lookup.get_ushort_type() | |
| 278 | else | |
| 279 | type = _innate_symbol_lookup.get_short_type() | |
| 280 | fi | |
| 281 | ||
| 282 | when 'i', 'I' then | |
| 283 | if is_unsigned then | |
| 284 | type = _innate_symbol_lookup.get_uint_type() | |
| 285 | else | |
| 286 | type = _innate_symbol_lookup.get_int_type() | |
| 287 | fi | |
| 288 | ||
| 289 | when 'l', 'L' then | |
| 290 | is_wide = true | |
| 291 | ||
| 292 | if is_unsigned then | |
| 293 | type = _innate_symbol_lookup.get_ulong_type() | |
| 294 | else | |
| 295 | type = _innate_symbol_lookup.get_long_type() | |
| 296 | fi | |
| 297 | ||
| 298 | when 'w', 'W' then | |
| 299 | is_wide = true | |
| 300 | ||
| 301 | if is_unsigned then | |
| 302 | type = _innate_symbol_lookup.get_uword_type() | |
| 303 | conv = ILOpCode.CONV_U | |
| 304 | else | |
| 305 | type = _innate_symbol_lookup.get_word_type() | |
| 306 | conv = ILOpCode.CONV_I | |
| 307 | fi | |
| 308 | ||
| 309 | else | |
| 310 | if is_unsigned then | |
| 311 | type = _innate_symbol_lookup.get_uint_type() | |
| 312 | else | |
| 313 | type = _innate_symbol_lookup.get_int_type() | |
| 314 | fi | |
| 315 | esac | |
| 316 | ||
| 317 | working = working.substring(0, cutoff) | |
| 318 | else | |
| 319 | type = _innate_symbol_lookup.get_int_type() | |
| 320 | fi | |
| 321 | ||
| 322 | if !type? then | |
| 323 | return null | |
| 324 | fi | |
| 325 | ||
| 326 | let (ok, parsed) = parse_magnitude(working) | |
| 327 | ||
| 328 | if !ok \/ !fits(location, working, type) then | |
| 329 | return null | |
| 330 | fi | |
| 331 | ||
| 332 | let constant = | |
| 333 | if is_wide then | |
| 334 | Literal.CONSTANT.I8(cast long(parsed)) | |
| 335 | else | |
| 336 | Literal.CONSTANT.I4(cast int(cast uint(parsed))) | |
| 337 | fi | |
| 338 | ||
| 339 | return Literal.NUMBER(constant, type, conv) | |
| 340 | si | |
| 341 | ||
| 342 | // The magnitude a literal denotes, whatever radix it is written | |
| 343 | // in. Both the range check and the constant the literal loads | |
| 344 | // need it, so it is read once here rather than each reading the | |
| 345 | // spelling for itself. | |
| 346 | parse_magnitude(value_string: string) -> (ok: bool, value: ulong) => | |
| 347 | if value_string.starts_with("0x") \/ value_string.starts_with("0X") then | |
| 348 | parse_hex(value_string.substring(2, value_string.length - 2)) | |
| 349 | else | |
| 350 | ( | |
| 351 | let v: ulong mut = _ | |
| 352 | let parsed_ok = ulong.try_parse(value_string, v ref) | |
| 353 | (parsed_ok, v) | |
| 354 | ) | |
| 355 | fi | |
| 356 | ||
| 357 | // Range-check a parsed integer literal against its target | |
| 358 | // type. Comparing as a ulong using each type's unsigned max | |
| 359 | // preserves the standard "literal fits in N unsigned bits, | |
| 360 | // unary minus negates separately" convention. Without this, | |
| 361 | // oversize literals reach IL emission, where the constant no | |
| 362 | // longer fits the instruction it is loaded by (issue #580). | |
| 363 | fits(location: LOCATION, value_string: string, type: Type) -> bool is | |
| 364 | let (ok, parsed) = parse_magnitude(value_string) | |
| 365 | ||
| 366 | if !ok then | |
| 367 | _logger.error(location, "numeric literal {value_string} does not fit in {type}") | |
| 368 | return false | |
| 369 | fi | |
| 370 | ||
| 371 | let max_value = max_unsigned_for(type) | |
| 372 | ||
| 373 | if max_value > 0UL /\ parsed > max_value then | |
| 374 | _logger.error(location, "numeric literal {value_string} does not fit in {type}") | |
| 375 | return false | |
| 376 | fi | |
| 377 | ||
| 378 | return true | |
| 379 | si | |
| 380 | ||
| 381 | // Parse a hex digit string (no `0x` prefix) into a ulong. | |
| 382 | // Returns (false, 0) for empty, > 16 digits, or any non-hex | |
| 383 | // character. | |
| 384 | parse_hex(s: string) -> (ok: bool, value: ulong) is | |
| 385 | if s.length == 0 \/ s.length > 16 then | |
| 386 | return (false, 0UL) | |
| 387 | fi | |
| 388 | ||
| 389 | let value: ulong mut = 0UL | |
| 390 | ||
| 391 | for c in s do | |
| 392 | let digit: int mut | |
| 393 | ||
| 394 | if c >= '0' /\ c <= '9' then | |
| 395 | digit = cast int(c) - cast int('0') | |
| 396 | elif c >= 'a' /\ c <= 'f' then | |
| 397 | digit = cast int(c) - cast int('a') + 10 | |
| 398 | elif c >= 'A' /\ c <= 'F' then | |
| 399 | digit = cast int(c) - cast int('A') + 10 | |
| 400 | else | |
| 401 | return (false, 0UL) | |
| 402 | fi | |
| 403 | ||
| 404 | value = (value << 4) + cast ulong(digit) | |
| 405 | od | |
| 406 | ||
| 407 | return (true, value) | |
| 408 | si | |
| 409 | ||
| 410 | // Largest non-negative literal value the type accepts, or 0 | |
| 411 | // to signal "no narrower bound than the ulong-parse range" | |
| 412 | // (used for ulong / uword: ulong.try_parse already enforces | |
| 413 | // the 64-bit ceiling). 0 makes `fits` skip the narrower- | |
| 414 | // bound check. | |
| 415 | max_unsigned_for(type: Type) -> ulong is | |
| 416 | if type.matches(_innate_symbol_lookup.get_byte_type()) then | |
| 417 | return 127UL | |
| 418 | elif type.matches(_innate_symbol_lookup.get_ubyte_type()) then | |
| 419 | return 255UL | |
| 420 | elif type.matches(_innate_symbol_lookup.get_short_type()) then | |
| 421 | return 32767UL | |
| 422 | elif type.matches(_innate_symbol_lookup.get_ushort_type()) then | |
| 423 | return 65535UL | |
| 424 | elif type.matches(_innate_symbol_lookup.get_char_type()) then | |
| 425 | return 65535UL | |
| 426 | elif type.matches(_innate_symbol_lookup.get_int_type()) then | |
| 427 | return 2147483647UL | |
| 428 | elif type.matches(_innate_symbol_lookup.get_uint_type()) then | |
| 429 | return 4294967295UL | |
| 430 | elif type.matches(_innate_symbol_lookup.get_long_type()) then | |
| 431 | return 9223372036854775807UL | |
| 432 | elif type.matches(_innate_symbol_lookup.get_word_type()) then | |
| 433 | return 9223372036854775807UL | |
| 434 | fi | |
| 435 | ||
| 436 | return 0UL | |
| 437 | si | |
| 438 | si | |
| 439 | si |