this repo has no description
at cactus 276 lines 6.4 kB view raw
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 })*/