aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/044-specs/json_check.py
blob: b5d62f0bc9409ae7bc2dd1bb35194076395b611d (plain)
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
113
114
115
# Whether bytes are one JSON text (RFC 8259): the design of aup's examples/json.aup in plain Python, the reference
# 140 counts its lines against (read once from the start, a stack of the arrays and objects open).
def space(b):
    return b in (0x20, 0x09, 0x0a, 0x0d)


def digit(b):
    return 0x30 <= b <= 0x39


def hexdigit(b):
    return digit(b) or 0x61 <= b <= 0x66 or 0x41 <= b <= 0x46


def valid(d):
    try:
        t = d.decode('utf-8')
    except UnicodeDecodeError:
        return False
    n, i, mode, ok, open_ = len(d), 0, 0, True, []  # mode 0: a value, 1: after one, 2: a key, 3: a key or }, 4: a value or ]
    while ok:
        while i < n and space(d[i]):
            i += 1
        if i == n:
            return mode == 1 and not open_
        b = d[i]
        if mode == 1:
            if not open_:
                ok = False
            elif b == 0x2c:
                mode = 0 if open_[-1] == 0x5b else 2
            elif b == open_[-1] + 2:
                open_.pop()
            else:
                ok = False
            i += 1
        elif mode == 3 and b == 0x7d or mode == 4 and b == 0x5d:
            open_.pop()
            mode = 1
            i += 1
        elif mode in (2, 3):
            ok = b == 0x22
            mode = 5
        elif b in (0x5b, 0x7b):
            open_.append(b)
            mode = 4 if b == 0x5b else 3
            i += 1
        elif b == 0x22 or mode == 5:
            i += 1
            while i < n and d[i] != 0x22 and ok:
                if d[i] < 0x20:
                    ok = False
                elif d[i] == 0x5c and i + 1 < n and d[i + 1] == 0x75:
                    ok = i + 5 < n and all(hexdigit(x) for x in d[i + 2:i + 6])
                    i += 6
                elif d[i] == 0x5c:
                    ok = i + 1 < n and d[i + 1] in b'"\\/bfnrt'
                    i += 2
                else:
                    i += 1
            ok = ok and i < n
            i += 1
            if mode == 5:
                while i < n and space(d[i]):
                    i += 1
                ok = ok and i < n and d[i] == 0x3a
                i += 1
                mode = 0
            else:
                mode = 1
        elif b == 0x2d or digit(b):
            if b == 0x2d:
                i += 1
            ok = i < n and digit(d[i])
            if ok and d[i] == 0x30:
                i += 1
            else:
                while i < n and digit(d[i]):
                    i += 1
            if ok and i < n and d[i] == 0x2e:
                i += 1
                ok = i < n and digit(d[i])
                while i < n and digit(d[i]):
                    i += 1
            if ok and i < n and d[i] in (0x65, 0x45):
                i += 1
                if i < n and d[i] in (0x2b, 0x2d):
                    i += 1
                ok = i < n and digit(d[i])
                while i < n and digit(d[i]):
                    i += 1
            mode = 1
        else:
            word = b'true' if b == 0x74 else b'false' if b == 0x66 else b'null'
            ok = d[i:i + len(word)] == word
            i += len(word)
            mode = 1
    return False


if __name__ == '__main__':
    import os, sys, time
    suite = sys.argv[1]
    files = sorted(os.listdir(suite))
    data = [open(os.path.join(suite, f), 'rb').read() for f in files]
    for f, d in zip(files, data):
        assert not f.startswith('y_') or valid(d), f
        assert not f.startswith('n_') or not valid(d), f
    big = b'[' + b','.join(b'{"key%d": [1.5e3, true, null, "text \\u00e9 %d"]}' % (k, k) for k in range(2000)) + b']'
    runs = []
    for _ in range(7):
        start = time.perf_counter()
        valid(big)
        runs.append((time.perf_counter() - start) * 1e9 / len(big))
    print('{"size":%d,"python_ns_per_byte":%.1f}' % (len(big), sorted(runs)[3]))