this repo has no description
1"use strict"
2
3function clean(tree) {
4 if ('string'==typeof tree)
5 return tree
6 let ret = {type:tree.type}
7 if (tree.args)
8 ret.args = tree.args
9 if (tree.content)
10 ret.content = tree.content.map(x=>clean(x))
11 return ret
12}
13
14function make_env() {
15 let x = document.createElement('iframe')
16 x.srcdoc
17}
18
19function INIT(th, defs) {
20 Object.defineProperties(th, defs)
21 Object.seal(th)
22}
23
24class Test {
25 constructor({name}, input, lang, correct) {
26 INIT(this, {
27 name: {value: name},
28 lang: {value: lang},
29 input: {value: input},
30 correct: {value: correct},
31 status: {writable: true},
32 result: {writable: true},
33 parse_time: {writable: true},
34 })
35
36 this.reset()
37
38 //if (Test.all.find(test=>test.input == input))
39 // console.warn('duplicate test!', this)
40 if (Test.set.has(this.input))
41 return
42 Test.all.push(this)
43 Test.set.add(this.input)
44 }
45
46 run() {
47 this.reset()
48
49 let t, p
50 try {
51 p = performance.now()
52 t = Test.LANGS.parse(this.input, this.lang)
53 this.parse_time = performance.now() - p
54 } catch (e) {
55 console.warn('parse error?')
56 this.parse_time = performance.now() - p
57 this.status = -2
58 this.result = "Error during parsing!\n"+e+"\n"+e.stack
59 return false
60 }
61
62 try {
63 let p = new Comparator(this.correct, t)
64 p.run()
65 } catch (e) {
66 this.status = -1
67 this.result = e
68 return false
69 }
70
71 this.status = 1
72 this.result = "ok"
73 return true
74 }
75
76 reset() {
77 this.status = 0
78 this.result = null
79 this.parse_time = null
80 }
81
82 to_entry() {
83 return `🟩 ${this.name}\n${this.input}\n🟩 ${JSON.stringify(clean(this.correct))}`
84 }
85
86 //static all = []
87
88 static run_all() {
89 for (let test of this.all) {
90 test.run()
91 }
92 }
93
94 static clear() {
95 this.all = []
96 this.set = new Set()
97 }
98
99 static load_text(text) {
100 this.clear()
101 text = text.replace(/\r/g, "")
102 // todo: indent? (\t*) and then \1 backref match on other lines
103 let r = /^🟩[ \t]?(.*)\n([^🟩]*)\n🟩[ \t]*([{].*)$|(🟩)/gum
104 let m
105 while (m = r.exec(text)) {
106 let [, name, input, output, fail] = m
107 if (fail) {
108 let line = text.substr(0, m.index).match(/\n/g).length+1
109 console.warn("error parsing tests file:", line)
110 } else {
111 let test = new Test({name: name}, input, "12y2", clean(JSON.parse(output)))
112 }
113 }
114 }
115}
116Test.clear()
117
118class InvalidTree extends Error {
119 constructor(msg) {
120 super(msg)
121 this.name = 'InvalidTree'
122 }
123}
124
125class Mismatch extends Error {
126 constructor(tree, thing, correct, got) {
127 super(thing)
128 this.correct = correct
129 this.got = got
130 this.tree = tree
131 this.thing = thing
132 this.name = 'Mismatch'
133 }
134 message() {
135 return `${this.tree}\n${this.thing}\nExpect: ${safe_string(this.correct)}\n Got: ${safe_string(this.got)}`
136 }
137}
138
139function safe_string(obj) {
140 if (obj===undefined)
141 return "<empty>"
142 try {
143 return JSON.stringify(obj)
144 } catch(e) {
145 return "<???>"
146 }
147}
148
149// todo: this could be more fancy. integrate it with Test directly. instead of making a new one
150// rather than keeping a stack, just add annotations onto `correct` once at the start, then reuse them.
151class Comparator {
152 constructor(correct, got) {
153 this.stack = []
154 this.correct = correct
155 this.got = got
156 }
157 run() {
158 this.compare_node(this.correct, this.got)
159 }
160 mismatch(msg, correct, got) {
161 throw new Mismatch(this.print(), msg, correct, got)
162 }
163 push(correct) {
164 this.stack.push({node:correct})
165 }
166 index(i) {
167 this.stack[this.stack.length-1].index = i
168 }
169 pop() {
170 this.stack.pop()
171 }
172 print() {
173 let s = "", i = 0
174 for (let {node, index} of this.stack) {
175 if (!node)
176 break
177 index = index==null ? "" : ""+(index+1)+" of "+node.content.length+" in "
178 node = 'string'==typeof node ? JSON.stringify(node) : node.type
179 let prefix = i==0 ? "" : " ".repeat(i-1)+"└ "
180 s += prefix+index+node+"\n"
181 i++
182 }
183 return s
184 }
185 is_object(x) {
186 return x && Object.getPrototypeOf(x)==Object.prototype
187 }
188 json_type(x) {
189 if (x===null || x===undefined) return 'null'
190 let t = typeof x
191 if (t=='number' || t=='boolean' || t=='string')
192 return t
193 if (t=='object') {
194 if (Array.isArray(x)) return 'array'
195 if (this.is_object(x)) return 'object'
196 }
197 throw new InvalidTree("value has illegal type")
198 }
199 compare_args(correct, got) {
200 if (correct==null) {
201 if (got!=null)
202 this.mismatch("node.args", correct, got)
203 return
204 }
205 if (!this.is_object(correct)) {
206 this.mismatch("ref node.args", 'object', correct)
207 }
208 if (!this.is_object(got))
209 this.mismatch("node.args", correct, got)
210
211 for (let obj of [correct, got])
212 for (let key in obj)
213 if (correct[key] !== got[key])
214 this.mismatch(`node.args.${key}`, correct[key], got[key])
215 }
216 compare_content(correct, got) {
217 // todo: whether a node has .content depends on the node type
218 // i.e. /italic/ always has content, --- <hr> never does, etc.
219 // so instead of this check, perhaps we should have a table of which blocks have contents?
220 if (correct==null) {
221 if (got!=null)
222 this.mismatch("node.content", correct, got)
223 return
224 }
225 //
226 if (!Array.isArray(correct))
227 throw new Error("invalid .content in reference tree")
228 if (!Array.isArray(got))
229 this.mismatch("node.content", correct, got)
230
231 for (let i=0; i<correct.length || i<got.length; i++) {
232 this.index(i)
233 if (i >= correct.length)
234 this.mismatch("node", undefined, got[i])
235 this.compare_node(correct[i], got[i])
236 }
237 }
238 compare_node(correct, got) {
239
240 // string node
241 if ('string'==typeof correct) {
242 if (got !== correct)
243 this.mismatch("textnode", correct, got)
244 } else {
245 // object node
246 if (!this.is_object(correct)) {
247 this.mismatch("ref node", 'object', correct)
248 }
249 if (!this.is_object(got))
250 this.mismatch("node", correct, got)
251 if (got.type !== correct.type)
252 this.mismatch("node.type", correct.type, got.type)
253 //
254 this.compare_args(correct.args, got.args)
255 //
256 this.push(correct)
257 this.compare_content(correct.content, got.content)
258 this.pop()
259 }
260
261 }
262}
263
264
265 /*
266 // this makes the tests/results render immediately when the page loads,
267 // but it's actually /too fast/ and it's hard to tell whether the tests actually ran
268
269 let x = new MutationObserver((events, observer)=>{
270 let pt = events.find(e=>e.target.id=='$data')
271 observer.disconnect()
272 run(pt.target.textContent)
273 }).observe(document.body, {
274 childList: true,
275 subtree: true,
276 })*/