1 /**
2 * .
3 * Copyright � 1999 Erich P G.
4 *
5 */
6
7 package creator.compiler;
8
9 import java.util.Vector;
10 import java.util.Stack;
11 import com.sun.xml.tree.XmlDocument;
12 import com.sun.xml.tree.XmlDocumentBuilder;
13 import com.sun.xml.tree.ElementNode;
14 import org.w3c.dom.Element;
15 import org.w3c.dom.NodeList;
16 import org.w3c.dom.Node;
17 import org.w3c.dom.CharacterData;
18
19 import autohit.Sim;
20 import autohit.vm.*;
21
22 /**
23 * This is the a Sim compiler. It will compile xml documents that conform
24 * to the "%AUTOHIT_ROOT%/lib/sim.dtd" dtd into a Sim object.
25 * <p>
26 * WARNING!!! For the compiler to work, the FIRST element of the java
27 * CLASSPATH <b>must</b> be the root for the autohit installation. The compiler
28 * needs to find the "sim.dtd" file in the "%AUTOHIT_ROOT%/lib" directory.
29 * <p>
30 * The private methos translate Token() matches the textual tokens, as defined in the
31 * dtd to numerics that are used by the rest of the compiler. Any new tokens
32 * need to be added to that method.
33 * <p>
34 * I can think of several more elegant approaches to making this compiler, but until the
35 * XML parsers settle a bit, I'm not going to bother...
36 * <p>
37 * IMPORTANT NOTE!!!! The XML parser WITH DTD ensure instructions are
38 * coded in the proper order. ALL of the code below assumes this!!!
39 * If instructions come out of order or in disallowed places
40 * (like a seek outside of a get), I GUARANTEE you'll get a runaway
41 * compiler...,
42 * <p>
43 * We will look for imbedded variables only in certain locations.
44 *
45 * @see autohit.Sim
46 *
47 * @author Erich P. Gatejen
48 * @version 1.0
49 * <i>Version History</i>
50 * <code>EPG - Initial - 14Jan99
51 * EPG - Dumb-bug fix - 8Mar99</code>
52 *
53 */
54 public class SimCompiler extends XmlCompiler {
55
56 // --- FINAL FIELDS ------------------------------------------------------
57 private final static String dtdName = "sim.dtd";
58
59 private final static int tokNULL = 0;
60 private final static int tokNAME = 1;
61 private final static int tokNOTE = 2;
62 private final static int tokVERSION = 3;
63 private final static int tokSET = 4;
64 private final static int tokFOR = 5;
65 private final static int tokWHILE = 6;
66 private final static int tokIF = 7;
67 private final static int tokWAIT = 8;
68 private final static int tokGET = 9;
69 private final static int tokHEADER = 10;
70 private final static int tokNV = 11;
71 private final static int tokBLOCK = 12;
72 private final static int tokADD = 13;
73 private final static int tokVERIFY = 14;
74 private final static int tokSEEK = 15;
75 private final static int tokEXEC = 16;
76
77 // --- FIELDS ------------------------------------------------------------
78
79 /**
80 * The work-in-progress Sim object.
81 */
82 protected Sim workingSim;
83
84 // --- PUBLIC METHODS ----------------------------------------------------
85
86 /**
87 * Constructor. This is the only and default constructor.
88 *
89 * @throws Exception any exception invalidates the compiler.
90 */
91 public SimCompiler() throws Exception {
92
93 super(dtdName);
94 }
95
96 /**
97 * Compile the xml tree into a Sim object. This method implements
98 * the abstract method in XmlCompiler. It will be automatically
99 * called after the source document is parsed into an xml tree.
100 *
101 * @param xd A parsed XML document.
102 * @return a reference to the target object.
103 *
104 * @see autohit.Sim
105 * @see creator.compiler.SimCompiler
106 */
107 public Object build(XmlDocument xd) {
108
109 // Any exception or verification check aborts the compile
110 try {
111
112 // Ok, build our working Sim object
113 workingSim = new Sim();
114 workingSim.init();
115
116 // Get the root element and normalize
117 ElementNode root = (ElementNode) xd.getDocumentElement();
118 root.normalize();
119
120 // Peal out the <info> and <code> sub-trees
121 NodeList rootChildren = root.getChildNodes();
122 ElementNode itemTree = (ElementNode) rootChildren.item(0);
123 ElementNode codeTree = (ElementNode) rootChildren.item(1);
124
125 // Deal with the <info> tree
126 for (int idx = 0; idx < itemTree.getLength(); idx++) {
127 processItem((ElementNode)itemTree.item(idx));
128 }
129
130 // Deal with the <code> tree
131 // Basicall, I'm gonna go wtih recursion. I don't think it should
132 // get very deep.
133 try {
134 processCode(codeTree);
135
136 // Put a NOP on the end of the executable
137 workingSim.exec.add(new VMINop());
138 workingSim.exec.trimToSize();
139
140 } catch (Exception e) {
141 // an otherwise uncaught exception. A runaway compiler...
142 err.error("Runaway compilation errors. Stopping compile...");
143 return null;
144 }
145
146 } catch (Exception e) {
147 err.error("Compiler internal error. Stopping compile...");
148 return null; // leave the objectCode as null;
149 }
150
151 return workingSim;
152 }
153
154
155
156 // == ===============================================================================
157 // == = PRIVATE METHODS =
158 // == ===============================================================================
159
160
161 /**
162 * Processes info section tags.
163 */
164 private void processItem(ElementNode en) {
165
166 switch( translateToken(en.getNodeName()) ) {
167
168 case tokNAME:
169 // Pull the child text element and place it the Sim.name;
170 String nameText = getText(en.getFirstChild());
171 if (nameText == null) {
172 err.error("Empty <name> tag.");
173 } else {
174 workingSim.name = nameText;
175 }
176 break;
177 // For now, we are ignoring UIDs
178
179 case tokNOTE:
180 // Notes will append. Pull the child text element and paste it to the
181 // Sim.note.
182 String noteText = getText(en.getFirstChild());
183 if (noteText == null) {
184 err.error("Empty <note> tag.");
185 } else {
186 workingSim.note = new String(workingSim.note + noteText);
187 }
188 break;
189
190 case tokVERSION:
191 // For now, ignore VERSION
192 break;
193
194 case tokNULL:
195 default:
196 err.error("Software Detected Fault: creator.compiler.SimCompiler.processItem(). The textual token [" + en.getNodeName() + "] should have NOT reached this code. Check the XML DTD associated with the SimCompiler.");
197
198 }
199 }
200
201
202 /**
203 * Every raw element will enter here. Bascially, it dispatches it to it's specific
204 * handler. It will do so for every child in the passed node.
205 */
206 private void processCode(ElementNode en) throws Exception {
207
208 ElementNode child;
209
210 //DEBUG
211 //System.out.println("ENTER --- " + en.getNodeName());
212
213 for (int idx = 0; idx < en.getLength(); idx++) {
214
215 child = (ElementNode)en.item(idx);
216 //DEBUG
217 //System.out.println(" --- CHILD " + child.getNodeName());
218
219 switch( translateToken(child.getNodeName()) ) {
220
221 case tokSET:
222 handleSet(child);
223 break;
224
225 case tokWAIT:
226 handleWait(child);
227 break;
228
229 case tokGET:
230 handleGet(child);
231 break;
232
233 case tokHEADER:
234 handleHeader(child);
235 break;
236
237 case tokNV:
238 handleNV(child);
239 break;
240
241 case tokFOR:
242 handleFor(child);
243 break;
244
245 case tokWHILE:
246 handleWhile(child);
247 break;
248
249 case tokIF:
250 handleIf(child);
251 break;
252
253 case tokBLOCK:
254 handleBlock(child);
255 break;
256
257 case tokADD:
258 handleAdd(child);
259 break;
260
261 case tokVERIFY:
262 handleVerify(child);
263 break;
264
265 case tokSEEK:
266 handleSeek(child);
267 break;
268
269 case tokEXEC:
270 handleExec(child);
271 break;
272
273 case tokNULL:
274 default:
275 err.error("Software Detected Fault: creator.compiler.SimCompiler.processItem(). The textual token [" + en.getNodeName() + "] should have NOT reached this code. Check the XML DTD associated with the SimCompiler.");
276 throw (new Exception("Software Detected Fault in creator.compiler.SimCompiler.processItem()."));
277
278 } // end switch
279
280 } // end for
281
282 //DEBUG
283 //System.out.println("EXIT --- " + en.getNodeName());
284
285 }
286
287 // == ===============================================================================
288 // == = TOKEN HANDLERS =
289 // == ===============================================================================
290
291 /**
292 * Emit a set instruction. Simple enough.
293 */
294 private void handleSet(ElementNode en) {
295
296 String name = en.getAttribute("name");
297 String value = en.getAttribute("value");
298
299 if (!isValid(name)) {
300 errBadAttribute(en.toString(), "name", "set");
301 } else if (!isValid(value)) {
302 errBadAttribute(en.toString(), "value", "set");
303 } else {
304
305
306 VMISet emitable = new VMISet();
307 emitable.name = name;
308 emitable.value = value;
309 emitable.orIV(seekIV(name)||seekIV(value));
310
311 workingSim.exec.add(emitable);
312 }
313 }
314
315
316 /**
317 * Emit an add instruction.
318 */
319 private void handleAdd(ElementNode en) {
320
321 String name = en.getAttribute("name");
322 String value = en.getAttribute("value");
323 if (!isValid(name)) {
324 errBadAttribute(en.toString(), "name", "add");
325 } else if (!isValid(value)) {
326 errBadAttribute(en.toString(), "value", "add");
327 } else {
328
329 VMIAdd emitable = new VMIAdd();
330 emitable.name = name;
331 emitable.value = value;
332 emitable.orIV(seekIV(name)||seekIV(value));
333
334 workingSim.exec.add(emitable);
335 }
336 }
337
338 /**
339 * Emit a wait instruction. Simple enough.
340 */
341 private void handleWait(ElementNode en) {
342
343 String time = en.getAttribute("time");
344 if (isValid(time)) {
345
346 VMIWait emitable = new VMIWait();
347 emitable.time = time;
348 emitable.orIV(seekIV(time));
349 workingSim.exec.add(emitable);
350
351 } else {
352 errBadAttribute(en.toString(), "time", "wait");
353 }
354 }
355
356 /**
357 * Emit a get instruction.
358 */
359 private void handleGet(ElementNode en) {
360
361 String qs = en.getAttribute("qs");
362 if (isValid(qs)) {
363
364 // Prepare the instruction
365 VMIGet emitable = new VMIGet();
366 emitable.qs = qs;
367 emitable.orIV(seekIV(qs));
368
369 // Put out a SCOPE
370 workingSim.exec.add(new VMIScope());
371
372 // Process inner block and add the get
373 try {
374 processCode(en);
375 workingSim.exec.add(emitable);
376
377 } catch (Exception e) {
378 // Stop an error unravelling here....
379 }
380
381 // Add the RSCOPE
382 workingSim.exec.add(new VMIRScope());
383
384 } else {
385 errBadAttribute(en.toString(), "qs", "get");
386 }
387 }
388
389
390 /**
391 * Emit a Header instruction.
392 */
393 private void handleHeader(ElementNode en) {
394
395 String name = en.getAttribute("name");
396 String value = en.getAttribute("value");
397 if (!isValid(name)) {
398 errBadAttribute(en.toString(), "name", "header");
399 } else if (!isValid(value)) {
400 errBadAttribute(en.toString(), "value", "header");
401 } else {
402
403 VMIHeader emitable = new VMIHeader();
404 emitable.name = name;
405 emitable.value = value;
406 emitable.orIV(seekIV(name)||seekIV(value));
407 workingSim.exec.add(emitable);
408 }
409 }
410
411 /**
412 * Emit a NV instruction.
413 */
414 private void handleNV(ElementNode en) {
415
416 String name = en.getAttribute("name");
417 String valueText = getText(en.getFirstChild());
418 if (!isValid(name)) {
419 errBadAttribute(en.toString(), "name", "nv");
420 } else if (!isValid(valueText)) {
421 valueText = "\n";
422 } else {
423 VMINV emitable = new VMINV();
424 emitable.name = name;
425 emitable.value = valueText;
426 emitable.orIV(seekIV(name)||seekIV(valueText));
427 workingSim.exec.add(emitable);
428 }
429 }
430
431
432 /**
433 * handle a block. easy enough. just scope it!.
434 */
435 private void handleBlock(ElementNode en) {
436
437 // Put out a SCOPE
438 workingSim.exec.add(new VMIScope());
439
440 // Process inner block and add the get
441 try {
442 processCode(en);
443
444 } catch (Exception e) {
445 // Stop an error unravelling here....
446 }
447
448 // Add the RSCOPE
449 workingSim.exec.add(new VMIRScope());
450 }
451
452 /**
453 * Emit a Verify instruction.
454 */
455 private void handleVerify(ElementNode en) {
456
457 String size = en.getAttribute("size");
458 String crc = en.getAttribute("crc");
459
460 VMIVerify emitable = new VMIVerify();
461 if (isValid(size)) {
462
463 try {
464 emitable.size = Integer.parseInt(size);
465 } catch (Exception e) {
466 errBadAttribute(en.toString(), "size", "verify");
467 return;
468 }
469 }
470
471 // Add a CRC instruction, if the attribute is specified.
472 if (isValid(crc)) {
473
474 try {
475
476 int val = Integer.parseInt(crc);
477 VMICrc eCRC = new VMICrc();
478 eCRC.expected = val;
479 workingSim.exec.add(eCRC);
480
481 } catch (Exception e) {
482 errBadAttribute(en.toString(), "crc", "verify");
483 return;
484 }
485
486 }
487 workingSim.exec.add(emitable);
488
489 // Handle any SEEK or EXEC children
490 try {
491 processCode(en);
492
493 } catch (Exception e) {
494 // Stop an error unravelling here....
495 }
496 }
497
498 /**
499 * Handle a seek.
500 */
501 private void handleSeek(ElementNode en) {
502
503 String seekText = en.getAttribute("string");
504 if (!isValid(seekText)) {
505 errBadAttribute(en.toString(), "string", "seek");
506 } else {
507 VMISeek emitable = new VMISeek();
508 emitable.expected = seekText;
509 emitable.orIV(seekIV(seekText));
510 workingSim.exec.add(emitable);
511 }
512 }
513
514 /**
515 * Handle an Exec.
516 */
517 private void handleExec(ElementNode en) {
518
519 // we don't care if the content is mangled or empty.
520
521 String content = getText(en.getFirstChild());
522 String invoke = en.getAttribute("invoke");
523 if (!isValid(invoke)) {
524 errBadAttribute(en.toString(), "invoke", "exec");
525 } else {
526 VMIExec emitable = new VMIExec();
527 emitable.invocation = invoke;
528 emitable.content = content;
529 emitable.orIV(seekIV(content)||seekIV(invoke));
530 workingSim.exec.add(emitable);
531 }
532 }
533
534 /**
535 * Handle an IF.
536 *
537 * Bounce out any exceptions, cause a screwed IF will spoil a
538 * parent scope.
539 */
540 private void handleIf(ElementNode en) throws Exception {
541
542 String expression = en.getAttribute("e");
543 String value = en.getAttribute("value");
544 if (!isValid(expression)) {
545 errBadAttribute(en.toString(), "e", "if");
546 } else if (!isValid(value)) {
547 errBadAttribute(en.toString(), "value", "if");
548 } else {
549
550 VMIIf emitable = new VMIIf();
551 emitable.e = expression;
552 emitable.value = value;
553 emitable.orIV(seekIV(expression)||seekIV(value));
554 workingSim.exec.add(emitable);
555 // ^^^^ We'll fixup the target in a bit...
556
557 // Put out a SCOPE
558 workingSim.exec.add(new VMIScope());
559
560 // Process inner block and add the get
561 try {
562 processCode(en);
563
564 } catch (Exception e) {
565 // Stop inner scope unravellings here...
566 }
567
568 // Add the RSCOPE
569 workingSim.exec.add(new VMIRScope());
570
571 // Fixup the target
572 emitable.target = workingSim.exec.size(); // Points to where the NEXT intruction
573 // should be put...
574 }
575 }
576
577 /**
578 * Handle an For
579 */
580 private void handleFor(ElementNode en) {
581
582 int target;
583 VMISet initSet = null;
584
585 String count = en.getAttribute("count");
586 String init = en.getAttribute("init");
587 if (!isValid(count)) {
588 errBadAttribute(en.toString(), "count", "for");
589 } else {
590
591 // Prep the FOR
592 VMIFor emitable = new VMIFor();
593 emitable.count = count;
594 emitable.orIV(seekIV(count));
595
596 // Prep the INIT
597 if (init != null) {
598
599 initSet = new VMISet();
600 initSet.value = init;
601 initSet.name = count;
602 initSet.orIV(seekIV(init)||seekIV(count));
603 }
604
605 // Put out a SCOPE
606 workingSim.exec.add(new VMIScope());
607
608 // Process inner block and add the get
609 try {
610
611 if (init != null) {
612 workingSim.exec.add(initSet);
613 }
614
615 // Prepare a jump back and Emit the FOR
616 VMIJump emitjump = new VMIJump();
617 emitjump.target = workingSim.exec.size();
618 workingSim.exec.add(emitable);
619
620 // Work the inner scope
621 processCode(en);
622
623 // Emit the jump and points the FOR right past it.
624 workingSim.exec.add(emitjump);
625 emitable.target = workingSim.exec.size();
626
627 } catch (Exception e) {
628 // Stop inner scope unravellings here...
629 }
630
631 // Add the RSCOPE
632 workingSim.exec.add(new VMIRScope());
633 }
634 }
635
636
637 /**
638 * Handle an WHILE.
639 *
640 * Bounce out any exceptions, cause a screwed IF will spoil a
641 * parent scope.
642 */
643 private void handleWhile(ElementNode en) throws Exception {
644
645 String expression = en.getAttribute("e");
646 String value = en.getAttribute("value");
647 if (!isValid(expression)) {
648 errBadAttribute(en.toString(), "e", "while");
649 } else if (!isValid(value)) {
650 errBadAttribute(en.toString(), "value", "while");
651 } else {
652
653 // Prep the while
654 VMIWhile emitable = new VMIWhile();
655 emitable.e = expression;
656 emitable.value = value;
657 emitable.orIV(seekIV(expression)||seekIV(value));
658
659 // Emit a SCOPE
660 workingSim.exec.add(new VMIScope());
661
662 // Emit a NOP and point the while at it
663 emitable.target = workingSim.exec.size();
664 workingSim.exec.add(new VMINop());
665
666 // Process inner block
667 try {
668 processCode(en);
669
670 } catch (Exception e) {
671 // Stop inner scope unravellings here...
672 }
673
674 // Emit the WHILE and RSCOPE
675 workingSim.exec.add(emitable);
676 workingSim.exec.add(new VMIRScope());
677
678 }
679 }
680
681 // == ===============================================================================
682 // == = HELPERS =
683 // == ===============================================================================
684
685 /**
686 * See if a string contains an imbedded variable.
687 *
688 * It uses VMInstruction.IVToken as the delimiter.
689 *
690 * @param s the string to check.
691 * @return true if yes, otherwise false
692 */
693 private boolean seekIV(String s) {
694
695 int sl = s.length() - 1;
696 for (int idx = 0; idx < sl; idx++) {
697 if ((s.charAt(idx) == VMInstruction.IVToken)&&
698 (s.charAt(idx+1) != VMInstruction.IVToken))
699 return true;
700 }
701 return false;
702 }
703
704 /**
705 * Valid string.
706 *
707 * Checks if the reference is not null and the srring contains characters.
708 *
709 * @param s the string to check.
710 * @return true if valid, otherwise false
711 */
712 private boolean isValid(String s) {
713 if ((s == null)||(s.length() < 1)) return false;
714 else return true;
715 }
716
717
718 /**
719 * Translate a textual token into a numeric token.
720 *
721 * @param text token text.
722 * @return token numeric.
723 */
724 private int translateToken(String text) {
725
726 // I'm going to let the parser try and optimize this...
727 //DEBUG
728 //System.out.println("XLATE token: [" + text + "]");
729 try {
730 switch(text.charAt(0)) {
731
732 case 'n': switch(text.charAt(1)) {
733 case 'a': return tokNAME;
734 case 'o': return tokNOTE;
735 case 'v': return tokNV;
736 default: return tokNULL;
737 }
738
739 case 'v': switch(text.charAt(3)) {
740 case 'i': return tokVERIFY;
741 case 's': return tokVERSION;
742 default: return tokNULL;
743 }
744
745 case 'w': switch(text.charAt(1)) {
746 case 'h': return tokWHILE;
747 case 'a': return tokWAIT;
748 default: return tokNULL;
749 }
750
751 case 'i': return tokIF;
752 case 's': switch(text.charAt(2)) {
753 case 't': return tokSET;
754 case 'e': return tokSEEK;
755 default: return tokNULL;
756 }
757
758 case 'f': return tokFOR;
759 case 'g': return tokGET;
760 case 'h': return tokHEADER;
761 case 'b': return tokBLOCK;
762 case 'a': return tokADD;
763 case 'e': return tokEXEC;
764
765 default: return tokNULL;
766 }
767 } catch (Exception e) { }
768 return tokNULL;
769 }
770
771 /**
772 * Get the text out of an XML node.
773 *
774 * @param cdn XML node.
775 * @return the text.
776 */
777 private String getText(Node cdn) {
778
779 try {
780 if ((cdn.getNodeType() == Node.TEXT_NODE)||(cdn.getNodeType() != Node. CDATA_SECTION_NODE)) {
781 CharacterData cdnc = (CharacterData)cdn;
782 return cdnc.getData();
783 }
784 } catch (Exception e) { } // ignore. re are returning empty anyway
785 return null;
786 }
787
788 /**
789 * Get the text out of an XML node.
790 *
791 * @param where where it was bad.
792 * @param which which attribute was bad.
793 * @param tag which tag.
794 * @return the text.
795 */
796 private void errBadAttribute(String where, String which, String tag) {
797 err.error("Invalid '" + which + "' attribute for <" + tag + ">. @" + where);
798 }
799
800 // --- INTERNAL CLASSES ---------------------------------------------------
801
802 // --- DEBUG METHODS ------------------------------------------------------
803
804 }
|