-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodegen.py
More file actions
1122 lines (1043 loc) · 51.6 KB
/
codegen.py
File metadata and controls
1122 lines (1043 loc) · 51.6 KB
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -----------------------------------------------------------------------------
# narrtr: codegen.py
# This file contains the Code Generator to generate target Python code.
#
# Copyright (C) 2015 Team narratr
# All Rights Reserved
# Team narratr: Yelin Hong, Shloka Kini, Nivvedan Senthamil Selvan, Jonah
# Smith, Cecilia Watt
#
# File Created: 01 April 2015
# Primary Authors: Jonah Smith, Yelin Hong, Shloka Kini
#
# Any questions, bug reports and complaints are to be directed at the primary
# author.
#
# -----------------------------------------------------------------------------
from sys import stderr, exit
from node import Node
class CodeGen:
def __init__(self):
self.frontmatter = "#!/usr/bin/env python\n" + \
"from __future__ import division\n" + \
"from sys import exit\n\n"
self.scenes = []
self.scene_nums = []
self.items = []
self.item_names = []
self.main = ""
def process(self, node, symtab):
"""Call first: generate target code given narratr AST and symbol table.
This function takes as its arguments the root node of the AST and the
symbol table. It saves the symbol table to a class variable (so it is
accessible anywhere in the code using self), and looks through the AST
to identify the high level nodes (i.e. scenes, items, and startstate),
sending the appropriate nodes to the appropriate functions for
processing. Note we know the structure of the AST, so we don't need
DFS or other tree searching algorithms, which improves efficiency."""
self.symtab = symtab
if len(node.children) != 1 or node[0].type != "blocks":
self._process_error("Unexpected Parse Tree - Incorrect number" +
"or type of children for the top node",
node.lineno)
blocks = node[0].children
for block in blocks:
if type(block) is dict:
for key, s_i in block.iteritems():
if s_i.type == "scene_block":
self._add_scene(self._scene_gen(s_i, key))
elif s_i.type == "item_block":
self._add_item(self._item_gen(s_i, key))
elif block.type == "start_state":
self._add_main(block)
else:
self._process_error("Found unexpected block types.",
block.lineno)
def construct(self, outputfile="stdout"):
"""Class second: write the generated code to a file.
This function takes the instance variables constructed by the
process() function and writes them to an output file. (As such, it must
be run AFTER process()! It is intended to be called externally, within
the main compiler. It takes one argument, outputfile, which is a string
of the location where the file should be written. By convention, that
file should be in the form of *.ntrc. If no outputfile is specified or
the outputfile is specified as "stdout", the code prints to standard
out (e.g. usually the terminal window). That's mainly for debugging
purposes, and should not be used in the production compiler, as the
line breaks are only approximations."""
if self.main == "":
self._process_warning("No start scene specified. " +
"Defaulting to $1.")
self._add_main(1)
if outputfile == "stdout":
print self.frontmatter
print "\n".join(self.scenes)
print "\n".join(self.items)
print self.main
else:
with open(outputfile, 'w') as f:
f.write(self.frontmatter)
f.write("\n")
f.write("\n".join(self.scenes))
f.write("\n\n")
f.write("\n".join(self.items))
f.write("\n\n")
f.write(self.main)
# This function is used internally to add a scene to the scene list. It
# takes a string *with correct indentation*.
def _add_scene(self, scene):
self.scenes.append(scene)
# This function is used internally to add a item to the item list. It
# takes a string *with correct indentation*.
def _add_item(self, item):
self.items.append(item)
# This function generates the code for a start state given a start state
# node. If start state code has already been generated, it produces a
# warning and keeps the start state declared higher in the program. If
# called without a node, it triggers the default action, which is a start
# state of 1. This should only be used internally.
# Comments on the literal Python functions are in-line below.
def _add_main(self, startstate):
if self.main == "":
# ABOUT THE POCKET CLASS: here we define the pocket class and
# initialize a global instance. The methods are fairly self
# explanatory.
self.main = '''class pocket_class:
def __init__(self):
self.data = {}
def add(self, key, val, verbose=True):
if self.data.get(key, None):
print " ** '" + key + "' is already in your pocket. **"
else:
self.data[key] = val
if verbose:
print " ** '" + key + "' is now in your pocket. **"
def update(self, key, val):
self.data[key] = val
def get(self, key):
return self.data.get(key)
def remove(self, key):
del self.data[key]
def has(self, key):
if self.data.get(key, None):
return True
return False
pocket = pocket_class()\n'''
# ABOUT THE RESPONSE CODE: the default response code, which is dropped
# into a function called get_response(), waits for user input. When it
# it receives this input, it strips the case (i.e. everything is made
# lower case), removes all punctuation except double quotes (to allow
# the programmer to add conversational capabilities), converts all
# whitespace characters into a single space, and then checks for specific
# situations we agree with the programmer to handle by default. 'exit'
# will terminate the game (there is no current way to save game state),
# and "move" followed by a single token will check the dictionary of
# directions (which it takes as an argument) for an applicable direction.
# If it does not appear in the dictionary, an error is reported so the user
# is not confused. If it does appear, it encodes the next scene's function
# call within a list so that it can easily be identified by the caller
# function, which will return that piece of code. This is a centerpiece of
# our approach to avoiding an overflow of activation records in large
# games.
self.main += '''def get_response(direction):
response = raw_input(" -->> ")
response = response.lower()
response = response.translate(None,
"!#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~")
response = ' '.join(response.split())
if response == "exit":
print "== GAME TERMINATED =="
exit(0)
elif response[:5] == "move " and len(response.split(" ")) == 2:
if response.split(" ")[1] in direction:
return ["s_" + str(direction[response.split(" ")[1]])\\
+ "_inst.setup()"]
else:
print "\\"" + response.split(" ")[1] + "\\" is not a "\\
+ "valid direction from this scene."
else:
return response\n\n'''
# Create an instance of each scene that has been declared.
for s in self.scene_nums:
self.main += "s_" + str(s) + "_inst = s_" + str(s) + "()\n"
if isinstance(startstate, Node):
ss = startstate.value
else:
ss = startstate
if ss in self.scene_nums:
self.startstate = ss
else:
self._process_error("Start scene $" + str(ss) +
" does not exist.")
self.main += "if __name__ == '__main__':\n next = s_"\
+ str(self.startstate) + "_inst.setup()\n while True:\n"\
+ " exec 'next = ' + next"
else:
self._process_error("Multiple start scene declarations.",
startstate.lineno)
# This function takes a scene node and processes it, translating into
# valid Python (really, a Python class). Iterates through the children
# of the input node and constructs the setup, cleanup, and action blocks
# using boilerplate code. This should only be used internally. In the
# action_block part, adding while(true) loop to get response and then
# process it. def achition is now taking direction as argument, direction
# is an empty dictionary by default.
def _scene_gen(self, scene, sid):
commands = []
direction_sign = False
for c in scene.children:
if c.type == "SCENEID":
sid = c.value
elif c.type == "setup_block":
commands += self._process_setup_block(c)
elif c.type == "cleanup_block":
commands += self._process_cleanup_block(c)
elif c.type == "action_block":
commands += self._process_action_block(c)
self.scene_nums.append(sid)
scene_code = "class s_" + str(sid) + ":\n def __init__(self):"\
+ "\n self.__namespace = {}\n "\
+ "self.directions = {}\n\n " + "\n ".join(commands)
return scene_code
# This function takes a item node and processes the node. It creates
# a class for the item which includes initiation function and other
# functions for different item types.
def _item_gen(self, item, iid):
iid = item.value
self.item_names.append(iid)
item_code = "class " + str(iid) + ":\n "
if len(item.children) not in [1, 2]:
self._process_error("Wrong number of children of item",
item.lineno)
elif item[0].type != "itemparams":
self._process_error("Wrong number of items", item.lineno)
else:
item_code += "def __init__(self"
item_code += self._process_itemparams(item[0])
item_code += "):"
if len(item.children) == 1:
item_code += "\n pass"
elif len(item.children) == 2:
if item[1].type != "suite":
self._process_error("Wrong type of child for item",
item.lineno)
else:
item_code += self._process_suite(item[1], 2)
return item_code
# This function takes item parameters and processes its first children
# node if it exits.
def _process_itemparams(self, itemparams):
commands = ""
if len(itemparams.children) not in [0, 1]:
self._process_error("Wrong number of children of itemparams",
itemparams.lineno)
if len(itemparams.children) == 1:
if itemparams[0].type != "fparams":
self._process_error("Wrong type of child of itemparams",
itemparams.lineno)
else:
commands += self._process_fparams(itemparams[0])
return commands
# This function takes function parameters as input and generates
# code for function for item class
def _process_fparams(self, fparams):
commands = []
for i, param in enumerate(fparams.children):
commands.append(str(param.value))
return ", " + ", ".join(commands)
# Code for adding a setup block. Takes as input a single "setup block"
# node. Adds boilerplate code (function definition, empty dictionary for
# direction, and at the end, the code to move to the action block), and
# sends the child nodes to _process_suite() to generate their code.
def _process_setup_block(self, c):
commands = []
commands.append("def setup(self):")
if len(c.children) not in [0, 1]:
self._process_error("setup block has wrong number of children")
if len(c.children) == 1:
if c[0].type != "suite":
self._process_error("setup block doesn't have suite child")
else:
commands.append(self._process_suite(c[0], 2))
commands.append(" return self.action()\n")
return commands
# Code for adding a cleanup block. Takes as input a single "cleanup block"
# node. Adds boilerplate code (function definition and "pass" if necessary,
# explained below), then sends the child nodes to _process_suite() to
# generate their code. "pass" is required in the scenario that there are no
# child nodes, in which case Python syntactically requires code, we need to
# be able to execute the function, but we don't want anything to happen. #
# "pass" is a Python command that does nothing, so it fits the bill.
def _process_cleanup_block(self, c):
commands = []
commands.append("def cleanup(self):")
if len(c.children) not in [0, 1]:
self._process_error("cleanup block has wrong number of children")
if len(c.children) == 1:
if c[0].type != "suite":
self._process_error("cleanup block doesn't have suite child")
else:
commands.append(self._process_suite(c[0], 2))
commands.append(" self.__namespace = {}")
return commands
# Code for adding an action block. Takes as input a single "action block"
# node. Adds boilerplate code (function definition, initialize "response"
# as an empty string so it does not trip up the REPL loop, and add a "while
# True:" loop to get the REPL loop). The action block itself takes the
# direction dictionary for that scene as a parameter so it can pass it to
# the get_response() function. It also passes the name of the class so
# get_response() knows which scene's cleanup block to call if the user is
# trying to move between scenes.
def _process_action_block(self, c):
commands = []
commands.append("def action(self):")
commands.append(" response = \"\"\n while True:")
if len(c.children) not in [0, 1]:
self._process_error("action block has wrong number of children")
commands.append(" response = get_response(" +
"self.directions)\n " +
"if isinstance(response, list):" +
"\n self.cleanup()\n" +
" return response[0]\n")
if len(c.children) == 1:
if c[0].type != "suite":
self._process_error("action block doesn't have suite child")
else:
commands.append(self._process_suite(c[0], 3)[5:])
return commands
# This function processes suite node and distinguishes its children
# nodes from simple statement if the value of the suite is "simple"
# and statements if the value is not specified.
def _process_suite(self, suite, indentlevel=1):
commands = ""
if len(suite.children) != 1:
self._process_error("Too many children in suite.")
else:
if suite.value == "simple":
commands += self._process_simple_smt(suite[0],
indentlevel)
else:
commands += self._process_statements(suite[0],
indentlevel)
return commands
# This function processes statements node which contains
# several statement nodes as its children nodes.
def _process_statements(self, statements, indentlevel=1):
commands = ''
for smt in statements.children:
commands += self._process_statement(smt, indentlevel)
return commands
# This function processes statement node which contains two
# different types of statements: simple statement and
# block statement.
def _process_statement(self, statement, indentlevel=1):
commands = ''
if statement.value == "simple":
commands += self._process_simple_smt(statement[0],
indentlevel)
elif statement.value == "block":
commands += self._process_block_smt(statement[0],
indentlevel)
else:
self._process_error("Not accepted ")
return commands
# Statement is actually a suite node, but we're keeping the name for
# backwards-compatability. This function takes suite node and passes
# to different types of simple statements based on the value of the
# suite node.
def _process_simple_smt(self, smt, indentlevel=1):
commands = ''
prefix = "\n" + " "*indentlevel
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'simple statement'. Unfortunately, that is " +
"all we know.")
if len(smt.children) == 0:
self._process_error("Simple statement has no children to process.",
smt.lineno)
if smt.value == "say":
commands += prefix + self._process_say_smt(smt[0])
elif smt.value == "exposition":
commands += prefix + self._process_expo_smt(smt[0])
elif smt.value == "win":
commands += self._process_win_smt(smt[0], indentlevel)
elif smt.value == "lose":
commands += self._process_lose_smt(smt[0], indentlevel)
elif smt.value == "expression":
commands += self._process_expression_smt(smt[0],
indentlevel)
elif smt.value == "flow":
commands += self._process_flow_smt(smt[0], indentlevel)
return commands
# This function takes block statement node which includes
# "if statement" and "while statement" type children
# nodes.
def _process_block_smt(self, smt, indentlevel):
commands = ''
prefix = "\n" + " "*indentlevel
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'block statement'. Unfortunately, that is " +
"all we know.")
if len(smt.children) != 1:
self._process_error("Block statement has no children to process.",
smt.lineno)
if smt[0].type == "if_statement":
commands += prefix + \
self._process_ifstatement(smt[0], indentlevel)
elif smt[0].type == "while_statement":
commands += prefix + \
self._process_whilestatement(smt[0],
indentlevel)
else:
self._process_error("Block statement does not have valid child " +
"node.")
return commands
# Say statement function is called from simple statement and
# passes node to _process_testlist()
def _process_say_smt(self, smt):
commands = 'print '
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'say statement'. Unfortunately, that is " +
"all we know.")
if len(smt.children) == 0:
self._process_error("Say statement has no children to process.",
smt.lineno)
commands += self._process_testlist(smt[0])
return commands
# Exposition statement passes node to _process_testlist
def _process_expo_smt(self, smt):
commands = 'print '
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'exposition statement'. Unfortunately, " +
"that is all we know.")
if len(smt.children) == 0:
self._process_error("Exposition statement has no children to" +
" process.", smt.lineno)
commands += self._process_testlist(smt[0])
return commands
# Win statement prints the string if there is and exits the scene
def _process_win_smt(self, smt, indentlevel):
prefix = "\n" + " "*indentlevel
commands = ""
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'win statement'. Unfortunately, that is " +
"all we know.")
if len(smt.children) != 0:
commands += prefix + "print "\
+ self._process_testlist(smt[0])
commands += prefix + "exit(0)"
return commands
# Lose statement prints the string if there is and exits the scene
def _process_lose_smt(self, smt, indentlevel):
prefix = "\n" + " "*indentlevel
commands = ""
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'lose statement'. Unfortunately, that is " +
"all we know.")
if len(smt.children) != 0:
commands += prefix + "print "\
+ self._process_testlist(smt[0])
commands += prefix + "exit(0)"
return commands
# Expression statement takes expression statement node. If the value
# of the node is "testlist", then the function passes it to testlist
# function. If the value of the node is "is", it indicates that a
# new variable being declared. If the value of the node is "godis",
# the function creates namespace for the god variable and makes sure
# that the god variable can only be declared onece.
def _process_expression_smt(self, smt, indentlevel):
prefix = '\n' + ' '*indentlevel
commands = ''
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'expression statement'. Unfortunately," +
" that is all we know.")
if len(smt.children) == 0:
self._process_error("expression statement has no children to" +
" process.",
smt.lineno)
elif smt.value == "testlist":
commands += prefix + self._process_testlist(smt[0])
elif smt.value == "is":
entry = self.symtab.getWithKey(smt[0].key)
if entry and (entry.god or isinstance(entry.scope, str) and
entry.scope.startswith("item")):
commands += prefix + "self." + smt[0].value + " = "
else:
commands += prefix + "self.__namespace['" + \
smt[0].value + "'] = "
commands += self._process_testlist(smt[1])
elif smt.value == "godis":
commands += prefix + "try:"
commands += prefix + " self." + smt[0].value
commands += prefix + "except AttributeError:"
commands += prefix + " self." + smt[0].value + " = "
commands += self._process_testlist(smt[1])
return commands
# This function takes flow statement node and passes the node to different
# flow statements.
def _process_flow_smt(self, smt, indentlevel):
prefix = "\n" + " "*indentlevel
commands = ""
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'flow statement'. Unfortunately, that is " +
"all we know.")
if len(smt.children) != 1:
self._process_error("Flow statement has incorrect number of " +
"children to process.", smt.lineno)
if smt[0].type == "continue_statement":
commands += prefix + self._process_continue(smt[0])
elif smt[0].type == "break_statement":
commands += prefix + self._process_break(smt[0])
elif smt[0].type == "moves_declaration":
commands += prefix + self._process_moves_dec(smt[0])
elif smt[0].type == "moveto_statement":
commands += self._process_moveto(smt[0], indentlevel)
else:
self._process_error("flow statement has wrong type of child")
return commands
# This function takes continue statement node and return "continue"
# if there is no error.
def _process_continue(self, smt):
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'continue statement'. Unfortunately, that " +
"is all we know.")
return "continue"
# This function takes break statement node and "return" "break" if
# there is no error
def _process_break(self, smt):
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'break statement'. Unfortunately, that is " +
"all we know.")
return "break"
# This function takes moves_declaration type and produces a direction
# dictionary whose the key is the direction and the value is the scene
# id.
def _process_moves_dec(self, smt):
commands = "if not self.directions: self.directions = {"
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'moves declaration'. Unfortunately, that " +
"is all we know.")
if len(smt.children) != 1:
self._process_error("moves declaration has wrong number of " +
"children")
elif smt[0].type != "directionlist":
self._process_error("moves declaration has wrong type of children")
else:
commands += self._process_directionlist(smt[0]) + "}"
return commands
# This function creates the directionlist.
def _process_directionlist(self, smt):
commands = ""
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'directionlist'. Unfortunately, that is " +
"all we know.")
if len(smt.children) < 1:
self._process_error("directionlist has no children")
l = len(smt.children) - 1
for i, d in enumerate(smt.children):
commands += "'" + str(self._process_direction(d)) + "': "
if len(d.children) != 1:
self._process_error("incorrect children of direction")
else:
commands += str(d[0].value)
if l != i:
commands += ", "
return commands
# This function takes moveto_statement type node and adds return
# function in current cleanup() function.
def _process_moveto(self, smt, indentlevel):
commands = ""
prefix = "\n" + " "*indentlevel
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'moveto statement'. Unfortunately, that is " +
"all we know.")
commands += prefix + "self.cleanup()"
if len(smt.children) != 1:
self._process_error("moveto has the wrong number of children")
elif smt[0].type != "sceneid":
self._process_error("moveto has wrong kind of child")
else:
commands += prefix + "return 's_" + str(smt[0].value)
commands += "_inst.setup()'"
return commands
# This function returns the value of the direction node.
def _process_direction(self, smt):
if not isinstance(smt, Node):
self._process_error("Something bad happened while processing " +
"'direction statement'. Unfortunately, that " +
"is all we know.")
return smt.value
# This function takes testlist node and iterates through all its children
# nodes which are test nodes.
def _process_testlist(self, testlist):
if not isinstance(testlist, Node):
self._process_error("Something bad happened while processing " +
"'testlist'. Unfortunately, that is all we " +
"know.")
if len(testlist.children) == 0:
self._process_error("Testlist has no children to process.",
testlist.lineno)
tests = testlist.children
testcode = []
for test in tests:
testcode.append(self._process_test(test))
return ", ".join(testcode)
# This function takes test node. Since the only children node of test node
# is or_test node, the function passes the node to or_test fuction after
# doing the error checking.
def _process_test(self, test):
if not isinstance(test, Node) or test.type != "test":
self._process_error("Something bad happened while processing " +
"'test'. Unfortunately, that is all we know.")
if len(test.children) != 1:
self._process_error("'test' has incorrect number of children.",
test.lineno)
return self._process_or_test(test[0])
# This function takes or_test node. The number of children node of
# or_test node can only be one or two. If the value of the node is
# "or", it indicates an or logic expression. If it is not, it indicates
# the children node is an and test node.
def _process_or_test(self, or_test):
if not isinstance(or_test, Node) or or_test.type != "or_test":
self._process_error("Something bad happened while processing " +
"'or_test'. Unfortunately, that is all we " +
"know.")
if len(or_test.children) not in [1, 2]:
self._process_error("'or_test' has incorrect number of children.",
or_test.lineno)
if or_test.value == 'or':
return '(' + self._process_or_test(or_test[0]) + ') or ' \
+ self._process_and_test(or_test[1])
else:
return self._process_and_test(or_test[0])
# This function takes and_test node. The number of children node of
# and_test node can only be one or two. If the value of the node is
# "and", it indicates an and logic expression. If it is not, it indicates
# a not test node.
def _process_and_test(self, and_test):
if not isinstance(and_test, Node) or and_test.type != "and_test":
self._process_error("Something bad happened while processing " +
"'and_test'. Unfortunately, that is all we " +
"know.")
if len(and_test.children) not in [1, 2]:
self._process_error("'and_test' has incorrect number of children.",
and_test.lineno)
if and_test.value == 'and':
return '(' + self._process_and_test(and_test[0]) + \
') and ' + self._process_not_test(and_test[1])
else:
return self._process_not_test(and_test[0])
# This function takes not test node. The number of its children node
# can only be one. If the value of the node is "not", the function
# would add "not" to the logic expression and recursively calls the
# not_test function. Otherwise, the function passes the children node
# to comparison function.
def _process_not_test(self, not_test):
if not isinstance(not_test, Node) or not_test.type != "not_test":
self._process_error("Something bad happened while processing " +
"'not_test'. Unfortunately, that is all we " +
"know.")
if len(not_test.children) != 1:
self._process_error("'not_test' has incorrect number of children.",
not_test.lineno)
if not_test.value == 'not':
return 'not ' + self._process_not_test(not_test[0])
else:
return self._process_comparison(not_test[0])
# This function takes comparison node. The children nodes of comparison
# node are comparison, comparison operator and expression if the
# value is "comparison". Otherwise, the children node is expression node.
def _process_comparison(self, comparison):
if not isinstance(comparison, Node) or comparison.type != "comparison":
self._process_error("Something bad happened while processing " +
"'comparison'. Unfortunately, that is all we" +
" know.")
if len(comparison.children) not in [1, 3]:
self._process_error("'comparison' has incorrect number of " +
"children.", comparison.lineno)
if comparison.value == 'comparison':
return '(' + self._process_comparison(comparison[0]) + \
') ' + self._process_comparisonop(comparison[1]) \
+ " " + self._process_expression(comparison[2])
else:
return self._process_expression(comparison[0])
# This function takes comparison operator node.
def _process_comparisonop(self, comparisonop):
return comparisonop.value
# This function takes while node. If the value of its children node
# is test, the function passes the children node to _process_test
# function to get the while condition. If it is not, it passes the
# children node to the _process_suite, adds one to indentlevel and
# processes the statement.
def _process_whilestatement(self, smt, indentlevel=1):
commands = "while "
if smt[0].type != "test":
self._process_error("No test in while loop", smt.lineno)
else:
commands += self._process_test(smt[0]) + ":"
if smt[1].type != "suite":
self._process_error("No suite in while loop", smt.lineno)
else:
commands += self._process_suite(smt[1], indentlevel+1)
return commands
# This function takes statement node with "if" value, or an elif node.
# Note, because of the embedding structure, we need to process it this
# way, and the constructions are identical, except "if" vs "elif" token.
def _process_ifstatement(self, smt, indentlevel):
prefix = "\n" + " "*indentlevel
commands = prefix + "if "
commands += self._process_test(smt[0]) + ":"
commands += self._process_suite(smt[1], indentlevel+1)
if smt[2]:
commands += self._process_elifstatements(smt[2],
indentlevel)
if smt[3]:
commands += prefix + "else:"
commands += self._process_suite(smt[3], indentlevel+1)
return commands
# This function takes elifstaments node and iterates through
# all its children node which is elifstatement node.
def _process_elifstatements(self, elif_smts, indentlevel):
commands = ""
for child in elif_smts.children:
if child.type != "elif_statement":
self._process_error("Invalid child of elif_statements",
elif_smts.lineno)
else:
commands += self._process_elifstatement(child, indentlevel)
return commands
# This function takes elif statement node. If the children type
# is test, it passes the node to process_test. Otherwise, it
# passes the children node to process_test. This function has
# a simliar structure with while statement.
def _process_elifstatement(self, smt, indentlevel):
prefix = "\n" + " "*indentlevel
commands = prefix + "elif "
if smt[0].type != "test":
self._process_error("Invalid elif tree", smt.lineno)
else:
commands += self._process_test(smt[0]) + ":"
if smt[1].type != "suite":
self._process_error("Invalid elif tree", smt.lineno)
else:
commands += self._process_suite(smt[1], indentlevel+1)
return commands
# This function takes "expression" node. Expression node
# only has arithmetic_expression as its children node. The function
# passes the node to the process_arithmetic_expression function.
def _process_expression(self, expression):
return self._process_arithmetic_expression(expression[0])
# This function takes atom nodes. If the atom node is a leaf node,
# it could be a string node or an id node. For the string node, the
# function returns the value. For the id or the godid node, the
# function returns "self.__namespace" or "self." If the node is not
# a leaf node, the function passes children node to different
# functions which deal with test, list, number and boolean.
def _process_atom(self, atom):
if not isinstance(atom, Node) or atom.type != "atom":
self._process_error("Something bad happened while processing " +
"'atom'. Unfortunately, that is all we " +
"know.")
if atom.is_leaf():
if atom.v_type == "string":
return repr(str(atom.value))
else:
if not atom.v_type:
self._process_error("Name Error: " + str(atom.value) +
" is not defined.", atom.lineno)
else:
entry = self.symtab.getWithKey(atom.key)
if not entry:
return atom.value
if entry and entry.god:
return "self." + atom.value
else:
return "self.__namespace['" + atom.value + "']"
if len(atom.children) != 1:
self._process_error("'atom' has incorrect number of " +
"children.", atom.lineno)
if atom.value == "test":
return "(" + self._process_test(atom[0]) + ")"
elif atom.value == "list":
return self._process_list(atom[0])
elif atom.value == "number":
return self._process_number(atom[0])
elif atom.value == "boolean":
return self._process_boolean(atom[0])
else:
self._process_error("'atom' has unknown child type.", atom.lineno)
# This function processes number nodes.
def _process_number(self, number):
if not isinstance(number, Node) or number.type != "number":
self._process_error("Something bad happened while processing " +
"'number'. Unfortunately, that is all we " +
"know.")
if number.is_leaf():
return str(number.value)
else:
self._process_error("'number' has children. It should be sterile.",
number.lineno)
# This function processes boolean nodes.
def _process_boolean(self, boolean):
if not isinstance(boolean, Node) or boolean.type != "boolean":
self._process_error("Something bad happened while processing " +
"'boolean'. Unfortunately, that is all we " +
"know.")
if boolean.is_leaf():
return str(boolean.value)
else:
self._process_error("'boolean' has children. It should be " +
"sterile.", boolean.lineno)
# This function takes arithmetic expressions node. The function
# recursively processes the arithmetic expression if the value
# of its children node is not term. The arithmetic expression
# is composed of arithmetic expression, arithmetic operator and
# term.
def _process_arithmetic_expression(self, arith_exp):
if not isinstance(arith_exp, Node) or \
arith_exp.type != "arithmetic_expression":
self._process_error("Something bad happened while processing " +
"'arithmetic_expression'. Unfortunately, " +
"that is all we know.")
if len(arith_exp.children) not in [1, 2]:
self._process_error("'arithmetic_expression' has incorrect " +
"number of children.", arith_exp.lineno)
if arith_exp.value == "term":
return self._process_term(arith_exp[0])
elif arith_exp.value in ['+', '-']:
return '(' + \
self._process_arithmetic_expression(arith_exp[0]) + \
') ' + arith_exp.value + ' ' + \
self._process_term(arith_exp[1])
else:
self._process_error("Illegal operation type for " +
"'arithmetic_expression'", arith_exp.lineno)
# This function takes term node. Term node deals with multiple and divide,
# which have higher precedence than plus and minus. If the value of
# children node is factor, the function passes the node to
# _process_factor function.
def _process_term(self, term):
if not isinstance(term, Node) or term.type != "term":
self._process_error("Something bad happened while processing " +
"'term'. Unfortunately, " +
"that is all we know.")
if len(term.children) not in [1, 2]:
self._process_error("'term' has incorrect " +
"number of children.", term.lineno)
if term.value == "factor":
return self._process_factor(term[0])
elif term.value in ['*', '/', '//']:
return '(' + \
self._process_term(term[0]) + \
') ' + term.value + ' ' + \
self._process_factor(term[1])
else:
self._process_error("Illegal operation type for " +
"'term'", term.lineno)
# This function takes factor node. If the value of factor is power,
# the function passes the node to process_power function. Otherwise,
# the value of factor should fall in plus and minus.
def _process_factor(self, factor):
if not isinstance(factor, Node) or factor.type != "factor":
self._process_error("Something bad happened while processing " +
"'factor'. Unfortunately, " +
"that is all we know.")
if len(factor.children) != 1:
self._process_error("'factor' has incorrect " +
"number of children.", factor.lineno)
if factor.value == "power":
return self._process_power(factor[0])
elif factor.value in ['+', '-']:
return '(' + factor.value + \
self._process_factor(factor[0]) + ')'
else:
self._process_error("Illegal operation type for " +
"'factor'", factor.lineno)
# This function takes power node. If the value of the power is "atom",
# the function passes the children node to function _process_atom. If
# the value of the power is "trailer", the function then processes
# the value type of its first child. If the value type is id, the function
# just passes. If the value type is poceket or otherwise (for narratr it
# could only be list), the function passes nodes to process_pocket or
# process_list functions.
def _process_power(self, power):
if not isinstance(power, Node) or power.type != "power":
self._process_error("Something bad happened while processing " +
"'power'. Unfortunately, " +
"that is all we know.")
if power.value == "atom":
return self._process_atom(power[0])
elif power.value == "trailer":
if power[0].v_type == "id":
if power[0].value in ["str", "int", "float"]:
pass
elif not self.symtab.get(power[0].value, "GLOBAL"):
if power[0].value == "pocket":
return self._process_pocket(power)
atom = self._process_atom(power[0])
trailers = ''
for trailer in power.children[1:]:
trailers += self._process_trailer(trailer)
return atom + trailers
else:
self._process_error("Illegal operation type for " +
"'power'", power.lineno)
# This function processes trailers.
def _process_trailer(self, trailer):
if not isinstance(trailer, Node) or trailer.type != "trailer":
self._process_error("Something bad happened while processing " +
"'trailer'. Unfortunately, " +
"that is all we know.")
if len(trailer.children) != 1:
self._process_error("'trailer' has incorrect " +
"number of children.", trailer.lineno)
if trailer.value == "dot":
return "." + str(trailer[0])
elif trailer.value == "calllist":
return self._process_calllist(trailer[0])
else:
self._process_error("Illegal value type for 'trailer'",
trailer.lineno)
# This function processes calllist. If the value of calllist is
# "args", the function passes its children node to process_args
# function.
def _process_calllist(self, calllist):
if not isinstance(calllist, Node) or calllist.type != "calllist":
self._process_error("Something bad happened while processing " +
"'calllist'. Unfortunately, " +
"that is all we know.")
if len(calllist.children) not in [0, 1]:
self._process_error("'calllist' has incorrect " +
"number of children.", calllist.lineno)
if not calllist.value:
return '()'
elif calllist.value == 'args':
return '(' + self._process_args(calllist[0]) + ')'