typia 15.0.0 → 15.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/lib/http.d.ts +49 -24
  2. package/lib/http.js.map +1 -1
  3. package/lib/http.mjs.map +1 -1
  4. package/lib/internal/_httpFormDataReadBigint.js +7 -5
  5. package/lib/internal/_httpFormDataReadBigint.js.map +1 -1
  6. package/lib/internal/_httpFormDataReadBigint.mjs +1 -1
  7. package/lib/internal/_httpFormDataReadBigint.mjs.map +1 -1
  8. package/lib/internal/_httpFormDataReadBoolean.js +12 -10
  9. package/lib/internal/_httpFormDataReadBoolean.js.map +1 -1
  10. package/lib/internal/_httpFormDataReadBoolean.mjs +1 -1
  11. package/lib/internal/_httpFormDataReadBoolean.mjs.map +1 -1
  12. package/lib/internal/_httpFormDataReadNumber.js +7 -5
  13. package/lib/internal/_httpFormDataReadNumber.js.map +1 -1
  14. package/lib/internal/_httpFormDataReadNumber.mjs +1 -1
  15. package/lib/internal/_httpFormDataReadNumber.mjs.map +1 -1
  16. package/lib/internal/_httpFormDataReadString.d.ts +9 -1
  17. package/lib/internal/_httpFormDataReadString.js +10 -2
  18. package/lib/internal/_httpFormDataReadString.js.map +1 -1
  19. package/lib/internal/_httpFormDataReadString.mjs +9 -1
  20. package/lib/internal/_httpFormDataReadString.mjs.map +1 -1
  21. package/lib/internal/_httpHeaderReadBigint.js +5 -1
  22. package/lib/internal/_httpHeaderReadBigint.js.map +1 -1
  23. package/lib/internal/_httpHeaderReadBigint.mjs +2 -1
  24. package/lib/internal/_httpHeaderReadBigint.mjs.map +1 -1
  25. package/lib/internal/_httpHeaderReadNumber.js +5 -1
  26. package/lib/internal/_httpHeaderReadNumber.js.map +1 -1
  27. package/lib/internal/_httpHeaderReadNumber.mjs +2 -1
  28. package/lib/internal/_httpHeaderReadNumber.mjs.map +1 -1
  29. package/lib/internal/_httpParameterReadBigint.js +4 -0
  30. package/lib/internal/_httpParameterReadBigint.js.map +1 -1
  31. package/lib/internal/_httpParameterReadBigint.mjs +1 -0
  32. package/lib/internal/_httpParameterReadBigint.mjs.map +1 -1
  33. package/lib/internal/_httpParameterReadNumber.js +4 -0
  34. package/lib/internal/_httpParameterReadNumber.js.map +1 -1
  35. package/lib/internal/_httpParameterReadNumber.mjs +1 -0
  36. package/lib/internal/_httpParameterReadNumber.mjs.map +1 -1
  37. package/lib/internal/_httpParameterReadString.d.ts +9 -1
  38. package/lib/internal/_httpParameterReadString.js +9 -1
  39. package/lib/internal/_httpParameterReadString.js.map +1 -1
  40. package/lib/internal/_httpParameterReadString.mjs +9 -1
  41. package/lib/internal/_httpParameterReadString.mjs.map +1 -1
  42. package/lib/internal/_httpQueryReadBigint.js +8 -1
  43. package/lib/internal/_httpQueryReadBigint.js.map +1 -1
  44. package/lib/internal/_httpQueryReadBigint.mjs +1 -1
  45. package/lib/internal/_httpQueryReadBigint.mjs.map +1 -1
  46. package/lib/internal/_httpQueryReadBoolean.js +4 -1
  47. package/lib/internal/_httpQueryReadBoolean.js.map +1 -1
  48. package/lib/internal/_httpQueryReadBoolean.mjs +1 -1
  49. package/lib/internal/_httpQueryReadBoolean.mjs.map +1 -1
  50. package/lib/internal/_httpQueryReadNumber.js +8 -1
  51. package/lib/internal/_httpQueryReadNumber.js.map +1 -1
  52. package/lib/internal/_httpQueryReadNumber.mjs +1 -1
  53. package/lib/internal/_httpQueryReadNumber.mjs.map +1 -1
  54. package/lib/internal/_httpQueryReadString.d.ts +9 -1
  55. package/lib/internal/_httpQueryReadString.js +9 -1
  56. package/lib/internal/_httpQueryReadString.js.map +1 -1
  57. package/lib/internal/_httpQueryReadString.mjs +9 -1
  58. package/lib/internal/_httpQueryReadString.mjs.map +1 -1
  59. package/lib/internal/_randomBigint.d.ts +12 -1
  60. package/lib/internal/_randomBigint.js +90 -2
  61. package/lib/internal/_randomBigint.js.map +1 -1
  62. package/lib/internal/_randomBigint.mjs +70 -2
  63. package/lib/internal/_randomBigint.mjs.map +1 -1
  64. package/lib/internal/_randomInteger.js +21 -12
  65. package/lib/internal/_randomInteger.js.map +1 -1
  66. package/lib/internal/_randomInteger.mjs +21 -16
  67. package/lib/internal/_randomInteger.mjs.map +1 -1
  68. package/lib/internal/_randomNumber.js +19 -10
  69. package/lib/internal/_randomNumber.js.map +1 -1
  70. package/lib/internal/_randomNumber.mjs +19 -14
  71. package/lib/internal/_randomNumber.mjs.map +1 -1
  72. package/native/cmd/ttsc-typia/llm_evaluation_rejects_invalid_probability_test.go +9 -0
  73. package/native/cmd/ttsc-typia/random_recursive_min_items_diagnostic_test.go +18 -1
  74. package/native/core/factories/ExpressionFactory.go +16 -1
  75. package/native/core/factories/MetadataCommentTagFactory.go +177 -29
  76. package/native/core/factories/expression_factory_number_spells_non_finite_values_test.go +69 -0
  77. package/native/core/factories/metadata_comment_tag_factory_holds_bigint_tags_exact_test.go +168 -0
  78. package/native/core/factories/metadata_comment_tag_factory_reads_javascript_numbers_test.go +158 -0
  79. package/native/core/programmers/functional/FunctionalAssertParametersProgrammer.go +1 -3
  80. package/native/core/programmers/functional/FunctionalIsParametersProgrammer.go +107 -9
  81. package/native/core/programmers/functional/FunctionalValidateParametersProgrammer.go +1 -3
  82. package/native/core/programmers/http/HttpFormDataProgrammer.go +41 -2
  83. package/native/core/programmers/http/HttpHeadersProgrammer.go +56 -4
  84. package/native/core/programmers/http/HttpParameterProgrammer.go +1 -3
  85. package/native/core/programmers/http/HttpQueryProgrammer.go +26 -43
  86. package/native/core/programmers/iterate/json_schema_jsDocTags.go +10 -3
  87. package/native/core/programmers/iterate/json_schema_jsdoc_tags_cast_reads_javascript_numbers_test.go +64 -0
  88. package/native/core/programmers/llm/LlmEvaluationProgrammer.go +6 -2
  89. package/native/core/programmers/llm/LlmSchemaProgrammer.go +34 -4
  90. package/native/core/utils/NumberUtil.go +195 -0
  91. package/native/core/utils/number_util_rational_keeps_written_value_test.go +62 -0
  92. package/native/core/utils/number_util_reads_javascript_number_grammar_test.go +147 -0
  93. package/package.json +3 -3
  94. package/src/http.ts +61 -34
  95. package/src/internal/_httpFormDataReadBigint.ts +3 -1
  96. package/src/internal/_httpFormDataReadBoolean.ts +3 -1
  97. package/src/internal/_httpFormDataReadNumber.ts +3 -1
  98. package/src/internal/_httpFormDataReadString.ts +10 -1
  99. package/src/internal/_httpHeaderReadBigint.ts +6 -1
  100. package/src/internal/_httpHeaderReadNumber.ts +6 -1
  101. package/src/internal/_httpParameterReadBigint.ts +3 -0
  102. package/src/internal/_httpParameterReadNumber.ts +3 -0
  103. package/src/internal/_httpParameterReadString.ts +12 -2
  104. package/src/internal/_httpQueryReadBigint.ts +7 -1
  105. package/src/internal/_httpQueryReadBoolean.ts +3 -1
  106. package/src/internal/_httpQueryReadNumber.ts +7 -1
  107. package/src/internal/_httpQueryReadString.ts +10 -1
  108. package/src/internal/_randomBigint.ts +111 -3
  109. package/src/internal/_randomInteger.ts +31 -16
  110. package/src/internal/_randomNumber.ts +23 -12
@@ -30,6 +30,14 @@ type HttpQueryProgrammer_DecomposeProps struct {
30
30
  AllowOptional bool
31
31
  Type *shimchecker.Type
32
32
  Name *string
33
+
34
+ // Missing makes the decoder throw `Error("missing <key>")` for an absent
35
+ // required non-array property. Only `http.query`, which validates nothing
36
+ // else, asks for it. The `is`, `assert`, and `validate` variants decode
37
+ // through this same function and must report the absence through their own
38
+ // contract instead (`null`, an `IValidation` failure, `TypeGuardError`), so
39
+ // they leave it off (samchon/typia#2444).
40
+ Missing bool
33
41
  }
34
42
 
35
43
  var httpQueryProgrammer_factory = shimast.NewNodeFactory(shimast.NodeFactoryHooks{})
@@ -78,9 +86,11 @@ func (httpQueryProgrammerNamespace) Decompose(props HttpQueryProgrammer_Decompos
78
86
  statements := httpQueryProgrammer_decode_object(struct {
79
87
  Context nativecontext.ITypiaContext
80
88
  Object *schemametadata.MetadataObjectType
89
+ Missing bool
81
90
  }{
82
91
  Context: props.Context,
83
92
  Object: object,
93
+ Missing: props.Missing,
84
94
  })
85
95
  return nativeinternal.FeatureProgrammer_IDecomposed{
86
96
  Functions: map[string]*shimast.Node{},
@@ -124,6 +134,7 @@ func (httpQueryProgrammerNamespace) Write(props HttpQueryProgrammer_IProps) *shi
124
134
  AllowOptional: props.AllowOptional,
125
135
  Type: props.Type,
126
136
  Name: props.Name,
137
+ Missing: true,
127
138
  })
128
139
  return nativeinternal.FeatureProgrammer.WriteDecomposed(nativeinternal.FeatureProgrammer_WriteDecomposedProps{
129
140
  Modulo: props.Modulo,
@@ -203,6 +214,7 @@ func (httpQueryProgrammerNamespace) Validate(props struct {
203
214
  func httpQueryProgrammer_decode_object(props struct {
204
215
  Context nativecontext.ITypiaContext
205
216
  Object *schemametadata.MetadataObjectType
217
+ Missing bool
206
218
  }) []*shimast.Node {
207
219
  f := nativecontext.EmitFactoryOf(httpQueryProgrammer_factory, props.Context.Emit)
208
220
  input := f.NewIdentifier("input")
@@ -212,9 +224,11 @@ func httpQueryProgrammer_decode_object(props struct {
212
224
  properties = append(properties, httpQueryProgrammer_decode_regular_property(struct {
213
225
  Context nativecontext.ITypiaContext
214
226
  Property *schemametadata.MetadataProperty
227
+ Missing bool
215
228
  }{
216
229
  Context: props.Context,
217
230
  Property: p,
231
+ Missing: props.Missing,
218
232
  }))
219
233
  }
220
234
  return []*shimast.Node{
@@ -253,6 +267,7 @@ func httpQueryProgrammer_decode_object(props struct {
253
267
  func httpQueryProgrammer_decode_regular_property(props struct {
254
268
  Context nativecontext.ITypiaContext
255
269
  Property *schemametadata.MetadataProperty
270
+ Missing bool
256
271
  }) *shimast.Node {
257
272
  f := nativecontext.EmitFactoryOf(httpQueryProgrammer_factory, props.Context.Emit)
258
273
  key := httpProgrammer_property_key(props.Property)
@@ -277,9 +292,9 @@ func httpQueryProgrammer_decode_regular_property(props struct {
277
292
  }),
278
293
  shimast.NodeFlagsNone,
279
294
  )
280
- if value.IsRequired() {
281
- input = httpQueryProgrammer_assert_required_array(key, input, props.Context.Emit)
282
- }
295
+ // An absent key reads as an empty list. A query string has no other
296
+ // spelling for `[]`, so a required array decodes it as `[]` rather than as
297
+ // a missing property, and a nullable one as `null` (samchon/typia#2444).
283
298
  input = f.NewCallExpression(
284
299
  nativefactories.IdentifierFactory.Access(
285
300
  props.Context.Emit,
@@ -301,11 +316,13 @@ func httpQueryProgrammer_decode_regular_property(props struct {
301
316
  httpQueryProgrammer_decode_value(struct {
302
317
  Context nativecontext.ITypiaContext
303
318
  Type string
319
+ Nullable bool
304
320
  Coalesce bool
305
321
  Input *shimast.Node
306
322
  }{
307
323
  Context: props.Context,
308
324
  Type: typ,
325
+ Nullable: httpProgrammer_decode_nullable(value),
309
326
  Coalesce: false,
310
327
  Input: f.NewIdentifier("elem"),
311
328
  }),
@@ -323,18 +340,20 @@ func httpQueryProgrammer_decode_regular_property(props struct {
323
340
  Input: input,
324
341
  })
325
342
  } else {
326
- if value.IsRequired() {
343
+ if value.IsRequired() && props.Missing {
327
344
  input = httpQueryProgrammer_assert_required_value(key, input, props.Context.Emit)
328
345
  }
329
346
  input = httpQueryProgrammer_decode_value(struct {
330
347
  Context nativecontext.ITypiaContext
331
348
  Type string
349
+ Nullable bool
332
350
  Coalesce bool
333
351
  Input *shimast.Node
334
352
  }{
335
353
  Context: props.Context,
336
354
  Type: typ,
337
- Coalesce: value.Nullable == false && value.IsRequired() == false,
355
+ Nullable: httpProgrammer_decode_nullable(value),
356
+ Coalesce: value.Nullable == false && value.Any == false && value.IsRequired() == false,
338
357
  Input: input,
339
358
  })
340
359
  }
@@ -350,6 +369,7 @@ func httpQueryProgrammer_decode_regular_property(props struct {
350
369
  func httpQueryProgrammer_decode_value(props struct {
351
370
  Context nativecontext.ITypiaContext
352
371
  Type string
372
+ Nullable bool
353
373
  Coalesce bool
354
374
  Input *shimast.Node
355
375
  }) *shimast.Node {
@@ -358,7 +378,7 @@ func httpQueryProgrammer_decode_value(props struct {
358
378
  httpParameterProgrammer_internal(props.Context, "httpQueryRead"+httpParameterProgrammer_capitalize(props.Type)),
359
379
  nil,
360
380
  nil,
361
- f.NewNodeList([]*shimast.Node{props.Input}),
381
+ f.NewNodeList(httpProgrammer_read_arguments(props.Type, props.Nullable, props.Input, props.Context.Emit)),
362
382
  shimast.NodeFlagsNone,
363
383
  )
364
384
  if props.Coalesce == false {
@@ -410,43 +430,6 @@ func httpQueryProgrammer_assert_required_value(key string, input *shimast.Node,
410
430
  )
411
431
  }
412
432
 
413
- func httpQueryProgrammer_assert_required_array(key string, input *shimast.Node, emit ...*shimprinter.EmitContext) *shimast.Node {
414
- f := nativecontext.EmitFactoryOf(httpQueryProgrammer_factory, emit...)
415
- value := f.NewIdentifier("value")
416
- return f.NewCallExpression(
417
- f.NewArrowFunction(
418
- nil,
419
- nil,
420
- f.NewNodeList(nil),
421
- nil,
422
- nil,
423
- f.NewToken(shimast.KindEqualsGreaterThanToken),
424
- f.NewBlock(f.NewNodeList([]*shimast.Node{
425
- nativefactories.StatementFactory.Constant(nativefactories.StatementFactory_ConstantProps{
426
- Name: "value",
427
- Value: input,
428
- }, emit...),
429
- f.NewIfStatement(
430
- f.NewBinaryExpression(
431
- nil,
432
- nativefactories.ExpressionFactory.Number(0, emit...),
433
- nil,
434
- f.NewToken(shimast.KindEqualsEqualsEqualsToken),
435
- nativefactories.IdentifierFactory.Access(nil, value, "length"),
436
- ),
437
- f.NewThrowStatement(httpQueryProgrammer_missing_error(key, emit...)),
438
- nil,
439
- ),
440
- f.NewReturnStatement(value),
441
- }), true),
442
- ),
443
- nil,
444
- nil,
445
- nil,
446
- shimast.NodeFlagsNone,
447
- )
448
- }
449
-
450
433
  func httpQueryProgrammer_missing_error(key string, emit ...*shimprinter.EmitContext) *shimast.Node {
451
434
  f := nativecontext.EmitFactoryOf(httpQueryProgrammer_factory, emit...)
452
435
  return f.NewNewExpression(
@@ -1,10 +1,10 @@
1
1
  package iterate
2
2
 
3
3
  import (
4
- "strconv"
5
4
  "strings"
6
5
 
7
6
  nativemetadata "github.com/samchon/typia/packages/typia/native/core/schemas/metadata"
7
+ nativeutils "github.com/samchon/typia/packages/typia/native/core/utils"
8
8
  )
9
9
 
10
10
  func json_schema_jsDocTags(schema JsonSchema, jsDocTags []nativemetadata.IJsDocTagInfo) JsonSchema {
@@ -31,6 +31,13 @@ func json_schema_jsDocTags_value(tag nativemetadata.IJsDocTagInfo) (string, bool
31
31
  return "", false
32
32
  }
33
33
 
34
+ // json_schema_jsDocTags_cast reads an `@x-*` tag's text as a JSON value.
35
+ //
36
+ // Numbers use JavaScript's `Number()` grammar, the grammar of the v12 cast this
37
+ // ports (samchon/typia#2442), so `0x10` is 16 while Go-only spellings such as
38
+ // `0x1p4`, `1_000`, and `inf` stay text. Only finite numbers become numbers: the
39
+ // schema is JSON, which has no NaN or infinity, so `NaN`, `Infinity`, and an
40
+ // overflowing `1e400` keep their text instead of degrading to `null`.
34
41
  func json_schema_jsDocTags_cast(value string) any {
35
42
  if value == "true" {
36
43
  return true
@@ -38,8 +45,8 @@ func json_schema_jsDocTags_cast(value string) any {
38
45
  if value == "false" {
39
46
  return false
40
47
  }
41
- if num, err := strconv.ParseFloat(value, 64); err == nil {
42
- return num
48
+ if reading := nativeutils.NumberUtil.Read(value); reading.Numeric && reading.Finite {
49
+ return reading.Value
43
50
  }
44
51
  if value == "null" {
45
52
  return nil
@@ -0,0 +1,64 @@
1
+ package iterate
2
+
3
+ import (
4
+ "encoding/json"
5
+ "math"
6
+ "reflect"
7
+ "testing"
8
+ )
9
+
10
+ // TestJsonSchemaJsDocTagsCastReadsJavaScriptNumbers verifies the `@x-*`
11
+ // extension cast.
12
+ //
13
+ // The Go port swapped the v12 cast's JavaScript `Number()` grammar for Go's
14
+ // float syntax (samchon/typia#2442). `NaN` and every `Inf` spelling then became
15
+ // floats that `encoding/json` refuses and JavaScript prints as `null`, while
16
+ // `0x10` stopped being a number and `0x1p4` / `1_000` started being one. The
17
+ // expectations restore v12's numbers where they are finite and keep the text
18
+ // where JSON has no number: a non-finite spelling stays a string instead of
19
+ // degrading to `null`, and empty text stays `""` instead of v12's `0`.
20
+ //
21
+ // 1. Cast every row of the issue's table.
22
+ // 2. Assert no result is NaN or an infinity, and every result marshals.
23
+ func TestJsonSchemaJsDocTagsCastReadsJavaScriptNumbers(t *testing.T) {
24
+ for _, item := range []struct {
25
+ text string
26
+ expected any
27
+ }{
28
+ {"NaN", "NaN"},
29
+ {"nan", "nan"},
30
+ {"Infinity", "Infinity"},
31
+ {"-Infinity", "-Infinity"},
32
+ {"infinity", "infinity"},
33
+ {"inf", "inf"},
34
+ {"Inf", "Inf"},
35
+ {"1e400", "1e400"},
36
+ {"0x10", float64(16)},
37
+ {"0b101", float64(5)},
38
+ {"0o7", float64(7)},
39
+ {"0x1p4", "0x1p4"},
40
+ {"1_000", "1_000"},
41
+ {"", ""},
42
+ {"-0", float64(0)},
43
+ {"+5", float64(5)},
44
+ {".5", 0.5},
45
+ {"1e3", float64(1000)},
46
+ {"true", true},
47
+ {"false", false},
48
+ {"null", nil},
49
+ {"text", "text"},
50
+ } {
51
+ actual := json_schema_jsDocTags_cast(item.text)
52
+ if reflect.DeepEqual(actual, item.expected) == false {
53
+ t.Fatalf("cast(%q) = %#v, expected %#v", item.text, actual, item.expected)
54
+ }
55
+ if number, ok := actual.(float64); ok {
56
+ if math.IsNaN(number) || math.IsInf(number, 0) || math.Signbit(number) && number == 0 {
57
+ t.Fatalf("cast(%q) = %v is not a JSON number", item.text, number)
58
+ }
59
+ }
60
+ if _, err := json.Marshal(actual); err != nil {
61
+ t.Fatalf("cast(%q) does not marshal: %v", item.text, err)
62
+ }
63
+ }
64
+ }
@@ -12,6 +12,7 @@ import (
12
12
  nativecontext "github.com/samchon/typia/packages/typia/native/core/context"
13
13
  nativefactories "github.com/samchon/typia/packages/typia/native/core/factories"
14
14
  schemametadata "github.com/samchon/typia/packages/typia/native/core/schemas/metadata"
15
+ nativeutils "github.com/samchon/typia/packages/typia/native/core/utils"
15
16
  )
16
17
 
17
18
  type llmEvaluationProgrammerNamespace struct{}
@@ -449,10 +450,13 @@ func llmEvaluation_parse_jsdoc_probability(tags []schemametadata.IJsDocTagInfo)
449
450
  if len(texts) != 1 {
450
451
  return 0, false, "LLM evaluation @probability is declared more than once; keep only one."
451
452
  }
452
- value, err := strconv.ParseFloat(texts[0], 64)
453
- if err != nil || math.IsNaN(value) || math.IsInf(value, 0) {
453
+ // The same JavaScript `Number()` grammar every other numeric JSDoc tag reads
454
+ // (samchon/typia#2442); Go's float syntax would also admit `0x1p-1`.
455
+ reading := nativeutils.NumberUtil.Read(texts[0])
456
+ if reading.Numeric == false || reading.Finite == false {
454
457
  return 0, false, fmt.Sprintf("LLM evaluation @probability must be a number in [0, 1], but got %q.", texts[0])
455
458
  }
459
+ value := reading.Value
456
460
  if value < 0 || value > 1 {
457
461
  return 0, false, fmt.Sprintf("LLM evaluation @probability must be in [0, 1], but got %s.", texts[0])
458
462
  }
@@ -15,6 +15,7 @@ import (
15
15
  nativeiterate "github.com/samchon/typia/packages/typia/native/core/programmers/iterate"
16
16
  nativejson "github.com/samchon/typia/packages/typia/native/core/programmers/json"
17
17
  schemametadata "github.com/samchon/typia/packages/typia/native/core/schemas/metadata"
18
+ nativeutils "github.com/samchon/typia/packages/typia/native/core/utils"
18
19
  )
19
20
 
20
21
  type llmSchemaProgrammerNamespace struct{}
@@ -709,11 +710,11 @@ func llmSchemaProgrammer_shift_array(schema map[string]any) map[string]any {
709
710
  output := llmSchemaProgrammer_clone(schema)
710
711
  tags := []string{}
711
712
  if value, ok := output["minItems"]; ok {
712
- tags = append(tags, "@minItems "+fmt.Sprint(value))
713
+ tags = append(tags, "@minItems "+llmSchemaProgrammer_tag_text(value))
713
714
  delete(output, "minItems")
714
715
  }
715
716
  if value, ok := output["maxItems"]; ok {
716
- tags = append(tags, "@maxItems "+fmt.Sprint(value))
717
+ tags = append(tags, "@maxItems "+llmSchemaProgrammer_tag_text(value))
717
718
  delete(output, "maxItems")
718
719
  }
719
720
  if value, ok := output["uniqueItems"].(bool); ok {
@@ -732,7 +733,7 @@ func llmSchemaProgrammer_shift_numeric(schema map[string]any) map[string]any {
732
733
  tags := []string{}
733
734
  for _, key := range []string{"minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf", "default"} {
734
735
  if value, ok := output[key]; ok {
735
- tags = append(tags, "@"+key+" "+fmt.Sprint(value))
736
+ tags = append(tags, "@"+key+" "+llmSchemaProgrammer_tag_text(value))
736
737
  delete(output, key)
737
738
  }
738
739
  }
@@ -745,7 +746,7 @@ func llmSchemaProgrammer_shift_string(schema map[string]any) map[string]any {
745
746
  tags := []string{}
746
747
  for _, key := range []string{"minLength", "maxLength", "format", "pattern", "contentMediaType", "default"} {
747
748
  if value, ok := output[key]; ok {
748
- tags = append(tags, "@"+key+" "+fmt.Sprint(value))
749
+ tags = append(tags, "@"+key+" "+llmSchemaProgrammer_tag_text(value))
749
750
  delete(output, key)
750
751
  }
751
752
  }
@@ -753,6 +754,35 @@ func llmSchemaProgrammer_shift_string(schema map[string]any) map[string]any {
753
754
  return output
754
755
  }
755
756
 
757
+ // llmSchemaProgrammer_tag_text writes a shifted constraint value as `@typia/utils`'
758
+ // OpenApiConstraintShifter does, through JavaScript's template interpolation.
759
+ //
760
+ // A numeric literal from a type tag arrives as the compiler's `jsnum.Number`,
761
+ // whose `String()` already follows JavaScript, but a comment tag's value is a
762
+ // plain float64, and `fmt.Sprint` spells it the Go way: `1e+06` for 1000000,
763
+ // `1e-07` for 1e-7, `+Inf` for an infinity (samchon/typia#2452). Every float is
764
+ // therefore spelled by `NumberUtil.String`, whatever its defined type, and a
765
+ // pointer is followed to the value it holds.
766
+ func llmSchemaProgrammer_tag_text(value any) string {
767
+ reflected := reflect.ValueOf(value)
768
+ for reflected.IsValid() && reflected.Kind() == reflect.Pointer && reflected.IsNil() == false {
769
+ reflected = reflected.Elem()
770
+ }
771
+ if reflected.IsValid() == false {
772
+ return fmt.Sprint(value)
773
+ }
774
+ switch reflected.Kind() {
775
+ case reflect.Float32, reflect.Float64:
776
+ return nativeutils.NumberUtil.String(reflected.Float())
777
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
778
+ // JavaScript holds the count as a double, rounding past 2^53.
779
+ return nativeutils.NumberUtil.String(float64(reflected.Int()))
780
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
781
+ return nativeutils.NumberUtil.String(float64(reflected.Uint()))
782
+ }
783
+ return fmt.Sprint(reflected.Interface())
784
+ }
785
+
756
786
  func llmSchemaProgrammer_write_tag_with_description(schema map[string]any, tags []string) {
757
787
  if len(tags) == 0 {
758
788
  return
@@ -0,0 +1,195 @@
1
+ package utils
2
+
3
+ import (
4
+ "math"
5
+ "math/big"
6
+ "regexp"
7
+ "strconv"
8
+ "strings"
9
+ "unicode"
10
+ )
11
+
12
+ // NumberUtil reads numeric text the way JavaScript's `Number()` does.
13
+ //
14
+ // Tag values and JSDoc extensions are written in TypeScript source, and typia
15
+ // splices numbers it reads from them into the JavaScript it emits, so their
16
+ // grammar is JavaScript's StringNumericLiteral, not Go's float syntax. The two
17
+ // differ in both directions: `strconv.ParseFloat` accepts `NaN`, `Inf`, and
18
+ // `infinity` in any case, hexadecimal floats (`0x1p4`), and digit separators
19
+ // (`1_000`), all of which `Number()` reads as NaN, while it rejects the
20
+ // `0x`/`0o`/`0b` integers that `Number()` accepts. A spelling Go accepts but
21
+ // JavaScript does not also becomes invalid emitted code once it is spliced into
22
+ // a validator (samchon/typia#2442).
23
+ type numberUtilNamespace struct{}
24
+
25
+ var NumberUtil = numberUtilNamespace{}
26
+
27
+ // NumberUtil_Reading is the outcome of reading one numeric text.
28
+ type NumberUtil_Reading struct {
29
+ // Value is what `Number(text)` evaluates to, with negative zero read as zero.
30
+ Value float64
31
+
32
+ // Numeric is false when `Number(text)` is NaN, or when the text is blank.
33
+ Numeric bool
34
+
35
+ // Finite is false for `Infinity`, `-Infinity`, and values that overflow to
36
+ // them, such as `1e400`. JSON has no spelling for either.
37
+ Finite bool
38
+ }
39
+
40
+ // Read evaluates text with the grammar of JavaScript's `Number()`.
41
+ //
42
+ // Surrounding JavaScript whitespace is ignored, as `Number()` ignores it. One
43
+ // deliberate difference remains: `Number("")` and `Number(" ")` are 0, but
44
+ // blank text names no number, so it reads as non-numeric here.
45
+ func (numberUtilNamespace) Read(text string) NumberUtil_Reading {
46
+ text = strings.TrimFunc(text, numberUtil_isWhiteSpace)
47
+ if text == "" {
48
+ return NumberUtil_Reading{}
49
+ }
50
+ if match := numberUtil_NON_DECIMAL.FindStringSubmatch(text); match != nil {
51
+ base := 16
52
+ switch match[1] {
53
+ case "o", "O":
54
+ base = 8
55
+ case "b", "B":
56
+ base = 2
57
+ }
58
+ integer, ok := new(big.Int).SetString(match[2], base)
59
+ if ok == false {
60
+ return NumberUtil_Reading{}
61
+ }
62
+ // big.Float rounds to nearest, ties to even, as the specification's
63
+ // mathematical-value conversion does, and reports overflow as an infinity.
64
+ value, _ := new(big.Float).SetInt(integer).Float64()
65
+ return numberUtil_reading(value)
66
+ }
67
+ if numberUtil_DECIMAL.MatchString(text) == false {
68
+ return NumberUtil_Reading{}
69
+ }
70
+ if strings.HasSuffix(text, "Infinity") {
71
+ if strings.HasPrefix(text, "-") {
72
+ return numberUtil_reading(math.Inf(-1))
73
+ }
74
+ return numberUtil_reading(math.Inf(1))
75
+ }
76
+ // The pattern admits only plain decimal notation, which ParseFloat reads with
77
+ // IEEE round-to-nearest. An out-of-range magnitude comes back as an infinity
78
+ // with ErrRange, which is exactly what `Number()` yields; underflow comes back
79
+ // as zero without an error, as it does in JavaScript.
80
+ value, err := strconv.ParseFloat(text, 64)
81
+ if err != nil && math.IsInf(value, 0) == false {
82
+ return NumberUtil_Reading{}
83
+ }
84
+ return numberUtil_reading(value)
85
+ }
86
+
87
+ // Integer reads text that spells an integer exactly: decimal digits with an
88
+ // optional sign, or an unsigned `0x` / `0o` / `0b` literal.
89
+ //
90
+ // `Number()` rounds such text to the nearest double, so `9007199254740993`
91
+ // reads as 9007199254740992. Splicing the digits as written keeps a check that
92
+ // reads them as a BigInt literal (`$input % 9007199254740993n`) exact. Any
93
+ // other spelling, including an integer written with an exponent or a point
94
+ // (`1e3`, `1.0`), reports false; its value is the double `Read` returns.
95
+ func (numberUtilNamespace) Integer(text string) (*big.Int, bool) {
96
+ text = strings.TrimFunc(text, numberUtil_isWhiteSpace)
97
+ if match := numberUtil_NON_DECIMAL.FindStringSubmatch(text); match != nil {
98
+ base := 16
99
+ switch match[1] {
100
+ case "o", "O":
101
+ base = 8
102
+ case "b", "B":
103
+ base = 2
104
+ }
105
+ return new(big.Int).SetString(match[2], base)
106
+ }
107
+ if numberUtil_INTEGER.MatchString(text) == false {
108
+ return nil, false
109
+ }
110
+ return new(big.Int).SetString(strings.TrimPrefix(text, "+"), 10)
111
+ }
112
+
113
+ // Rational is the exact value a finite numeric text writes.
114
+ //
115
+ // `Read` evaluates the text as `Number()` does, rounding it to the nearest
116
+ // double; this keeps what the text says, so a caller can tell whether that
117
+ // double is the written value itself. `9007199254740993` and
118
+ // `9007199254740993.0` both read as 9007199254740992, but their rational value
119
+ // is 9007199254740993. Text `Read` does not find numeric and finite reports
120
+ // false, and so does text `big.Rat` refuses to expand: an exponent outside the
121
+ // int64 range, or a non-zero mantissa whose exponent, net of its fraction
122
+ // digits, lies beyond a million in magnitude (`1e-1000001` reads as 0 but has
123
+ // no exact value here).
124
+ func (numberUtilNamespace) Rational(text string) (*big.Rat, bool) {
125
+ if reading := NumberUtil.Read(text); reading.Numeric == false || reading.Finite == false {
126
+ return nil, false
127
+ }
128
+ if integer, ok := NumberUtil.Integer(text); ok {
129
+ return new(big.Rat).SetInt(integer), true
130
+ }
131
+ // `Read` admitted only StrDecimalLiteral here, whose spellings `big.Rat`
132
+ // parses: `5.`, `.5`, `+5`, and `1E3` included.
133
+ return new(big.Rat).SetString(strings.TrimFunc(text, numberUtil_isWhiteSpace))
134
+ }
135
+
136
+ // String spells a value exactly as JavaScript's `String(value)` does.
137
+ //
138
+ // That is `Number.prototype.toString`: positional notation from 1e-6 up to
139
+ // 1e21, exponent notation outside with a signed exponent of no padded zeros
140
+ // (`1e+21`, `1e-7`), and `NaN`, `Infinity`, `-Infinity`, and `0` for negative
141
+ // zero. Each result is also JavaScript source text that evaluates to the value,
142
+ // negative zero reading back as zero.
143
+ func (numberUtilNamespace) String(value float64) string {
144
+ switch {
145
+ case math.IsNaN(value):
146
+ return "NaN"
147
+ case math.IsInf(value, 1):
148
+ return "Infinity"
149
+ case math.IsInf(value, -1):
150
+ return "-Infinity"
151
+ case value == 0:
152
+ return "0"
153
+ }
154
+ magnitude := math.Abs(value)
155
+ if magnitude >= 1e-6 && magnitude < 1e21 {
156
+ return strconv.FormatFloat(value, 'f', -1, 64)
157
+ }
158
+ // Go pads the exponent to two digits (`1e-07`); JavaScript does not.
159
+ text := strconv.FormatFloat(value, 'e', -1, 64)
160
+ index := strings.IndexByte(text, 'e')
161
+ mantissa, sign, digits := text[:index], text[index+1:index+2], strings.TrimLeft(text[index+2:], "0")
162
+ return mantissa + "e" + sign + digits
163
+ }
164
+
165
+ func numberUtil_reading(value float64) NumberUtil_Reading {
166
+ if value == 0 {
167
+ value = 0 // negative zero
168
+ }
169
+ return NumberUtil_Reading{
170
+ Value: value,
171
+ Numeric: true,
172
+ Finite: math.IsInf(value, 0) == false,
173
+ }
174
+ }
175
+
176
+ // numberUtil_isWhiteSpace is StrWhiteSpaceChar: WhiteSpace and LineTerminator.
177
+ func numberUtil_isWhiteSpace(r rune) bool {
178
+ switch r {
179
+ case '\t', '\v', '\f', ' ', 0x00a0, 0xfeff, '\n', '\r', 0x2028, 0x2029:
180
+ return true
181
+ }
182
+ return unicode.Is(unicode.Zs, r)
183
+ }
184
+
185
+ // numberUtil_NON_DECIMAL is NonDecimalIntegerLiteral without separators. It
186
+ // takes no sign: `Number("-0x10")` is NaN.
187
+ var numberUtil_NON_DECIMAL = regexp.MustCompile(`^0([xXoObB])([0-9a-fA-F]+)$`)
188
+
189
+ // numberUtil_INTEGER is a decimal integer with an optional sign.
190
+ var numberUtil_INTEGER = regexp.MustCompile(`^[+-]?[0-9]+$`)
191
+
192
+ // numberUtil_DECIMAL is StrDecimalLiteral: an optional sign before `Infinity`
193
+ // or a decimal literal whose digits may omit either side of the point, with an
194
+ // optional exponent. Separators are not part of it.
195
+ var numberUtil_DECIMAL = regexp.MustCompile(`^[+-]?(?:Infinity|(?:[0-9]+\.?[0-9]*|\.[0-9]+)(?:[eE][+-]?[0-9]+)?)$`)
@@ -0,0 +1,62 @@
1
+ package utils
2
+
3
+ import (
4
+ "math/big"
5
+ "testing"
6
+ )
7
+
8
+ // TestNumberUtilRationalKeepsWrittenValue verifies NumberUtil.Rational keeps the
9
+ // value a numeric text writes, not the double `Number()` rounds it to.
10
+ //
11
+ // A bigint comment tag must know whether a double is the written integer itself
12
+ // before it states a bound with it (samchon/typia#2457), so the exact value has
13
+ // to survive every spelling `Read` admits: points, exponents, signs, radix
14
+ // prefixes, and surrounding whitespace. Text `Read` refuses, or finds infinite,
15
+ // has no exact value.
16
+ //
17
+ // 1. Keep integers past 2^53, in every spelling.
18
+ // 2. Keep fractions and point-edge spellings exactly.
19
+ // 3. Refuse what `Read` refuses, and infinities.
20
+ func TestNumberUtilRationalKeepsWrittenValue(t *testing.T) {
21
+ exact := func(text string) *big.Rat {
22
+ value, ok := new(big.Rat).SetString(text)
23
+ if ok == false {
24
+ t.Fatalf("bad expectation %s", text)
25
+ }
26
+ return value
27
+ }
28
+ for _, item := range []struct {
29
+ text string
30
+ expected string
31
+ }{
32
+ // 1. integers past 2^53
33
+ {"9007199254740993", "9007199254740993"},
34
+ {"9007199254740993.0", "9007199254740993"},
35
+ {"9.007199254740993e15", "9007199254740993"},
36
+ {"+9007199254740993", "9007199254740993"},
37
+ {"-9007199254740993", "-9007199254740993"},
38
+ {"0x20000000000001", "9007199254740993"},
39
+ {"0o400000000000000001", "9007199254740993"},
40
+ {" 9007199254740993\t", "9007199254740993"},
41
+ {"\u00a09007199254740993.0 ", "9007199254740993"},
42
+ {"\t.5\u2028", "1/2"},
43
+ // 2. fractions and point edges
44
+ {".5", "1/2"},
45
+ {"5.", "5"},
46
+ {"-.5e1", "-5"},
47
+ {"1e-3", "1/1000"},
48
+ {"-0", "0"},
49
+ } {
50
+ value, ok := NumberUtil.Rational(item.text)
51
+ if ok == false || value.Cmp(exact(item.expected)) != 0 {
52
+ t.Fatalf("%q must be exactly %s, got %v (ok=%v)", item.text, item.expected, value, ok)
53
+ }
54
+ }
55
+
56
+ // 3. what Read refuses, and infinities
57
+ for _, text := range []string{"", " ", "1_000", "0x1p4", "inf", "NaN", "-0x10", "Infinity", "-Infinity", "1e400"} {
58
+ if value, ok := NumberUtil.Rational(text); ok {
59
+ t.Fatalf("%q must have no exact value, got %v", text, value)
60
+ }
61
+ }
62
+ }