Libraryless. Click here for Pure Java version (4730L/29K).
1 | static Object unstructure(String text) { |
2 | ret unstructure(text, false); |
3 | } |
4 | |
5 | static Object unstructure(String text, final boolean allDynamic) { |
6 | ret unstructure(text, allDynamic, null); |
7 | } |
8 | |
9 | static int structure_internStringsLongerThan = 50; |
10 | static int unstructure_unquoteBufSize = 100; |
11 | |
12 | static int unstructure_tokrefs; // stats |
13 | |
14 | abstract sclass unstructure_Receiver { |
15 | abstract void set(O o); |
16 | } |
17 | |
18 | // classFinder: func(name) -> class (optional) |
19 | static Object unstructure(String text, boolean allDynamic, |
20 | O classFinder) { |
21 | if (text == null) ret null; |
22 | ret unstructure_tok(javaTokC_noMLS_iterator(text), allDynamic, classFinder); |
23 | } |
24 | |
25 | static O unstructure_reader(BufferedReader reader) { |
26 | ret unstructure_tok(javaTokC_noMLS_onReader(reader), false, null); |
27 | } |
28 | |
29 | static O unstructure_tok(final Producer<S> tok, final boolean allDynamic, final O _classFinder) { |
30 | final boolean debug = unstructure_debug; |
31 | |
32 | final class X { |
33 | int i = -1; |
34 | final O classFinder = _classFinder != null ? _classFinder : _defaultClassFinder(); |
35 | new HashMap<Integer, O> refs; |
36 | new HashMap<Integer, O> tokrefs; |
37 | new HashSet<S> concepts; |
38 | new HashMap<S, Class> classesMap; |
39 | new L<Runnable> stack; |
40 | S curT; |
41 | char[] unquoteBuf = new char[unstructure_unquoteBufSize]; |
42 | |
43 | Class findAClass(S fullClassName) { |
44 | ret classFinder != null ? (Class) callF(classFinder, fullClassName) : findClass_fullName(fullClassName); |
45 | } |
46 | |
47 | S unquote(S s) { |
48 | ret unquoteUsingCharArray(s, unquoteBuf); |
49 | } |
50 | |
51 | // look at current token |
52 | S t() { |
53 | ret curT; |
54 | } |
55 | |
56 | // get current token, move to next |
57 | S tpp() { |
58 | S t = curT; |
59 | consume(); |
60 | ret t; |
61 | } |
62 | |
63 | void parse(final unstructure_Receiver out) { |
64 | S t = t(); |
65 | |
66 | int refID = 0; |
67 | if (structure_isMarker(t, 0, l(t))) { |
68 | refID = parseInt(t.substring(1)); |
69 | consume(); |
70 | } |
71 | final int _refID = refID; |
72 | |
73 | // if (debug) print("parse: " + quote(t)); |
74 | |
75 | final int tokIndex = i; |
76 | parse_inner(refID, tokIndex, new unstructure_Receiver { |
77 | void set(O o) { |
78 | if (_refID != 0) |
79 | refs.put(_refID, o); |
80 | if (o != null) |
81 | tokrefs.put(tokIndex, o); |
82 | out.set(o); |
83 | } |
84 | }); |
85 | } |
86 | |
87 | void parse_inner(int refID, int tokIndex, final unstructure_Receiver out) { |
88 | S t = t(); |
89 | |
90 | // if (debug) print("parse_inner: " + quote(t)); |
91 | |
92 | Class c = classesMap.get(t); |
93 | if (c == null) { |
94 | if (t.startsWith("\"")) { |
95 | S s = internIfLongerThan(unquote(tpp()), structure_internStringsLongerThan); |
96 | out.set(s); ret; |
97 | } |
98 | |
99 | if (t.startsWith("'")) { |
100 | out.set(unquoteCharacter(tpp())); ret; |
101 | } |
102 | if (t.equals("bigint")) { |
103 | out.set(parseBigInt()); ret; |
104 | } |
105 | if (t.equals("d")) { |
106 | out.set(parseDouble()); ret; |
107 | } |
108 | if (t.equals("fl")) { |
109 | out.set(parseFloat()); ret; |
110 | } |
111 | if (t.equals("sh")) { |
112 | consume(); |
113 | t = tpp(); |
114 | if (t.equals("-")) { |
115 | t = tpp(); |
116 | out.set((short) (-parseInt(t)); ret; |
117 | } |
118 | out.set((short) parseInt(t)); ret; |
119 | } |
120 | if (t.equals("-")) { |
121 | consume(); |
122 | t = tpp(); |
123 | out.set(isLongConstant(t) ? (O) (-parseLong(t)) : (O) (-parseInt(t))); ret; |
124 | } |
125 | if (isInteger(t) || isLongConstant(t)) { |
126 | consume(); |
127 | //if (debug) print("isLongConstant " + quote(t) + " => " + isLongConstant(t)); |
128 | if (isLongConstant(t)) { |
129 | out.set(parseLong(t)); ret; |
130 | } |
131 | long l = parseLong(t); |
132 | bool isInt = l == (int) l; |
133 | ifdef unstructure_debug |
134 | print("l=" + l + ", isInt: " + isInt); |
135 | endifdef |
136 | out.set(isInt ? (O) Integer.valueOf((int) l) : (O) Long.valueOf(l)); ret; |
137 | } |
138 | if (t.equals("false") || t.equals("f")) { |
139 | consume(); out.set(false); ret; |
140 | } |
141 | if (t.equals("true") || t.equals("t")) { |
142 | consume(); out.set(true); ret; |
143 | } |
144 | if (t.equals("-")) { |
145 | consume(); |
146 | t = tpp(); |
147 | out.set(isLongConstant(t) ? (O) (-parseLong(t)) : (O) (-parseInt(t))); ret; |
148 | } |
149 | if (isInteger(t) || isLongConstant(t)) { |
150 | consume(); |
151 | //if (debug) print("isLongConstant " + quote(t) + " => " + isLongConstant(t)); |
152 | if (isLongConstant(t)) { |
153 | out.set(parseLong(t)); ret; |
154 | } |
155 | long l = parseLong(t); |
156 | bool isInt = l == (int) l; |
157 | ifdef unstructure_debug |
158 | print("l=" + l + ", isInt: " + isInt); |
159 | endifdef |
160 | out.set(isInt ? (O) Integer.valueOf((int) l) : (O) Long.valueOf(l)); ret; |
161 | } |
162 | |
163 | if (t.equals("File")) { |
164 | consume(); |
165 | File f = new File(unquote(tpp())); |
166 | out.set(f); ret; |
167 | } |
168 | |
169 | if (t.startsWith("r") && isInteger(t.substring(1))) { |
170 | consume(); |
171 | int ref = Integer.parseInt(t.substring(1)); |
172 | O o = refs.get(ref); |
173 | if (o == null) |
174 | fail("unsatisfied back reference " + ref); |
175 | out.set(o); ret; |
176 | } |
177 | |
178 | if (t.startsWith("t") && isInteger(t.substring(1))) { |
179 | consume(); |
180 | int ref = Integer.parseInt(t.substring(1)); |
181 | O o = tokrefs.get(ref); |
182 | if (o == null) |
183 | fail("unsatisfied token reference " + ref + " at " + tokIndex); |
184 | out.set(o); ret; |
185 | } |
186 | |
187 | if (t.equals("hashset")) ret with parseHashSet(out); |
188 | if (t.equals("lhs")) ret with parseLinkedHashSet(out); |
189 | if (t.equals("treeset")) ret with parseTreeSet(out); |
190 | if (t.equals("ciset")) ret with parseCISet(out); |
191 | |
192 | if (eqOneOf(t, "hashmap", "hm")) { |
193 | consume(); |
194 | parseMap(new HashMap, out); |
195 | ret; |
196 | } |
197 | if (t.equals("lhm")) { |
198 | consume(); |
199 | parseMap(new LinkedHashMap, out); |
200 | ret; |
201 | } |
202 | if (t.equals("tm")) { |
203 | consume(); |
204 | parseMap(new TreeMap, out); |
205 | ret; |
206 | } |
207 | if (t.equals("cimap")) { |
208 | consume(); |
209 | parseMap(ciMap(), out); |
210 | ret; |
211 | } |
212 | |
213 | if (t.equals("ll")) { |
214 | consume(); |
215 | ret with parseList(new LinkedList, out); |
216 | } |
217 | |
218 | if (t.equals("syncLL")) { // legacy |
219 | consume(); |
220 | ret with parseList(synchroLinkedList(), out); |
221 | } |
222 | |
223 | if (t.equals("sync")) { |
224 | consume(); |
225 | ret with parse(new unstructure_Receiver { |
226 | void set(O value) { |
227 | if (value instanceof Map) { |
228 | ifndef Android // Java 7 |
229 | if (value instanceof NavigableMap) |
230 | ret with out.set(Collections.synchronizedNavigableMap((NavigableMap) value)); |
231 | endifndef |
232 | if (value instanceof SortedMap) |
233 | ret with out.set(Collections.synchronizedSortedMap((SortedMap) value)); |
234 | ret with out.set(Collections.synchronizedMap((Map) value)); |
235 | } else |
236 | ret with out.set(Collections.synchronizedList((L) value); |
237 | } |
238 | }); |
239 | } |
240 | |
241 | if (t.equals("{")) { |
242 | parseMap(out); ret; |
243 | } |
244 | if (t.equals("[")) { |
245 | this.parseList(new ArrayList, out); ret; |
246 | } |
247 | if (t.equals("bitset")) { |
248 | parseBitSet(out); ret; |
249 | } |
250 | if (t.equals("array") || t.equals("intarray") || t.equals("dblarray")) { |
251 | parseArray(out); ret; |
252 | } |
253 | if (t.equals("ba")) { |
254 | consume(); |
255 | S hex = unquote(tpp()); |
256 | out.set(hexToBytes(hex)); ret; |
257 | } |
258 | if (t.equals("boolarray")) { |
259 | consume(); |
260 | int n = parseInt(tpp()); |
261 | S hex = unquote(tpp()); |
262 | out.set(boolArrayFromBytes(hexToBytes(hex), n)); ret; |
263 | } |
264 | if (t.equals("class")) { |
265 | out.set(parseClass()); ret; |
266 | } |
267 | if (t.equals("l")) { |
268 | parseLisp(out); ret; |
269 | } |
270 | if (t.equals("null")) { |
271 | consume(); out.set(null); ret; |
272 | } |
273 | |
274 | if (eq(t, "c")) { |
275 | consume(); |
276 | t = t(); |
277 | assertTrue(isJavaIdentifier(t)); |
278 | concepts.add(t); |
279 | } |
280 | |
281 | // custom deserialization (new static method method) |
282 | if (eq(t, "cu")) { |
283 | consume(); |
284 | t = tpp(); |
285 | assertTrue(isJavaIdentifier(t)); |
286 | S fullClassName = "main$" + t; |
287 | Class _c = findAClass(fullClassName); |
288 | if (_c == null) fail("Class not found: " + fullClassName); |
289 | parse(new unstructure_Receiver { |
290 | void set(O value) { |
291 | ifdef unstructure_debug |
292 | print("Consumed custom object, next token: " + t()); |
293 | endifdef |
294 | out.set(call(_c, "_deserialize", value); |
295 | } |
296 | }); |
297 | ret; |
298 | } |
299 | } |
300 | |
301 | if (eq(t, "j")) { |
302 | consume("j"); |
303 | out.set(parseJava()); ret; |
304 | } |
305 | |
306 | if (c == null && !isJavaIdentifier(t)) |
307 | throw new RuntimeException("Unknown token " + (i+1) + ": " + quote(t)); |
308 | |
309 | // any other class name (or package name) |
310 | consume(); |
311 | S className, fullClassName; |
312 | |
313 | // Is it a package name? |
314 | if (eq(t(), ".")) { |
315 | consume(); |
316 | className = fullClassName = t + "." + assertIdentifier(tpp()); |
317 | } else { |
318 | className = t; |
319 | fullClassName = "main$" + t; |
320 | } |
321 | |
322 | if (c == null) { |
323 | // First, find class |
324 | if (allDynamic) c = null; |
325 | else c = findAClass(fullClassName); |
326 | if (c != null) |
327 | classesMap.put(className, c); |
328 | } |
329 | |
330 | // Check if it has an outer reference |
331 | bool hasBracket = eq(t(), "("); |
332 | if (hasBracket) consume(); |
333 | bool hasOuter = hasBracket && eq(t(), "this$1"); |
334 | |
335 | DynamicObject dO = null; |
336 | O o = null; |
337 | fS thingName = t; |
338 | if (c != null) { |
339 | o = hasOuter ? nuStubInnerObject(c, classFinder) : nuEmptyObject(c); |
340 | if (o instanceof DynamicObject) dO = (DynamicObject) o; |
341 | } else { |
342 | if (concepts.contains(t) && (c = findAClass("main$Concept")) != null) |
343 | o = dO = (DynamicObject) nuEmptyObject(c); |
344 | else |
345 | dO = new DynamicObject; |
346 | dO.className = className; |
347 | ifdef unstructure_debug |
348 | print("Made dynamic object " + t + " " + shortClassName(dO)); |
349 | endifdef |
350 | } |
351 | |
352 | // Save in references list early because contents of object |
353 | // might link back to main object |
354 | |
355 | if (refID != 0) |
356 | refs.put(refID, o != null ? o : dO); |
357 | tokrefs.put(tokIndex, o != null ? o : dO); |
358 | |
359 | // NOW parse the fields! |
360 | |
361 | final new LinkedHashMap<S, O> fields; // preserve order |
362 | final O _o = o; |
363 | final DynamicObject _dO = dO; |
364 | if (hasBracket) { |
365 | stack.add(r { |
366 | ifdef unstructure_debug |
367 | print("in object values, token: " + t()); |
368 | endifdef |
369 | if (eq(t(), ",")) consume(); |
370 | if (eq(t(), ")")) { |
371 | consume(")"); |
372 | objRead(_o, _dO, fields, hasOuter); |
373 | out.set(_o != null ? _o : _dO); |
374 | } else { |
375 | final S key = unquote(tpp()); |
376 | S t = tpp(); |
377 | if (!eq(t, "=")) |
378 | fail("= expected, got " + t + " after " + quote(key) + " in object " + thingName /*+ " " + sfu(fields)*/); |
379 | stack.add(this); |
380 | parse(new unstructure_Receiver { |
381 | void set(O value) { |
382 | fields.put(key, value); |
383 | /*ifdef unstructure_debug |
384 | print("Got field value " + value + ", next token: " + t()); |
385 | endifdef*/ |
386 | //if (eq(t(), ",")) consume(); |
387 | } |
388 | }); |
389 | } |
390 | }); |
391 | } else { |
392 | objRead(o, dO, fields, hasOuter); |
393 | out.set(o != null ? o : dO); |
394 | } |
395 | } |
396 | |
397 | void objRead(O o, DynamicObject dO, Map<S, O> fields, bool hasOuter) { |
398 | ifdef unstructure_debug |
399 | print("objRead " + className(o) + " " + className(dO) + " " + struct(fields)); |
400 | endifdef |
401 | if (o != null) { |
402 | if (dO != null) { |
403 | ifdef unstructure_debug |
404 | printStructure("setOptAllDyn", fields); |
405 | endifdef |
406 | setOptAllDyn(dO, fields); |
407 | } else { |
408 | setOptAll_pcall(o, fields); |
409 | ifdef unstructure_debug |
410 | print("objRead now: " + struct(o)); |
411 | endifdef |
412 | } |
413 | if (hasOuter) |
414 | fixOuterRefs(o); |
415 | } else for (Map.Entry<S, O> e : fields.entrySet()) |
416 | setDynObjectValue(dO, intern(e.getKey()), e.getValue()); |
417 | |
418 | if (o != null) |
419 | pcallOpt_noArgs(o, "_doneLoading"); |
420 | } |
421 | |
422 | void parseSet(final Set set, final unstructure_Receiver out) { |
423 | this.parseList(new ArrayList, new unstructure_Receiver { |
424 | void set(O o) { |
425 | set.addAll((L) o); |
426 | out.set(set); |
427 | } |
428 | }); |
429 | } |
430 | |
431 | void parseLisp(final unstructure_Receiver out) { |
432 | ifclass Lisp |
433 | consume("l"); |
434 | consume("("); |
435 | final new ArrayList list; |
436 | stack.add(r { |
437 | if (eq(t(), ")")) { |
438 | consume(")"); |
439 | out.set(Lisp((S) list.get(0), subList(list, 1))); |
440 | } else { |
441 | stack.add(this); |
442 | parse(new unstructure_Receiver { |
443 | void set(O o) { |
444 | list.add(o); |
445 | if (eq(t(), ",")) consume(); |
446 | } |
447 | }); |
448 | } |
449 | }); |
450 | if (false) // skip fail line |
451 | endif |
452 | |
453 | fail("class Lisp not included"); |
454 | } |
455 | |
456 | void parseBitSet(final unstructure_Receiver out) { |
457 | consume("bitset"); |
458 | consume("{"); |
459 | final new BitSet bs; |
460 | stack.add(r { |
461 | if (eq(t(), "}")) { |
462 | consume("}"); |
463 | out.set(bs); |
464 | } else { |
465 | stack.add(this); |
466 | parse(new unstructure_Receiver { |
467 | void set(O o) { |
468 | bs.set((Integer) o); |
469 | if (eq(t(), ",")) consume(); |
470 | } |
471 | }); |
472 | } |
473 | }); |
474 | } |
475 | |
476 | void parseList(final L list, final unstructure_Receiver out) { |
477 | tokrefs.put(i, list); |
478 | consume("["); |
479 | stack.add(r { |
480 | if (eq(t(), "]")) { |
481 | consume(); |
482 | ifdef unstructure_debug |
483 | print("Consumed list, next token: " + t()); |
484 | endifdef |
485 | out.set(list); |
486 | } else { |
487 | stack.add(this); |
488 | parse(new unstructure_Receiver { |
489 | void set(O o) { |
490 | //if (debug) print("List element type: " + getClassName(o)); |
491 | list.add(o); |
492 | if (eq(t(), ",")) consume(); |
493 | } |
494 | }); |
495 | } |
496 | }); |
497 | } |
498 | |
499 | void parseArray(final unstructure_Receiver out) { |
500 | final S type = tpp(); |
501 | consume("{"); |
502 | final List list = new ArrayList; |
503 | |
504 | stack.add(r { |
505 | if (eq(t(), "}")) { |
506 | consume("}"); |
507 | out.set( |
508 | type.equals("intarray") ? toIntArray(list) |
509 | : type.equals("dblarray") ? toDoubleArray(list) |
510 | : list.toArray()); |
511 | } else { |
512 | stack.add(this); |
513 | parse(new unstructure_Receiver { |
514 | void set(O o) { |
515 | list.add(o); |
516 | if (eq(t(), ",")) consume(); |
517 | } |
518 | }); |
519 | } |
520 | }); |
521 | } |
522 | |
523 | Object parseClass() { |
524 | consume("class"); |
525 | consume("("); |
526 | S name = unquote(tpp()); |
527 | consume(")"); |
528 | Class c = allDynamic ? null : findAClass(name); |
529 | if (c != null) ret c; |
530 | new DynamicObject dO; |
531 | dO.className = "java.lang.Class"; |
532 | name = dropPrefix("main$", name); |
533 | dO.fieldValues.put("name", name); |
534 | ret dO; |
535 | } |
536 | |
537 | Object parseBigInt() { |
538 | consume("bigint"); |
539 | consume("("); |
540 | S val = tpp(); |
541 | if (eq(val, "-")) |
542 | val = "-" + tpp(); |
543 | consume(")"); |
544 | ret new BigInteger(val); |
545 | } |
546 | |
547 | Object parseDouble() { |
548 | consume("d"); |
549 | consume("("); |
550 | S val = unquote(tpp()); |
551 | consume(")"); |
552 | ret Double.parseDouble(val); |
553 | } |
554 | |
555 | Object parseFloat() { |
556 | consume("fl"); |
557 | S val; |
558 | if (eq(t(), "(")) { |
559 | consume("("); |
560 | val = unquote(tpp()); |
561 | consume(")"); |
562 | } else { |
563 | val = unquote(tpp()); |
564 | } |
565 | ret Float.parseFloat(val); |
566 | } |
567 | |
568 | void parseHashSet(unstructure_Receiver out) { |
569 | consume("hashset"); |
570 | parseSet(new HashSet, out); |
571 | } |
572 | |
573 | void parseLinkedHashSet(unstructure_Receiver out) { |
574 | consume("lhs"); |
575 | parseSet(new LinkedHashSet, out); |
576 | } |
577 | |
578 | void parseTreeSet(unstructure_Receiver out) { |
579 | consume("treeset"); |
580 | parseSet(new TreeSet, out); |
581 | } |
582 | |
583 | void parseCISet(unstructure_Receiver out) { |
584 | consume("ciset"); |
585 | parseSet(ciSet(), out); |
586 | } |
587 | |
588 | void parseMap(unstructure_Receiver out) { |
589 | parseMap(new TreeMap, out); |
590 | } |
591 | |
592 | O parseJava() { |
593 | S j = unquote(tpp()); |
594 | new Matches m; |
595 | if (jmatch("java.awt.Color[r=*,g=*,b=*]", j, m)) |
596 | ret nuObject("java.awt.Color", parseInt($1), parseInt($2), parseInt($3)); |
597 | else { |
598 | warn("Unknown Java object: " + j); |
599 | null; |
600 | } |
601 | } |
602 | |
603 | void parseMap(final Map map, final unstructure_Receiver out) { |
604 | consume("{"); |
605 | stack.add(new Runnable { |
606 | bool v; |
607 | O key; |
608 | |
609 | public void run() { |
610 | if (v) { |
611 | v = false; |
612 | stack.add(this); |
613 | if (!eq(tpp(), "=")) |
614 | fail("= expected, got " + t() + " in map of size " + l(map)); |
615 | |
616 | parse(new unstructure_Receiver { |
617 | void set(O value) { |
618 | map.put(key, value); |
619 | ifdef unstructure_debug |
620 | print("parseMap: Got value " + getClassName(value) + ", next token: " + quote(t())); |
621 | endifdef |
622 | if (eq(t(), ",")) consume(); |
623 | } |
624 | }); |
625 | } else { |
626 | if (eq(t(), "}")) { |
627 | consume("}"); |
628 | out.set(map); |
629 | } else { |
630 | v = true; |
631 | stack.add(this); |
632 | parse(new unstructure_Receiver { |
633 | void set(O o) { |
634 | key = o; |
635 | } |
636 | }); |
637 | } |
638 | } // if v else |
639 | } // run() |
640 | }); |
641 | } |
642 | |
643 | /*void parseSub(unstructure_Receiver out) { |
644 | int n = l(stack); |
645 | parse(out); |
646 | while (l(stack) > n) |
647 | stack |
648 | }*/ |
649 | |
650 | void consume() { curT = tok.next(); ++i; } |
651 | |
652 | void consume(S s) { |
653 | if (!eq(t(), s)) { |
654 | /*S prevToken = i-1 >= 0 ? tok.get(i-1) : ""; |
655 | S nextTokens = join(tok.subList(i, Math.min(i+2, tok.size()))); |
656 | fail(quote(s) + " expected: " + prevToken + " " + nextTokens + " (" + i + "/" + tok.size() + ")");*/ |
657 | fail(quote(s) + " expected, got " + quote(t())); |
658 | } |
659 | consume(); |
660 | } |
661 | |
662 | // outer wrapper function getting first token and unwinding the stack |
663 | void parse_initial(unstructure_Receiver out) { |
664 | consume(); // get first token |
665 | parse(out); |
666 | while (nempty(stack)) |
667 | popLast(stack).run(); |
668 | } |
669 | } |
670 | |
671 | Bool b = DynamicObject_loading!; |
672 | DynamicObject_loading.set(true); |
673 | try { |
674 | final new Var v; |
675 | new X x; |
676 | x.parse_initial(new unstructure_Receiver { |
677 | void set(O o) { v.set(o); } |
678 | }); |
679 | unstructure_tokrefs = x.tokrefs.size(); |
680 | ret v.get(); |
681 | } finally { |
682 | DynamicObject_loading.set(b); |
683 | } |
684 | } |
685 | |
686 | static boolean unstructure_debug; |
Began life as a copy of #1025231
download show line numbers debug dex old transpilations
Travelled to 7 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv
No comments. add comment
Snippet ID: | #1027604 |
Snippet name: | unstructure (v16, better class finding, dev.) |
Eternal ID of this version: | #1027604/3 |
Text MD5: | d18fa0e7fbb750668fbbd64411102b16 |
Transpilation MD5: | 29174077cd4cf0cf64e714d056f65361 |
Author: | stefan |
Category: | javax |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2020-03-25 16:53:44 |
Source code size: | 20391 bytes / 686 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 151 / 214 |
Version history: | 2 change(s) |
Referenced in: | [show references] |