garnet.l, garnet.y,
and as many .c files as you like. (Yes, you can put
C++ code in a file whose filename extension is .c.)All these files must appear in the same directory in which you place the Makefile.
Sources containing one line such as the following:
CSOURCES=AST.c foo.c
This line assigns the Makefile variable CSOURCES a string value
which is the sequence of .c file names of
all the .c files (other than
lex.yy.c and garnet.tab.c that
will be used in creating your executable file.
#include <iostream> using namespace std;
ASTNode class containing the pure virtual
method GarnetObject eval(). This class will be the
superclass of all AST nodes that your parser creates.
The GarnetObject class will be discussed further below.
ASTNode class must contain the pure virtual method
void ASTprint(ostream &o). Each subclass will
implement the ASTprint method in such a way that a
representation
of code that could have led to the construction of that
node will be printed. A literal prints by printing the
representation of its internally stored value that would be
printed using the ostream& operator << of C++.
Each semicolon separated expression should be printed on one
line with a terminating semicolon.
ASTNode objects is handled by
providing the following method definition:
ostream & operator << (ostream &o, ASTNode a)
{
a.ASTprint(o);
return o;
}
ASTNode *.
ASTNode subclass for that grammar symbol.
For example, you may have an ASTNode subclass for integers,
such as IntegerNode. An IntegerNode would no doubt contain
an int value field.
ASTNode objects it associates with terminal
symbols.
MethodInvocationNode
object, so that the appropriate method in the receiver object
can be called.
ASTprint function for the MethodInvocationNode shall
print
Class, Method, TrueClass, FalseClass,
NilClass, and String to the class
hierarchy
self
nil
NilClass (a false value)
true
TrueClass (a typical true value)
false
FalseClass (a false value)
You can implement the pseudo variable nature of these objects by binding their names in the Global frame.
Class, Method, Object, Float, Integer, String,
Array, TrueClass, FalseClass, and NilClass.
You can get extra credit for implementing Hash.
:: |
[] . |
- (unary) + (unary) ! |
* / |
+ - |
> >= < <= |
== |
&& |
|| |
= |
Object),
then the undefined_method method is called with the
receiver object and method name as its arguments.
ASTNode subclass, define the method eval
to do the right thing to evaluate the
specified expression and yield a GarnetObject result value.Remember, every expression yields an object as its value.
The value returned by the Garnet print function is
nil.
Class, Method, Object, Float, Integer, String,
Array, TrueClass, FalseClass, and NilClass)
by hand.
The referencing environment can be thought of as a map
from names to values. Each value is a GarnetObject
(usually allocated in the heap).
At this point, don't worry about how to handle constants. We don't have any yet.
One might define a class somewhat like the following for representing these objects:
class GarnetObject {
private:
GarnetObject *myclass;
vector attributes;
...
}
Class, Method, Object, Float, Integer, String,
Array, TrueClass, FalseClass, and NilClass),
contains the following instance attributes:
superclass:class_attributes:instance_attributes:
When a method is called (whether primitive or not), it is
passed a single Garnet Array object containing
pointers to its receiver objects, and
all of its argument objects in order from left to right.
It is possible to optimize this behavior for speed by
changing the evaluation rule of the ASTNodes to permit the
possibility that an ASTNode could eval to something other
than a GarnetObject, however, you needn't make such an
optimization in your project.
print in the class Object should do its job
by invoking to_s on its argument
to convert it to a String. This string should then
be printed.Each built-in class should have a to_s method defined for it that does the right thing.
Integer) must be associated with a primitive value of
the associated type (such as a C++ int for integer).There are a variety ways this can be achieved.
One way is to store the primitive value (or a pointer to it)
in the GarnetObject representation of the attribute value for
the object. You are free to use this idea or any other idea of your
choosing.
Primitive operations such as +, - and so forth are implemented
in such a way that they manipulate these primitive values properly
to achieve the desired end. You may want to write a complement of
primitive-value-extracting functions such as int
_getint(const GarnetObject *o) that will inspect the
argument GarnetObject, determine if it belongs to the
appropriate class (Integer in this case), and return the primitive
value.
For this increment of the project, you need only provide homogeneous operations, i.e. + between two integers or two floats, but not between and integer and float or float and integer.
Extra credit will be assigned for those who implement heterogeneous operations that promote integer values to float.