-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjevkoToHtml.js
133 lines (120 loc) · 4.7 KB
/
jevkoToHtml.js
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import {escape} from 'https://cdn.jsdelivr.net/gh/jevko/[email protected]/mod.js'
export const jevkoToHtml = (jevko, schema) => {
const {type} = schema
if (type === 'string') return toString(jevko, schema)
if (type === 'float64' || type === 'number') return toFloat64(jevko, schema)
if (type === 'boolean') return toBoolean(jevko, schema)
if (type === 'null') return toNull(jevko, schema)
if (type === 'array') return toArray(jevko, schema)
if (type === 'tuple') return toTuple(jevko, schema)
if (type === 'object') return toObject(jevko, schema)
if (type === 'first match') return toFirstMatch(jevko, schema)
throw Error(`Unknown schema type ${type}`)
}
const toString = (jevko, schema) => {
const {subjevkos, suffix} = jevko
if (subjevkos.length > 0) throw Error('nonempty subjevkos in string')
return `<span class="string">${escape(suffix)}</span>`
}
const toFloat64 = (jevko, schema) => {
const {subjevkos, suffix} = jevko
if (subjevkos.length > 0) throw Error('nonempty subjevkos in string')
const trimmed = suffix.trim()
if (trimmed === 'NaN') return `<span class="float64">NaN</span>`
const num = Number(trimmed)
if (Number.isNaN(num) || trimmed === '') throw Error(`Not a number (${trimmed})`)
return `<span class="float64">${num}</span>`
}
const toBoolean = (jevko, schema) => {
const {subjevkos, suffix} = jevko
if (subjevkos.length > 0) throw Error('nonempty subjevkos in string')
if (suffix === 'true') return `<span class="boolean">true</span>`
if (suffix === 'false') return `<span class="boolean">false</span>`
throw Error('not a boolean')
}
const toNull = (jevko, schema) => {
const {subjevkos, suffix} = jevko
if (subjevkos.length > 0) throw Error('nonempty subjevkos in string')
if (suffix === 'null' || suffix === '') return `<span class="null">${suffix}</span>`
throw Error(`not a null (${suffix})`)
}
const toArray = (jevko, schema) => {
const {subjevkos, suffix} = jevko
if (suffix.trim() !== '') throw Error('suffix !== ""')
let ret = ''
const {itemSchema} = schema
for (const {prefix, jevko} of subjevkos) {
if (prefix.trim() !== '') throw Error('nonempty prefix')
ret += `${prefix}[<span class="item">${jevkoToHtml(jevko, itemSchema)}</span>]`
}
return `<span class="array">${ret}${suffix}</span>`
}
const toTuple = (jevko, schema) => {
const {subjevkos, suffix} = jevko
if (suffix.trim() !== '') throw Error('suffix !== ""')
let ret = ''
const {itemSchemas, isSealed} = schema
if (itemSchemas.length > subjevkos.length) throw Error('bad tuple')
if (isSealed && itemSchemas.length !== subjevkos.length) throw Error('also bad tuple')
for (let i = 0; i < itemSchemas.length; ++i) {
const {prefix, jevko} = subjevkos[i]
if (prefix.trim() !== '') throw Error('nonempty prefix')
ret += `${prefix}[<span class="item">${jevkoToHtml(jevko, itemSchemas[i])}</span>]`
}
return `<span class="${ret === ''? 'empty ': ''}tuple">${ret}${suffix}</span>`
}
const toObject = (jevko, schema) => {
const {subjevkos, suffix} = jevko
if (suffix.trim() !== '') throw Error('suffix !== ""')
const keyJevkos = Object.create(null)
let ret = ''
const {optional = [], isSealed = true, props} = schema
const keys = Object.keys(props)
for (const {prefix, jevko} of subjevkos) {
const [pre, key, post] = trim3(prefix)
if (key.startsWith('-')) {
ret += `<span class="ignored">${escape(prefix)}[${jevkoToString(jevko)}]</span>`
continue
}
// todo: key starts with | -- use trim()
if (key === '') throw Error('empty key')
if (key in keyJevkos) throw Error('duplicate key')
if (isSealed && keys.includes(key) === false) throw Error(`unknown key (${key}) ${post}`)
ret += `<span class="item">${pre}<span class="key">${escape(key)}</span>${post}[<span class="value">${jevkoToHtml(jevko, props[key])}</span>]</span>`
}
return `<span class="${ret === ''? 'empty ': ''}object">${ret}${suffix}</span>`
}
const toFirstMatch = (jevko, schema) => {
const {alternatives} = schema
for (const alt of alternatives) {
try {
const x = jevkoToHtml(jevko, alt)
return x
} catch (e) {
continue
}
}
throw Error('union: invalid jevko')
}
const trim3 = (prefix) => {
let i = 0, j = 0
for (; i < prefix.length; ++i) {
if (isWhitespace(prefix[i]) === false) break
}
for (j = prefix.length - 1; j > i; --j) {
if (isWhitespace(prefix[j]) === false) break
}
++j
return [prefix.slice(0, i), prefix.slice(i, j), prefix.slice(j)]
}
const isWhitespace = (c) => {
return ' \n\r\t'.includes(c)
}
const jevkoToString = (jevko) => {
const {subjevkos, suffix} = jevko
let ret = ''
for (const {prefix, jevko} of subjevkos) {
ret += `${prefix}[${jevkoToString(jevko)}]`
}
return ret + suffix
}