All files / yaml / _type / float.ts

100.00% Branches 36/36
100.00% Lines 81/81
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
 
 
 
 
 
 
x18
 
x18
 
x18
 
 
x18
 
x18
 
x18
 
 
x491
x491
x491
 
 
x491
x491
x913
x913
 
x542
x491
 
x69
x69
x69
 
x69
x73
x73
 
x69
x71
x71
x69
x70
x70
x117
x69
 
x18
 
x36
 
x36
x36
 
x36
x36
x39
x39
x40
x39
x40
x39
x40
x39
x36
x54
x54
x55
x54
x55
x54
x55
x54
x51
x66
x66
x67
x66
x67
x66
x67
x66
x63
x73
x73
 
x44
 
 
 
 
x36
x36
 
x340
x340
x340
x340
x340
 
x18
x18
x18
x18
x18
x18
x18
x18
x18














































































































// Ported from js-yaml v3.13.1:
// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
// Copyright 2018-2025 the Deno authors. MIT license.

import type { StyleVariant, Type } from "../_type.ts";
import { isNegativeZero } from "../_utils.ts";

const YAML_FLOAT_PATTERN = new RegExp(
  // 2.5e4, 2.5 and integers
  "^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?" +
    // .2e4, .2
    // special case, seems not from spec
    "|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?" +
    // .inf
    "|[-+]?\\.(?:inf|Inf|INF)" +
    // .nan
    "|\\.(?:nan|NaN|NAN))$",
);

function resolveYamlFloat(data: string): boolean {
  if (
    !YAML_FLOAT_PATTERN.test(data) ||
    // Quick hack to not allow integers end with `_`
    // Probably should update regexp & check speed
    data[data.length - 1] === "_"
  ) {
    return false;
  }

  return true;
}

function constructYamlFloat(data: string): number {
  let value = data.replace(/_/g, "").toLowerCase();
  const sign = value[0] === "-" ? -1 : 1;

  if (value[0] && "+-".includes(value[0])) {
    value = value.slice(1);
  }

  if (value === ".inf") {
    return sign === 1 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
  }
  if (value === ".nan") {
    return NaN;
  }
  return sign * parseFloat(value);
}

const SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/;

function representYamlFloat(
  // deno-lint-ignore ban-types
  object: number | Number,
  style?: StyleVariant,
): string {
  const value = object instanceof Number ? object.valueOf() : object;
  if (isNaN(value)) {
    switch (style) {
      case "lowercase":
        return ".nan";
      case "uppercase":
        return ".NAN";
      case "camelcase":
        return ".NaN";
    }
  } else if (Number.POSITIVE_INFINITY === value) {
    switch (style) {
      case "lowercase":
        return ".inf";
      case "uppercase":
        return ".INF";
      case "camelcase":
        return ".Inf";
    }
  } else if (Number.NEGATIVE_INFINITY === value) {
    switch (style) {
      case "lowercase":
        return "-.inf";
      case "uppercase":
        return "-.INF";
      case "camelcase":
        return "-.Inf";
    }
  } else if (isNegativeZero(value)) {
    return "-0.0";
  }

  const res = value.toString(10);

  // JS stringifier can build scientific format without dots: 5e-100,
  // while YAML requires dot: 5.e-100. Fix it with simple hack

  return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace("e", ".e") : res;
}

function isFloat(object: unknown): object is number {
  if (object instanceof Number) object = object.valueOf();
  return typeof object === "number" &&
    (object % 1 !== 0 || isNegativeZero(object));
}

export const float: Type<"scalar", number> = {
  tag: "tag:yaml.org,2002:float",
  construct: constructYamlFloat,
  defaultStyle: "lowercase",
  kind: "scalar",
  predicate: isFloat,
  represent: representYamlFloat,
  resolve: resolveYamlFloat,
};