4. C++

4.1. C++ Application Development

4.1.1. Example 4.1

_images/cppcompilation.png

Fig. 1: <strong>Fig. 4.2: C++ Compile</strong>

1
2
3
4
5
#include <iostream>
using namespace std;
int main(int argc, char* argv[]) {
  cout << "Hello World!" << endl;
}

Fig. 4.1: hello.cpp

4.1.2. The Macro Processor

4.1.3. Namespaces

4.1.4. Example 4.2

from iostream import * # merges the namespace with the current module
import iostream # preserves the namespace while importing the module
import java.iostream.cout
1
2
3
4
#include <iostream>
int main(int argc, char* argv[]) {
  std::cout << "Hello World!" << std::endl;
}

Fig. 4.3: Namespace std

4.1.5. Defining the Main Function

4.1.6. I/O Streams

4.1.7. Example 4.3

cout << "Hello World";
(cout << "Hello World") << endl;

4.1.8. Compiling a C++ Program

4.1.9. Example 4.4

1
2
3
4
My Computer> g++ hello.cpp
My Computer> a.out
Hello World!
My Computer>

Fig. 4.4: compiling hello.cpp

1
2
3
4
My Computer> g++ -g -o hello hello.cpp
My Computer> hello
Hello World!
My Computer>

Fig. 4.5: Include Debug and Name

4.2. Overview of The CoCo Virtual Machine

_images/CoCoStructure.png

Fig. 2: <strong>Fig. 4.6: CoCo</strong>

4.3. Building a Large Project

4.3.1. Separate Compilation

4.3.2. Example 4.5

g++ -g -c -std=c++0x PyObject.cpp
g++ -o coco -std=c++0x main.o PyObject.o PyInt.o ...

4.3.3. Example 4.6

g++ -g -c -std=c++0x *.cpp
g++ -g -o coco -std=c++0x *.o

4.3.4. The Make Facility

4.3.5. Example 4.7

PyObject.o: PyObject.cpp PyObect.h
  g++ -g -c -std=c++0x PyObject.cpp
coco: main.o PyObject.o PyInt.o PyType.o ....
  g++ -o coco -std=c++0x main.o PyObject.o PyInt.o PyType.o ....
./rebuild
./configure
make

4.4. Static Type Checking

4.5. Declaring Variables

int x;
int* y = new int;

4.5.1. Example 4.8

_images/heapstack.png

Fig. 3: <strong>Fig. 4.3: Variable Declaration</strong>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream>
using namespace std;

int main(int argc, char* argv[]) {
  int x;
  int* y = new int;

  x = 6;
  *y = 5;

  cout << x << *y << endl;
}

Fig. 4.7: Ints and Pointers

4.5.2. Example 4.9

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
int main(int argc, char* argv[]) {
    char* filename;
    ...
    try {
        istream* in = new ifstream(filename);
        PyScanner* scan = new PyScanner(in);
        PyParser* parser = new PyParser(scan);
        vector<PyCode*>* code = parser->parse();
        string indent = "";
        ...
        unordered_map<string, PyObject*> globals;
        ...
        bool foundMain = false;
        ...
        vector<PyObject*>* args = new vector<PyObject*>();
        PyObject* result = globals["main"]->callMethod("__call__", args);
    } catch (PyException* ex) {
        ...
    }
    return 0;
}

Fig. 4.8: main.cpp

Practice 4.1

Which of the variables declared above appear in the run-time stack. Which of these variables point to values on the heap?

You can check your answer(s) here.

4.6. Pointers and Arrays

1
2
3
4
int x = 5;
int* y = &x;
(*y)++;
cout << x << endl;

Fig. 4.9: Stack Address

4.6.1. Example 4.10

_images/chararrays.png

Fig. 4: <strong>Fig. 4.11: Character Arrays</strong>

 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
#include <iostream>
using namespace std;

int main(int argc, char* argv[]) {
  int i = 0;
  char s[] = "hello world";
  char* t = new char[15];
  char* u;
  strcpy(t,s);
  cout << t << endl;
  while (s[i]!=0) {
    cout << s[i];
    i++;
  }
  cout << endl;
  i = 0;
  while (t[i]!='\0') {
    cout << t[i];
    i++;
  }
  cout << endl;
  u = t;
  while(*u!=0) {
    cout << *u;
    u++;
  }
  cout << endl;
}

Fig. 4.10: Array Operations

4.6.2. Example 4.11

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
int main(int argc, char* argv[]) {
    char* filename;
    ...
    if (argc != 2 && argc != 3) {
        cerr << "Invoke as : coco [-v] filename" << endl;
        return 0;
    }
    PyTypes = initTypes();
    if (argc == 2)
        filename = argv[1];
    else {
        filename = argv[2];
        verbose = true;
    }

Fig. 4.12: Command-line Arguments

coco -v myprog.casm
int main(int argc, char** argv) {
  ...
1
2
3
if (!strcmp(s,t)) {
  cout << "they are equal" << endl;
}

Fig. 4.13: strcmp function

4.6.3. Example 4.12

4.7. Writing Functions

4.7.1. Example 4.13

1
2
3
4
5
6
void A(int a1, char* a2) {
  ...
}
int main(int argc, char* argv[]) {
  A();
}

Fig. 4.14: Function Declarations

1
2
3
4
5
6
7
8
9
void A(int a1, char* a2) {
  B();
}
void B(int b1, char* b2) {
  A();
}
int main(int argc, char* argv[]) {
  A();
}

Fig. 4.15: Mutually Recursive

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void B(int, char*);
void A(int a1, char* a2) {
  B();
}
void B(int b1, char* b2) {
  A();
}
int main(int argc, char* argv[]) {
  A();
}

Fig. 4.16: Function Prototypes

int main(int argc, char* argv[]);
void main();
void main(int argc, char* argv[]);
int main();

Fig. 4.17: Signatures of Main

4.8. Parameter Passing and Return Values

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <iostream>
using namespace std;

int add(int x, int y) {
  x = x + y;
  return x;
}

int main(int argc, char* argv[]) {
  int a = 5;
  int b = 6;
  int c = add(a,b);

  cout << a << "+" << b << " = " << c << endl;
}

Fig. 4.18: Pass By Value

4.8.1. Example 4.14

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;

void printit(char* msg) {
  while (*msg!=0) {
    cout << *msg;
    msg++;
  }
  cout << endl;
}

int main(int argc, char* argv[]) {
  char msg[] = "hello world";
  printit(msg);
  cout << "original msg is :" << msg << endl;
}

Fig. 4.19: Passing Arrays

4.8.2. Example 4.15

_images/overwritten.png

Fig. 5: <strong>Fig. 4.21: Bad Return Value</strong>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;

void printit(int times, char* msg) {
  int k;
  for (k=0;k<times;k++) {
    cout << msg << endl;
  }
}
char* getMsg() {
  char msg[] = "hello world";
  return msg;
}
int main(int argc, char* argv[]) {
  char* msg = getMsg();
  printit(4,msg);
}

Fig. 4.20: A Poorly Written Program

MyComputer> g++ test3.cpp
test3.cpp:15:9: warning: address of stack memory associated with local variable 'msg' returned...
        return msg;
               ^~~
1 warning generated.
#include <iostream>
using namespace std;

void swap(int* x, int* y) {
  int tmp;
  tmp = *x;
  *x = *y;
  *y = tmp;
}
int main(int argc, char* argv[]) {
  int a = 6;
  int b = 7;
  swap(&a,&b);
  cout << a << " " << b << endl;
}

Fig. 4.22: Pointer Swap

4.8.3. Example 4.16

Practice 4.2

Earlier in the chapter the function strcpy was introduced in an example on the section on Pointers and Arrays. The strcpy(target,source) function copies all the characters in source to target. Write the function strcpy and write a little code to test it.

You can check your answer(s) here.

4.9. C++ References

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <iostream>
using namespace std;

void swap(int& x, int& y) {
  int tmp;
  tmp = x;
  x = y;
  y = tmp;
}
int main(int argc, char* argv[]) {
  int a = 6;
  int b = 7;
  swap(a,b);
  cout << a << " " << b << endl;
}

Fig. 4.23: Reference Swap

4.9.1. Example 4.17

4.10. Const in C++

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <iostream>
using namespace std;

int add(const int& x, const int& y) {
  x = x + y;
  return x;
}

int main(int argc, char* argv[]) {
  int a = 5;
  int b = 6;
  int c = add(a,b);

  cout << a << "+" << b << " = " << c << endl;
}

Fig. 4.24: Constant References First Try

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <iostream>
using namespace std;

int add(const int& x, const int& y) {
  return x + y;
}

int main(int argc, char* argv[]) {
  int a = 5;
  int b = 6;
  int c = add(a,b);

  cout << a << "+" << b << " = " << c << endl;
}

Fig. 4.25: Constant References

4.10.1. Example 4.18

4.11. Header Files

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include "intfunctions.h"
#include <iostream>
using namespace std;
int main(int argc, char* argv[]) {
  int a = 5;
  int b = 6;
  int c = add(a,b);
  cout << a << "+" << b <<
      " = " << c << endl;
}

Fig. 4.26: test.cpp

1
2
3
4
#include "intfunctions.h"
int add(int x, int y) {
  return x + y;
}

Fig. 4.27: intfunctions.cpp

1
2
3
4
#ifndef intfunctions_h
#define intfunctions_h
int add(int x, int y);
#endif

Fig. 4.28: intfunctions.h

4.11.1. Example 4.19

1
2
3
4
5
My Computer> g++ -c intfunctions.cpp
My Computer> g++ -c test.cpp
My Computer> g++ -o test test.o intfunctions.o
My Computer> test
5+6 = 11

Fig. 4.29: Separate Compilation

4.12. OOP using C++

4.13. Pre-defined C++ Classes

4.13.1. Example 4.20

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <string>
#include <sstream>
#include <iostream>
using namespace std;

int main(int argc, char* argv[]) {
  string s = "6";
  int x;
    try {
        istringstream in(s);
        in.exceptions(ios_base::failbit | ios_base::badbit);
        in >> x;
        cout << x + 1 << endl;
    } catch (...) {
        cout << "An error occurred." << endl;
    }
}

Fig. 4.30: String Streams

4.13.1.1. Stream Classes

4.14. The Standard Template Library

#include <vector>

   vector<PyCode*>* vec = new vector<PyCode*>();
   vec->push_back(code);
   vec = FunctionList(vec);
   return vec;

Fig. 4.31: Vector Code

4.14.1. Example 4.21

4.14.1.1. The unordered_map Template

#include <unordered_map>

      unordered_map<string, PyObject*> globals;
      globals["print"] = new PyBuiltInPrint();
      globals["fprint"] = new PyBuiltInFPrint();
      globals["tprint"] = new PyBuiltInTPrint();
      globals["input"] = new PyBuiltInInput();
      globals["iter"] = new PyBuiltInIter();
      globals["int"] = PyTypes[PyIntType];
      globals["float"] = PyTypes[PyFloatType];
      globals["str"] = PyTypes[PyStrType];
      globals["funlist"] = PyTypes[PyFunListType];
      globals["type"] = PyTypes[PyTypeType];
      globals["bool"] = PyTypes[PyBoolType];
      globals["range"] = PyTypes[PyRangeTypeId];
      globals["Exception"] = PyTypes[PyExceptionTypeId];
      globals["len"] = new PyBuiltInLen();
      globals["concat"] = new PyBuiltInConcat();

Fig. 4.31: Unordered Map Code

4.15. Operator Overloading

ostream& operator <<(ostream &os, PyObject &t) {
    return os << t.toString();
}

Fig. 4.32: Operator Overloading

4.15.1. Example 4.22

4.16. Classes and Objects

4.16.1. Example 4.23

 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
#ifndef PYOBJECT_H_
#define PYOBJECT_H_

#include <string>
#include <unordered_map>
#include <vector>
#include <iostream>
using namespace std;

class PyType;

class PyObject {
public:
  PyObject();
  virtual ~PyObject();
  virtual PyType* getType();
  virtual string toString();
  void decRef();
  void incRef();
  int getRefCount() const;
  PyObject* callMethod(string name, vector<PyObject*>* args);

protected:
  unordered_map<string, PyObject* (PyObject::*)(vector<PyObject*>*)> dict;
  int refCount;
  virtual PyObject* __str__(vector<PyObject*>* args);
  virtual PyObject* __type__(vector<PyObject*>* args);
};

ostream& operator << (ostream& os, PyObject& t);
extern bool verbose;
#endif /* PYOBJECT_H_ */

Fig. 4.33: PyObject.h

 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
#include "PyObject.h"
#include "PyException.h"
#include "PyStr.h"
#include <iostream>
#include "PyType.h"
#include <sstream>
using namespace std;

PyObject::PyObject() {
    dict["__str__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyObject::__str__);
    dict["__type__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyObject::__type__);
}
PyObject::~PyObject() {
}
// some methods omitted.
PyType* PyObject::getType() {
    return NULL;
}
string PyObject::toString() {
    return "PyObject()";
}
PyObject* PyObject::__str__(vector<PyObject*>* args) {
    ostringstream msg;
    if (args->size() != 0) {
        msg << "TypeError: expected 0 arguments, got " << args->size();
        throw new PyException(PYWRONGARGCOUNTEXCEPTION,msg.str());
    }
    return new PyStr(toString());
}
PyObject* PyObject::__type__(vector<PyObject*>* args) {
    ostringstream msg;
    if (args->size() != 0) {
        msg << "TypeError: expected 0 arguments, got " << args->size();
        throw new PyException(PYWRONGARGCOUNTEXCEPTION,msg.str());
    }
    return (PyObject*)this->getType();
}

Fig. 4.34: PyObject.cpp

4.17. Inheritance

4.17.1. Example 4.24

 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
#ifndef PYINT_H
#define PYINT_H

#include "PyCallable.h"
#include <vector>
using namespace std;

class PyInt : public PyObject {
public:
    PyInt(int val);
    PyInt(const PyInt& orig);
    virtual ~PyInt();
    PyType* getType();
    string toString();
    int getVal();

protected:
    int val;

    virtual PyObject* __add__(vector<PyObject*>* args);
    virtual PyObject* __sub__(vector<PyObject*>* args);
    virtual PyObject* __mul__(vector<PyObject*>* args);
    virtual PyObject* __floordiv__(vector<PyObject*>* args);
    virtual PyObject* __truediv__(vector<PyObject*>* args);
    virtual PyObject* __eq__(vector<PyObject*>* args);
    virtual PyObject* __gt__(vector<PyObject*>* args);
    virtual PyObject* __lt__(vector<PyObject*>* args);
    virtual PyObject* __ge__(vector<PyObject*>* args);
    virtual PyObject* __le__(vector<PyObject*>* args);
    virtual PyObject* __float__(vector<PyObject*>* args);
    virtual PyObject* __int__(vector<PyObject*>* args);
    virtual PyObject* __bool__(vector<PyObject*>* args);
};

#endif  /* PYINT_H */

Fig. 4.35: PyInt.h

 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
#include "PyInt.h"
#include "PyType.h"
#include "PyStr.h"
#include "PyBool.h"
#include "PyFloat.h"
#include "PyException.h"
#include <iostream>
#include <sstream>
#include <math.h>
using namespace std;

PyInt::PyInt(int val): PyObject() {
    this->val = val;
    dict["__add__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__add__);
    dict["__sub__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__sub__);
    dict["__mul__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__mul__);
    dict["__floordiv__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__floordiv__);
    dict["__truediv__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__truediv__);
    dict["__eq__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__eq__);
    dict["__gt__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__gt__);
    dict["__lt__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__lt__);
    dict["__ge__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__ge__);
    dict["__le__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__le__);
    dict["__float__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__float__);
    dict["__int__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__int__);
    dict["__bool__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__bool__);
}
PyInt::PyInt(const PyInt& orig): PyInt(orig.val) {
}
// some methods omitted
string PyInt::toString() {
    stringstream ss;
    ss << val;
    return ss.str();
}
PyObject* PyInt::__add__(vector<PyObject*>* args) {
    PyInt* x;
    PyFloat* y;
    ostringstream msg;
    if (args->size() != 1) {
        msg << "TypeError: expected 1 arguments, got " << args->size();
        throw new PyException(PYWRONGARGCOUNTEXCEPTION,msg.str());
    }
    PyObject* arg = (*args)[0];
    switch (arg->getType()->typeId()) {
        case PyIntType:
            x = (PyInt*) arg;
            return new PyInt(this->val + x->val);
        case PyFloatType:
            y = (PyFloat*) arg;
            return new PyFloat(this->val + y->getVal());
        default:
            throw new PyException(PYILLEGALOPERATIONEXCEPTION,
                 "Invalid types for +: int and " + arg->getType()->toString());
    }
}
PyObject* PyInt::__eq__(vector<PyObject*>* args) {
    ostringstream msg;
    if (args->size() != 1) {
        msg << "TypeError: expected 1 arguments, got " << args->size();
        throw new PyException(PYWRONGARGCOUNTEXCEPTION,msg.str());
    }
    //We should check the type of args[0] before casting it.
    PyInt* other = (PyInt*) (*args)[0];
    return new PyBool(val == other->val);
}
PyType* PyInt::getType() {
    return PyTypes[PyIntType];
}
PyObject* PyInt::__float__(vector<PyObject*>* args) {
    ostringstream msg;
    if (args->size() != 0) {
        msg << "TypeError: expected 0 arguments, got " << args->size();
        throw new PyException(PYWRONGARGCOUNTEXCEPTION,msg.str());
    }
    return new PyFloat(this->getVal());
}

Fig. 4.36: PyInt.cpp

4.18. Constructors and Initilization Lists

4.18.1. Example 4.25

1
2
3
4
5
6
PyObject po;
PyObject qo;
PyObject* pop = new PyObject();
PyInt i(6);
PyInt* j = new PyInt(5);
PyInt k(i);

Fig. 4.37: Object Construction

1
2
3
4
5
PyType::PyType(string typeString,
        PyTypeId id) : PyCallable() {
    this->typeString = typeString;
    this->index = id;
}

Fig. 4.38: PyType Constructor

4.19. Polymorphism

4.19.1. Example 4.26

1
2
PyObject* obj = new PyInt(6);
cout << obj->toString << endl;

Fig. 4.39: Polymorphism

4.20. Abstract Classes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#ifndef PYCALLABLE_H
#define PYCALLABLE_H
#include "PyObject.h"
#include <string>
using namespace std;

class PyCallable : public PyObject {
public:
    PyCallable();
    PyCallable(const PyCallable& orig);
    virtual ~PyCallable();
protected:
    virtual PyObject* __call__(
        vector<PyObject*>* args) = 0;
};

#endif  /* PYCALLABLE_H */

Fig. 4.40: PyCallable.h

4.20.1. Example 4.27

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
PyObject::PyObject() {
    dict["__str__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyObject::__str__);
    dict["__type__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyObject::__type__);
}
PyCallable::PyCallable() : PyObject() {
    dict["__call__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyCallable::__call__);
}
PyType::PyType(string typeString, PyTypeId id) : PyCallable() {
    this->typeString = typeString;
    this->index = id;
}

Fig. 4.41: Three Constructors

4.21. Memory Management

4.21.1. Example 4.28

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
case CALL_FUNCTION:
   args = new vector<PyObject*>();
   //NOTE: Arguments are added backwards because they are popped
   //off the stack in reverse order.
   for (i = 0; i < operand; i++) {
       u = safetyPop();
       args->push_back(u);
   }
   u = safetyPop();
   v = u->callMethod("__call__", args);
   opStack->push(v);
   try {
       delete args;
   } catch (...) {
       cerr << "Delete of CALL_FUNCTION args caused an exception for some reason." << endl;
   }

   break;

Fig. 4.42: The CALL_FUNCTION Instruction

4.21.2. Example 4.29

 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
class PyParser {
public:
    PyParser(string filename);
    PyParser(const PyParser& orig);
    virtual ~PyParser();
    vector<PyCode*>* parse();
private:
    PyScanner* in;
    unordered_map<string,int> target;
    int index;

    vector<PyCode*>* PyAssemblyProg();
    vector<PyCode*>* FunctionListPart();
    vector<PyCode*>* FunctionList(vector<PyCode*>* vec);
    PyCode* FunDef();
    vector<PyObject*>* ConstPart(vector<PyCode*>* nestedFuns);
    vector<PyObject*>* ValueList(vector<PyObject*>* constants, vector<PyCode*>* nestedFunctions);
    vector<PyObject*>* ValueRest(vector<PyObject*>* constants, vector<PyCode*>* nestedFunctions);
    PyObject* Value(vector<PyCode*>* nestedFunctions);
    vector<string>* LocalsPart();
    vector<string>* FreeVarsPart();
    vector<string>* CellVarsPart();
    vector<string>* IdList(vector<string>* lst);
    vector<string>* IdRest(vector<string>* lst);
    vector<string>* GlobalsPart();
    vector<PyByteCode*>* BodyPart();
    vector<PyByteCode*>* InstructionList(vector<PyByteCode*>*);
    PyByteCode* LabeledInstruction();

};

Fig. 4.43: PyParser.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
PyParser::PyParser(string filename) {
    istream* stream = new ifstream(filename);
    this->in = new PyScanner(stream);

}
PyParser::~PyParser() {
    try {
        delete in;
    } catch (...) {
    }
}

Fig. 4.44: Excerpt of PyParser.cpp

4.22. Writing Templates

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
template <typename K, typename V>
void printMap(const unordered_map<K, V>& m) {
  typename unordered_map<K,V>::const_iterator it;
        for (it=m.begin();it != m.end(); it++) {
             cout << "key: \"" << it->first << "\" "
             << "value: " << it->second << endl;
        }
};
// To call the template function you write this...
printMap<string,int>(target);

Fig. 4.45: The printMap Function

 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
template<class T>
class __PyStackElement {
public:
  __PyStackElement(T element);
  virtual ~__PyStackElement();
  T object;
  __PyStackElement<T>* next;
};
template<class T>
class PyStack {
public:
  PyStack();
  virtual ~PyStack();
  T pop();
  void push(T object);
  T top();
  bool isEmpty();
  string toString();
  int getCount();
private:
  __PyStackElement<T>* tos;
  int count;
};
template <class T>
PyStack<T>::PyStack() {
  tos = NULL;
}
template <class T>
PyStack<T>::~PyStack() {
  if (tos != NULL) {
    try {
      delete tos;
    } catch (...) {}
  }
}
template <class T>
__PyStackElement<T>::~__PyStackElement() {
    if (next != NULL) {
    try {
      delete next;
    } catch (...) {}
  }
}
template <class T>
T PyStack<T>::pop() {
  if (tos != NULL) {
    __PyStackElement<T>* elem = tos;
    T val = elem->object;
    tos = tos->next;
    elem->next = NULL;
    delete elem;
    count --;
    return val;
  }
  throw new PyException(PYEMPTYSTACKEXCEPTION,
      "Attempt to pop an empty stack");
}
template <class T>
void PyStack<T>::push(T object) {
  __PyStackElement<T>* elem =
      new __PyStackElement<T>(object);
  elem->next = tos;
  tos = elem;
  count++;
}

Fig. 4.46: PyStack Template

4.22.1. Example 4.30

4.23. C++ Error Handling

4.24. Exception Handling

4.24.1. Example 4.31

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
PyObject* PyRange::indexOf(int index) {
    int val = start + index * increment;

    if (increment > 0 && val >= stop) {
        throw new PyException(PYSTOPITERATIONEXCEPTION,"Stop Iteration");
    }

    if (increment < 0 && val <= stop) {
        throw new PyException(PYSTOPITERATIONEXCEPTION,"Stop Iteration");
    }

    return new PyInt(start + increment*index);
}

Fig. 4.47: Throwing an Exception

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
case FOR_ITER:
    u = safetyPop();
    args = new vector<PyObject*>();
    try {
        v = u->callMethod("__next__", args);
        opStack->push(u);
        opStack->push(v);
    } catch (PyException* ex) {
        if (ex->getExceptionType() == PYSTOPITERATIONEXCEPTION) {
            PC = operand;
        } else
            throw ex;
    }
    try {
        delete args;
    } catch (...) {
        cerr << "Delete of FOR_ITER args caused an exception for some reason." << endl;
    }

    break;

Fig. 4.48: Catching an Exception

4.25. Signals

4.25.1. Example 4.32

 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
void sigHandler(int signum) {
    cerr << "\n\n";
    cerr << "*********************************************************" << endl;
    cerr << "            An Uncaught Exception Occurred" << endl;
    cerr << "*********************************************************" << endl;
    cerr << "Signal: ";
    switch (signum) {
        case SIGABRT:
            cerr << "Program Execution Aborted" << endl;
            break;
        case SIGFPE:
            cerr << "Arithmetic or Overflow Error" << endl;
            break;
        case SIGILL:
            cerr << "Illegal Instruction in Virtual Machine" << endl;
            break;
        case SIGINT:
            cerr << "Execution Interrupted" << endl;
            break;
        case SIGSEGV:
            cerr << "Illegal Memory Access" << endl;
            break;
        case SIGTERM:
            cerr << "Termination Requested" << endl;
            break;
    }

    cerr << "---------------------------------------------------------" << endl;
    cerr << "              The Exception's Traceback" << endl;
    cerr << "---------------------------------------------------------" << endl;
    for (int k=callStack.size()-1;k>=0;k--) {
        cerr << "==========> At PC=" << (callStack[k]->getPC()-1) << " in this function. " << endl;
        cerr << callStack[k]->getCode().prettyString("",true);
    }
    exit(0);
}

int main(int argc, char* argv[]) {
    char* filename;

    signal(SIGABRT,sigHandler);
    signal(SIGFPE,sigHandler);
    signal(SIGILL,sigHandler);
    signal(SIGINT,sigHandler);
    signal(SIGSEGV,sigHandler);
    signal(SIGTERM,sigHandler);
    ...

Fig. 4.49: Signal Handling

4.26. CoCo Components

_images/cocooverview.png

Fig. 6: <strong>Fig. 4.50: The CoCo Virtual Machine</strong>

4.26.1. The CoCo Scanner

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class PyScanner {
public:
  PyScanner(istream* in);
  virtual ~PyScanner();
  PyToken* getToken();
  void putBackToken();
private:
  istream* in;
  PyToken* lastToken;
  bool needToken;
  int colCount;
  int lineCount;
};

Fig. 4.51: PyScanner.h

_images/cocolexer.png

Fig. 7: <strong>Fig. 4.52: The CoCo Scanner FSM</strong>

4.26.2. Example 4.33

  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
PyScanner::PyScanner(istream* in) {
    this->in = in;
    this->needToken = true;
    this->colCount = -1;
    this->lineCount = 1;
    this->error = false;
}
PyScanner::~PyScanner() {
    try {
        delete in;
    } catch (...) {}
}
PyToken* PyScanner::getToken() {
    if (!needToken) {
        needToken = true;
        return lastToken;
    }
    PyToken* t;
    int state = 0;
    bool foundOne = false;
    char c;
    string lex;
    int type;
    int k;
    int column, line;
    c = in->get();
    while (!foundOne) {
        colCount++;
        switch (state) {
            case 0:
                lex = "";
                column = colCount;
                line = lineCount;
                if (isLetter(c)) state = 1;
                else if (isDigit(c)) state = 2;
                else if (c == '-') state = 11;
                else if (c == ':') state = 3;
                else if (c == ',') state = 4;
                else if (c == SINGLE_QUOTE) state = 6;
                else if (c == '"') state = 7;
                else if (c == '/') state = 8;
                else if (c == '(') state = 9;
                else if (c == ')') state = 10;
                else if (c == -1) {
                    foundOne = true;
                    type = PYEOFTOKEN;
                } else if (c == '\n') {
                    colCount = -1;
                    lineCount++;
                } else if (isWhiteSpace(c)) {
                } else if (in->eof()) {
                    foundOne = true;
                    type = PYEOFTOKEN;
                } else {
                    if (!error) {
                        cerr << "Unrecognized Character '" << c << "' found at line " <<
                                line << " and column " << column << endl;
                        error = true;
                    }
                    type = PYBADTOKEN;
                    lex = c;
                    foundOne = true;
                }
                break;
            case 1:
                if (isLetter(c) || isDigit(c)) state = 1;
                else {
                    for (k = 0; k < numberOfKeywords; k++)
                        if (lex == keywd[k]) {
                            foundOne = true;
                            type = PYKEYWORDTOKEN;
                        }
                    if (!foundOne) {
                        type = PYIDENTIFIERTOKEN;
                        foundOne = true;
                    }
                }
                break;
            case 2:
                if (isDigit(c)) state = 2;
                else if (c == '.') state = 5;
                else {
                    type = PYINTEGERTOKEN;
                    foundOne = true;
                }
                break;
            case 3:
                type = PYCOLONTOKEN;
                foundOne = true;
                break;
            case 4:
                type = PYCOMMATOKEN;
                foundOne = true;
                break;
            case 5:
                if (isDigit(c)) state = 5;
                else {
                    type = PYFLOATTOKEN;
                    foundOne = true;
                }
                break;
            case 6:
                if (c == SINGLE_QUOTE) {
                    type = PYSTRINGTOKEN;
                    lex = lex + c;
                    //eliminate the quotes on each end.
                    lex = lex.substr(1, lex.size() - 2);
                    c = in->get();
                    foundOne = true;

                    if (in->eof()) {
                        type = PYBADTOKEN;
                    }
                } else {
                    if (in->eof()) {
                        type = PYBADTOKEN;
                        foundOne = true;
                    }
                }
                break;
            case 7:
                if (c == '"') {
                    type = PYSTRINGTOKEN;

                    lex = lex + c;
                    //eliminate the quotes on each end.
                    lex = lex.substr(1, lex.size() - 2);
                    c = in->get();
                    foundOne = true;
                } else {
                    if (in->eof()) {
                        type = PYBADTOKEN;
                        foundOne = true;
                    }
                }
                break;
            case 8:
                foundOne = true;
                type = PYSLASHTOKEN;
                break;
            case 9:
                foundOne = true;
                type = PYLEFTPARENTOKEN;
                break;
            case 10:
                foundOne = true;
                type = PYRIGHTPARENTOKEN;
                break;
            case 11:
                if (isDigit(c))
                    state = 2;
                else {
                    type = PYBADTOKEN;
                    foundOne = true;
                }
        }
        if (!foundOne) {
            lex = lex + c;
            c = in->get();
        }
    }
    in->putback(c);
    colCount--;
    t = new PyToken(type, lex, line, column);
    lastToken = t;
    return t;
}
void PyScanner::putBackToken() {
    needToken = false;
}

Fig. 4.53: PyScanner.cpp

4.26.3. The CoCo Parser

 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
class PyParser {
public:
    PyParser(string filename);
    PyParser(const PyParser& orig);
    virtual ~PyParser();
    vector<PyCode*>* parse();
private:
    PyScanner* in;
    // These two fields are used when determining the addresses of
    // labels within the code that is generated for jump instructions.
    unordered_map<string,int> target;
    int index;
    vector<PyCode*>* PyAssemblyProg();
    vector<PyCode*>* FunctionListPart();
    vector<PyCode*>* FunctionList(vector<PyCode*>* vec);
    PyCode* FunDef();
    vector<PyObject*>* ConstPart(vector<PyCode*>* nestedFuns);
    vector<PyObject*>* ValueList(vector<PyObject*>* constants, vector<PyCode*>* nestedFunctions);
    vector<PyObject*>* ValueRest(vector<PyObject*>* constants, vector<PyCode*>* nestedFunctions);
    PyObject* Value(vector<PyCode*>* nestedFunctions);
    vector<string>* LocalsPart();
    vector<string>* FreeVarsPart();
    vector<string>* CellVarsPart();
    vector<string>* IdList(vector<string>* lst);
    vector<string>* IdRest(vector<string>* lst);
    vector<string>* GlobalsPart();
    vector<PyByteCode*>* BodyPart();
    vector<PyByteCode*>* InstructionList(vector<PyByteCode*>*);
    PyByteCode* LabeledInstruction();
};

Fig. 4.54: PyParser.h

 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
vector<PyCode*>* PyParser::FunctionList(vector<PyCode*>* vec) {
    PyToken* tok = in->getToken();
    in->putBackToken();
    if (tok->getLex() == "Function") {
        PyCode* code = FunDef();
        vec->push_back(code);
        vec = FunctionList(vec);
    }
    return vec;
}
PyCode* PyParser::FunDef() {
    PyToken* tok = in->getToken();
    if (tok->getLex() != "Function")
        badToken(tok, "Expected Function keyword.");
    tok = in->getToken();
    if (tok->getLex() != ":")
        badToken(tok, "Expected a colon.");
    PyToken* funName = in->getToken();
    if (funName->getType() != PYIDENTIFIERTOKEN)
        badToken(funName, "Expected an identifier.");
    tok = in->getToken();
    if (tok->getLex() != "/")
        badToken(tok, "Expected a '/'.");
    PyToken* numArgsToken = in->getToken();
    int numArgs;
    if (numArgsToken->getType() != PYINTEGERTOKEN)
        badToken(numArgsToken, "Expected an integer for the argument count.");
    istringstream numArgsIn(numArgsToken->getLex(), istringstream::in);
    numArgsIn.exceptions(ios_base::failbit | ios_base::badbit);
    numArgsIn >> numArgs;
    vector<PyCode*>* nestedFunctions = new vector<PyCode*>();
    nestedFunctions = FunctionList(nestedFunctions);
    vector<PyObject*>* constants = ConstPart(nestedFunctions);
    vector<string>* locals = LocalsPart();
    vector<string>* freevars = FreeVarsPart();
    vector<string>* cellvars = CellVarsPart();
    vector<string>* globals = GlobalsPart();
    vector<PyByteCode*>* instructions = BodyPart();
    return new PyCode(funName->getLex(), nestedFunctions, constants, locals,
                  freevars, cellvars, globals, instructions, numArgs);
}
vector<PyObject*>* PyParser::ConstPart(vector<PyCode*>* nestedFunctions) {
    vector<PyObject*>* constants = new vector<PyObject*>();
    PyToken* tok = in->getToken();
    if (tok->getLex() != "Constants") {
        in->putBackToken();
        return constants;
    }
    tok = in->getToken();
    if (tok->getLex() != ":")
        badToken(tok, "Expected a ':'.");
    constants = ValueList(constants, nestedFunctions);
    return constants;
}

Fig. 4.55: PyParse.cpp

<FunctionList> ::= <FunDef> <FunctionList> | <null>
<FunDef> ::= Function colon Identifier slash Integer <FunctionList> <ConstPart> <LocalsPart>
               <FreeVarsPart> <CellVarsPart> <GlobalsPart> <BodyPart>
<ConstPart> ::= <null> | Constants colon <ValueList>
 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
Function: main/0
Constants: None, "Enter a list: "
Locals: x, lst, b
Globals: input, split, print
BEGIN
          LOAD_GLOBAL                    0
          LOAD_CONST                     1
          CALL_FUNCTION                  1
          STORE_FAST                     0
          LOAD_FAST                      0
          LOAD_ATTR                      1
          CALL_FUNCTION                  0
          STORE_FAST                     1
          SETUP_LOOP               label02
          LOAD_FAST                      1
          GET_ITER
label00:  FOR_ITER                 label01
          STORE_FAST                     2
          LOAD_GLOBAL                    2
          LOAD_FAST                      2
          CALL_FUNCTION                  1
          POP_TOP
          JUMP_ABSOLUTE            label00
label01:  POP_BLOCK
label02:  LOAD_CONST                     0
          RETURN_VALUE
END

Fig. 4.56: listiter.casm

4.26.4. The Assembler

4.26.5. Example 4.35

<BodyPart> ::= BEGIN <InstructionList> END
<InstructionList> ::= <null> | <LabeledInstruction> <InstructionList>
<LabeledInstruction> ::= Identifier colon <LabeledInstruction> |
                         <Instruction> | <OpInstruction>
<Instruction> ::= STOP_CODE | NOP | POP_TOP | ROT_TWO | ROT_THREE | ...
 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
vector<PyByteCode*>* PyParser::BodyPart() {
    vector<PyByteCode*>* instructions = new vector<PyByteCode*> ();
    PyToken* tok = in->getToken();
    target.clear();
    index = 0;
    if (tok->getLex() != "BEGIN")
        badToken(tok, "Expected a BEGIN keyword.");
    instructions = InstructionList(instructions);
    //Find the target of any labels in the byte code instructions
    int i;
    for (i = 0; i < instructions->size(); i++) {
        PyByteCode* inst = (*instructions)[i];
        string label = inst->getLabel();
        if (label != "") {
            string op = inst->getOpCodeName();
            delete (*instructions)[i];
            (*instructions)[i] = new PyByteCode(op, target[label]);
        }
    }
    tok = in->getToken();
    if (tok->getLex() != "END")
        badToken(tok, "Expected a END keyword.");
    return instructions;
}
PyByteCode* PyParser::LabeledInstruction() {
    PyToken* tok1 = in->getToken();
    string tok1Lex = tok1->getLex();
    PyToken* tok2 = in->getToken();
    string tok2Lex = tok2->getLex();
    if (tok2Lex == ":") {
        if (target.find(tok1Lex) == target.end()) {
            target[tok1Lex] = index;
        } else
            badToken(tok1, "Duplicate label found.");
        return LabeledInstruction();
    }
    ... // code omitted here.
}

Fig. 4.57: Assembly

4.26.6. ByteCode Objects

 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
enum PyOpCode {
    STOP_CODE,
    NOP,
    POP_TOP,
    ROT_TWO,
    ROT_THREE,
    ... // rest omitted here.


class PyByteCode {
public:
    PyByteCode(string opcode);
    PyByteCode(string opcode, int operand);
    PyByteCode(string opcode, string label);
    PyByteCode(const PyByteCode& orig);
    virtual ~PyByteCode();
    PyOpCode getOpCode() const;
    int getOperand() const;
    string getOpCodeName() const;
    string getLabel() const;
    string toString() const;
    static int numArgs(string opcode);

private:
    PyOpCode opcode;
    int operand;
    string label;
};

Fig. 4.58: PyByteCode.h

 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
bool create_map(unordered_map<string, PyOpCode>& m) {
    m["STOP_CODE"] = STOP_CODE;
    m["NOP"] = NOP;
    m["POP_TOP"] = POP_TOP;
    m["ROT_TWO"] = ROT_TWO;
    m["ROT_THREE"] = ROT_THREE;
    ... // code omitted here.
    return true;
}

unordered_map<string, PyOpCode> OpCodeMap;
bool dummy = create_map(OpCodeMap);

bool create_arg_map(unordered_map<string, int>& m) {
    m["STOP_CODE"] = 0;
    m["NOP"] = 0;
    m["POP_TOP"] = 0;
    m["ROT_TWO"] = 0;
    m["ROT_THREE"] = 0;
    ... // code omitted here.
    return true;
}

unordered_map<string, int> ArgMap;
bool dummy2 = create_arg_map(ArgMap);

const char* opcodeNames[] = {
    "STOP_CODE",
    "NOP",
    "POP_TOP",
    "ROT_TWO",
    "ROT_THREE",
    ... // code omitted here.
}

Fig. 4.59: Static Initialization

4.26.7. Function Objects

4.26.8. Example 4.36

 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
PyFunction::PyFunction(PyCode& theCode, unordered_map<string,
                       PyObject*>& theGlobals, PyObject* env) :
        PyCallable(), code(theCode), globals(theGlobals)
{
    PyTuple* tuple = (PyTuple*) env;
    for (int i = 0; i < theCode.getFreeVars().size(); i++) {
        cellvars[theCode.getFreeVars()[i]] = (PyCell*)tuple->getVal(i);
    }
}
PyObject* PyFunction::__call__(vector<PyObject*>* args) {
    ostringstream msg;
    if (args->size() != code.getArgCount()) {
        msg << "TypeError: expected " << code.getArgCount() << " arguments, got "
            << args->size() << " for function " << code.getName();
        throw new PyException(PYWRONGARGCOUNTEXCEPTION,msg.str());
    }
    PyFrame* frame = new PyFrame(code,args,globals,code.getConsts(),cellvars);
    PyObject* result = frame->execute();
    try {
        delete frame;
    } catch (...) {
        cerr << "Frame deletion caused an exception for some reason.";
    }
    return result;
}

Fig. 4.60: PyFunction.cpp

4.26.9. Frame Objects

 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
PyObject* PyFrame::execute() {
    // Variable declarations omitted here.
    PC = 0;
    //This registers the frame for the signal handler in case a signal occurs.
    pushFrame(this);
    while (true) {
        inst = code.getInstructions()[PC];
        PC++;
        opcode = inst->getOpCode();
        operand = inst->getOperand();
        try {
            switch (opcode) {
                case LOAD_FAST:
                    u = locals[code.getLocals()[operand]];
                    if (u == 0) {
                        throw new PyException(PYILLEGALOPERATIONEXCEPTION,
                            "LOAD_FAST failed to find value " + code.getLocals()[operand]);
                    }
                    opStack->push(u);
                    break;
                case LOAD_CONST:
                    u = consts[operand];
                    opStack->push(u);
                    break;
                case BINARY_SUBTRACT:
                    v = safetyPop();
                    u = safetyPop();
                    args = new vector<PyObject*>();
                    args->push_back(v);
                    w = u->callMethod("__sub__", args);
                    opStack->push(w);
                    try {
                        delete args;
                    } catch (...) {
                        cerr << "Delete of BINARY_SUBTRACT args caused an exception." << endl;
                    }
                    break;
                case RETURN_VALUE:
                    if (opStack->isEmpty()) {
                        throw new PyException(PYILLEGALOPERATIONEXCEPTION,
                              "Attempt to pop empty operand stack in RETURN_VALUE.");
                    }
                    u = safetyPop();
                    //Now deregister the frame with the signal handler.
                    popFrame();
                    return u;
                    break;
            ... // rest of instructions are omitted here.
          }
        } catch (PyException* ex) {
            int x;
            bool found = false;
            while (!found && !blockStack->isEmpty()) {
                x = blockStack->pop();
                if (x < 0) {
                    // A value less than or equal to zero on the block stack indicates a
                    // try except block
                    found = true;
                    if (verbose) {
                        cerr << "**************Handling Exception*****************" << endl;
                        cerr << "The exception was: " << ex->toString() << endl;
                        cerr << "-------------------------------------------------" << endl;
                        cerr << "              The Exception's Traceback" << endl;
                        cerr << "-------------------------------------------------" << endl;
                        ex->printTraceBack();
                        cerr << "**************End Handling Exception*************" << endl;
                    }
                    //The Exception is pushed onto the operand stack for processing.
                    opStack->push(ex->getTraceBack()); //The traceback at TOS2
                    opStack->push(ex); //The parameter (in our case the exception) at TOS1
                    opStack->push(ex); //The Exception at TOS
                    //The location to resume execution was found on the block stack.
                    PC = -1 * x;
                    //An implicitly pushed exception handling block is pushed for the handler
                    blockStack->push(0);
                }
            }
            if (!found) {
                ex->tracebackAppend(this);
                throw ex;
            }
        } catch (...) {
            PyException* ex = new PyException(PYILLEGALOPERATIONEXCEPTION,
                "Unknown Error while executing instruction " + inst->getOpCodeName());
            ex->tracebackAppend(this);
            throw ex;
        }
    }
}

Fig. 4.61: PyFunction.cpp

4.26.10. Exceptions and Tracebacks

_images/pytypes.png

Fig. 4.62: CoCo Type Hierarchy

4.26.11. Calling Magic Methods

4.26.12. Example 4.37

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class PyObject {
public:
  PyObject();
  virtual ~PyObject();
  virtual PyType* getType();
        virtual string toString();
        void decRef();
        void incRef();
        int getRefCount() const;
        PyObject* callMethod(string name, vector<PyObject*>* args);
protected:
    unordered_map<string, PyObject* (PyObject::*)(vector<PyObject*>*)> dict;
    int refCount;

    virtual PyObject* __str__(vector<PyObject*>* args);
    virtual PyObject* __type__(vector<PyObject*>* args);
};

Fig. 4.63: PyObject.h

1
2
3
4
PyObject::PyObject() {
    dict["__str__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyObject::__str__);
    dict["__type__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyObject::__type__);
}

Fig. 4.64: PyObject’s Constructor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
PyInt::PyInt(int val): PyObject() {
    this->val = val;
    dict["__add__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__add__);
    dict["__sub__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__sub__);
    dict["__mul__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__mul__);
    dict["__floordiv__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__floordiv__);
    dict["__truediv__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__truediv__);
    dict["__eq__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__eq__);
    dict["__gt__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__gt__);
    dict["__lt__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__lt__);
    dict["__ge__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__ge__);
    dict["__le__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__le__);
    dict["__float__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__float__);
    dict["__int__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__int__);
    dict["__bool__"] = (PyObject* (PyObject::*)(vector<PyObject*>*)) (&PyInt::__bool__);
}

Fig. 4.65: PyInt’s Constructor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
PyObject* PyObject::callMethod(string name, vector<PyObject*>* args) {
    PyObject* (PyObject::*mbr)(vector<PyObject*>*);
    if (dict.find(name) == dict.end()) {
        throw new PyException(PYILLEGALOPERATIONEXCEPTION,
            "TypeError: '"+ getType()->toString() +
            "' object has no attribute '" + name + "'");
    }
    mbr = dict[name];
    return (this->*mbr)(args);
}

Fig. 4.66: PyObject’s callMethod

4.26.13. The CoCo Type Hierarchy

MyComputer> python3.2
Python 3.2 (r32:88452, Feb 20 2011, 10:19:59)
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> type(int)
<class 'type'>
>>> type(type(int))
<class 'type'>
>>>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
PyObject* PyType::__call__(vector<PyObject*>* args) {
    ostringstream msg;
    if (args->size() != 1) {
        msg << "TypeError: expected 1 arguments, got " << args->size();
        throw new PyException(PYWRONGARGCOUNTEXCEPTION,msg.str());
    }
    vector<PyObject*>* emptyArgs = new vector<PyObject*>();
    PyObject* arg = (*args)[0];
    string funName = "__"+this->toString()+"__";
    return arg->callMethod(funName,emptyArgs);
}

Fig. 4.67: PyType __call__ method

 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
PyObject* PyRangeType::__call__(vector<PyObject*>* args) {
    int start;
    int stop;
    int increment;
    switch (args->size()) {
        case 1:
            if ((*args)[0]->getType()->typeId() != PyIntType) {
                throw new PyException(PYILLEGALOPERATIONEXCEPTION,
                    "range arguments must be of int type.");
            }
            start = 0;
            stop = ((PyInt*) ((*args)[0]))->getVal();
            increment = 1;
            break;
        case 2:
            if ((*args)[0]->getType()->typeId()!=PyIntType) {
                throw new PyException(PYILLEGALOPERATIONEXCEPTION,
                    "range arguments must be of int type.");
            }
            if ((*args)[1]->getType()->typeId()!=PyIntType) {
                throw new PyException(PYILLEGALOPERATIONEXCEPTION,
                    "range arguments must be of int type.");
            }
            start = ((PyInt*) ((*args)[1]))->getVal();
            stop = ((PyInt*) ((*args)[0]))->getVal();
            increment = 1;
            break;
        case 3:
            if ((*args)[0]->getType()->typeId()!=PyIntType) {
                throw new PyException(PYILLEGALOPERATIONEXCEPTION,
                    "range arguments must be of int type.");
            }
            if ((*args)[1]->getType()->typeId()!=PyIntType) {
                throw new PyException(PYILLEGALOPERATIONEXCEPTION,
                    "range arguments must be of int type.");
            }
            if ((*args)[2]->getType()->typeId()!=PyIntType) {
                throw new PyException(PYILLEGALOPERATIONEXCEPTION,
                    "range arguments must be of int type.");
            }
            start = ((PyInt*) ((*args)[2]))->getVal();
            stop = ((PyInt*) ((*args)[1]))->getVal();
            increment = ((PyInt*) ((*args)[0]))->getVal();
            break;
        default:
            throw new PyException(PYILLEGALOPERATIONEXCEPTION,
                "Incorrect number of arguments for built-in range function.");
            break;
    }
    return new PyRange(start,stop,increment);
}

Fig. 4.68: PyRangeType __call__ method

4.27. Implementing Dictionaries

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def main():
  d = {}
  d["hello"] = "goodbye"
  d["dog!"] = "cat!"
  d["young"] = "old"
  s = "hello young dog!"
  t = s.split()
  for x in t:
    print(x)
  for x in t:
    print(d[x])
  for x in d.keys():
    print(x, d[x])
  for y in d.values():
    print(y)
  for key in d:
    print(key, d[key])
  print(type(d))
  print(type(type(d)))
main()

Fig. 4.69: dicttest.py

4.27.1. Two New Classes

 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
#include "PyObject.h"
#include "PyType.h"
#include <vector>
#include <unordered_map>
using namespace std;

class PyHash
{
public:
    std::size_t operator() (const PyObject* key) const;
};
class PyKeysEqual
{
public:
    bool operator() (const PyObject* key1, const PyObject* key2) const;
};
class PyDict : public PyObject {
public:
    PyDict();
    virtual ~PyDict();
    PyType* getType();
    string toString();
    PyObject* getVal(PyObject* key);
    void setVal(PyObject* key, PyObject* val);
protected:
    unordered_map<PyObject*,PyObject*,PyHash,PyKeysEqual> map;
    virtual PyObject* __getitem__(vector<PyObject*>* args);
    virtual PyObject* __setitem__(vector<PyObject*>* args);
    virtual PyObject* __len__(vector<PyObject*>* args);
    virtual PyObject* __iter__(vector<PyObject*>* args);
    virtual PyObject* keys(vector<PyObject*>* args);
    virtual PyObject* values(vector<PyObject*>* args);
};

Fig. 4.70: PyDict.h

unordered_map<key_type,value_type,hash_function_type,equals_function_type>
1
2
3
4
5
std::size_t PyHash::operator() (const PyObject* key) const {
    vector<PyObject*> args;
    PyInt* hashVal = (PyInt*) const_cast<PyObject*>(key)->callMethod("__hash__",&args);
    return hashVal->getVal();
}

Fig. 4.71: The PyHash Implementation

1
2
3
4
std::hash<std::string> hash_string;
PyObject* PyStr::__hash__(vector<PyObject*>* args) {
    return new PyInt(hash_string(this->val));
}

Fig. 4.72: The PyStr Hash Function

1
2
3
4
5
6
bool PyKeysEqual::operator() (const PyObject* key1, const PyObject* key2) const {
    vector<PyObject*> args;
    args.push_back(const_cast<PyObject*>(key2));
    PyBool* test = (PyBool*) const_cast<PyObject*>(key1)->callMethod("__eq__",&args);
    return test->getVal();
}

Fig. 4.73: The PyKeysEqual Implementation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
string PyDict::toString() {
    unordered_map<PyObject*,PyObject*,PyHash,PyKeysEqual>::iterator it;
    ostringstream s;
    s << "{";
    it = map.begin();
    while (it!=map.end()) {
        s << it->first->toString() << ": " << it->second->toString();
        it++;
        if (it!=map.end())
            s << ", ";
    }
    s << "}";
    return s.str();
}
PyObject* PyDict::__iter__(vector<PyObject*>* args) {
    ostringstream msg;

    if (args->size() != 0) {
        msg << "TypeError: expected 0 arguments, got " << args->size();
        throw new PyException(PYWRONGARGCOUNTEXCEPTION,msg.str());
    }

    return new PyDictIterator(map);
}

Fig. 4.74: The PyDict toString and __iter__ Methods

4.27.2. Two New Types

4.27.3. Two New Instructions

1
2
3
def main():
  d = { "Kent":"Denise", "Sophus":"Addie"}
  print(d)

Fig. 4.75: Initializing a Dictionary

4.28. Chapter Summary

4.29. Review Questions

  1. What is the number one problem that C/C++ programs must deal with? Why is this not a problem for Java and Python programs?
  2. How does the C++ compiler distinguish between macro processor statements and C/C++ statements?
  3. What is a namespace in C++? What is comparable to a namespace in Java? In Python?
  4. What is the default executable name for a compiled C++ program?
  5. What is separate compilation and why is it important?
  6. What does the make program do for programmers and how does it work?
  7. What does static type checking mean? Does C++ have it? Does Python have it? Does Java have it?
  8. Why would you never want to return a pointer into the run-time stack from a function call?
  9. What is a function prototype? Provide an example with only the essential parts.
  10. What are the arguments given to main and what is returned from main in a C++ program?
  11. How do references parameters differ from pointer parameters in C++ when calling a function?
  12. Why are constant references important in C++?
  13. What is a template? How do you declare a vector in C++?
  14. How do you append to the end of a vector in C++?
  15. When operators are overloaded in C++, how does C++ find the right operator to call? What is the term that describes this capability?
  16. When implementing a class in C++ two files must be written. What is the purpose of each file?
  17. What are the two purposes of inheritance?
  18. What is an initialization list? Why is it important to classes that use inheritance?
  19. What is polymorphism? When are two times it is put to use in C++?
  20. What is a destructor and when is it necessary to write one?
  21. When writing a template, how does it start and why?
  22. What is the difference between a signal and an exception in C++?
  23. The CoCo scanner is based on a finite state machine. How is the finite state machine implemented? What are the major constructs used by a finite state machine?
  24. Does the CoCo parser run bottom-up or top-down?
  25. In CoCo how are a PyCode object and a PyFunction object related?
  26. What is a traceback and why is it important?
  27. What is the type(6) in CoCo and Python? How about the type(type(6))? How about the type(type(type(6)))? Why isn’t it interesting to go any further?
  28. Why would it be necessary to cast away constness in a function?
  29. What is a functor and how do you create one in C++?
  30. Hash values must be non-negative integral values. If you had to come up with a hash function for PyInt, how might you write it? Write a hash function implementation for the PyInt class for your answer to this question.

4.30. Exercises

  1. Alter the finite state machine of PyScanner.cpp to allow strings to include the escape character. Any character following the backslash, or escape character, in a string should be allowed. This project can be implemented by altering the PyScanner.cpp class to allow the escape character to appear within a string. Hint: Two extra states will be needed to implement this code. Note that CoCo will already allow pretty much any character, including tabs and newline characters, to be included in a string constant. The only characters that pose problems are single and double quotes. The escape character should not be included in the constant string, only the character that follows the escape character.

  2. Implement true division and floor division for floats in CoCo. Write a test program to thoroughly test these new operations supported by floats. The test program and the source code are both required for the solution to this problem. You may use the disassembler to help generate your test program.

  3. Alter the CoCo grammar to allow each line of a function’s code to be either a CoCo instruction or a source code line. Any source code line should be preceeded by a pound sign, a line number, and a colon followed by the text of the source code line. A source code line would reflect a line from a source language other than CoCo which was compiled to the CoCo assembly language. Then, when an uncaught exception occurs in the CoCo program, the traceback should be printed along with the source code line that caused the exception. This is a challenging exercise and requires changes to the scanner, parser, internal storage of PyCode objects, and traceback handling.

  4. Add a dictionary object type to CoCo by following the description at the end of this chapter. This project requires significant programming and there are pieces in the last part of the chapter that are left out. However, the provided code samples along with other similar code in the CoCo project provides enough details to be able to complete it. When done, the successful project will be able to run the disassembled code from figures 4.69 and 4.75. The output should appear to be identical to the output produced by running the Python programs. However, the order of keys may be different since dictionaries are implemented with an unordered_map datatype.

  5. Empty type calls produce empty results in Python but not in CoCo. For instance, when int() is called in Python, the object 0 is created. In CoCo this produces an error. Use Python to determine what should happen for all the empty type calls that CoCo supports. Then modify CoCo so it will behave in a similar fashion.

  6. When a list of strings is printed in Python it appears as something like [‘hello’, ‘world’]. However, when that same disassembled program is run in CoCo it prints as [hello, world]. This is because Python distinguishes between calling __str__ and calling __repr__. When a list is printed, the list is converted to a string. However, the elements of the list are converted to their string representation by Python, using the __repr__ magic method. Add __repr__ to all object types in CoCo and modify toString methods so lists print correctly as in Python. If you have completed the previous exercise, then the output from the disassembled code in figure 4.75 can also be corrected by completing this exercise.

  7. Modify CoCo to allow instructions like LOAD_FAST x in addition to LOAD_FAST 0. Currently, the LOAD_FAST and STORE_FAST instructions insist on an integer operand. If an identifier operand were provided then the identifier must exist in the sequence of LOCALS. If it does not, the parser should signal an error. Internally, the LOAD_FAST and STORE_FAST instructions should not change. The conversion from identifier to integer should happen in the parser. Convert the LOAD_GLOBAL, and LOAD_ATTR instructions to allow either an identifier or integer operand in the same manner. Do not try to modify the LOAD_CONST instruction since it would be impossible to distinguish between indices and values for constants.

    This project is not too hard to implement. Labels are already converted to offsets in the parser in the BodyPart method. That code has to be modified slightly to handle identifiers for things other than labels. The identifiers for the load and store instructions can be converted to integer operands in the FunDef function.

  8. Currently the assembler has three different load instructions including LOAD_FAST, LOAD_GLOBAL, and LOAD_DEREF that all use indices into different lists as operands. Define a new pseudo LOAD instruction that lets you specify an identifier for a value to load. For instance LOAD x would result in scanning the LOCALS list for x. If x were found in the first posiition of the locals list, then the LOAD x would be changed to a LOAD_FAST 0 instruction. Otherwise, if x was not in the list of locals, then the GLOBALS would be scanned next and if x were found there a LOAD_GLOBAL instruction would replace the LOAD pseudo instruction. If x was not found in the globals, then the cellvars could be scanned and finally the freevars. Create a STORE pseudo instruction as well for the STORE_FAST and STORE_DEREF instructions.

    Do not try to implement the pseudo instructions for any of the other load or store instructions. For instance, it would be impossible to know whether a LOAD referred to a LOAD_DEREF or a LOAD_CLOSURE if you tried to include LOAD_CLOSURE in your pseudo instruction.

4.31. Solutions to Practice Problems

These are solutions to the practice problem s. You should only consult these answers after you have tried each of them for yourself first. Practice problems are meant to help reinforce the material you have just read so make use of them.

4.31.1. Solution to Practice Problem 4.1

All of the variables are declared in the main frame on the run-time stack. Any variable that is declared inside main resides in its stack frame. The variables that point to values on the run-time stack include all those that have the keyword new in their initialization line. This is in, scan, parser, and args. The variables filename, code, result, and ex might also point to values on the heap, but you can’t tell by looking at just this code. You would have to look at what each of the function calls returned or where the filename variable was initialized. In fact, all but filename do point to values on the heap.

The variables indent, globals, and foundMain definitely do not point to values on the heap since they are not pointers.

4.31.2. Solution to Practice Problem 4.2

Here is the code. Notice when strcpy is called, no & is needed. This is because arrays are already pointers to the array data. The strcpy function written here increments the pointers. Array notation could be used as well to copy the array contents. Also notice that in main the variable t must be declared to be bigger or the same size as s or an exception could occur.

#include <iostream>
using namespace std;

void strcpy(char* target, char* source) {
  while (*source!=0) {
    *target = *source;
    target++;
    source++;
  }
}
int main(int argc, char* argv[]) {
  char s[] = "hello world!";
  char t[20];
  strcpy(t,s);
  cout << t << endl;
}