Coverage Report

Created: 2026-10-30 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/harfbuzz/src/hb-number-parser.hh
Line
Count
Source
1
/*
2
 * Copyright © 2019-2026  Ebrahim Byagowi
3
 *
4
 *  This is part of HarfBuzz, a text shaping library.
5
 *
6
 * Permission is hereby granted, without written agreement and without
7
 * license or royalty fees, to use, copy, modify, and distribute this
8
 * software and its documentation for any purpose, provided that the
9
 * above copyright notice and the following two paragraphs appear in
10
 * all copies of this software.
11
 *
12
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16
 * DAMAGE.
17
 *
18
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
21
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23
 *
24
 */
25
26
#ifndef HB_NUMBER_PARSER_HH
27
#define HB_NUMBER_PARSER_HH
28
29
#include "hb.hh"
30
31
static const double _powers_of_10[] =
32
{ 1.0e+256, 1.0e+128, 1.0e+64, 1.0e+32, 1.0e+16, 1.0e+8, 10000., 100., 10. };
33
  
34
/* Grammar:
35
 *   number  := sign? mantissa exponent?
36
 *   sign    := '+' | '-'
37
 *   mantissa:= digits ('.' digits?)?
38
 *            | '.' digits
39
 *   exponent:= ('e'|'E') sign? digits
40
 */
41
struct hb_number_parser_t
42
{
43
  /* Returns true if c was accepted; false ends the parse (c not consumed). */
44
  bool consume (char c)
45
0
  {
46
0
    switch (state)
47
0
    {
48
0
      case Start:
49
0
  if (c == '+' || c == '-') { neg = (c == '-'); state = Sign; pos++; return true; }
50
0
  if ('0' <= c && c <= '9') { value = value * 10. + (c - '0'); state = IntDigits; pos++; return true; }
51
0
  if (c == '.') { state = AfterDotFromStart; pos++; return true; }
52
0
  return false;
53
54
0
      case Sign:
55
0
  if ('0' <= c && c <= '9') { value = value * 10. + (c - '0'); state = IntDigits; pos++; return true; }
56
0
  if (c == '.') { state = AfterDotFromStart; pos++; return true; }
57
0
  return false;
58
59
0
      case IntDigits:
60
0
  if ('0' <= c && c <= '9') { value = value * 10. + (c - '0'); pos++; return true; }
61
0
  if (c == '.') { state = AfterDotFromInt; pos++; return true; }
62
0
  if (c == 'e' || c == 'E') { enter_exponent (); return true; }
63
0
  return false;
64
65
0
      case AfterDotFromInt:
66
0
  if ('0' <= c && c <= '9') { fraction.add (c - '0'); state = FracDigits; pos++; return true; }
67
0
  if (c == 'e' || c == 'E') { enter_exponent (); return true; }
68
0
  return false;
69
70
0
      case AfterDotFromStart:
71
0
  if ('0' <= c && c <= '9') { fraction.add (c - '0'); state = FracDigits; pos++; return true; }
72
0
  return false;
73
74
0
      case FracDigits:
75
0
  if ('0' <= c && c <= '9') { fraction.add (c - '0'); pos++; return true; }
76
0
  if (c == 'e' || c == 'E') { enter_exponent (); return true; }
77
0
  return false;
78
79
0
      case ExpStart:
80
0
  if (c == '+' || c == '-') { exp_neg = (c == '-'); state = ExpSign; pos++; return true; }
81
0
  if ('0' <= c && c <= '9') { exp = c - '0'; state = ExpDigits; pos++; return true; }
82
0
  rollback_exponent ();
83
0
  return false;
84
85
0
      case ExpSign:
86
0
  if ('0' <= c && c <= '9') { exp = c - '0'; state = ExpDigits; pos++; return true; }
87
0
  rollback_exponent ();
88
0
  return false;
89
90
0
      case ExpDigits:
91
0
  if ('0' <= c && c <= '9')
92
0
  {
93
0
    constexpr unsigned MAX_EXP = 0x7FFu; /* 2^11-1 */
94
0
    if (likely (exp * 10 + (c - '0') <= MAX_EXP)) exp = exp * 10 + (c - '0');
95
0
    else exp_overflow = true;
96
0
    pos++;
97
0
    return true;
98
0
  }
99
0
  return false;
100
0
    }
101
0
    return false;
102
0
  }
103
104
  /* Signal end of input.  Rolls back a dangling exponent.  Returns the
105
   * accepted length, or 0 if no valid number was parsed. */
106
  size_t finish ()
107
0
  {
108
0
    if (state == ExpStart || state == ExpSign) rollback_exponent ();
109
0
    return (state == Sign || state == AfterDotFromStart ||
110
0
      state == ExpStart || state == ExpSign) ? 0 : pos;
111
0
  }
112
113
  /* Valid after finish() returned true. */
114
  double get_value () const
115
0
  {
116
0
    double v = value;
117
0
    if (!fraction.is_empty ()) v += fraction.get_value ();
118
0
    if (neg) v *= -1.;
119
120
0
    if (unlikely (exp_overflow))
121
0
    {
122
0
      if (v == 0)  return v; /* preserves -0.0 for "-0e<overflow>" */
123
0
      if (exp_neg) return neg ? -.0 : .0;
124
0
      else         return neg ? -HUGE_VAL : HUGE_VAL;
125
0
    }
126
127
0
    if (exp)
128
0
    {
129
0
      unsigned e = exp;
130
0
      if (exp_neg)
131
0
      {
132
  /* 10^308 is the largest finite power of ten, so two reductions of 308 suffice. */
133
0
  if (unlikely (e > 308))
134
0
  {
135
0
    v /= 1e308; e -= 308;
136
0
    if (unlikely (e > 308)) { v /= 1e308; e -= 308; }
137
0
  }
138
0
  v /= _pow10 (e);
139
0
      }
140
0
      else
141
0
  v *= _pow10 (e);
142
0
    }
143
0
    return v;
144
0
  }
145
146
private:
147
  static constexpr uint64_t MAX_FRACT = 0xFFFFFFFFFFFFFull; /* 2^52-1 */
148
  struct /* fraction */
149
  {
150
    void add (int digit)
151
0
    {
152
0
      if (likely (fraction <= MAX_FRACT / 10))
153
0
      {
154
0
  fraction = fraction * 10. + digit;
155
0
  ++count;
156
0
      }
157
0
      else if (drop < 0) drop = digit;
158
0
      else if (digit != 0) sticky = true;
159
0
    }
160
161
    double get_value () const
162
0
    {
163
0
      double result = fraction;
164
      /* Round-to-nearest-even using the recorded tail */
165
0
      if (drop > 5 || (drop == 5 && ((((uint64_t) fraction) & 1) || sticky)))
166
0
  result += 1;
167
0
      return result / _pow10 (count);
168
0
    }
169
170
0
    bool is_empty () const { return !count; }
171
172
  private:
173
    unsigned count = 0;
174
    double fraction = 0;
175
    int drop = -1; /* drop is the first digit we couldn't accumulate */
176
    bool sticky = false; /* sticky is set when any later digit was nonzero. */
177
  } fraction;
178
179
  enum /* State */
180
  {
181
    Start, Sign, IntDigits,
182
    AfterDotFromInt, AfterDotFromStart, FracDigits,
183
    ExpStart, ExpSign, ExpDigits,
184
  } state = Start, pre_exp_state = Start;
185
186
  double value = 0;
187
  unsigned exp = 0;
188
  bool neg = false, exp_neg = false, exp_overflow = false, exp_speculation_active = false;
189
  size_t pos = 0, pre_exp_pos = 0;
190
191
  void enter_exponent ()
192
0
  {
193
0
    pre_exp_state = state;
194
0
    pre_exp_pos = pos;
195
0
    exp_speculation_active = true;
196
0
    state = ExpStart;
197
0
    pos++;
198
0
  }
199
200
  void rollback_exponent ()
201
0
  {
202
0
    if (unlikely (!exp_speculation_active)) return;
203
0
    state = pre_exp_state;
204
0
    pos = pre_exp_pos;
205
0
    exp = 0;
206
0
    exp_neg = false;
207
0
    exp_overflow = false;
208
0
    exp_speculation_active = false;
209
0
  }
210
211
  /* Handles 0..308; anything larger overflows double and returns HUGE_VAL. */
212
  static double _pow10 (unsigned exponent)
213
0
  {
214
    /* DBL_MAX is between 10^308 and 10^309 */
215
0
    if (unlikely (exponent > 308)) return HUGE_VAL;
216
0
    unsigned mask = 1 << (ARRAY_LENGTH (_powers_of_10) - 1);
217
0
    double result = 1;
218
0
    for (const double *power = _powers_of_10; mask; ++power, mask >>= 1)
219
0
      if (exponent & mask) result *= *power;
220
0
    return result;
221
0
  }
222
};
223
224
#endif /* HB_NUMBER_PARSER_HH */