commit 1ba45365882fda81f907e588c97a1803bcee9e41 Author: Michael Zhang Date: Mon Jan 29 17:24:20 2018 -0600 f diff --git a/_templates/templates b/_templates/templates new file mode 100755 index 0000000..d65cc32 Binary files /dev/null and b/_templates/templates differ diff --git a/_templates/templates.cc b/_templates/templates.cc new file mode 100644 index 0000000..69d2e0b --- /dev/null +++ b/_templates/templates.cc @@ -0,0 +1,144 @@ +#include +#include +#include +#include + +class ArenaEntity { + public: + ArenaEntity(int r = 10) : radius(r) {} + void Print() { std::cout << "Entity with radius " << radius << std::endl; } + + private: + int radius; +}; + +// A polymorphic swap function for any type of object +template +void MySwap(T& x, T& y) { + T temp; + temp = x; + x = y; + y = temp; +} + +// A polymorphic class holding an [x,y] position of any type +template +class Position { + public: + Position(T x = 0, T y = 0) : x_(x), y_(y) {} + friend std::ostream& operator<<(std::ostream& os, const Position& p) { + return os << "[" << p.x_ << ", " << p.y_ << "]"; + } + // >>>> ADD SETTERS AND GETTERS + T get_x() { return x_; } + void set_x(T x) { x_ = x; } + T get_y() { return y_; } + void set_y(T y) { y_ = y; } + // >>>>> OVERLOAD OPERATORS + and - binary operators as member functions. + Position operator+(const Position& other) { + return Position(x_ + other.x_, y_ + other.y_); + } + Position operator-(const Position& other) { + return Position(x_ - other.x_, y_ - other.y_); + } + + private: + T x_; + T y_; +}; + +// >>>>> CREATE AN ARRAY TEMPLATE that takes type and number of elements +// template ... +// >>>>> OVERLOAD operator []. Do a check on index to be in range +// >>>>> METHODS to push and pop +template +class + AnArrayClassThatsWorseThanVectorButApparentlyBetterThanBuiltinArraysButNotReally { + public: + T operator[](const int index) { + if (index > N) { + int* x = (int*)NULL; + printf("%d\n", *x); + // cause a segfault cuz why not + } + return array[index]; + } + void push(T a) { + if (array.size() >= N) return; + array.push_back(a); + } + T pop() { + auto last = *(array.rbegin()); + array.erase(array.rbegin()); + return last; + } + + private: + std::vector array; // use a vector internally because it's the best! +}; + +int main(void) { + // ----------------------------------------------------------------- + // Demonstration of the use of the template Vector and its iterator + std::vector entities_; + entities_.push_back(new ArenaEntity(20)); + entities_.push_back(new ArenaEntity(25)); + + for (std::vector::iterator ent = entities_.begin(); + ent != entities_.end(); ++ent) { + (*ent)->Print(); + } + + // This is the same as above, except with the convenience of auto typing + std::cout << std::endl; + for (auto ent : entities_) { + ent->Print(); + } + + // ----------------------------------------------------------------- + // Demonstration of the use of template for function overloading + int intA, intB; + float floatA, floatB; + char charA, charB; + intA = 10; + intB = 20; + floatA = 2.5; + floatB = 10.5; + charA = 'a'; + charB = 'b'; + + std::cout << std::endl; + std::cout << "int A,B: [" << intA << ", " << intB << "]" << std::endl; + std::cout << "float A,B: [" << floatA << ", " << floatB << "]" << std::endl; + std::cout << "char A,B: [" << charA << ", " << charB << "]" << std::endl; + MySwap(intA, intB); + MySwap(floatA, floatB); + MySwap(charA, charB); + std::cout << std::endl; + std::cout << "int A,B: [" << intA << ", " << intB << "]" << std::endl; + std::cout << "float A,B: [" << floatA << ", " << floatB << "]" << std::endl; + std::cout << "char A,B: [" << charA << ", " << charB << "]" << std::endl; + + // ----------------------------------------------------------------- + // Demonstration of the use of template for class definition + Position intPos(10, 10); + Position floatPos(1.5, 1.5); + Position charPos('A', 'B'); + std::cout << std::endl; + std::cout << intPos << std::endl; + std::cout << floatPos << std::endl; + std::cout << charPos << std::endl; + + Position intPos2(20, 20); + intPos2.set_x(30); + std::cout << intPos + intPos2 << std::endl; + + // ----------------------------------------------------------------- + // Demonstration of the use of template for class ARRAY + AnArrayClassThatsWorseThanVectorButApparentlyBetterThanBuiltinArraysButNotReally< + int, 1> + a; + a.push(5); + a.push(6); + printf("value: %d\n", a[2]); +} diff --git a/aaaa b/aaaa new file mode 160000 index 0000000..10983be --- /dev/null +++ b/aaaa @@ -0,0 +1 @@ +Subproject commit 10983be67c48557abadab23de493ba4bae41289a diff --git a/class-repo-public/.gitignore b/class-repo-public/.gitignore new file mode 100644 index 0000000..a7b02f4 --- /dev/null +++ b/class-repo-public/.gitignore @@ -0,0 +1,3 @@ +*.o +*.*~ +*.out \ No newline at end of file diff --git a/class-repo-public/Assignments/CSwriting.md b/class-repo-public/Assignments/CSwriting.md new file mode 100644 index 0000000..78acf47 --- /dev/null +++ b/class-repo-public/Assignments/CSwriting.md @@ -0,0 +1,74 @@ +CSCI3081 Fall 2017 + +## Writing Assignment : Responding to Writing in CS + + +Due: Monday, September 25 at 10:00pm + + +### Introduction + +There are many different types of writing in computer science. This assignment gives you a chance to reflect on an example of your choice and, ignoring the technical content for the moment, reflect upon just the writing style and approach to determine what makes it successful or unsuccessful. + +### Your Assignment + +Find one piece of computer science writing that is of interest to you and read it.  This can be online or hardcopy material, and "computer science writing" can be interpreted widely here.  In this class, we are interested in software design and development, so you might look for some writing that relates to software design patterns, coding style, libraries or toolkits for software development, or even research on software engineering.  Anything a software developer might find useful as a part of doing his or her job, honing his or her skills, etc. is fine -- pick something that is interesting to you.  The writing should be more than a couple pages long. Once you have read the material, answer the following questions in a typed document with separate headings for Question 1, Question 2, etc. **Submit a PDF of your responses directly to the Writing Assignment link in Moodle**. + +#### Question 1  + +What is the title of the writing and where can it be found? Give a citation or URL.  + +#### Question 2 + +What is the purpose of the writing?  For example, is the purpose to present a new technique, describe a software testing plan, argue for adopting a particular methodology for software design, etc.? + +#### Question 3  + +As best as you can tell, who is the intended audience?  + +#### Question 4  + +At the end of this page is a list of Elements of Successful Writing in CS that was recently compiled by the faculty in the Department of Computer Science & Engineering.  How important is each item on this list for the type of writing that you picked? Copy the list below, and for each item, rate how important you think it is using the following scale:  (a) extremely important, (b) useful but not critical, (c) not very important, or (d) not applicable.  Note, we are not (yet) asking you to judge the quality of what you read based on these "successful elements".  First, let's just establish which of these elements are most important for the type of writing (e.g., a scholarly report, a blog post, a user manual, a programmer's style guide) you have selected. + +#### Question 5 + +Now, let's evaluate the writing.  What do you think is the most successful element of the writing (e.g., a good organization and logical flow, good use of visuals)?  Write one paragraph to explain why you think this element is so critical to making the writing work for the intended audience and purpose. + +#### Question 6  + +Finally, what is the least successful element of the writing?  Write one paragraph to explain how the structure, visuals, organization, or whatever other element you think is weak in this example, limits the writing in achieving its purpose for the intended audience. + +## Elements of Successful Writing in CS: +
    + +
  1. States key information clearly (important findings, recommendations, ideas, issues, etc.).  + +
  2. Addresses the target audience with an appropriate tone and level of explanation.  + +
  3. Presents any needed background explanation clearly or informatively.  + +
  4. Persuasively justifies a choice of algorithm, design, problem approach, problem solution, etc.  + +
  5. Describes algorithms, data structures, software system components, etc. accurately.  + +
  6. Describes algorithms, data structures, software system components, etc. informatively and concisely.  + +
  7. Uses terminology and notation correctly.  + +
  8. Explains high-level ideas with low-level details.  + +
  9. Presents low-level details clearly and accurately.  + +
  10. Uses appropriate structures (e.g., lists, diagrams, equations, code samples, etc.).  + +
  11. Includes clear and informative tables, diagrams, references, etc.  + +
  12. Smoothly integrates tables, diagrams, references, etc. into the text.  + +
  13. Has a good organization and logical flow.  + +
  14. Uses correct grammar, spelling, and mechanics.  + +
  15. Avoids including irrelevant information, overemphasizing less important material, or writing verbosely. + +
diff --git a/class-repo-public/Exercises/CartDesign/cart b/class-repo-public/Exercises/CartDesign/cart new file mode 100755 index 0000000..269d7a1 Binary files /dev/null and b/class-repo-public/Exercises/CartDesign/cart differ diff --git a/class-repo-public/Exercises/CartDesign/cart.cpp b/class-repo-public/Exercises/CartDesign/cart.cpp new file mode 100644 index 0000000..6245994 --- /dev/null +++ b/class-repo-public/Exercises/CartDesign/cart.cpp @@ -0,0 +1,20 @@ +#include "cart.h" + +CartEntry::CartEntry( float p, int q) { + price = p; + quantity = q; +} + +Order::Order( CartContents c, float s ) { + cart = c; + salesTax = s; +} + +float Order::OrderTotal() { + float cartTotal = 0; + for (int i=0; i < cart.itemCount; i++) { + cartTotal += cart.items[i].price * cart.items[i].quantity; + } + cartTotal += cartTotal * salesTax; + return cartTotal; +} diff --git a/class-repo-public/Exercises/CartDesign/cart.h b/class-repo-public/Exercises/CartDesign/cart.h new file mode 100644 index 0000000..975f51f --- /dev/null +++ b/class-repo-public/Exercises/CartDesign/cart.h @@ -0,0 +1,26 @@ +// A class for a single element in a cart +class CartEntry { +public: + float price; + int quantity; + CartEntry( float p=0, int q=0); +}; + +// A collection of elements in a cart +// CartContents "has a" CartEntry - using composition +class CartContents{ +public: + int itemCount; // number of elements in cart + CartEntry* items; +}; + +// An order is the combination of a cart and tax +// different states apply different salesTax rates. +class Order { +private: + CartContents cart; + float salesTax; +public: + Order(CartContents cart, float salesTax); + float OrderTotal(); +}; diff --git a/class-repo-public/Exercises/CartDesign/main.cpp b/class-repo-public/Exercises/CartDesign/main.cpp new file mode 100644 index 0000000..dc53d62 --- /dev/null +++ b/class-repo-public/Exercises/CartDesign/main.cpp @@ -0,0 +1,27 @@ +#include + +#include "cart.h" + +int main() { + + // Create an array of CartEntry's to put in the cart + // Arbitrary values for price and quantity + CartEntry inCart[5]; + for (int i=0; i<5; i++) { + inCart[i].price = i*2; + inCart[i].quantity = i; + } + + // Place the CartEntry array in a CartContents + CartContents purchases; + purchases.items = inCart; + purchases.itemCount = 5; + + // Make this an order with associated tax + Order order1(purchases, .077); + + // Get total price of order + std::cout << '$' << order1.OrderTotal() << std::endl; + + return 0; +} diff --git a/class-repo-public/Exercises/CartDesign/makefile b/class-repo-public/Exercises/CartDesign/makefile new file mode 100644 index 0000000..13b60c6 --- /dev/null +++ b/class-repo-public/Exercises/CartDesign/makefile @@ -0,0 +1,18 @@ +# CSCI3081 cart exercise makefile + +CC = g++ +DEBUG = -g +CFLAGS = -Wall -c $(DEBUG) +LFLAGS = -Wall $(DEBUG) + +all: main.o cart.o + $(CC) $(LFLAGS) main.o cart.o -o cart + +main.o : main.cpp + $(CC) $(CFLAGS) main.cpp -o main.o + +robot.o : cart.cpp + $(CC) $(CFLAGS) cart.cpp -o cart.o + +clean: + \rm *.o *.*~ cart diff --git a/class-repo-public/Exercises/CartDesign/readme.md b/class-repo-public/Exercises/CartDesign/readme.md new file mode 100644 index 0000000..cfb7c4c --- /dev/null +++ b/class-repo-public/Exercises/CartDesign/readme.md @@ -0,0 +1,8 @@ +### Shopping with Bad Design + +This example demonstrates bad design. There are many things wrong with respect to the design principles of +encapsulation, coupling, cohesion, and information hiding. The interface of the class, which is dictated +by the class definition, is integral to the quality of the design. + +Can you identify some of the problems with this design? Think about our enemy _modification_, and where +in the code would modifications have a ripple effect that requires several changes in several places. \ No newline at end of file diff --git a/class-repo-public/Exercises/Inheritance/object.cpp b/class-repo-public/Exercises/Inheritance/object.cpp new file mode 100644 index 0000000..9cd601c --- /dev/null +++ b/class-repo-public/Exercises/Inheritance/object.cpp @@ -0,0 +1,83 @@ +#include + +using std::cout; +using std::endl; + +class ObjectClass { +private: + int privateVar; +protected: + int protectedVar; +public: + ObjectClass() : + privateVar(10), protectedVar(15) {} + ObjectClass(int a, int b) : + privateVar(a), protectedVar(b) {} + void print() { + cout << "in objectClass. "; + cout << "priv, prot = " << privateVar << ' '<< protectedVar << endl; + } + void setPrivate(int a) { privateVar = a;} + void setProtected(int a) { protectedVar = a;} +}; + +class ComposedClass { +private: + ObjectClass object; + //int privateVar; +public: + void print() { + //cout << "in composedClass. "; + //cout << "privateVar " << privateVar << endl; + //cout << "protectedVar " << protectedVar << endl; + //cout << "object privateVar " << object.privateVar << endl; + //cout << "object protectedVar " << object.protectedVar << endl; + object.print(); + } +protected: + //int protectedVar; +}; + +class DerivedClass : public ObjectClass { +private: + /* + int privateVar; + int protectedVar; + */ +public: + // DerivedClass() {} + //DerivedClass() : ObjectClass(1,2) {} + /*{ + privateVar = 20; + protectedVar = 25; + }*/ + void print() { + //cout << "in derivedClass. "; + //cout << "privateVar " << privateVar << endl; + //cout << "protectedVar " << protectedVar << endl; + //cout << "object privateVar " << ObjectClass::privateVar << endl; + //cout << "object protectedVar " << ObjectClass::protectedVar << endl; + ObjectClass::print(); + } +}; + +int main() { + ObjectClass baseObject_default; + ObjectClass baseObject_2_3(2,3); + ComposedClass hasAobject_default; + DerivedClass isAobject_default; + // DerivedClass isAobject_4_5(4,5); + + cout << "Base default: "; + baseObject_default.print(); + cout << "Base 2,3 : "; + baseObject_2_3.print(); + cout << "Composed default : "; + hasAobject_default.print(); + cout << "Derived default : "; + isAobject_default.print(); + cout << "Derived 4,5 : "; + // isAobject_4_5.print(); + + return 1; +} diff --git a/class-repo-public/Exercises/Inheritance/readme.md b/class-repo-public/Exercises/Inheritance/readme.md new file mode 100644 index 0000000..b88ff91 --- /dev/null +++ b/class-repo-public/Exercises/Inheritance/readme.md @@ -0,0 +1,42 @@ +### _ComposedClass_ +
    + +
  1. Uncomment each of the 'cout' statements, explain why it doesn't compile: +
      +
    • privateVar
    • +
    • protectedVar
    • +
    • object.privateVar
    • +
    • object.protectedVar
    • +
    +
  2. Uncomment the declaration of privateVar and protectedVar inside the ComposedClass definition. +
    • + Which of the 'cout' statements can now be successfully compiled? Why? +
    +
  3. ComposedClass probably needs to interact with _object_ members. How will you give it access? + +
+ +### DerivedClass + +
    +
  1. Uncomment each of the 'cout' statements, determine which successfully compiles and explain why or why not: +
      +
    • privateVar +
    • protectedVar +
    • ObjectClass::privateVar +
    • ObjectClass::protectedVar +
    + +
  2. How is it that you redefined _print()_ in the derived class, and yet you can still call _print()_ in the ObjectClass? + +
  3. In main, the declaration of isAobject_4_5(4,5) is commented out because it will not compile. Why not? There isn't a constructor for DerivedClass with no parameters and that works. + +
  4. Uncomment the DerivedClass constructor that has no parameters (i.e. 'DerivedClass() :' ). + - Determine which of the cout statements compiles and explain why. + +
  5. Uncomment the members privateVar and protectedVar in DerivedClass, and modify comments in the code so that DerivedClass() : sets those vars to 20 and 25. +
    • + Report the values for each 'cout' in Derived and explain the results. +
    +
  6. In main, now fix DerivedClass so that the statement 'DerivedClass isAobject_4_5(4,5)' works. +
diff --git a/class-repo-public/Exercises/TestSensor.cc b/class-repo-public/Exercises/TestSensor.cc new file mode 100644 index 0000000..272d7a8 --- /dev/null +++ b/class-repo-public/Exercises/TestSensor.cc @@ -0,0 +1,89 @@ +#include +#include +#include + +using namespace std; + +class Robot; +class EventDistress; + +/*! + * @brief The [x,y] position with respect to the graphics arena + * + * \todo Implement operator overloading for '-' and '<<' + */ +struct Position { + Position( double in_x=0, double in_y=0) : x(in_x), y(in_y) {} + double x; + double y; + // Operator Overload '-' + // Operator Overload '<<' +}; + +/** + * @brief Activates when a distress call is within its range. + * + * SensorDistress is a part of a robot. Implented with the Observer Pattern + * the Arena (i.e. the subject) will call \ref Accept to alert the sensor + * of all distress calls in the arena. The SensorDistress has to determine + * whether or not this call will activate its sensor (which is part of a Robot). + * + * Either the Robot or parts of the Robot (e.g. RobotMotionHandler) will use + * the sensor output to effect behavior. + * + */ +class SensorDistress { +public: + SensorDistress(const Robot * robot, double range): + robot_(robot), range_(range) {} + void Accept(const EventDistress & ed); + int Output(); +private: + double range_; + const Robot * robot_; + std::vector entity_ids_; +}; + +// As an observer, the sensor gets alerted of the "subject" arena +void SensorDistress::Accept(const EventDistress & ed ) { + // Is this distress call from the robot that I am part of? + // Is this a distress call I can hear? +} + +int Output() { + return 1; + // Is this activated or not? +} + +/*! + * @brief A simple robot class that has an id and position + * + */ +class Robot { +public: + Robot(Position p, int id) : pos_(p), id_(id), + sensor_distress_(new SensorDistress(this,50)) {} + Position get_pos() { return pos_; } + void set_pos(Position p) { pos_ = p; } + int get_id() { return id_; } + void set_id(int i) { id_ = i; } +private: + SensorDistress * sensor_distress_; + Position pos_; + int id_; +}; + +// Communication of the event that a distress call was emitted in the arena +class EventDistress { +public: + EventDistress(Position p, int id) : pos_(p), entity_id_(id) {} + Position get_pos() { return pos_; } + int get_entity_id() { return entity_id_; } +private: + Position pos_; + int entity_id_; +}; + +int main() { + Robot robbie(Position(100,100),1); +} diff --git a/class-repo-public/Exercises/_templates/templates.cc b/class-repo-public/Exercises/_templates/templates.cc new file mode 100644 index 0000000..f58d01e --- /dev/null +++ b/class-repo-public/Exercises/_templates/templates.cc @@ -0,0 +1,106 @@ +#include +#include +#include +#include + +class ArenaEntity { +public: + ArenaEntity(int r=10) : radius(r) {} + void Print() { + std::cout << "Entity with radius " << radius << std::endl; + } +private: + int radius; +}; + +// A polymorphic swap function for any type of object +template +void MySwap( T& x, T& y) { + T temp; + temp = x; + x = y; + y = temp; +} + +// A polymorphic class holding an [x,y] position of any type +template +class Position { +public: + Position(T x=0, T y=0) : x_(x), y_(y) {} + friend std::ostream& operator<<(std::ostream& os, const Position& p) { + return os << "[" << p.x_ << ", " << p.y_ << "]"; + } + // >>>> ADD SETTERS AND GETTERS + // >>>>> OVERLOAD OPERATORS + and - binary operators as member functions. +private: + T x_; + T y_; +}; + +// >>>>> CREATE AN ARRAY TEMPLATE that takes type and number of elements +// template ... +// >>>>> OVERLOAD operator []. Do a check on index to be in range +// >>>>> METHODS to push and pop + + + +int main(void) { + // ----------------------------------------------------------------- + // Demonstration of the use of the template Vector and its iterator + std::vector entities_; + entities_.push_back(new ArenaEntity(20)); + entities_.push_back(new ArenaEntity(25)); + + for (std::vector::iterator ent = entities_.begin(); + ent != entities_.end(); + ++ent ) { + (*ent)->Print(); + } + + // This is the same as above, except with the convenience of auto typing + std::cout << std::endl; + for (auto ent : entities_) { + ent->Print(); + } + + // ----------------------------------------------------------------- + // Demonstration of the use of template for function overloading + int intA, intB; + float floatA, floatB; + char charA, charB; + intA = 10; intB = 20; + floatA = 2.5; floatB = 10.5; + charA = 'a'; charB = 'b'; + + std::cout << std::endl; + std::cout << "int A,B: [" << intA << ", " << intB << "]" << std::endl; + std::cout << "float A,B: [" << floatA << ", " << floatB << "]" << std::endl; + std::cout << "char A,B: [" << charA << ", " << charB << "]" << std::endl; + MySwap(intA,intB); + MySwap(floatA,floatB); + MySwap(charA,charB); + std::cout << std::endl; + std::cout << "int A,B: [" << intA << ", " << intB << "]" << std::endl; + std::cout << "float A,B: [" << floatA << ", " << floatB << "]" << std::endl; + std::cout << "char A,B: [" << charA << ", " << charB << "]" << std::endl; + + + // ----------------------------------------------------------------- + // Demonstration of the use of template for class definition + Position intPos(10,10); + Position floatPos(1.5,1.5); + Position charPos('A','B'); + std::cout << std::endl; + std::cout << intPos << std::endl; + std::cout << floatPos << std::endl; + std::cout << charPos << std::endl; + + Position intPos2(20,20); + //intPos2.set_x(30); + //std::cout << intPos+intPos2 << std::endl; + + // ----------------------------------------------------------------- + // Demonstration of the use of template for class ARRAY + + +} diff --git a/class-repo-public/Exercises/classDefRobots/.DS_Store b/class-repo-public/Exercises/classDefRobots/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/class-repo-public/Exercises/classDefRobots/.DS_Store differ diff --git a/class-repo-public/Exercises/classDefRobots/.gitignore b/class-repo-public/Exercises/classDefRobots/.gitignore new file mode 100644 index 0000000..80bf8a2 --- /dev/null +++ b/class-repo-public/Exercises/classDefRobots/.gitignore @@ -0,0 +1,3 @@ +*.out +*.o +robot diff --git a/class-repo-public/Exercises/classDefRobots/classDefExercise.md b/class-repo-public/Exercises/classDefRobots/classDefExercise.md new file mode 100644 index 0000000..7774d86 --- /dev/null +++ b/class-repo-public/Exercises/classDefRobots/classDefExercise.md @@ -0,0 +1,267 @@ +### Demonstration of Class Definition in C++ + +
+This first example makes use of the default constructor, but this is problematic due to initialization. + +#### Robot.h + +```C++ +class Robot { +private: + float directionAngle; + int position[2]; + +public: + void moveForward( int distance ); + void display(); +}; +``` + +#### Robot.cpp + +```C++ +#include "robot.h" +#include +#include + +void Robot::moveForward(int distance) { + position[0] = position[0] + distance*cos(directionAngle); + position[1] = position[1] + distance*sin(directionAngle); +} + +void Robot::display() { + cout << "Pos [" << position[0] << " " << position[1] << "]. angle " + << directionAngle << endl; +} +``` + +#### main.cpp + +In C++, _main()_ serves as the entry point to the program. It must exist to generate an executable. + +```C++ +#include "robot.h" +#include + +using std::cout; + +int main() { + Robot rosyRobot; + rosyRobot.display(); +} +``` + +You can compile the program using the provided makefile, then call the executable _robot_. + +``` +make +./robot +``` + +
+We will fix the initialization problem by adding "setters" and add some "getters" while we are at it. + +#### Robot.h + +```C++ +class Robot { +private: + float directionAngle; + int position[2]; + +public: + // setters and getters + void position( int x, int y); + int* position(); + void radianDirectionAngle(float theta); + float radianDirectionAngle(); + + void moveForward( int distance ); + void display(); +}; +``` + +#### Robot.cpp + +Adding the following to the source code. + +```C++ +// setters and getters +void Robot::xyPosition( int x, int y) { + position[0] = x; + position[1] = y; +} +int* Robot::xyPosition() { + return position; +} + +void Robot::radianDirectionAngle(float theta) { + directionAngle = theta; +} +float Robot::radianDirectionAngle() { + return directionAngle; +} +``` + +#### main.cpp + +```C++ +int main() { + Robot rosyRobot; + rosyRobot.xyPosition(0,0); + rosyRobot.radianDirectionAngle(0); + + rosyRobot.display(); +} +``` + +
+This is dangerous to require users to initialize. Let's fix this by forcing the user to provide initialization. + +#### Adding to Robot.h + +We can pass along an array, but let's not do that right now. We will see why later. + +```C++ + Robot( int x, int y, float theta ); +``` + + +#### Adding to Robot.cpp + +```C++ +Robot( int x, int y, float theta ) { + position[0] = x; + position[1] = y; + directionAngle = theta; +} +``` + +
+This can be cumbersome, let's give the user some options. + +OPTION 1: Add a no-parameter constructor that initializes to reasonable values. + +
+ +#### Adding to robot.h + +```C++ +public: + Robot(); +``` + +#### Adding to robot.cpp + +```C++ +Robot::Robot() { + position[0] = 0; + position[1] = 0; + directionAngle = 0; +} +``` + +#### Change main.cpp + +```C++ +int main() { + Robot rosyRobot; + rosyRobot.display(); +} +``` + +OPTION 2: Add a constructor with a subset of the member variables. + +#### Adding to robot.h + +```C++ +public: + Robot(); + Robot( int x, int y ); + Robot(float theta); +``` + +Then you will add these definitions to robot.cpp. Let's see how we use them. + +#### main.cpp + +```C++ +int main() { + Robot rosyRobot; + Robot c3poRobot(100,100); + Robot halRobot(0,0,3.14/2); + Robot eveRobot(-100,-100,-3.14/4); + rosyRobot.display(); + c3poRobot.display(); + halRobot.display(); + eveRobot.display(); +} +``` + +Looks like this when you run it ... + +``` +Pos [0 0]. angle 0 +Pos [100 100]. angle 0 +Pos [0 0]. angle 1.57 +Pos [-100 -100]. angle -0.785 +``` + +OPTION 3: (Probably the best) Create a single constructor that can take 1 to all member variable initializations. + +Now we have to undo some of our work. We need only the one constructor, because we will add _default_ values. + +#### robot.h + +```C++ + Robot( int x=0, int y=0, float theta=0); +``` + +#### robot.cpp + +```C++ +Robot::Robot( int x, int y, float theta ) { + position[0] = x; + position[1] = y; + directionAngle = theta; +} +``` + +#### main.cpp + +There was no need to change anything in main from before. All of the various forms of constructors was captured in the one constructor with default arguments. + +```C++ +int main() { + Robot rosyRobot; + Robot c3poRobot(100,100); + Robot halRobot(3.14); + Robot eveRobot(-100,-100,-3.14/4); + rosyRobot.display(); + c3poRobot.display(); + halRobot.display(); + eveRobot.display(); +} +``` + +And it comes out like this ... +``` +Pos [0 0]. angle 0 +Pos [100 100]. angle 0 +Pos [3 0]. angle 0 +Pos [-100 -100]. angle -0.785 +``` + +*__OOPS__*. What happened to hal? We specified an angle of 3.14, but that didn't work. Can you explain that? + +
+#### Position is awkward and prone to error. Let's fix that with a class definition. + +``` +struct Position { + int x; + int y; +}; +``` + +But now we have to change all of our code. Too bad we didn't think about this change before we started! diff --git a/class-repo-public/Exercises/classDefRobots/main.cpp b/class-repo-public/Exercises/classDefRobots/main.cpp new file mode 100644 index 0000000..997fa67 --- /dev/null +++ b/class-repo-public/Exercises/classDefRobots/main.cpp @@ -0,0 +1,27 @@ +#include "robot.h" +#include + +using std::cout; + +int main() { + + Robot rosyRobot(0,0,0); + + + //rosyRobot.xyPosition(0,0); + //rosyRobot.radianDirectionAngle(0); + + rosyRobot.display(); + + + /* + c3poRobot.display(); + Robot c3poRobot(100,100); + + halRobot.display(); + Robot halRobot(3.14); + + Robot eveRobot(-100,-100,-3.14/4); + eveRobot.display(); + */ +} diff --git a/class-repo-public/Exercises/classDefRobots/makefile b/class-repo-public/Exercises/classDefRobots/makefile new file mode 100644 index 0000000..1a8f584 --- /dev/null +++ b/class-repo-public/Exercises/classDefRobots/makefile @@ -0,0 +1,18 @@ +# CSCI3081 robot exercise makefile + +CC = g++ +DEBUG = -g +CFLAGS = -Wall -c $(DEBUG) +LFLAGS = -Wall $(DEBUG) + +all: main.o robot.o + $(CC) $(LFLAGS) main.o robot.o -o robot + +main.o : main.cpp + $(CC) $(CFLAGS) main.cpp -o main.o + +robot.o : robot.cpp + $(CC) $(CFLAGS) robot.cpp -o robot.o + +clean: + \rm *.o *.*~ robot diff --git a/class-repo-public/Exercises/classDefRobots/robot.cpp b/class-repo-public/Exercises/classDefRobots/robot.cpp new file mode 100644 index 0000000..46153d3 --- /dev/null +++ b/class-repo-public/Exercises/classDefRobots/robot.cpp @@ -0,0 +1,41 @@ +#include "robot.h" + +#include +#include + +using std::cout; +using std::endl; + +Robot::Robot( int x, int y, float theta) { + position[0] = x; + position[1] = y; + directionAngle = theta; +} + +void Robot::moveForward(int distance) { + position[0] = position[0] + distance*cos(directionAngle); + position[1] = position[1] + distance*sin(directionAngle); +} + +void Robot::display() { + cout + << "Pos [" << position[0] << " " << position[1] << "]. angle " + << directionAngle << endl; +} + +void Robot::xyPosition( int x, int y) { + position[0] = x; + position[1] = y; +} + +int* Robot::xyPosition() { + return position; +} + +void Robot::radianDirectionAngle(float d) { + directionAngle = d; + +} +float Robot::radianDirectionAngle() { + return directionAngle; +} diff --git a/class-repo-public/Exercises/classDefRobots/robot.h b/class-repo-public/Exercises/classDefRobots/robot.h new file mode 100644 index 0000000..4102b3c --- /dev/null +++ b/class-repo-public/Exercises/classDefRobots/robot.h @@ -0,0 +1,19 @@ +class Robot { + + private: + float directionAngle; + int position[2]; + + public: + Robot(int x=0, int y=0, float theta=0); + + void moveForward( int distance ); + void display(); + + // setters and getters + void xyPosition( int x, int y); + int* xyPosition(); + void radianDirectionAngle(float d); + float radianDirectionAngle(); + + }; diff --git a/class-repo-public/Exercises/collision.py b/class-repo-public/Exercises/collision.py new file mode 100644 index 0000000..b6dd9f1 --- /dev/null +++ b/class-repo-public/Exercises/collision.py @@ -0,0 +1,218 @@ +import math +import turtle + +# -------------- CLASS DEFINITIONS ----------------- +# ---------------------------------------------------- + +def Velocity(angle, length): + x = length*math.cos(angle) + y = length*math.sin(angle) + return Vector(x,y) + +class Vector: + def __init__(self, x, y): + self.x = x + self.y = y + self.angle = math.atan2(y,x) + self.pen = turtle.Turtle() + self.pen.hideturtle() + + def Draw(self,color="black"): + self.pen.pu() + #self.pen.goto(self.ox, self.oy) + self.pen.goto(0,0) + self.pen.left(self.angle*180/math.pi) + self.pen.pd() + self.pen.color(color) + #self.pen.goto(int(self.x+self.ox), int(self.y+self.oy)) + self.pen.goto(int(self.x), int(self.y)) + + def Clear(self): + self.pen.clear() + + +class Circle: + def __init__( self, radius, heading, pos, color ): + self.radius = radius + self.pos = pos + self.heading = heading + self.color = color + self.pen = turtle.Turtle() + self.pen.hideturtle() + self.vel = Velocity(self.heading, self.radius) + + def Draw(self): + self.pen.pu() + self.pen.goto(self.pos[0], self.pos[1]) + self.pen.pd() + self.pen.dot(self.radius*2, self.color) + + def DrawHeading(self): + self.vel.Draw() + + def Clear(self): + self.pen.clear() + self.vel.Clear() + + +# --------------------------------------------------- +def main(wall,test): + # wall==true, means test walls, else test quadrants + # test quadrant: 1:NE, 2:NW, 3:SW, 4:SE + # test wall: 0:right, 1:left, 2:up, 3:down, -1:quadrants instead + + if wall: + # test wall: 0:right, 1:left, 2:up, 3:down, -1:quadrants instead + if 0 == test: + collision_angle_rad = math.atan2(0,40) + heading_angle_rad = math.atan2(10,20) + elif 1 == test: + collision_angle_rad = math.atan2(0,-40) + heading_angle_rad = math.atan2(10,-20) + elif 2 == test: + collision_angle_rad = math.atan2(40,0) + heading_angle_rad = math.atan2(20,10) + elif 3 == test: + collision_angle_rad = math.atan2(-40,0) + heading_angle_rad = math.atan2(-20,10) + else: + print('Error'); return + else: + # test quadrant: 1:NE, 2:NW, 3:SW, 4:SE + if 0 == test: + collision_angle_rad = math.pi/5 + heading_angle_rad = math.pi/7 + elif 1 == test: + collision_angle_rad = math.pi/2+ math.pi/5 + heading_angle_rad = math.pi/2 + math.pi/7 + elif 2 == test: + collision_angle_rad = math.pi+math.pi/5 + heading_angle_rad = math.pi+math.pi/7 + elif 3 == test: + collision_angle_rad = -math.pi/5 + heading_angle_rad = -math.pi/7 + else: + print('Error'); return + + print('H: ',heading_angle_rad,', ',heading_angle_rad*180/math.pi) + print('CA: ',collision_angle_rad,', ',collision_angle_rad*180/math.pi) + + radius = 40 + robot = Circle( radius, heading_angle_rad, [ 0,0 ], "yellow") + stationary = Circle( radius, 0, + [2*radius*math.cos(collision_angle_rad), \ + 2*radius*math.sin(collision_angle_rad)], "black") + print(robot.pos) + print(stationary.pos) + + robot.Draw() + robot.DrawHeading() + stationary.Draw() + + # All code above: this would be set by the actual position and heading + # of the robot and colliding obstacle. This does assume that the two + # balls are not overlapping. See flatredball if you want to reposition + # them to a touching, but not overlapping position + + # This code based on the flatredball resource + # http://flatredball.com/documentation/tutorials/math/circle-collision/ + # robot = circle 1 + + # Determine the point of collision, which also defines the angle + collision = Vector( stationary.pos[0]-robot.pos[0], \ + stationary.pos[1]-robot.pos[1] ) + collision.Draw("red") + + # Define the tangent to the point of collision + collision_tangent = Vector( stationary.pos[1]-robot.pos[1], \ + -(stationary.pos[0]-robot.pos[0])) + collision_tangent.Draw("purple") + print('tangent ',collision_tangent.x,' ', collision_tangent.y) + + # Normalize the tangent by making it of length 1 + tangent_length = (collision_tangent.x**2 + collision_tangent.y**2)**0.5 + normal_tangent = Vector( collision_tangent.x/tangent_length, \ + collision_tangent.y/tangent_length) + print('NormTang :',normal_tangent.x,' ',normal_tangent.y) + normal_tangent.Draw('blue') + + # relative velocity = robot because stationary circle has 0 velocity + # See flatredball to modify code for 2 moving objects + rel_velocity = robot.vel + print('RelVel :',rel_velocity.x,' ',rel_velocity.y) + rel_velocity.Draw('black') + + # Determine the velocity vector along the tangent + length = rel_velocity.x*normal_tangent.x + rel_velocity.y*normal_tangent.y + print('length ',length) + print('NormTang :',normal_tangent.x,' ',normal_tangent.y) + tangent_velocity = Vector( normal_tangent.x*length, normal_tangent.y*length) + print('VonT ',tangent_velocity.x,' ',tangent_velocity.y) + tangent_velocity.Draw('orange') + + # Determine the velocity vector perpendicular to the tangent + perpendicular = Vector(rel_velocity.x-tangent_velocity.x, \ + rel_velocity.y-tangent_velocity.y ) + print("Vperp ",perpendicular.x,' ',perpendicular.y) + perpendicular.Draw("green") + + # New Heading + # This is for robot only. See flatredball to move both entities + new_heading = Vector( (robot.vel.x-2*perpendicular.x), \ + (robot.vel.y-2*perpendicular.y)) + print('new heading ',new_heading.x,' ',new_heading.y) + new_heading.Draw("blue") + + input("to continue") + robot.Clear() + stationary.Clear() + collision_tangent.Clear() + perpendicular.Clear() + +def TestWalls(): + for i in range(4): + main(True,i) + +def TestQuadrants(): + for i in range(4): + main(False,i) + +TestWalls() +TestQuadrants() + +# --------------------------------------------------------------- +# --------------------------------------------------------------- +# This is identical to above without the printing and drawing ... +def DetermineNewHeading( robot, stationary ): + + # Determine the point of collision, which also defines the angle + collision = Vector( stationary.pos[0]-robot.pos[0], \ + stationary.pos[1]-robot.pos[1] ) + + # Define the tangent to the point of collision + collision_tangent = Vector( stationary.pos[1]-robot.pos[1], \ + -(stationary.pos[0]-robot.pos[0])) + + # Normalize the tangent making it length 1 + tangent_length = (collision_tangent.x**2 + collision_tangent.y**2)**0.5 + normal_tangent = Vector( collision_tangent.x/tangent_length, \ + collision_tangent.y/tangent_length) + + # relative velocity = robot because stationary circle has 0 velocity + # See flatredball to modify code for 2 moving objects + rel_velocity = robot.vel + + # Determine the velocity vector along the tangent + length = rel_velocity.x*normal_tangent.x + rel_velocity.y*normal_tangent.y + tangent_velocity = Vector( normal_tangent.x*length, normal_tangent.y*length) + + # Determine the velocity vector perpendicular to the tangent + perpendicular = Vector(rel_velocity.x-tangent_velocity.x, \ + rel_velocity.y-tangent_velocity.y ) + + # New Heading + # This is for robot only. See flatredball to move both entities + new_heading = Vector( (robot.vel.x-2*perpendicular.x), \ + (robot.vel.y-2*perpendicular.y)) + + diff --git a/class-repo-public/Exercises/constants/Robot.cpp b/class-repo-public/Exercises/constants/Robot.cpp new file mode 100644 index 0000000..bd95a54 --- /dev/null +++ b/class-repo-public/Exercises/constants/Robot.cpp @@ -0,0 +1,32 @@ +#include + +class Robot { + private: + const int radius; + const int color; + int speed; + public: + Robot() : radius(50), color(0xFF0000) {speed = 250;} + Robot(int r, int c, int s) { + radius=r; + color=c; + speed=s; + void setSpeed(int inS) {speed = inS;} + void setColor (int inC) {color = inC;} + int getSpeed() { return speed; } + int getRadius() { return radius; } + int getColor() { return color; } +}; + +int main() { + Robot robot1(20, 0x00FF00, 200); + const Robot robot2; + + std::cout << "Setting Robot1 speed to 45" << std::endl; + robot1.setSpeed(45); + int s = robot1.getSpeed(); + std::cout << "speed: " << s << std::endl; + + robot2.setSpeed(250); + int s2 = robot2.getSpeed(); +} diff --git a/class-repo-public/Exercises/constants/chain_reference.cpp b/class-repo-public/Exercises/constants/chain_reference.cpp new file mode 100644 index 0000000..2dcf835 --- /dev/null +++ b/class-repo-public/Exercises/constants/chain_reference.cpp @@ -0,0 +1,54 @@ +#include + +/* YOUR ASSIGNMENT. change +Pos incPos(Pos p) to Pos incPos( const &Pos p) +*/ + +class Pos{ + private: + int x; + int y; + public: + Pos(): x(0), y(0) {} + Pos(int inX, int inY): x(inX), y(inY) {} + void setX(int inX) { x=inX;} + void setY(int inY) {y=inY;} + int getX() {return x;} + int getY() {return y;} +}; + +Pos incPos(Pos p) { + std::cout << "=====In incPos=====" << std::endl; + std::cout << "p x: " << p.getX() << ", p y: " << p.getY() << std::endl; + int x = p.getX() + 2; + int y = p.getY() + 3; + p.setX(30); + Pos pos(x, y); + std::cout << "pos x: " << pos.getX() << ", pos y: " << pos.getY() << std::endl; + std::cout << "=====Leaving incPos=====" << std::endl; + return pos; +} + +int calcDist(Pos p1, Pos p2) { + std::cout << "=====In calcDist=====" << std::endl; + std::cout << "p1 x: " << p1.getX() << ", p1 y: " << p1.getY() << std::endl; + std::cout << "p2 x: " << p2.getX() << ", p2 y: " << p2.getY() << std::endl; + + std::cout << "=====Leaving calcDist=====" << std::endl; + return 23; +} + +int main() { + Pos pos1(2,5); + int dist; + std::cout << "======Starting in main=========" << std::endl; + std::cout << "pos1 x: " << pos1.getX() << ", pos1 y: " << pos1.getY() << std::endl; + int x = pos1.getX() + 2; + std::cout << "x value: " << x << std::endl; + int x2 = incPos(pos1).getX(); + std::cout << "x2 value: " << x2 << std::endl; + dist = calcDist(incPos(pos1), pos1); + std::cout << "After calculations" << std::endl; + std::cout << "x: " << x << ", x2: " << x2 << ", Dist: " << dist << std::endl; + std::cout << "pos1 x: " << pos1.getX() << ", pos1 y: " << pos1.getY() << std::endl; +} diff --git a/class-repo-public/Exercises/constants/constants.cc b/class-repo-public/Exercises/constants/constants.cc new file mode 100644 index 0000000..9915888 --- /dev/null +++ b/class-repo-public/Exercises/constants/constants.cc @@ -0,0 +1,44 @@ +int main() { + +//const int myConst1; +const int myConst1 = 100; +const int myConst2 = 200; +const int* pMyConst; + +// init integer +//myConst1 = 100; + +// init pointer +//pMyConst = &myConst2; + +// change pointer +//pMyConst = &myConst2; + +// change integer +//myConst2 = myConst2 + 5; + +// change integer using pointer +//*pMyConst = myConst1 + 1; + +int myInt_3; +int myInt_4 = 400; +int* const cpMyInt_3 = &myInt_3; + +// initialize the pointer +//int* const cpMyInt_4; +//int* const cpMyInt_4 = &myInt_4; + +// initialize integer +//myInt_3 = 300; + +// change the integer +//myInt_4 = myInt_4 + 5; + +// change integer using pointer +//*cpMyInt_3 = myInt_4; + +// change the pointer +//cpMyInt_3 = &myInt_4; + + +} diff --git a/class-repo-public/Exercises/duckVisitor/.DS_Store b/class-repo-public/Exercises/duckVisitor/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/class-repo-public/Exercises/duckVisitor/.DS_Store differ diff --git a/class-repo-public/Exercises/duckVisitor/.gitignore b/class-repo-public/Exercises/duckVisitor/.gitignore new file mode 100644 index 0000000..db5bdb8 --- /dev/null +++ b/class-repo-public/Exercises/duckVisitor/.gitignore @@ -0,0 +1,9 @@ +*.o +*.obj +*.out + +*.*~ +*~ + +duckVisitor + diff --git a/class-repo-public/Exercises/duckVisitor/Duck.cpp b/class-repo-public/Exercises/duckVisitor/Duck.cpp new file mode 100644 index 0000000..c6d5b07 --- /dev/null +++ b/class-repo-public/Exercises/duckVisitor/Duck.cpp @@ -0,0 +1,53 @@ +#include +#include +#include "Visitor.h" +#include "Fly.h" +#include "Quack.h" + +#include "Duck.h" + +using namespace std; + +//---------------------------------------------- +// THE DUCKS + +Duck::Duck() { + flyBehavior = new FlyBehavior; + quackBehavior = new QuackBehavior; + name = "mystery"; +} + +void Duck::accept( Visitor * v ) { v->visit(this); } + +void Duck::display() { cout << "I am a duck." << endl; } +void Duck::fly() { flyBehavior->fly(); } +void Duck::quack() { quackBehavior->quack(); } + +double Duck::getSpeed() { return flyBehavior->getSpeed(); } +double Duck::getDB() { return quackBehavior->getDB(); } + +void Duck::setName(string inName) { name = inName; } +string Duck::getName() { return name; } + +//---------------------------------------------- +// MALLARD + +Mallard::Mallard() { + flyBehavior = new FlyWithWings; + quackBehavior = new Quack; +} + +void Mallard::accept( Visitor * v) { v->visit(this); } +void Mallard::display() { cout << "I am a Mallard." << endl; } + +//---------------------------------------------- +// THE RUBBERDUCK + +RubberDuck::RubberDuck() { + flyBehavior = new NoFly; + quackBehavior = new Squeak(2); +} + +void RubberDuck::accept( Visitor * v) { v->visit(this); } + +void RubberDuck::display() { cout << "I am a Rubber Duck." << endl; } diff --git a/class-repo-public/Exercises/duckVisitor/Duck.h b/class-repo-public/Exercises/duckVisitor/Duck.h new file mode 100644 index 0000000..dc34c94 --- /dev/null +++ b/class-repo-public/Exercises/duckVisitor/Duck.h @@ -0,0 +1,50 @@ +#ifndef DUCKS_EXERCISES_DUCK_H_ +#define DUCKS_EXERCISES_DUCK_H_ + +#include +#include +#include "Visitor.h" +#include "Fly.h" +#include "Quack.h" + +using namespace std; + +//---------------------------------------------- +// THE DUCKS + +class Duck { +protected: + FlyBehavior * flyBehavior; + QuackBehavior * quackBehavior; + string name; +public: + Duck(); + + virtual void accept( Visitor * v ); + + virtual void display(); + virtual void fly(); + virtual void quack(); + + virtual double getSpeed(); + virtual double getDB(); + + virtual void setName(string inName); + virtual string getName(); +}; + +class Mallard : public Duck { +public: + Mallard(); + void accept( Visitor * v); + void display(); +}; + +class RubberDuck : public Duck { +public: + RubberDuck(); + void accept( Visitor * v); + void display(); +}; + +#endif diff --git a/class-repo-public/Exercises/duckVisitor/Fly.cpp b/class-repo-public/Exercises/duckVisitor/Fly.cpp new file mode 100644 index 0000000..afce721 --- /dev/null +++ b/class-repo-public/Exercises/duckVisitor/Fly.cpp @@ -0,0 +1,40 @@ +#include +#include + +#include "Fly.h" + +//----------------------------------------------- +// FLYING BEHAVIOR + +using namespace std; + +FlyBehavior::FlyBehavior() { + milesPerHour = MPH_DEFAULT; +} + +void FlyBehavior::fly() { + cout << "Generic Flying at " << milesPerHour << " mph." << endl; +} + +double FlyBehavior::getSpeed() { + return milesPerHour; } + +//----------------------------------------------- +// FLY WITH WINGS + +FlyWithWings::FlyWithWings() { +} + +void FlyWithWings::fly() { + cout << "Fly with wings at speed of " << milesPerHour << " mph." << endl; +} + +//----------------------------------------------- +// NO FLYING + +//NoFly::NoFly() {} + +//----------------------------------------------- +// FLY WITH ROCKET + +//FlyWithRocket::FlyWithRocket() {} diff --git a/class-repo-public/Exercises/duckVisitor/Fly.h b/class-repo-public/Exercises/duckVisitor/Fly.h new file mode 100644 index 0000000..169e321 --- /dev/null +++ b/class-repo-public/Exercises/duckVisitor/Fly.h @@ -0,0 +1,38 @@ +#ifndef DUCKS_EXERCISES_FLY_H_ +#define DUCKS_EXERCISES_FLY_H_ + +#include +#include + +//----------------------------------------------- +// FLYING + +#define MPH_DEFAULT 5 + +using namespace std; + +class FlyBehavior { +protected: + double milesPerHour; +public: + FlyBehavior(); + virtual void fly(); + virtual double getSpeed(); +}; + +class FlyWithWings : public FlyBehavior { +public: + FlyWithWings(); + virtual void fly(); +}; + +class NoFly : public FlyBehavior { +public: + // cannot fly <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Fill this in; +}; + +class FlyWithRocket : public FlyBehavior { + // Can fly REALLY fast - don't use the default speed <<<<<<<<<< Fill this in; +}; + +#endif diff --git a/class-repo-public/Exercises/duckVisitor/Quack.cpp b/class-repo-public/Exercises/duckVisitor/Quack.cpp new file mode 100644 index 0000000..30886fc --- /dev/null +++ b/class-repo-public/Exercises/duckVisitor/Quack.cpp @@ -0,0 +1,64 @@ +#include +#include + +//----------------------------------------------- +// QUACKING BEHAVIOR + +#include "Quack.h" + +using namespace std; + +QuackBehavior::QuackBehavior() : volume(DB_DEFAULT) { +} + +void QuackBehavior::quack() { + cout << "Generic Quack at " << volume << " decibels" << endl; +} + +double QuackBehavior::getDB() { + return volume; +} + +//----------------------------------------------- +// QUACK + +Quack::Quack() { + volume = DB_DEFAULT; +} + +void Quack::quack() { + cout << "Quack at " << volume << " decibels" << endl; +} + +//----------------------------------------------- +// MUTE + +Mute::Mute() { + volume = 0; +} + +void Mute::quack() { + cout << "Cannot talk." << endl; +} + +//----------------------------------------------- +// SQUEAK + +Squeak::Squeak() { +} + +Squeak::Squeak(int d) { + volume = d; +} + +void Squeak::quack() { + cout << "Squeak at " << volume << " decibels." << endl; +} + +//----------------------------------------------- +// HONK +/* +Honk::Honk() { + // This is a confused swan that honks instead of quacks. Fill this in. <<<<<<<<<<<<<<<< +}; +*/ diff --git a/class-repo-public/Exercises/duckVisitor/Quack.h b/class-repo-public/Exercises/duckVisitor/Quack.h new file mode 100644 index 0000000..8386f3a --- /dev/null +++ b/class-repo-public/Exercises/duckVisitor/Quack.h @@ -0,0 +1,46 @@ +#ifndef DUCKS_EXERCISES_QUACK_H_ +#define DUCKS_EXERCISES_QUACK_H_ + +#include +#include + +//----------------------------------------------- +// QUACKING + +#define DB_DEFAULT 10 + +using namespace std; + +class QuackBehavior { +protected: + double volume; +public: + QuackBehavior(); + virtual void quack(); + virtual double getDB(); +}; + +class Quack : public QuackBehavior { +public: + Quack(); + void quack(); +}; + +class Mute : public QuackBehavior { +public: + Mute(); + void quack(); +}; + +class Squeak : public QuackBehavior { +public: + Squeak(); + Squeak(int d); + void quack(); +}; + +class Honk : public QuackBehavior { + // This is a confused swan that honks instead of quacks. Fill this in. <<<<<<<<<<<<<<<< +}; + +#endif diff --git a/class-repo-public/Exercises/duckVisitor/Visitor.cpp b/class-repo-public/Exercises/duckVisitor/Visitor.cpp new file mode 100644 index 0000000..c1ea0c7 --- /dev/null +++ b/class-repo-public/Exercises/duckVisitor/Visitor.cpp @@ -0,0 +1,36 @@ +#include +#include +#include "Visitor.h" +#include "Duck.h" + +using namespace std; + +Visitor::Visitor() {} + +void Visitor::visit( Duck * d ) {} +void Visitor::visit( RubberDuck * r ) {} +void Visitor::visit( Mallard * m ) {} + +void RealDuckReport::visit( RubberDuck * r ) { + // RubberDucks aren't real ducks. do not include +} + +void RealDuckReport::visit( Mallard * m ) { + cout << "Mallard : " << m->getName() << " : " << m->getSpeed() << " MPH" << endl; +} + +void RealDuckReport::visit( Duck * d ) { + // Ducks are generic ducks. do not include +} + +void FakeDuckReport::visit( RubberDuck * r ) { + cout << "Rubberduck : " << r->getName() << " : " << r->getDB() << " DB" << endl; +} + +void FakeDuckReport::visit( Mallard * m ) { + // Mallards aren't fake. Do not include. +} + +void FakeDuckReport::visit( Duck * d ) { + // Ducks are generic ducks. do not include +} diff --git a/class-repo-public/Exercises/duckVisitor/Visitor.h b/class-repo-public/Exercises/duckVisitor/Visitor.h new file mode 100644 index 0000000..e3095dc --- /dev/null +++ b/class-repo-public/Exercises/duckVisitor/Visitor.h @@ -0,0 +1,30 @@ +#ifndef DUCKS_EXERCISES_VISITOR_H_ +#define DUCKS_EXERCISES_VISITOR_H_ + +class Duck; +class RubberDuck; +class Mallard; + +class Visitor { +public: + Visitor(); + virtual void visit( Duck * d ); + virtual void visit( RubberDuck * r ); + virtual void visit( Mallard * m ); +}; + +class RealDuckReport : public Visitor { +public: + void visit( RubberDuck * r ); + void visit( Mallard * m ); + void visit( Duck * d ); +}; + +class FakeDuckReport : public Visitor { +public: + void visit( RubberDuck * r ); + void visit( Mallard * m ); + void visit( Duck * d ); +}; + +#endif diff --git a/class-repo-public/Exercises/duckVisitor/duckVisitor.cpp b/class-repo-public/Exercises/duckVisitor/duckVisitor.cpp new file mode 100644 index 0000000..cf11c16 --- /dev/null +++ b/class-repo-public/Exercises/duckVisitor/duckVisitor.cpp @@ -0,0 +1,57 @@ +#include +#include +#include "Visitor.h" +#include "Duck.h" + +using namespace std; + +// helper defined at bottom of file +void duck_introduction( Duck * duck ); + +int main() { + + // EXAMPLE of polymorphism and the Strategy Pattern + Duck* ducks[3]; + ducks[0] = new Duck(); + ducks[0]->setName("Jane"); + + ducks[1] = new Mallard(); + ducks[1]->setName("Maloy"); + + ducks[2] = new RubberDuck(); + ducks[2]->setName("Lemon"); + + for (int i = 0; i<3; i++) { + duck_introduction( ducks[i] ); + } + + // EXAMPLE of the Visitor Pattern + + // This is a visitor of ducks. + // It needs information, but only from certain kinds of ducks. + RealDuckReport * rd = new RealDuckReport ; + + // Tell each duck to accept a visit from RealDuckReport + cout << "----- REPORT of Real Ducks -----" << endl; + for (int i=0; i<3; i++) { + ducks[i]->accept(rd); + } + cout << endl; + + // This is a visitor of ducks. + // It needs information, but only from certain kinds of ducks. + FakeDuckReport * fd = new FakeDuckReport ; + // Tell each duck to accept a visit from FakeDuckReport + cout << "----- REPORT of Fake Ducks -----" << endl; + for (int i=0; i<3; i++) { + ducks[i]->accept(fd); + } + cout << endl; +} + +void duck_introduction( Duck * duck ) { + duck->display(); + duck->fly(); + duck->quack(); + cout << endl; +} diff --git a/class-repo-public/Exercises/duckVisitor/makefile b/class-repo-public/Exercises/duckVisitor/makefile new file mode 100644 index 0000000..62a535c --- /dev/null +++ b/class-repo-public/Exercises/duckVisitor/makefile @@ -0,0 +1,17 @@ +# CSCI3081 Ducks Exercise makefile + +CXX = g++ +DEBUG = -g +CXXFLAGS = -Wall -c $(DEBUG) +LDFLAGS = -Wall $(DEBUG) +OBJS = Duck.o Visitor.o Fly.o Quack.o + +all: duckVisitor.o $(OBJS) + $(CXX) $(LDFLAGS) -o duckVisitor duckVisitor.o $(OBJS) + +%.o : %.cpp + $(CXX) $(CXXFLAGS) -o $@ $< + + +clean: + \rm *.o *.*~ duckVisitor diff --git a/class-repo-public/Exercises/duckVisitor/readme.md b/class-repo-public/Exercises/duckVisitor/readme.md new file mode 100644 index 0000000..1add20d --- /dev/null +++ b/class-repo-public/Exercises/duckVisitor/readme.md @@ -0,0 +1,25 @@ +### Visitor Pattern Example + +The visitors here want information from the ducks to generate a report. +However, they only need data from some of the types of ducks. + +The ducks are oblivious to this distinction or even what specific information +the visitor wants from them. They need only to welcome the visitor (i.e. accept(Visitor)). + +Notice that the visitor creates a visit() method for each type of Duck, this +way, ducks can be treated generically in some circumstances, but the visitor +can treat each type in a distinct way. + +YOUR TASKS: + +- create a decoy duck +- create a sea duck +- add these types, and a couple more of a type of your choosing to ducks[] +- view the reports + +- create a visitor subclass that speeds up ducks (increases their mph), but only for those that are real. +- again generate a report of real ducks + + +> No claim that this is excellent, efficient code! Feel free to clean it up or add helper functions. + diff --git a/class-repo-public/Exercises/duckVisitor/scratch.cpp b/class-repo-public/Exercises/duckVisitor/scratch.cpp new file mode 100644 index 0000000..e69de29 diff --git a/class-repo-public/Exercises/ducks/.gitignore b/class-repo-public/Exercises/ducks/.gitignore new file mode 100644 index 0000000..bd4e4c5 --- /dev/null +++ b/class-repo-public/Exercises/ducks/.gitignore @@ -0,0 +1,13 @@ +*.o +*.obj + +*.out + +*~ + +duckStrategyComplete.cpp +duckStrategyPolyComplete.cpp + +*_print.* + +*.txt diff --git a/class-repo-public/Exercises/ducks/duck1.h b/class-repo-public/Exercises/ducks/duck1.h new file mode 100644 index 0000000..d726cf3 --- /dev/null +++ b/class-repo-public/Exercises/ducks/duck1.h @@ -0,0 +1,17 @@ +#include +#include + +using std::string; + +class DuckClass { + protected: + string typeOf; + string name; + public: + DuckClass() { + typeOf = "duck"; + name = "noname"; + } + void display() { std::cout << "I am " << name << ", and I am a " << typeOf << ".\n"; } +}; + diff --git a/class-repo-public/Exercises/ducks/duck2.h b/class-repo-public/Exercises/ducks/duck2.h new file mode 100644 index 0000000..b4f3c4a --- /dev/null +++ b/class-repo-public/Exercises/ducks/duck2.h @@ -0,0 +1,20 @@ +#include +#include + +using std::string; + +class DuckClass { + protected: + string typeOf; + string name; + public: + DuckClass() { + typeOf = "duck"; + name = "noname"; + } + void display() { std::cout << "I am " << name << ", and I am a " << typeOf << ".\n"; } + void fly() { std::cout << "I fly.\n"; }; + void quack() { std::cout << "I quack.\n"; }; +}; + + diff --git a/class-repo-public/Exercises/ducks/duck3.h b/class-repo-public/Exercises/ducks/duck3.h new file mode 100644 index 0000000..e12369c --- /dev/null +++ b/class-repo-public/Exercises/ducks/duck3.h @@ -0,0 +1,31 @@ +#include +#include + +using std::string; + +class DuckClass { + protected: + string typeOf; + string name; + bool canFly; + bool canQuack; + public: + DuckClass() : canFly(true), canQuack(true) { + typeOf = "duck"; + name = "noname"; + } + void display() + { std::cout << "I am " << name << ", and I am a " << typeOf << ".\n"; } + void fly() { + if (canFly) std::cout << "I fly.\n"; + else std::cout << "I cannot fly.\n"; + } + void quack() { + if (canQuack) std::cout << "I quack.\n"; + else std::cout << "I do not quack.\n"; + } +}; + + + + diff --git a/class-repo-public/Exercises/ducks/duck4.h b/class-repo-public/Exercises/ducks/duck4.h new file mode 100644 index 0000000..4465a91 --- /dev/null +++ b/class-repo-public/Exercises/ducks/duck4.h @@ -0,0 +1,36 @@ +#include +#include + +using std::string; + +class DuckClass { + protected: + string typeOf; + string name; + public: + DuckClass() { + typeOf = "duck"; + name = "noname"; + } + void display() { std::cout << "I am " << name << ", and I am a " << typeOf << ".\n"; } + void fly() { std::cout << "I fly.\n"; }; + void quack() { std::cout << "I quack.\n"; }; +}; + +class NoFlyDuck : public DuckClass { + public: + NoFlyDuck( string n = "lame" ) { + typeOf = "noFly"; + name = n; + } + void fly() { std::cout << "I cannot fly.\n"; } +}; + +class NoQuackDuck : public DuckClass { + public: + NoQuackDuck( string n = "quiet" ) { + typeOf = "noQuack"; + name = n; + } + void quack() { std::cout << "I cannot quack.\n"; } +}; diff --git a/class-repo-public/Exercises/ducks/duckStrategy.cpp b/class-repo-public/Exercises/ducks/duckStrategy.cpp new file mode 100644 index 0000000..85c683d --- /dev/null +++ b/class-repo-public/Exercises/ducks/duckStrategy.cpp @@ -0,0 +1,133 @@ +#include +#include + +#define MPH_DEFAULT 5 +#define DB_DEFAULT 10 + +using namespace std; + +//----------------------------------------------- +// QUACKING + +class QuackBehavior { +protected: + double volume; +public: + QuackBehavior() : volume(DB_DEFAULT) {} + void quack() { cout << "Generic Quack at " << volume << " decibels" << endl; } +}; + +class Quack : public QuackBehavior { +public: + Quack() {} + void quack() { cout << "Quack at " << volume << " decibels" << endl; } +}; + +class Mute : public QuackBehavior { +public: + Mute() { volume = 0; } + void quack() { cout << "Cannot talk." << endl; } +}; + +class Squeak : public QuackBehavior { +public: + Squeak() {} + Squeak(int d) { volume = d; } + void quack() { cout << "Squeak at " << volume << " decibels." << endl; } +}; + +class Honk : public QuackBehavior { + // This is a confused swan that honks instead of quacks. Fill this in. <<<<<<<<<<<<<<<< +}; + + +//----------------------------------------------- +// FLYING + +class FlyBehavior { +protected: + double milesPerHour; +public: + FlyBehavior() : milesPerHour(MPH_DEFAULT) {} + void fly() { cout << "Generic Flying at " << milesPerHour << " mph." << endl; } +}; + +class FlyWithWings : public FlyBehavior { +public: + FlyWithWings() {} + void fly() { cout << "Fly at speed of " << milesPerHour << " mph." << endl; } +}; + +class NoFly : public FlyBehavior { +public: + // cannot fly <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Fill this in; +}; + +class FlyWithRocket : public FlyBehavior { + // Can fly REALLY fast - don't use the default speed <<<<<<<<<< Fill this in; +}; + +//---------------------------------------------- +// THE DUCKS + +class Duck { +protected: + FlyBehavior flyBehavior; + QuackBehavior quackBehavior; +public: + Duck() { + flyBehavior = FlyWithWings(); + quackBehavior = Quack(); + } + void display() { cout << "I am a duck." << endl; } + void fly() { flyBehavior.fly(); } + void quack() { quackBehavior.quack(); } +}; + +class Mallard : public Duck { +public: + Mallard() { + flyBehavior = FlyWithWings(); + quackBehavior = Quack(); + } + void display() { cout << "I am a Mallard." << endl; } + void quack() { quackBehavior.quack(); } + void fly() { flyBehavior.fly(); } +}; + +class RubberDuck : public Duck { +public: + RubberDuck() { + flyBehavior = NoFly(); + quackBehavior = Squeak(2); + } + void display() { cout << "I am a Rubber Duck." << endl; } + void quack() { quackBehavior.quack(); } + void fly() { flyBehavior.fly(); } +}; + +int main() { + + Mallard mary; + RubberDuck ralph; + Duck donald; + + cout << endl << "Donald does this ... " << endl; + donald.display(); + donald.fly(); + donald.quack(); + + cout << endl << "Mary does this ... " << endl; + mary.display(); + mary.fly(); + mary.quack(); + + cout << endl << "Ralph does this ... " << endl; + ralph.display(); + ralph.fly(); + ralph.quack(); + + cout << endl; +} + + diff --git a/class-repo-public/Exercises/ducks/duckStrategyPoly.cpp b/class-repo-public/Exercises/ducks/duckStrategyPoly.cpp new file mode 100644 index 0000000..a3d7dda --- /dev/null +++ b/class-repo-public/Exercises/ducks/duckStrategyPoly.cpp @@ -0,0 +1,142 @@ +#include +#include + +#define MPH_DEFAULT 5 +#define DB_DEFAULT 10 + +using namespace std; + +//----------------------------------------------- +// QUACKING + +class QuackBehavior { +protected: + double volume; +public: + QuackBehavior() : volume(DB_DEFAULT) {} + void quack() { cout << "Generic Quack at " << volume << " decibels" << endl; } +}; + +class Quack : public QuackBehavior { +public: + Quack() {} + void quack() { cout << "Quack at " << volume << " decibels" << endl; } +}; + +class Mute : public QuackBehavior { +public: + Mute() { volume = 0; } + void quack() { cout << "Cannot talk." << endl; } +}; + +class Squeak : public QuackBehavior { +public: + Squeak() {} + Squeak(int d) { volume = d; } + void quack() { cout << "Squeak at " << volume << " decibels." << endl; } +}; + +class Honk : public QuackBehavior { + // This is a confused swan that honks instead of quacks. Fill this in. <<<<<<<<<<<<<<<< +}; + + +//----------------------------------------------- +// FLYING + +class FlyBehavior { +protected: + double milesPerHour; +public: + FlyBehavior() : milesPerHour(MPH_DEFAULT) {} + void fly() { cout << "Generic Flying at " << milesPerHour << " mph." << endl; } +}; + +class FlyWithWings : public FlyBehavior { +public: + FlyWithWings() {} + void fly() { cout << "Fly with wings at speed of " << milesPerHour << " mph." << endl; } +}; + +class NoFly : public FlyBehavior { +public: + // cannot fly <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Fill this in; +}; + +class FlyWithRocket : public FlyBehavior { + // Can fly REALLY fast - don't use the default speed <<<<<<<<<< Fill this in; +}; + +//---------------------------------------------- +// THE DUCKS + +class Duck { +protected: + FlyBehavior flyBehavior; + QuackBehavior quackBehavior; +public: + Duck() { + flyBehavior = FlyBehavior(); + quackBehavior = QuackBehavior(); + } + void display() { cout << "I am a duck." << endl; } + void fly() { flyBehavior.fly(); } + void quack() { quackBehavior.quack(); } +}; + +class Mallard : public Duck { +public: + Mallard() { + flyBehavior = FlyWithWings(); + quackBehavior = Quack(); + } + void display() { cout << "I am a Mallard." << endl; } +}; + +class RubberDuck : public Duck { +public: + RubberDuck() { + flyBehavior = NoFly(); + quackBehavior = Squeak(2); + } + void display() { cout << "I am a Rubber Duck." << endl; } +}; + +int main() { + + Mallard mary; + RubberDuck ralph; + Duck donald; + + cout << endl << "Donald does this ... " << endl; + donald.display(); + donald.fly(); + donald.quack(); + + cout << endl << "Mary does this ... " << endl; + mary.display(); + mary.fly(); + mary.quack(); + + cout << endl << "Ralph does this ... " << endl; + ralph.display(); + ralph.fly(); + ralph.quack(); + + cout << endl; + + + Duck* ducks[3]; + ducks[0] = new Duck(); + ducks[1] = new Mallard(); + ducks[2] = new RubberDuck(); + + for (int i = 0; i<3; i++) { + ducks[i]->display(); + ducks[i]->fly(); + ducks[i]->quack(); + cout << endl; + } +} + + diff --git a/class-repo-public/Exercises/ducks/ducks1.cpp b/class-repo-public/Exercises/ducks/ducks1.cpp new file mode 100644 index 0000000..523d46b --- /dev/null +++ b/class-repo-public/Exercises/ducks/ducks1.cpp @@ -0,0 +1,41 @@ +#include "duck1.h" + +using std::string; + +class Mallard : public DuckClass { +public: + Mallard(string n="nameless") { + typeOf = "mallard"; + name = n; + } + void fly() { std::cout << "I fly.\n"; } + void quack() { std::cout << "I quack.\n"; } +}; + +class Loon : public DuckClass { +public: + // This duck quacks and flies <<<<<<<<<<<< FILL THIS IN + Loon( string n = "nameless" ) { + typeOf = "loon"; + name = n; + } +}; + +class Decoy : public DuckClass { + // This duck quacks but cannot fly <<<<<<<<<<< FILL THIS IN +public: + Decoy( string n = "nameless" ) { + typeOf = "decoy"; + name = n; + } +}; + +// <<<<<<<<<<<< WHAT ABOUT A RUBBER DUCK ?? + +int main() { + Mallard molly("molly"); + molly.display(); + molly.fly(); + molly.quack(); +} + diff --git a/class-repo-public/Exercises/ducks/ducks2.cpp b/class-repo-public/Exercises/ducks/ducks2.cpp new file mode 100644 index 0000000..4402c72 --- /dev/null +++ b/class-repo-public/Exercises/ducks/ducks2.cpp @@ -0,0 +1,46 @@ +#include "duck2.h" + +using std::string; + +class Mallard : public DuckClass { +public: + Mallard(string n="nameless") { + typeOf = "mallard"; + name = n; + } +}; + +class Loon : public DuckClass { + // This duck flies and quacks <<<<< IMPLEMENT THIS +public: + Loon( string n="nameless") { + typeOf = "loon"; + name = n; + } +}; + +class RubberDuck : public DuckClass { + // This duck squeaks (no quack) and cannot fly <<<<< IMPLEMENT THIS +public: + RubberDuck( string n="nameless") { + typeOf = "rubber duck"; + name = n; + } +}; + +class Decoy : public DuckClass { + // This duck quacks but cannot fly <<<<<<< IMPLEMENT THIS +public: + Decoy (string n = "nameless") { + typeOf = "decoy"; + name = n; + } +}; + +int main() { + Mallard molly("molly"); + molly.display(); + molly.fly(); + molly.quack(); +} + diff --git a/class-repo-public/Exercises/ducks/ducks3.cpp b/class-repo-public/Exercises/ducks/ducks3.cpp new file mode 100644 index 0000000..747e4ef --- /dev/null +++ b/class-repo-public/Exercises/ducks/ducks3.cpp @@ -0,0 +1,38 @@ +#include "duck3.h" + +using std::string; + +class Mallard : public DuckClass { + // Flies and quacks <<<<<<<<<<<<<<< IMPLEMENT THIS +public: + Mallard(string n="nameless") { + typeOf = "mallard"; + name = n; + } +}; + +class Decoy : public DuckClass { + // This duck quacks but cannot fly <<<<<<<<<<< IMPLEMENT THIS +public: + Decoy( string n = "nameless" ) { + typeOf = "decoy"; + name = n; + } +}; + +class RubberDuck : public DuckClass { + // Squeaks (no quack) and cannot fly <<<<<<<<< IMPLEMENT THIS +public: + RubberDuck( string n = "nameless" ) { + typeOf = "loon"; + name = n; + } +}; + +int main() { + Mallard molly("molly"); + molly.display(); + molly.fly(); + molly.quack(); +} + diff --git a/class-repo-public/Exercises/ducks/ducks4.cpp b/class-repo-public/Exercises/ducks/ducks4.cpp new file mode 100644 index 0000000..7514140 --- /dev/null +++ b/class-repo-public/Exercises/ducks/ducks4.cpp @@ -0,0 +1,48 @@ +#include "duck4.h" + +using std::string; + +class Mallard : public DuckClass { +public: + Mallard(string n="nameless") { + typeOf = "mallard"; + name = n; + } +}; + +class Decoy : public NoFlyDuck { + // This duck quacks but cannot fly +public: + Decoy( string n = "nameless" ) { + typeOf = "decoy"; + name = n; + } +}; + +class DuckKite : public DuckClass { + // A kite, shaped like a duck, that flies but can't quack <<<<<< IMPLEMENT THIS +public: + DuckKite( string n = "nameless" ) { + typeOf = "kite"; + name = n; + } +}; + +class RubberDuck : public DuckClass { + // This duck squeeks (no quack) and cannot fly <<<<<<<<<<<<< IMPLEMENT THIS + // Inheriting from DuckClass gives it both behaviors. Need something else. +public: + RubberDuck( string n = "nameless" ) { + typeOf = "loon"; + name = n; + } +}; + +//------------------------------------------------- +int main() { + Mallard molly("molly"); + molly.display(); + molly.fly(); + molly.quack(); +} + diff --git a/class-repo-public/Exercises/ducks/makefile b/class-repo-public/Exercises/ducks/makefile new file mode 100644 index 0000000..058cf22 --- /dev/null +++ b/class-repo-public/Exercises/ducks/makefile @@ -0,0 +1,5 @@ +all: + g++ -Wall -o ducks main.o Duck.o MallardDuck.o DecoyDuck.o RubberDuck.o QuackBehavior.o Quack.o Squeak.o MuteQuack.o + +clean: + rm *.o ducks diff --git a/class-repo-public/Exercises/dynamicallocation/Makefile b/class-repo-public/Exercises/dynamicallocation/Makefile new file mode 100644 index 0000000..a82875a --- /dev/null +++ b/class-repo-public/Exercises/dynamicallocation/Makefile @@ -0,0 +1,16 @@ +# CSCI3081 In-Class Exercise dynamic allocation and arrays + +CC = g++ +DEBUG = -g +CFLAGS = -Wall -c $(DEBUG) +LFLAGS = -Wall $(DEBUG) +OBJS = array_pointer_test.o duck.o + +all: main.o $(OBJS) + $(CC) $(LFLAGS) -o ducks main.o $(OBJS) + +%.o : %.cc + $(CC) $(CFLAGS) -o $@ $< + +clean: + \rm *.o *.*~ ducks diff --git a/class-repo-public/Exercises/dynamicallocation/README.md b/class-repo-public/Exercises/dynamicallocation/README.md new file mode 100644 index 0000000..7e9a5c8 --- /dev/null +++ b/class-repo-public/Exercises/dynamicallocation/README.md @@ -0,0 +1,30 @@ +## Dynamic Allocation and Class destructor + +The ArrayPointerTest class has a collection of 4 arrays of Duck and Duck pointers. +main() calls a helper function in which it declares an instance of this class. +Then displaycontents is called, which simply makes every duck in every array +PerformQuack(). Upon exit of the helper, everything is destroyed (or is it!). + +Note that there are print statements inside the destructors to better understand when things are called. To make things even more evident, you can print the name of the duck before you destroy it. + +Here is your assignment: + +- Copy to a git repo of someone in your group. +- Put the name of eveyone in your group at the top of this readme! +- Fill in the initialization of all duck arrays in the constructor. +- Make all the ducks quack in DisplayContents(). +- Fix the delete's where appropriate in the destructor. +- Answer the following questions directly in the readme in your git repo: + 1. Decide which of these you think is correct and briefly justify: + ```C++ + printf("Initializing ducks1\n"); + ducks1_[0].SetName("angel"); + ``` + ```C++ + printf("Initializing ducks1\n"); + ducks1_[0] = Duck("angel"); + ``` + 2. In the destructor, there are 2 lines with the comment "WILL NOT COMPILE". Explain why these will not compile. + 3. For each delete statement that is insufficient, explain how you fixed it. + +- Push to git. diff --git a/class-repo-public/Exercises/dynamicallocation/array_pointer_test.cc b/class-repo-public/Exercises/dynamicallocation/array_pointer_test.cc new file mode 100644 index 0000000..87d1d28 --- /dev/null +++ b/class-repo-public/Exercises/dynamicallocation/array_pointer_test.cc @@ -0,0 +1,64 @@ +#include "array_pointer_test.h" + +#include + +ArrayPointerTest::ArrayPointerTest(int size) { + + size_ = size; + std::string names[3] = { "angel", "yang", "mia" }; + + /* USE THIS FOR INITIALIZING DUCKS + Duck::Duck(std::string name) { + SetName(name); + } + */ + + // Initialize Duck ducks1[3]; + printf("Initializing ducks1\n"); + + // Initialize Duck * ducks2; with "size" number of elements + printf("Initializing ducks2\n"); + + // Initialize Duck * ducks3_[3]; + printf("Initializing ducks3_[3]\n"); + + // Initialize Duck ** ducks4; with "size" number of elements + printf("Initializing ducks2\n"); +} + +ArrayPointerTest::~ArrayPointerTest() { + + printf("In destructor\n"); + + // delete Duck ducks1[3]; + printf("Deleting ducks1\n"); + // delete ducks1_; // WILL NOT COMPILE + + // delete Duck * ducks2; + printf("Deleting ducks2\n"); + delete[] ducks2_; + + // delete Duck * ducks3[3] + printf("Deleting ducks3\n"); + // delete[] ducks3_; // WILL NOT COMPILE + + // delete Duck ** ducks4; + printf("Deleting ducks4\n"); + delete[] ducks4_; + +} + +void ArrayPointerTest::DisplayContents() { + + // PerformQuack() for all in Duck ducks1[3]; + printf("Quack ducks1[3]\n"); + + // PerformQuack() for all in Duck * ducks2; + printf("Quack ducks2[size_]\n"); + + // PerformQuack() for all in Duck * ducks3[3] + printf("Quack ducks3[3]\n"); + + // PerformQuack() for all in Duck ** ducks4; + printf("Quack ducks4[size_]\n"); +} diff --git a/class-repo-public/Exercises/dynamicallocation/array_pointer_test.h b/class-repo-public/Exercises/dynamicallocation/array_pointer_test.h new file mode 100644 index 0000000..a528a08 --- /dev/null +++ b/class-repo-public/Exercises/dynamicallocation/array_pointer_test.h @@ -0,0 +1,15 @@ +#include "duck.h" + +class ArrayPointerTest { +public: + ArrayPointerTest(int size=3); + ~ArrayPointerTest(); + void DisplayContents(); + +private: + Duck ducks1_[3]; + Duck * ducks2_; + Duck * ducks3_[3]; + Duck ** ducks4_; + int size_; +}; diff --git a/class-repo-public/Exercises/dynamicallocation/duck.cc b/class-repo-public/Exercises/dynamicallocation/duck.cc new file mode 100644 index 0000000..c2a51cc --- /dev/null +++ b/class-repo-public/Exercises/dynamicallocation/duck.cc @@ -0,0 +1,25 @@ +// +// Duck.cpp +// +// Created by Sarit Ghildayal on 1/24/15. +// Copyright (c) 2015 Sarit Ghildayal. All rights reserved. +// + +#include "duck.h" + +#include + +using std::cout; +using std::endl; + +Duck::Duck() { + SetName("Duck"); +} + +Duck::Duck(std::string name) { + SetName(name); +} + +void Duck::PerformQuack() { + printf("Quack\n"); +} diff --git a/class-repo-public/Exercises/dynamicallocation/duck.h b/class-repo-public/Exercises/dynamicallocation/duck.h new file mode 100644 index 0000000..4cf248a --- /dev/null +++ b/class-repo-public/Exercises/dynamicallocation/duck.h @@ -0,0 +1,27 @@ +// +// Duck.h +// +// Created by Sarit Ghildayal on 1/24/15. +// Copyright (c) 2015 Sarit Ghildayal. All rights reserved. +// + +#ifndef DUCK_H_ +#define DUCK_H_ + +#include + +class Duck { +public: + // Constructors and Destructor + Duck(); + Duck(std::string name); + ~Duck() {printf("destroying duck\n");}; + // Mutators and Accessors + void PerformQuack(); + std::string GetName() {return m_name_;} + void SetName(std::string name ) {m_name_ = name;} +protected: + std::string m_name_; +}; + +#endif diff --git a/class-repo-public/Exercises/dynamicallocation/ducks b/class-repo-public/Exercises/dynamicallocation/ducks new file mode 100755 index 0000000..33503d3 Binary files /dev/null and b/class-repo-public/Exercises/dynamicallocation/ducks differ diff --git a/class-repo-public/Exercises/dynamicallocation/main.cc b/class-repo-public/Exercises/dynamicallocation/main.cc new file mode 100644 index 0000000..e2c91a6 --- /dev/null +++ b/class-repo-public/Exercises/dynamicallocation/main.cc @@ -0,0 +1,13 @@ +#include "array_pointer_test.h" + +void helper() { + printf("initializing all my ducks\n"); + ArrayPointerTest allMyDucks(3); + allMyDucks.DisplayContents(); + printf("about to delete all my ducks\n"); +} + +int main(void) { + helper(); + return 0; +} diff --git a/class-repo-public/Exercises/dynamicallocation/scratch.cc b/class-repo-public/Exercises/dynamicallocation/scratch.cc new file mode 100644 index 0000000..02aeefb --- /dev/null +++ b/class-repo-public/Exercises/dynamicallocation/scratch.cc @@ -0,0 +1,19 @@ + +int staticArray[50000]; + +void helper() { + + int stackArray[30000]; + int * heapArray; + heapArray = new int[100000]; +} + +int main(void) { + + int stackArray[25000]; + + int * heapArray; + heapArray = new int[20000]; + + helper(); +} diff --git a/class-repo-public/Exercises/it2_design.docx b/class-repo-public/Exercises/it2_design.docx new file mode 100644 index 0000000..6095862 Binary files /dev/null and b/class-repo-public/Exercises/it2_design.docx differ diff --git a/class-repo-public/Exercises/nano_cpplint.md b/class-repo-public/Exercises/nano_cpplint.md new file mode 100644 index 0000000..8328334 --- /dev/null +++ b/class-repo-public/Exercises/nano_cpplint.md @@ -0,0 +1,45 @@ +## How to install an example of nanogui. + +1. Go to the repo on GitHub: https://github.com/wjakob/nanogui +2. Find a good place on your machine to install nanogui. +3. Clone the repo with `--recursive` tag: + ```git clone --recursive https://github.com/wjakob/nanogui + ``` +4. Use cmake to create a make file (Windows might have other directions - look at documentation). + ``` + cd nanogui + mkdir build + cd build + cmake .. + ``` + What just happened? You created a directory in which to build. You called cmake + on the nanogui directory, which will create a makefile. + +5. Call the makefile to build the examples: `make` + +6. Run example1: `./example1`. There are 4 examples. + +7. Explore the source code for the example which can be found at `cd ../src`. + + +## How to check Google Style Compliance on a file. + +Google Style Guide: https://google.github.io/styleguide/cppguide.html +cpplint: https://github.com/google/styleguide/tree/gh-pages/cpplint + +1. Obtain cpplint (this is a python script, so if you don't have Python, + you have to start there: python.org). + +2. With Python3 installed, you should be able to get cpplint with: + `pip3 install cpplint` + +3. Now run a file through the checker: + `cpplint ` + +4. Follow the style guide and the comments to get to no errors. + +## Practice Google Style Compliance + +Try to get the Visitor Pattern example to comply or the Duck example from +lab03. I wouldn't try it on the in-class polymorphism duck example, as that +is so far from compliance, it would require a restructuring of the files. diff --git a/class-repo-public/Exercises/op_over.cc b/class-repo-public/Exercises/op_over.cc new file mode 100644 index 0000000..d8b564e --- /dev/null +++ b/class-repo-public/Exercises/op_over.cc @@ -0,0 +1,114 @@ +#include +#include +#include + +class VectorDouble; + +// Vector in an x,y plane +class Vector { +public: + Vector(int x=0, int y=0) : x_(x), y_(y) {} + //*** SPECIAL CONSTRUCTOR ****// + // Vector( VectorDouble v) {} + int get_x() { return x_; } + void set_x(int x) { x_ = x; } + int get_y() { return y_; } + void set_y(int y) { y_ = y; } + + friend class VectorDouble; + + // Overload = (binary) + const Vector operator=(const Vector& right) { + x_ = right.x_; + y_ = right.y_; + return *this; + } + + // Overload + (binary) + const Vector operator+(const Vector& right) { + return Vector(x_ + right.x_, y_ + right.y_); + } + + // Overload - (binary) + + // Overload * of Vector*Vector = x*x + y*y + + // Overload * of Vector*int = Vector(int*x, int*y) + + // Overland - (unary) + + // Overload << + friend std::ostream& operator<<(std::ostream& os, const Vector& v); + +private: + int x_; + int y_; +}; + +std::ostream& operator<<(std::ostream& os, const Vector& v) { + os << "[" << v.x_ << ", " << v.y_ << "]"; + return os; +} + +class VectorDouble { +public: + VectorDouble( double x=0, double y=0) : x_(x), y_(y) {} + //*** SPECIAL CONSTRUCTOR ****// + VectorDouble( Vector v) { x_ = v.get_x(); y_ = v.get_y(); } + double get_x() { return x_; } + void set_x(double x) { x_ = x; } + double get_y() { return y_; } + void set_y(double y) { y_ = y; } + + // Overload = (binary) + const VectorDouble operator=(const VectorDouble& right) { + x_ = right.x_; + y_ = right.y_; + return *this; + } + + // Overload + (binary) + const VectorDouble operator+(const VectorDouble& right) { + x_ = x_ + right.x_; + y_ = y_ + right.y_; + return *this; + } + + // Overload << + friend std::ostream& operator<<(std::ostream& os, const VectorDouble& v); + + const VectorDouble operator+(const Vector& right) { + return VectorDouble( x_ + right.x_, y_ + right.y_); + } + +private: + double x_; + double y_; +}; + +std::ostream& operator<<(std::ostream& os, const VectorDouble& v) { + os << "[" << v.x_ << ", " << v.y_ << "]"; + return os; +} + +int main(void) { + Vector v1(2,3); + Vector v2(5,8); + Vector v3; + VectorDouble d1(1.0,1.5); + VectorDouble d2(6.3,2.9); + + std::cout << v1 << std::endl; + std::cout << v2 << std::endl; + std::cout << d1 << std::endl; + std::cout << d2 << std::endl; + + v1 = v2; + std::cout << v1 << std::endl; + + v3 = v1+v2; + std::cout << v3 << std::endl; + + VectorDouble mix = d1+v3; + std::cout << mix << std::endl; +} diff --git a/class-repo-public/Exercises/proximity.py b/class-repo-public/Exercises/proximity.py new file mode 100644 index 0000000..e8aed96 --- /dev/null +++ b/class-repo-public/Exercises/proximity.py @@ -0,0 +1,133 @@ +import turtle +import math + +def Positive(angle): + # make every angle positive in range 0 to 360 + if angle<0: + return 360+angle + if angle>360: + return angle % 360 + return angle + +class Position: + def __init__(self, x, y): + self.x = x + self.y = y + +class SensorCone: + # Proximity sensor rendering. Distance is from center of robot. + # fov: field of view, centered on heading of robot + def __init__(self, entity, distance, fov ): + # param entity: robot entity to which it belongs + self.entity = entity + self.distance = distance + self.fov = fov + self.pen = turtle.Turtle() + self.pen.hideturtle() + self.pen.speed(0) + + def Draw(self): + # Draw perimeter around entire robot, but then show limited fov + self.pen.pu() + self.pen.goto(self.entity.pos.x, self.entity.pos.y) + self.pen.setheading(self.entity.heading_deg) + self.pen.pd() + self.pen.dot(self.distance*2,'yellow') + # draw the cone + self.pen.left(self.fov//2) + for i in [0,1,1]: + self.pen.right((self.fov//2)*i) + self.pen.pd() + self.pen.forward(self.distance) + self.pen.pu() + self.pen.goto(self.entity.pos.x, self.entity.pos.y) + +class Robot: + def __init__( self, radius, heading, pos, color ): + self.radius = radius + self.pos = pos + self.heading_deg = Positive(heading) + self.color = color + self.pen = turtle.Turtle() + self.pen.hideturtle() + self.pen.speed(0) + # default value of 3*radius for proximity sensor + self.sensor = SensorCone( self, radius*3, 30) + + def Draw(self): + self.pen.pu() + self.pen.goto(self.pos.x, self.pos.y) + self.pen.pd() + self.pen.dot(self.radius*2,self.color) + self.pen.pu() + + def DrawSensor(self): + self.sensor.Draw() + + def DrawTriangle(self, width_angle, dir_angle, dist): + # this is the triangle formed by this robot and the one being sensed + self.pen.pu() + self.pen.goto(self.pos.x, self.pos.y) + self.pen.setheading(dir_angle) + self.pen.right(width_angle) + self.pen.pd() + self.pen.forward(dist) + self.pen.pu() + self.pen.goto(self.pos.x, self.pos.y) + self.pen.setheading(dir_angle) + self.pen.left(width_angle) + self.pen.pd() + self.pen.forward(dist) + +def InRange( range1, range2 ): + print(range1) + print(range2) + + # if crossing zero, adjust all angles + if range1[0] > range1[1]: + distToCrossing = 360-range1[0] + range1 = [ (range1[0]+distToCrossing)%360, range1[1]+distToCrossing] + range2 = [ (range2[0]+distToCrossing)%360, (range2[1]+distToCrossing)%360] + if range2[0] > range2[1]: + distToCrossing = 360-range2[0] + range1 = [ (range1[0]+distToCrossing)%360, range1[1]+distToCrossing] + range2 = [ (range2[0]+distToCrossing)%360, (range2[1]+distToCrossing)%360] + + # if 2nd robot spans the proximiy sensor, then in range + if range2[0] < range1[0] and range2[1] > range1[1]: + return True + if range2[0] >= range1[0] and range2[0] <= range1[1]: + return True + if range2[1] >= range1[0] and range2[1] <= range1[1]: + return True + return False + +def SensorReading( r_sensing, r_sensed): + deltaX = r_sensed.pos.x-r_sensing.pos.x + deltaY = r_sensed.pos.y-r_sensing.pos.y + dist = (deltaX**2 + deltaY**2)**0.5 + if dist > r_sensing.sensor.distance + r_sensed.radius: + return False + # triangle is formed from center of r_sensing to the point along the tangents + # to r_sensed. The height is dist and the width is r_sensed.radius*2. + triangle_theta = Positive(math.atan(r_sensed.radius/dist)*180/3.14) + distance_theta = Positive(math.atan2(deltaY, deltaX)*180/3.14) + r_sensing.DrawTriangle(triangle_theta, distance_theta, dist) + sensor_lower = r_sensing.heading_deg - r_sensing.sensor.fov//2 + sensor_upper = r_sensing.heading_deg + r_sensing.sensor.fov//2 + sensed_lower = distance_theta - triangle_theta + sensed_upper = distance_theta + triangle_theta + if InRange( [ Positive(sensor_lower), Positive(sensor_upper) ], \ + [ Positive(sensed_lower), Positive(sensed_upper) ] ): + print('True: ', dist) + else: + print('False') + +#Robot(radius, heading, pos, color) +R = Robot( 50, 0, Position(0,0), 'green') +R2 = Robot( 20, 270, Position(150,-25),'red') +R.DrawSensor() +R.Draw() +R2.Draw() +SensorReading( R, R2) + diff --git a/class-repo-public/Exercises/sensor_distress_unittest.cc b/class-repo-public/Exercises/sensor_distress_unittest.cc new file mode 100644 index 0000000..13c7c9d --- /dev/null +++ b/class-repo-public/Exercises/sensor_distress_unittest.cc @@ -0,0 +1,158 @@ +/* + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "../src/common.h" +#include "../src/robot.h" +#include "../src/sensor_distress.h" +#include "../src/event_distress.h" + +//#ifdef SENSOR_DISTRESS_TEST + +/****************************************************** +* TEST FEATURE SetUp +*******************************************************/ +class SensorDistressTest : public ::testing::Test { + protected: + virtual void SetUp() { + // Initialize Distress Sensors for Collections of TESTS + robot = new csci3081::Robot(); + sensor = new csci3081::SensorDistress(robot); + robot_id = robot->get_id(); + + event_own_distress.set_id(robot_id); + + event_internal.set_id(robot_id+1); + event_internal.set_pos(csci3081::Position(10,0)); + + event_at_range.set_id(robot_id+1); + event_at_range.set_pos(csci3081::Position(50,0)); + + event_in_range.set_id(robot_id+1); + event_in_range.set_pos(csci3081::Position(30,0)); + + event_out_of_range.set_id(robot_id+1); + event_out_of_range.set_pos(csci3081::Position(200,200)); + } + + // Default robot is at position (0,0) heading 0 + csci3081::Robot * robot; + csci3081::SensorDistress * sensor; + int robot_id; + + // Default range and fov is 50 and 360 degrees + csci3081::EventDistress event_own_distress; + csci3081::EventDistress event_internal; + csci3081::EventDistress event_in_range; + csci3081::EventDistress event_at_range; + csci3081::EventDistress event_out_of_range; + +}; + +/******************************************************************************* + * Test Cases + ******************************************************************************/ + +TEST_F(SensorDistressTest, Constructor) { + // get range, fov, and activated + EXPECT_EQ(sensor->get_range(), DEFAULT_RANGE) << "FAIL: Range:Constructor"; + EXPECT_EQ(sensor->get_fov_angle(), DEFAULT_FOV_ANGLE) << "FAIL: FOV:Constructor"; + EXPECT_EQ(sensor->IsActivated(), false) << "FAIL: Active:Constructor"; + } + +TEST_F(SensorDistressTest, DistressCallInRange) { + // A robot distress in range, not itself + // should activate the sensor + sensor->Update(event_in_range); + EXPECT_EQ(sensor->IsActivated(), true); + } + +TEST_F(SensorDistressTest, DistressCallAtRange) { + // A robot distress in range, not itself + // should activate the sensor + sensor->Update(event_at_range); + EXPECT_EQ(sensor->IsActivated(), true); + } + +TEST_F(SensorDistressTest, MultipleDistressCalls) { + // Sensor should stay active until a reset, + // which Robot will perform at each update + sensor->Update(event_in_range); + sensor->Update(event_out_of_range); + EXPECT_EQ(sensor->IsActivated(), true); +} + +TEST_F(SensorDistressTest, Reset) { + sensor->Reset(); + EXPECT_EQ(sensor->get_range(), DEFAULT_RANGE) << "FAIL: Range, Reset"; + EXPECT_EQ(sensor->get_fov_angle(), DEFAULT_FOV_ANGLE) << "FAIL: FOV, Reset"; + EXPECT_EQ(sensor->IsActivated(), false) << "FAIL: Active, Reset"; +} + +// Receives notice of event of own distress +// Relies on Reset working properly +TEST_F(SensorDistressTest, EventOfSelf) { + sensor->Reset(); + sensor->Update(event_own_distress); + EXPECT_EQ(sensor->IsActivated(), false); +} + +TEST_F(SensorDistressTest, DistressCallOutOfRange) { + sensor->Reset(); + sensor->Update(event_out_of_range); + EXPECT_EQ(sensor->IsActivated(), false); +} + +TEST_F(SensorDistressTest, DistressCallInRobot) { + sensor->Reset(); + sensor->Update(event_internal); + EXPECT_EQ(sensor->IsActivated(), true); +} + +TEST_F(SensorDistressTest, SetRange) { + // Bad input that should be ignored + int range = sensor->get_range(); + sensor->set_range(1000); + EXPECT_EQ(sensor->get_range(), range) << "FAIL: Too big, Range."; + sensor->set_range(-10); + EXPECT_EQ(sensor->get_range(), range) << "FAIL: Negative, Range."; + + // Input should be converted to an int + sensor->set_range(1.2); + EXPECT_EQ(sensor->get_range(), 1) << "FAIL: Double, Range"; + + // Good input + sensor->set_range(DEFAULT_RANGE+10); + EXPECT_EQ(sensor->get_range(), DEFAULT_RANGE+10) << "FAIL: Good, Range"; + + // Acceptable input that essentially turns off sensor + sensor->set_range(0); + EXPECT_EQ(sensor->get_range(), 0) << "FAIL: Zero, range"; +} + +TEST_F(SensorDistressTest, SetFovAngle) { + + // Bad input that should be ignored + int angle = sensor->get_fov_angle(); + sensor->set_fov_angle(-10); + EXPECT_EQ(sensor->get_fov_angle(), angle) << "FAIL: Negative, FOV."; + sensor->set_fov_angle(500); + EXPECT_EQ(sensor->get_fov_angle(), angle) << "FAIL: Too big, FOV."; + + // Input should be converted to an int + sensor->set_fov_angle(3.1); + EXPECT_EQ(sensor->get_fov_angle(), 3) << "FAIL: double, FOV"; + + // Good input + sensor->set_fov_angle(DEFAULT_FOV_ANGLE-1); + EXPECT_EQ(sensor->get_fov_angle(), DEFAULT_FOV_ANGLE-1) << "FAIL: good, FOV"; + + // Acceptable input that essentially turns off sensor + sensor->set_fov_angle(0); + EXPECT_EQ(sensor->get_fov_angle(), 0) << "FAIL: zero, FOV"; +} + +//#endif /* SENSOR_DISTRESS_TEST */ diff --git a/class-repo-public/Exercises/settersGetters/README.md b/class-repo-public/Exercises/settersGetters/README.md new file mode 100644 index 0000000..3b2e635 --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/README.md @@ -0,0 +1,26 @@ +## Design of Class Member Variables + +The reason that there was a need for the fix to pos_ was due to an assumption +on my part about the design of the ArenaEntity. I did not look at it closely +enough and assumed setters and getters were implemented polymorphically. +Through testing it was discovered that the setters and getters were not +working when upcasting. It raises interesting questions about the design of +inheritance. + +By defining member variables only in the parent class and making them private, +the user of the class is forced to use the setters and getters, rather than +having direct access to the member variables. + +Questions: + +1. Explain why overriding the variable position\_ and its getter in the non-virtual version does not work. +2. Explain why position\_ must be protected, rather than private in the virtual (polymorphic) version. +3. Draw a picture of each version of a robot, including all objects enclosed. Show where the member variable +position resides within the objects. +4. Imagine the non-virtual version, but with the replacement of position and get_pos in that class (i.e. like the version that +was in the class-repo). Draw that robot. +5. Identify the advantages and disadvantages of each version (not the incorrect one). Consider robustness and other programmers +using the code. +6. Heading and speed are stored inside of the motion_handler\_, yet these values are sometimes needed outside of the class +(e.g. when being drawn). Notice that there is a setter and getter for these variables in the robot class, yet they are not member variables of robot. What do you think about this design with respect to both robustness and use by other programmers. Do you think heading should also exist in robot and somehow be maintained between the motion_handler and the robot class? + diff --git a/class-repo-public/Exercises/settersGetters/src/Makefile b/class-repo-public/Exercises/settersGetters/src/Makefile new file mode 100644 index 0000000..a437e07 --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/Makefile @@ -0,0 +1,16 @@ +# CSCI3081 setterGetter makefile + +CC = g++ +DEBUG = -g +CFLAGS = -Wall -c $(DEBUG) +LFLAGS = -Wall $(DEBUG) +OBJS = arena.o motion_behavior.o motion_handler.o robot_virtual.o robot.o + +all: main.o $(OBJS) + $(CC) $(LFLAGS) -o robots main.o $(OBJS) + +%.o : %.cpp + $(CC) $(CFLAGS) -o $@ $< + +clean: + \rm *.o *.*~ robots diff --git a/class-repo-public/Exercises/settersGetters/src/arena.cc b/class-repo-public/Exercises/settersGetters/src/arena.cc new file mode 100755 index 0000000..024f965 --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/arena.cc @@ -0,0 +1,104 @@ +/** + * @file arena.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include + +#include "arena.h" +#include "robot.h" +#include "robot_virtual.h" + +/******************************************************************************* + * Constructors/Destructor + ******************************************************************************/ +Arena::Arena() : + n_robots_virtual_(0), n_robots_(0) { +} + +Arena::~Arena(void) { + for (int i=0;iTimestepUpdate(); + } + // Some pairs of entities may now be close enough to be colliding + for (int i=0; iTimestepUpdate(); + } + // Some pairs of entities may now be close enough to be colliding + for (int i=0; iget_position().x; + double ent1_y = ent1->get_position().y; + double ent2_x = ent2->get_position().x; + double ent2_y = ent2->get_position().y; + double dist = std::sqrt(std::pow(ent2_x - ent1_x, 2) + + std::pow(ent2_y - ent1_y, 2)); + if (dist > (ent1->get_radius() + ent2->get_radius())) { + return false; + } else { + return true; + } +} /* CheckForCollision() */ + +bool Arena::CheckForCollisionVirtual(EntityVirtual* ent1, + EntityVirtual* ent2 ) { + /* Note: this assumes circular entities */ + double ent1_x = ent1->get_position().x; + double ent1_y = ent1->get_position().y; + double ent2_x = ent2->get_position().x; + double ent2_y = ent2->get_position().y; + double dist = std::sqrt(std::pow(ent2_x - ent1_x, 2) + + std::pow(ent2_y - ent1_y, 2)); + if (dist > (ent1->get_radius() + ent2->get_radius())) { + return false; + } else { + return true; + } +} /* CheckForCollisionVirtual() */ diff --git a/class-repo-public/Exercises/settersGetters/src/arena.h b/class-repo-public/Exercises/settersGetters/src/arena.h new file mode 100755 index 0000000..863f404 --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/arena.h @@ -0,0 +1,63 @@ +/** +* @file robot_arena.h +* +* @copyright 2017 3081 Staff, All rights reserved. +*/ + +#ifndef SRC_ARENA_H_ +#define SRC_ARENA_H_ + +/******************************************************************************* +* Includes +******************************************************************************/ +#include +#include +#include + +#include "robot.h" +#include "robot_virtual.h" + +class Arena { +public: + explicit Arena(); + ~Arena(void); + + void AddRobot(const Position& pos, const int rad); + void AddRobotVirtual(const Position& pos, const int rad); + + /** + * @brief Advance the simulation by the specified # of steps. + */ + void AdvanceTime(); + + +private: + /** + * @brief Determine if two entities have collided in the arena. Collision is + * defined as the difference between the extents of the two entities being less + * than a run-time parameter. + * + * @param ent1 Entity #1. + * @param ent2 Entity #2. + * @param pointer to a collision event + * + * Collision Event is populated appropriately. + */ + + bool CheckForCollision(Entity* ent1, Entity* ent2); + + bool CheckForCollisionVirtual(EntityVirtual* ent1, EntityVirtual* ent2); + + /** + * @brief Update all entities for a single timestep + */ + void UpdateEntities(void); + + // Entities populating the arena + int n_robots_; + Entity* robots_[3]; + int n_robots_virtual_; + EntityVirtual* robots_virtual_[3]; +}; + +#endif // SRC_ARENA_H_ diff --git a/class-repo-public/Exercises/settersGetters/src/entity.h b/class-repo-public/Exercises/settersGetters/src/entity.h new file mode 100755 index 0000000..56a769e --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/entity.h @@ -0,0 +1,39 @@ +/** + * @file arena_entity.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ARENA_ENTITY_H_ +#define SRC_ARENA_ENTITY_H_ + +#include +#include "position.h" + +class Entity { + public: + Entity(const Position& pos, int rad) : position_(pos), radius_(rad) {} + virtual ~Entity(void) {} + + /** + * @brief Perform whatever updates are needed for a particular entity after 1 + * timestep. + */ + virtual void TimestepUpdate() {} + + /** + * @brief Setter and Getter for position. Defined here only (not in subclass) + * + */ + void set_position(const Position& pos) { position_ = pos; } + const Position& get_position(void) const { return position_; } + + void set_radius(const int rad) { radius_ = rad; } + const int& get_radius(void) const { return radius_; } + + private: + Position position_; + int radius_; +}; + +#endif /* SRC_ARENA_ENTITY_H_ */ diff --git a/class-repo-public/Exercises/settersGetters/src/entity_virtual.h b/class-repo-public/Exercises/settersGetters/src/entity_virtual.h new file mode 100644 index 0000000..1a121ed --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/entity_virtual.h @@ -0,0 +1,35 @@ +/** + * @file arena_entity.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ARENA_ENTITY_VIRTUAL_H_ +#define SRC_ARENA_ENTITY_VIRTUAL_H_ + +#include +#include "position.h" + +class EntityVirtual { + public: + EntityVirtual(const Position& pos, int rad) : position_(pos), radius_(rad) {} + virtual ~EntityVirtual(void) {} + + /** + * @brief Perform whatever updates are needed for a particular entity after 1 + * timestep. + */ + virtual void TimestepUpdate() {} + + virtual void set_position(const Position& pos) = 0; + virtual const Position& get_position(void) const = 0; + + virtual void set_radius(const int& rad) = 0; + virtual const int& get_radius(void) const = 0; + +protected: + Position position_; + int radius_; +}; + +#endif /* SRC_ARENA_ENTITY_H_ */ diff --git a/class-repo-public/Exercises/settersGetters/src/main.cc b/class-repo-public/Exercises/settersGetters/src/main.cc new file mode 100755 index 0000000..4d2d73b --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/main.cc @@ -0,0 +1,21 @@ +/** + * @file main.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ + +#include "arena.h" + +/******************************************************************************* + * Non-Member Functions + ******************************************************************************/ +int main(int, char **) { + + Arena arena; + + return 0; +} diff --git a/class-repo-public/Exercises/settersGetters/src/mobile_entity.h b/class-repo-public/Exercises/settersGetters/src/mobile_entity.h new file mode 100755 index 0000000..ec44138 --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/mobile_entity.h @@ -0,0 +1,51 @@ +/** + * @file mobile_arena_entity.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ARENA_MOBILE_ENTITY_H_ +#define SRC_ARENA_MOBILE_ENTITY_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "entity.h" + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief A mobile entity in the arena, capable of updating its own position + * and/or velocity when asked by the simulation. + * + * All mobile entities must have a heading angle so that their orientation can + * be properly drawn by the viwer. + */ +class MobileEntity : public Entity { + public: + MobileEntity(const Position& pos, const int rad ) : Entity(pos, rad) {} + + /** + * @brief Get/Set the current heading angle for the entity. + */ + virtual double heading_angle(void) const = 0; + virtual void heading_angle(double heading_angle) = 0; + + /** + * @brief Get/Set the current speed of an arena entity. + */ + virtual double get_speed(void) const = 0; + virtual void set_speed(double sp) = 0; + + /** + * @brief Update an entity's position after the specified # of + * timesteps has elapsed. + */ + void TimestepUpdate() = 0; + + private: +}; + +#endif /* SRC_ARENA_MOBILE_ENTITY_H_ */ diff --git a/class-repo-public/Exercises/settersGetters/src/mobile_entity_virtual.h b/class-repo-public/Exercises/settersGetters/src/mobile_entity_virtual.h new file mode 100644 index 0000000..051d1c4 --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/mobile_entity_virtual.h @@ -0,0 +1,38 @@ +/** + * @file mobile_arena_entity.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ARENA_MOBILE_ENTITY_VIRTUAL_H_ +#define SRC_ARENA_MOBILE_ENTITY_VIRTUAL_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "entity_virtual.h" + +class MobileEntityVirtual : public EntityVirtual { + public: + MobileEntityVirtual(const Position& pos, const int rad) + : EntityVirtual(pos, rad) {} + + /** + * @brief Get/Set the current heading angle for the entity. + */ + virtual double heading_angle(void) const = 0; + virtual void heading_angle(double heading_angle) = 0; + + /** + * @brief Get/Set the current speed of an arena entity. + */ + virtual double get_speed(void) const = 0; + virtual void set_speed(double sp) = 0; + + void TimestepUpdate(); + + private: +}; + +#endif /* SRC_ARENA_MOBILE_ENTITY_H_ */ diff --git a/class-repo-public/Exercises/settersGetters/src/motion_behavior.cc b/class-repo-public/Exercises/settersGetters/src/motion_behavior.cc new file mode 100755 index 0000000..9d71af2 --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/motion_behavior.cc @@ -0,0 +1,36 @@ +/** + * @file robot_motion_behavior.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "motion_behavior.h" +#include "position.h" + +/******************************************************************************* + * Member Functions + ******************************************************************************/ +void MotionBehavior::UpdatePosition(MobileEntity * const ent) { + + Position new_pos; + + // Movement is always along the heading_angle (i.e. the hypotenuse) + new_pos.x += cos(ent->heading_angle()*3.14/180.0)*ent->get_speed()*1; + new_pos.y += sin(ent->heading_angle()*3.14/180.0)*ent->get_speed()*1; + ent->set_position(new_pos); +} /* update_position() */ + +void MotionBehavior::UpdatePosition( + MobileEntityVirtual * const ent) { + + Position new_pos; + + // Movement is always along the heading_angle (i.e. the hypotenuse) + new_pos.x += cos(ent->heading_angle()*3.14/180.0)*ent->get_speed()*1; + new_pos.y += sin(ent->heading_angle()*3.14/180.0)*ent->get_speed()*1; + ent->set_position(new_pos); +} /* update_position() */ diff --git a/class-repo-public/Exercises/settersGetters/src/motion_behavior.h b/class-repo-public/Exercises/settersGetters/src/motion_behavior.h new file mode 100755 index 0000000..041876c --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/motion_behavior.h @@ -0,0 +1,27 @@ +/** + * @file robot_motion_behavior.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ROBOT_MOTION_BEHAVIOR_H_ +#define SRC_ROBOT_MOTION_BEHAVIOR_H_ + +#include "mobile_entity.h" +#include "mobile_entity_virtual.h" + +class MotionBehavior { + public: + MotionBehavior(void) {} + + /** + * @brief Update the position for an ArenaEntity, based on its current + * position and velocity. + * + * @param[in] ent The entitity to update. + */ + void UpdatePosition(class MobileEntity * const ent); + void UpdatePosition(class MobileEntityVirtual * const ent); +}; + +#endif /* SRC_ROBOT_MOTION_BEHAVIOR_H_ */ diff --git a/class-repo-public/Exercises/settersGetters/src/motion_handler.cc b/class-repo-public/Exercises/settersGetters/src/motion_handler.cc new file mode 100755 index 0000000..3c022c9 --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/motion_handler.cc @@ -0,0 +1,26 @@ +/** + * @file robot_motion_handler.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "motion_handler.h" + +/******************************************************************************* + * Constructors/Destructor + ******************************************************************************/ +MotionHandler::MotionHandler() : + heading_angle_(0), speed_(0), max_speed_(5) { +} + +/******************************************************************************* + * Member Functions + ******************************************************************************/ +void MotionHandler::UpdateVelocity(bool collided) { + if (collided) { + heading_angle_ = -heading_angle_; + } +} diff --git a/class-repo-public/Exercises/settersGetters/src/motion_handler.h b/class-repo-public/Exercises/settersGetters/src/motion_handler.h new file mode 100755 index 0000000..3efe073 --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/motion_handler.h @@ -0,0 +1,42 @@ +/** + * @file actuator_handler.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ROBOT_MOTION_HANDLER_H_ +#define SRC_ROBOT_MOTION_HANDLER_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "mobile_entity.h" + +class MotionHandler { + public: + MotionHandler(); + + /** + * @brief Change the speed and direction according to the sensor readings. + * + * @param touch sensor that can be activated and contains point-of-contact. + * + */ + void UpdateVelocity(bool collided); + + double speed(void) const { return speed_; } + void speed(double sp) { speed_ = sp; } + + double heading_angle(void) const { return heading_angle_;} + void heading_angle(double ha) { heading_angle_ = ha; } + + double max_speed(void) const { return max_speed_; } + void max_speed(double ms) { max_speed_ = ms; } + + private: + double heading_angle_; + double speed_; + double max_speed_; +}; + +#endif /* SRC_ROBOT_MOTION_HANDLER_H_ */ diff --git a/class-repo-public/Exercises/settersGetters/src/position.h b/class-repo-public/Exercises/settersGetters/src/position.h new file mode 100644 index 0000000..276e3f8 --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/position.h @@ -0,0 +1,17 @@ +/** + * @file position.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_POSITION_H_ +#define SRC_POSITION_H_ + +struct Position { + Position(void) : x(0),y(0) { } + Position(int in_x, int in_y) : x(in_x), y(in_y) { } + int x; + int y; +}; + +#endif /* SRC_POSITION_H_ */ diff --git a/class-repo-public/Exercises/settersGetters/src/robot.cc b/class-repo-public/Exercises/settersGetters/src/robot.cc new file mode 100755 index 0000000..a9aa615 --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/robot.cc @@ -0,0 +1,37 @@ +/** + * @file robot.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "robot.h" +#include "motion_behavior.h" + + +/******************************************************************************* + * Constructors/Destructor + ******************************************************************************/ +Robot::Robot(const Position& pos, const int rad ) : + MobileEntity(pos, rad), + id_(-1), + motion_handler_(), + motion_behavior_() { + motion_handler_.heading_angle(270); + motion_handler_.speed(5); +} + + +/******************************************************************************* + * Member Functions + ******************************************************************************/ +void Robot::TimestepUpdate() { + + // Update heading and speed as indicated by touch sensor + motion_handler_.UpdateVelocity(true); + + // Use velocity and position to update position + motion_behavior_.UpdatePosition(this); +} /* TimestepUpdate() */ diff --git a/class-repo-public/Exercises/settersGetters/src/robot.h b/class-repo-public/Exercises/settersGetters/src/robot.h new file mode 100755 index 0000000..c4f647e --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/robot.h @@ -0,0 +1,45 @@ +/** + * @file robot.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ROBOT_H_ +#define SRC_ROBOT_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include + +#include "mobile_entity.h" +#include "motion_behavior.h" +#include "motion_handler.h" + +class Robot : public MobileEntity { + public: + explicit Robot(const Position& pos, const int rad ); + + + /** + * @brief Update the robot's position and velocity after the specified + * duration has passed. + * + * @param dt The # of timesteps that have elapsed since the last update. + */ + void TimestepUpdate(); + + double heading_angle(void) const { + return motion_handler_.heading_angle(); + } + void heading_angle(double ha) { motion_handler_.heading_angle(ha); } + double get_speed(void) const { return motion_handler_.speed(); } + void set_speed(double sp) { motion_handler_.speed(sp); } + + private: + int id_; + MotionHandler motion_handler_; + MotionBehavior motion_behavior_; +}; + +#endif /* SRC_ROBOT_H_ */ diff --git a/class-repo-public/Exercises/settersGetters/src/robot_virtual.cc b/class-repo-public/Exercises/settersGetters/src/robot_virtual.cc new file mode 100755 index 0000000..370a10f --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/robot_virtual.cc @@ -0,0 +1,38 @@ +/** + * @file robot.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "robot_virtual.h" +#include "motion_behavior.h" +#include "mobile_entity_virtual.h" + + +/******************************************************************************* + * Constructors/Destructor + ******************************************************************************/ +RobotVirtual::RobotVirtual(const Position& pos, const int rad) : + MobileEntityVirtual(pos, rad), + id_(-1), + motion_handler_(), + motion_behavior_() { + motion_handler_.heading_angle(270); + motion_handler_.speed(5); +} + + +/******************************************************************************* + * Member Functions + ******************************************************************************/ +void RobotVirtual::TimestepUpdate() { + + // Update heading and speed as indicated by touch sensor + motion_handler_.UpdateVelocity(true); + + // Use velocity and position to update position + motion_behavior_.UpdatePosition(this); +} /* TimestepUpdate() */ diff --git a/class-repo-public/Exercises/settersGetters/src/robot_virtual.h b/class-repo-public/Exercises/settersGetters/src/robot_virtual.h new file mode 100755 index 0000000..e80b49c --- /dev/null +++ b/class-repo-public/Exercises/settersGetters/src/robot_virtual.h @@ -0,0 +1,68 @@ +/** + * @file robot.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ROBOT_VIRTUAL_H_ +#define SRC_ROBOT_VIRTUAL_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include + +#include "mobile_entity_virtual.h" +#include "motion_behavior.h" +#include "motion_handler.h" + + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief Class representing a mobile robot within the arena. + * + * Robots have the capability of updating their own position when asked, and + * also track their own velocity and heading. They have a touch sensor for + * responding to collision events which is activated/deactivated on collision + * events. + * + */ +class RobotVirtual : public MobileEntityVirtual { + public: + explicit RobotVirtual(const Position& pos, const int rad ); + + /** + * @brief Setter and Getter for position. + */ + void set_position(const Position& pos) { position_ = pos; } + const Position& get_position(void) const { return position_; } + + /** + * @brief Setter and Getter for radius. + */ + void set_radius(const int& rad) { radius_ = rad; } + const int& get_radius(void) const { return radius_; } + + /** + * @brief Update the robot's position and velocity after the specified + * duration has passed. + */ + void TimestepUpdate(); + + + double heading_angle(void) const { + return motion_handler_.heading_angle(); + } + void heading_angle(double ha) { motion_handler_.heading_angle(ha); } + double get_speed(void) const { return motion_handler_.speed(); } + void set_speed(double sp) { motion_handler_.speed(sp); } + + private: + int id_; + MotionHandler motion_handler_; + MotionBehavior motion_behavior_; +}; + +#endif /* SRC_ROBOT_H_ */ diff --git a/class-repo-public/Exercises/testing/.gitignore b/class-repo-public/Exercises/testing/.gitignore new file mode 100644 index 0000000..81d9e10 --- /dev/null +++ b/class-repo-public/Exercises/testing/.gitignore @@ -0,0 +1,4 @@ +*.o +*.*~ +.DS_Store +unittests \ No newline at end of file diff --git a/class-repo-public/Exercises/testing/Makefile b/class-repo-public/Exercises/testing/Makefile new file mode 100644 index 0000000..476372d --- /dev/null +++ b/class-repo-public/Exercises/testing/Makefile @@ -0,0 +1,17 @@ +# CSCI3081 In-Class Exercise dynamic allocation and arrays + +CC = g++ +DEBUG = -g +CFLAGS = -Wall -c $(DEBUG) +LFLAGS = -Wall $(DEBUG) +TESTOBJS = +OBJS = main.o robot.o tests.o + +all: $(OBJS) + $(CC) $(LFLAGS) -o unittests $(OBJS) + +%.o : %.cc + $(CC) $(CFLAGS) -o $@ $< + +clean: + \rm *.o *.*~ unittests diff --git a/class-repo-public/Exercises/testing/README.md b/class-repo-public/Exercises/testing/README.md new file mode 100644 index 0000000..20ae11a --- /dev/null +++ b/class-repo-public/Exercises/testing/README.md @@ -0,0 +1,16 @@ +## Homegrown Testing Framework + +### Fibonacci + +1. Look at "tests.cc" to see the assert statement options. +2. Look at "fibonacci_test.cc" to see how these assert statements are used. +3. _make_ and run the unittests `./unittests` to see how successful these tests were in finding errors! +4. No need to fix these errors. The point is to understand how test cases are created and used to develop a suite of tests. + +### Robot + +1. Look at "robot.h" and robot.cc". +2. In the file "robot_test.cc" notice there are no tests written. Remedy this by writing a test that tests the DEFAULT constructor. This would be composed of the instantiation of a robot, then a series of the provided assert statements (e.g. _ExpectEqual()_) to check that the values for the radius and the position are as expected. The tests in "fibonacci_test.cc" provide a guide on how to create these. +3. Write a test for the other constructor. +4. Look at _Robot::CheckOverlap()_. Identify some scenarios that would be good to test. +5. For each scenario, write a collection of assert statements to confirm the results. diff --git a/class-repo-public/Exercises/testing/fibonacci.h b/class-repo-public/Exercises/testing/fibonacci.h new file mode 100644 index 0000000..cc66f8f --- /dev/null +++ b/class-repo-public/Exercises/testing/fibonacci.h @@ -0,0 +1,16 @@ +#ifndef TESTS_FIBONACCI_H_ +#define TESTS_FIBONACCI_H_ + +int fibonacci(int n) { + // Calculate the nth fibonacci number + int sum_n; + int sum_n2 = 1; + int sum_n1 = 1; + for (int i=0;i + +#include "fibonacci.h" +#include "tests.h" + +void fibonacci_unittest() { + + // test negative + ExpectEqual("Negative Input",-1,fibonacci(-1)); + + // test 0 + ExpectEqual("0 Input",0,fibonacci(0)); + + // test boundary + ExpectEqual("Border 1",1,fibonacci(1)); + ExpectEqual("Border 2",2,fibonacci(2)); + + // test typical + ExpectEqual("Typical 5", 5, fibonacci(5)); + ExpectEqual("Typical 10", 55, fibonacci(10)); +} diff --git a/class-repo-public/Exercises/testing/main.cc b/class-repo-public/Exercises/testing/main.cc new file mode 100644 index 0000000..e1994fb --- /dev/null +++ b/class-repo-public/Exercises/testing/main.cc @@ -0,0 +1,14 @@ +#include + +#include "fibonacci_test.cc" +#include "robot_test.cc" + +int main() { + std::cout << std::endl ; + std::cout << "FIBONACCI TESTS" << std::endl; + fibonacci_unittest(); + + std::cout << std::endl << std::endl; + std::cout << "ROBOT TESTS" << std::endl; + robot_unittest(); +} diff --git a/class-repo-public/Exercises/testing/robot.cc b/class-repo-public/Exercises/testing/robot.cc new file mode 100644 index 0000000..96c756d --- /dev/null +++ b/class-repo-public/Exercises/testing/robot.cc @@ -0,0 +1,23 @@ +#include +#include "robot.h" + + +Robot::Robot() : radius(5), position(0,0) { +} + +Robot::Robot(int r, int x, int y) : radius(r), position(x,y) { +} + +bool Robot::CheckOverlap(const Robot& robot2){ + // Get distance between centers + int deltaX = robot2.get_position().x - this->get_position().x; + int deltaY = robot2.get_position().y - this->get_position().y; + + int distance = sqrt((double)((deltaX*deltaX) + (deltaY*deltaY))); + + // check overlap + if (distance < this->get_radius() + robot2.get_radius()) { + return true; + } + return false; +} diff --git a/class-repo-public/Exercises/testing/robot.h b/class-repo-public/Exercises/testing/robot.h new file mode 100644 index 0000000..c2f3b0d --- /dev/null +++ b/class-repo-public/Exercises/testing/robot.h @@ -0,0 +1,23 @@ +#ifndef TESTS_ROBOT_H_ +#define TESTS_ROBOT_H_ + +struct Position { + int x; + int y; + Position(): x(0), y(0) {} + Position(int inX, int inY) : x(inX), y(inY) {} +}; + +class Robot { +public: + Robot(); + Robot(int r, int x, int y); + bool CheckOverlap(const Robot& robot2); + Position get_position(void) const { return position; } + int get_radius(void) const { return radius; } +private: + int radius; + Position position; +}; + +#endif diff --git a/class-repo-public/Exercises/testing/robot_test.cc b/class-repo-public/Exercises/testing/robot_test.cc new file mode 100644 index 0000000..4540f8a --- /dev/null +++ b/class-repo-public/Exercises/testing/robot_test.cc @@ -0,0 +1,7 @@ +#include + +#include "robot.h" + +void robot_unittest() { + +} diff --git a/class-repo-public/Exercises/testing/test.cc b/class-repo-public/Exercises/testing/test.cc new file mode 100644 index 0000000..e69de29 diff --git a/class-repo-public/Exercises/testing/tests.cc b/class-repo-public/Exercises/testing/tests.cc new file mode 100644 index 0000000..2babf96 --- /dev/null +++ b/class-repo-public/Exercises/testing/tests.cc @@ -0,0 +1,45 @@ +#include +#include +#include + +#include "tests.h" + +void ExpectEqual(std::string test, int expect, int got) { + std::cout << test << ":"; + if (expect != got) { + printf("FAIL: expected %d, returned %d\n", expect, got); + } else { + printf("PASS\n"); + } +} + +void ExpectEqual(std::string test, float expect, float got) { + std::cout << test << ":"; + if (expect != got) { + printf("FAIL:: expected %f, returned %f\n", expect, got); + } else { + printf("PASS\n"); + } +} + +void ExpectEqual(std::string test, bool expect, int got) { + char * expect_string; + char * got_string; + if (expect) { + expect_string = "True"; + } else { + expect_string = "False"; + } + if (got) { + got_string = "True"; + } else { + got_string = "False"; + } + std::cout << test << ":"; + if (expect != got) { + printf("FAIL: expected %s, returned %s\n", + expect_string, got_string ); + } else { + printf("PASS\n"); + } +} diff --git a/class-repo-public/Exercises/testing/tests.h b/class-repo-public/Exercises/testing/tests.h new file mode 100644 index 0000000..7ccc832 --- /dev/null +++ b/class-repo-public/Exercises/testing/tests.h @@ -0,0 +1,11 @@ +#ifndef TESTS_TESTS_H_ +#define TESTS_TESTS_H_ + +#include + +// These are my "homegrown" version of Google test asserts +void ExpectEqual(std::string test, int expect, int got); +void ExpectEqual(std::string test, float expect, float got); +void ExpectEqual(std::string test, bool expect, int got); + +#endif diff --git a/class-repo-public/Exercises/uml.md b/class-repo-public/Exercises/uml.md new file mode 100644 index 0000000..074eea7 --- /dev/null +++ b/class-repo-public/Exercises/uml.md @@ -0,0 +1,65 @@ +## Peer Reflection on UML Diagram + +The learning objectives: + +- Reflect on methods for communicating code structure. +- Critically analyze the UML diagram as written communication. +- Identify the key characteristics of this writing form that best communicate code structure for the purpose of assisting others in understanding the code. +- Identify strengths and weaknesses of your UML and paragraph drafts. + +### _Good_ Writing Criteria + +- States key information clearly (important findings, recommendations, ideas, +issues, etc.). +- Addresses target audience with an appropriate tone and level of explanation. +- Presents any needed background explanation clearly or informatively. +- Describes algorithms, data structures, software system components, etc. +accurately. +- Describes algorithms, data structures, software system components, etc. +informatively and concisely. +- Uses terminology and notation correctly. +- Uses appropriate structures (lists, diagrams, equations, code samples, etc.). +- Includes clear and informative tables, diagrams, references, etc. +- Has a good organization and logical flow. +- Avoids including irrelevant information, overemphasizing less important material, +or writing verbosely. + +## Form a Group + +Form a group of 3 to 4 people. Not 2 and not more than 4. Someone volunteer to start. + +> Take personal notes during the discussion to use to improve your UML, as well as develop the main page of doxygen documentation for next week's submission. + +### Each person ... + +1. Show your UML diagram and paragraph to the group, giving everyone a few minutes to look it over and read the paragraph. + +2. Explain your process in understanding the code structure and creating your UML diagram. Did you spend hours with the code then start your UML? Did you start with the UML and fill it in as your understanding of the code grew? + +3. State what tool you used. What was the good and bad of that tool? Would you recommend that tool to others? + +4. Look at the UML and paragraph as a unit. In what ways does the paragraph enhance or complement the UML? In what ways does the UML complement the paragraph? + +5. Share with peers what you found to be the most difficult aspect of generating the diagram. + +6. Share with the group something you want to improve upon in the diagram or the paragraph. + +### Next (after everyone shared) ... + +Collectively discuss the following: + +1. MEDIUM: Identify elements in each UML that is unique and brings value to understanding the code. (Keep this in mind as you edit your UML for next week's submission). + +2. DETAIL: Compare the levels of detail on each UML. Is there huge variety? Identify places in each UML where you think either more or less detail is needed. + +3. AUDIENCE: Would you change the level of detail for different audiences? For example, as someone who is now submerged in the code, do you want more or less detail? Think back to when you didn't know the code - do you think you want more or less detail? + +3. VISUALIZATION: Compare the overall layouts and formatting for each. Is there huge variety? Do fonts make a difference? Can you distinguish arrows? Identify places in each UML where layout or formatting could be improved. + +4. CORRECTNESS: Are things presented correctly - inheritance, composition, flow-of-information? + +5. WRITING PROCESS: How much do you think the UML contributes to understanding the code OR is it the _process_ of constructing the UML that is the most informative? What is your opinion on the utility of an auto-generated UML (which doxygen will do for you)? + +5. COMPLETENESS: Think back to when you first engaged the code. What was missing from what was provided to you to better assist you in understanding the code (other than the UML)? How can you incorporate those elements into either the UML or in the mainpage of your doxygen documentation? + +6. DOCUMENTATION PLAN: Collectively establish a rough outline of content for the doxygen documentation. This is an introduction to the project and to the html pages. Keep in mind your audience. Level of detail is important - how much detail do you need to go into? diff --git a/class-repo-public/README.md b/class-repo-public/README.md new file mode 100644 index 0000000..a1fb064 --- /dev/null +++ b/class-repo-public/README.md @@ -0,0 +1,20 @@ +# class-repo-public for CSCI3081 Fall 2017 + +This is a read-only repository. Students copy from this into their personal repo in the course organization. + +There is a second branch _iter1-update1_ whose source code is functionally equivalent to the master branch but +is refactored. At the start of iteration2, these branches will be merged. Students will receive instructions on +refactoring so that student repos are aligned with class-repo-public. + +### USE THE MASTER BRANCH FOR TESTS + +#### Latest Updates + +- 10/24/17: Bug fix. robot_motion_handler_unittest. speed changed in test for UP and DOWN. +- 10/24/17: Bug fix. Accept() in robot_battery.cc added scoping - RobotBattery:: +- 10/22/17: Bug fix. Added rmh.heading_angle(180) in robot-motion-handler-unittest +- 10/22/17: Simplified tests. Replaced, removed, or modified almost all of them. Email sent with FYI's. +- 10/21/17: Tests updated to use csci3081::Color +- 10/21/17: color.h added to src directory (not incorporated into other code - this is part of your work for priority 3) +- 10/21/17: Tests from iter1-tests moved into the master branch, without change. +- 10/21/17: New branch fix/priority1 has been created to fix issues related to priority1 tests. Do NOT use this branch - it is for development for staff. diff --git a/class-repo-public/cpplint.py b/class-repo-public/cpplint.py new file mode 100755 index 0000000..278891d --- /dev/null +++ b/class-repo-public/cpplint.py @@ -0,0 +1,6467 @@ +#!/usr/bin/env python +# +# Copyright (c) 2009 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Does google-lint on c++ files. + +The goal of this script is to identify places in the code that *may* +be in non-compliance with google style. It does not attempt to fix +up these problems -- the point is to educate. It does also not +attempt to find all problems, or to ensure that everything it does +find is legitimately a problem. + +In particular, we can get very confused by /* and // inside strings! +We do a small hack, which is to ignore //'s with "'s after them on the +same line, but it is far from perfect (in either direction). +""" + +import codecs +import copy +import getopt +import glob +import itertools +import math # for log +import os +import re +import sre_compile +import string +import sys +import unicodedata +import xml.etree.ElementTree + +# if empty, use defaults +_header_extensions = set([]) + +# if empty, use defaults +_valid_extensions = set([]) + + +# Files with any of these extensions are considered to be +# header files (and will undergo different style checks). +# This set can be extended by using the --headers +# option (also supported in CPPLINT.cfg) +def GetHeaderExtensions(): + if not _header_extensions: + return set(['h', 'hpp', 'hxx', 'h++', 'cuh']) + return _header_extensions + +# The allowed extensions for file names +# This is set by --extensions flag +def GetAllExtensions(): + if not _valid_extensions: + return GetHeaderExtensions().union(set(['c', 'cc', 'cpp', 'cxx', 'c++', 'cu'])) + return _valid_extensions + +def GetNonHeaderExtensions(): + return GetAllExtensions().difference(GetHeaderExtensions()) + + +_USAGE = """ +Syntax: cpplint.py [--verbose=#] [--output=emacs|eclipse|vs7|junit] + [--filter=-x,+y,...] + [--counting=total|toplevel|detailed] [--repository=path] + [--root=subdir] [--linelength=digits] [--recursive] + [--exclude=path] + [--headers=ext1,ext2] + [--extensions=hpp,cpp,...] + [file] ... + + The style guidelines this tries to follow are those in + https://google.github.io/styleguide/cppguide.html + + Every problem is given a confidence score from 1-5, with 5 meaning we are + certain of the problem, and 1 meaning it could be a legitimate construct. + This will miss some errors, and is not a substitute for a code review. + + To suppress false-positive errors of a certain category, add a + 'NOLINT(category)' comment to the line. NOLINT or NOLINT(*) + suppresses errors of all categories on that line. + + The files passed in will be linted; at least one file must be provided. + Default linted extensions are %s. + Other file types will be ignored. + Change the extensions with the --extensions flag. + + Flags: + + output=emacs|eclipse|vs7|junit + By default, the output is formatted to ease emacs parsing. Output + compatible with eclipse (eclipse), Visual Studio (vs7), and JUnit + XML parsers such as those used in Jenkins and Bamboo may also be + used. Other formats are unsupported. + + verbose=# + Specify a number 0-5 to restrict errors to certain verbosity levels. + Errors with lower verbosity levels have lower confidence and are more + likely to be false positives. + + quiet + Supress output other than linting errors, such as information about + which files have been processed and excluded. + + filter=-x,+y,... + Specify a comma-separated list of category-filters to apply: only + error messages whose category names pass the filters will be printed. + (Category names are printed with the message and look like + "[whitespace/indent]".) Filters are evaluated left to right. + "-FOO" and "FOO" means "do not print categories that start with FOO". + "+FOO" means "do print categories that start with FOO". + + Examples: --filter=-whitespace,+whitespace/braces + --filter=whitespace,runtime/printf,+runtime/printf_format + --filter=-,+build/include_what_you_use + + To see a list of all the categories used in cpplint, pass no arg: + --filter= + + counting=total|toplevel|detailed + The total number of errors found is always printed. If + 'toplevel' is provided, then the count of errors in each of + the top-level categories like 'build' and 'whitespace' will + also be printed. If 'detailed' is provided, then a count + is provided for each category like 'build/class'. + + repository=path + The top level directory of the repository, used to derive the header + guard CPP variable. By default, this is determined by searching for a + path that contains .git, .hg, or .svn. When this flag is specified, the + given path is used instead. This option allows the header guard CPP + variable to remain consistent even if members of a team have different + repository root directories (such as when checking out a subdirectory + with SVN). In addition, users of non-mainstream version control systems + can use this flag to ensure readable header guard CPP variables. + + Examples: + Assuming that Alice checks out ProjectName and Bob checks out + ProjectName/trunk and trunk contains src/chrome/ui/browser.h, then + with no --repository flag, the header guard CPP variable will be: + + Alice => TRUNK_SRC_CHROME_BROWSER_UI_BROWSER_H_ + Bob => SRC_CHROME_BROWSER_UI_BROWSER_H_ + + If Alice uses the --repository=trunk flag and Bob omits the flag or + uses --repository=. then the header guard CPP variable will be: + + Alice => SRC_CHROME_BROWSER_UI_BROWSER_H_ + Bob => SRC_CHROME_BROWSER_UI_BROWSER_H_ + + root=subdir + The root directory used for deriving header guard CPP variables. This + directory is relative to the top level directory of the repository which + by default is determined by searching for a directory that contains .git, + .hg, or .svn but can also be controlled with the --repository flag. If + the specified directory does not exist, this flag is ignored. + + Examples: + Assuming that src is the top level directory of the repository, the + header guard CPP variables for src/chrome/browser/ui/browser.h are: + + No flag => CHROME_BROWSER_UI_BROWSER_H_ + --root=chrome => BROWSER_UI_BROWSER_H_ + --root=chrome/browser => UI_BROWSER_H_ + + linelength=digits + This is the allowed line length for the project. The default value is + 80 characters. + + Examples: + --linelength=120 + + recursive + Search for files to lint recursively. Each directory given in the list + of files to be linted is replaced by all files that descend from that + directory. Files with extensions not in the valid extensions list are + excluded. + + exclude=path + Exclude the given path from the list of files to be linted. Relative + paths are evaluated relative to the current directory and shell globbing + is performed. This flag can be provided multiple times to exclude + multiple files. + + Examples: + --exclude=one.cc + --exclude=src/*.cc + --exclude=src/*.cc --exclude=test/*.cc + + extensions=extension,extension,... + The allowed file extensions that cpplint will check + + Examples: + --extensions=%s + + headers=extension,extension,... + The allowed header extensions that cpplint will consider to be header files + (by default, only files with extensions %s + will be assumed to be headers) + + Examples: + --headers=%s + + cpplint.py supports per-directory configurations specified in CPPLINT.cfg + files. CPPLINT.cfg file can contain a number of key=value pairs. + Currently the following options are supported: + + set noparent + filter=+filter1,-filter2,... + exclude_files=regex + linelength=80 + root=subdir + + "set noparent" option prevents cpplint from traversing directory tree + upwards looking for more .cfg files in parent directories. This option + is usually placed in the top-level project directory. + + The "filter" option is similar in function to --filter flag. It specifies + message filters in addition to the |_DEFAULT_FILTERS| and those specified + through --filter command-line flag. + + "exclude_files" allows to specify a regular expression to be matched against + a file name. If the expression matches, the file is skipped and not run + through the linter. + + "linelength" specifies the allowed line length for the project. + + The "root" option is similar in function to the --root flag (see example + above). + + CPPLINT.cfg has an effect on files in the same directory and all + subdirectories, unless overridden by a nested configuration file. + + Example file: + filter=-build/include_order,+build/include_alpha + exclude_files=.*\\.cc + + The above example disables build/include_order warning and enables + build/include_alpha as well as excludes all .cc from being + processed by linter, in the current directory (where the .cfg + file is located) and all subdirectories. +""" % (list(GetAllExtensions()), + ','.join(list(GetAllExtensions())), + GetHeaderExtensions(), + ','.join(GetHeaderExtensions())) + +# We categorize each error message we print. Here are the categories. +# We want an explicit list so we can list them all in cpplint --filter=. +# If you add a new error message with a new category, add it to the list +# here! cpplint_unittest.py should tell you if you forget to do this. +_ERROR_CATEGORIES = [ + 'build/class', + 'build/c++11', + 'build/c++14', + 'build/c++tr1', + 'build/deprecated', + 'build/endif_comment', + 'build/explicit_make_pair', + 'build/forward_decl', + 'build/header_guard', + 'build/include', + 'build/include_subdir', + 'build/include_alpha', + 'build/include_order', + 'build/include_what_you_use', + 'build/namespaces_literals', + 'build/namespaces', + 'build/printf_format', + 'build/storage_class', + 'legal/copyright', + 'readability/alt_tokens', + 'readability/braces', + 'readability/casting', + 'readability/check', + 'readability/constructors', + 'readability/fn_size', + 'readability/inheritance', + 'readability/multiline_comment', + 'readability/multiline_string', + 'readability/namespace', + 'readability/nolint', + 'readability/nul', + 'readability/strings', + 'readability/todo', + 'readability/utf8', + 'runtime/arrays', + 'runtime/casting', + 'runtime/explicit', + 'runtime/int', + 'runtime/init', + 'runtime/invalid_increment', + 'runtime/member_string_references', + 'runtime/memset', + 'runtime/indentation_namespace', + 'runtime/operator', + 'runtime/printf', + 'runtime/printf_format', + 'runtime/references', + 'runtime/string', + 'runtime/threadsafe_fn', + 'runtime/vlog', + 'whitespace/blank_line', + 'whitespace/braces', + 'whitespace/comma', + 'whitespace/comments', + 'whitespace/empty_conditional_body', + 'whitespace/empty_if_body', + 'whitespace/empty_loop_body', + 'whitespace/end_of_line', + 'whitespace/ending_newline', + 'whitespace/forcolon', + 'whitespace/indent', + 'whitespace/line_length', + 'whitespace/newline', + 'whitespace/operators', + 'whitespace/parens', + 'whitespace/semicolon', + 'whitespace/tab', + 'whitespace/todo', + ] + +# These error categories are no longer enforced by cpplint, but for backwards- +# compatibility they may still appear in NOLINT comments. +_LEGACY_ERROR_CATEGORIES = [ + 'readability/streams', + 'readability/function', + ] + +# The default state of the category filter. This is overridden by the --filter= +# flag. By default all errors are on, so only add here categories that should be +# off by default (i.e., categories that must be enabled by the --filter= flags). +# All entries here should start with a '-' or '+', as in the --filter= flag. +_DEFAULT_FILTERS = ['-build/include_alpha'] + +# The default list of categories suppressed for C (not C++) files. +_DEFAULT_C_SUPPRESSED_CATEGORIES = [ + 'readability/casting', + ] + +# The default list of categories suppressed for Linux Kernel files. +_DEFAULT_KERNEL_SUPPRESSED_CATEGORIES = [ + 'whitespace/tab', + ] + +# We used to check for high-bit characters, but after much discussion we +# decided those were OK, as long as they were in UTF-8 and didn't represent +# hard-coded international strings, which belong in a separate i18n file. + +# C++ headers +_CPP_HEADERS = frozenset([ + # Legacy + 'algobase.h', + 'algo.h', + 'alloc.h', + 'builtinbuf.h', + 'bvector.h', + 'complex.h', + 'defalloc.h', + 'deque.h', + 'editbuf.h', + 'fstream.h', + 'function.h', + 'hash_map', + 'hash_map.h', + 'hash_set', + 'hash_set.h', + 'hashtable.h', + 'heap.h', + 'indstream.h', + 'iomanip.h', + 'iostream.h', + 'istream.h', + 'iterator.h', + 'list.h', + 'map.h', + 'multimap.h', + 'multiset.h', + 'ostream.h', + 'pair.h', + 'parsestream.h', + 'pfstream.h', + 'procbuf.h', + 'pthread_alloc', + 'pthread_alloc.h', + 'rope', + 'rope.h', + 'ropeimpl.h', + 'set.h', + 'slist', + 'slist.h', + 'stack.h', + 'stdiostream.h', + 'stl_alloc.h', + 'stl_relops.h', + 'streambuf.h', + 'stream.h', + 'strfile.h', + 'strstream.h', + 'tempbuf.h', + 'tree.h', + 'type_traits.h', + 'vector.h', + # 17.6.1.2 C++ library headers + 'algorithm', + 'array', + 'atomic', + 'bitset', + 'chrono', + 'codecvt', + 'complex', + 'condition_variable', + 'deque', + 'exception', + 'forward_list', + 'fstream', + 'functional', + 'future', + 'initializer_list', + 'iomanip', + 'ios', + 'iosfwd', + 'iostream', + 'istream', + 'iterator', + 'limits', + 'list', + 'locale', + 'map', + 'memory', + 'mutex', + 'new', + 'numeric', + 'ostream', + 'queue', + 'random', + 'ratio', + 'regex', + 'scoped_allocator', + 'set', + 'sstream', + 'stack', + 'stdexcept', + 'streambuf', + 'string', + 'strstream', + 'system_error', + 'thread', + 'tuple', + 'typeindex', + 'typeinfo', + 'type_traits', + 'unordered_map', + 'unordered_set', + 'utility', + 'valarray', + 'vector', + # 17.6.1.2 C++ headers for C library facilities + 'cassert', + 'ccomplex', + 'cctype', + 'cerrno', + 'cfenv', + 'cfloat', + 'cinttypes', + 'ciso646', + 'climits', + 'clocale', + 'cmath', + 'csetjmp', + 'csignal', + 'cstdalign', + 'cstdarg', + 'cstdbool', + 'cstddef', + 'cstdint', + 'cstdio', + 'cstdlib', + 'cstring', + 'ctgmath', + 'ctime', + 'cuchar', + 'cwchar', + 'cwctype', + ]) + +# Type names +_TYPES = re.compile( + r'^(?:' + # [dcl.type.simple] + r'(char(16_t|32_t)?)|wchar_t|' + r'bool|short|int|long|signed|unsigned|float|double|' + # [support.types] + r'(ptrdiff_t|size_t|max_align_t|nullptr_t)|' + # [cstdint.syn] + r'(u?int(_fast|_least)?(8|16|32|64)_t)|' + r'(u?int(max|ptr)_t)|' + r')$') + + +# These headers are excluded from [build/include] and [build/include_order] +# checks: +# - Anything not following google file name conventions (containing an +# uppercase character, such as Python.h or nsStringAPI.h, for example). +# - Lua headers. +_THIRD_PARTY_HEADERS_PATTERN = re.compile( + r'^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$') + +# Pattern for matching FileInfo.BaseName() against test file name +_test_suffixes = ['_test', '_regtest', '_unittest'] +_TEST_FILE_SUFFIX = '(' + '|'.join(_test_suffixes) + r')$' + +# Pattern that matches only complete whitespace, possibly across multiple lines. +_EMPTY_CONDITIONAL_BODY_PATTERN = re.compile(r'^\s*$', re.DOTALL) + +# Assertion macros. These are defined in base/logging.h and +# testing/base/public/gunit.h. +_CHECK_MACROS = [ + 'DCHECK', 'CHECK', + 'EXPECT_TRUE', 'ASSERT_TRUE', + 'EXPECT_FALSE', 'ASSERT_FALSE', + ] + +# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE +_CHECK_REPLACEMENT = dict([(macro_var, {}) for macro_var in _CHECK_MACROS]) + +for op, replacement in [('==', 'EQ'), ('!=', 'NE'), + ('>=', 'GE'), ('>', 'GT'), + ('<=', 'LE'), ('<', 'LT')]: + _CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement + _CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement + _CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement + _CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement + +for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'), + ('>=', 'LT'), ('>', 'LE'), + ('<=', 'GT'), ('<', 'GE')]: + _CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement + _CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement + +# Alternative tokens and their replacements. For full list, see section 2.5 +# Alternative tokens [lex.digraph] in the C++ standard. +# +# Digraphs (such as '%:') are not included here since it's a mess to +# match those on a word boundary. +_ALT_TOKEN_REPLACEMENT = { + 'and': '&&', + 'bitor': '|', + 'or': '||', + 'xor': '^', + 'compl': '~', + 'bitand': '&', + 'and_eq': '&=', + 'or_eq': '|=', + 'xor_eq': '^=', + 'not': '!', + 'not_eq': '!=' + } + +# Compile regular expression that matches all the above keywords. The "[ =()]" +# bit is meant to avoid matching these keywords outside of boolean expressions. +# +# False positives include C-style multi-line comments and multi-line strings +# but those have always been troublesome for cpplint. +_ALT_TOKEN_REPLACEMENT_PATTERN = re.compile( + r'[ =()](' + ('|'.join(_ALT_TOKEN_REPLACEMENT.keys())) + r')(?=[ (]|$)') + + +# These constants define types of headers for use with +# _IncludeState.CheckNextIncludeOrder(). +_C_SYS_HEADER = 1 +_CPP_SYS_HEADER = 2 +_LIKELY_MY_HEADER = 3 +_POSSIBLE_MY_HEADER = 4 +_OTHER_HEADER = 5 + +# These constants define the current inline assembly state +_NO_ASM = 0 # Outside of inline assembly block +_INSIDE_ASM = 1 # Inside inline assembly block +_END_ASM = 2 # Last line of inline assembly block +_BLOCK_ASM = 3 # The whole block is an inline assembly block + +# Match start of assembly blocks +_MATCH_ASM = re.compile(r'^\s*(?:asm|_asm|__asm|__asm__)' + r'(?:\s+(volatile|__volatile__))?' + r'\s*[{(]') + +# Match strings that indicate we're working on a C (not C++) file. +_SEARCH_C_FILE = re.compile(r'\b(?:LINT_C_FILE|' + r'vim?:\s*.*(\s*|:)filetype=c(\s*|:|$))') + +# Match string that indicates we're working on a Linux Kernel file. +_SEARCH_KERNEL_FILE = re.compile(r'\b(?:LINT_KERNEL_FILE)') + +_regexp_compile_cache = {} + +# {str, set(int)}: a map from error categories to sets of linenumbers +# on which those errors are expected and should be suppressed. +_error_suppressions = {} + +# The root directory used for deriving header guard CPP variable. +# This is set by --root flag. +_root = None + +# The top level repository directory. If set, _root is calculated relative to +# this directory instead of the directory containing version control artifacts. +# This is set by the --repository flag. +_repository = None + +# Files to exclude from linting. This is set by the --exclude flag. +_excludes = None + +# Whether to supress PrintInfo messages +_quiet = False + +# The allowed line length of files. +# This is set by --linelength flag. +_line_length = 80 + +try: + xrange(1, 0) +except NameError: + # -- pylint: disable=redefined-builtin + xrange = range + +try: + unicode +except NameError: + # -- pylint: disable=redefined-builtin + basestring = unicode = str + +try: + long(2) +except NameError: + # -- pylint: disable=redefined-builtin + long = int + +if sys.version_info < (3,): + # -- pylint: disable=no-member + # BINARY_TYPE = str + itervalues = dict.itervalues + iteritems = dict.iteritems +else: + # BINARY_TYPE = bytes + itervalues = dict.values + iteritems = dict.items + +def unicode_escape_decode(x): + if sys.version_info < (3,): + return codecs.unicode_escape_decode(x)[0] + else: + return x + +# {str, bool}: a map from error categories to booleans which indicate if the +# category should be suppressed for every line. +_global_error_suppressions = {} + + + + +def ParseNolintSuppressions(filename, raw_line, linenum, error): + """Updates the global list of line error-suppressions. + + Parses any NOLINT comments on the current line, updating the global + error_suppressions store. Reports an error if the NOLINT comment + was malformed. + + Args: + filename: str, the name of the input file. + raw_line: str, the line of input text, with comments. + linenum: int, the number of the current line. + error: function, an error handler. + """ + matched = Search(r'\bNOLINT(NEXTLINE)?\b(\([^)]+\))?', raw_line) + if matched: + if matched.group(1): + suppressed_line = linenum + 1 + else: + suppressed_line = linenum + category = matched.group(2) + if category in (None, '(*)'): # => "suppress all" + _error_suppressions.setdefault(None, set()).add(suppressed_line) + else: + if category.startswith('(') and category.endswith(')'): + category = category[1:-1] + if category in _ERROR_CATEGORIES: + _error_suppressions.setdefault(category, set()).add(suppressed_line) + elif category not in _LEGACY_ERROR_CATEGORIES: + error(filename, linenum, 'readability/nolint', 5, + 'Unknown NOLINT error category: %s' % category) + + +def ProcessGlobalSuppresions(lines): + """Updates the list of global error suppressions. + + Parses any lint directives in the file that have global effect. + + Args: + lines: An array of strings, each representing a line of the file, with the + last element being empty if the file is terminated with a newline. + """ + for line in lines: + if _SEARCH_C_FILE.search(line): + for category in _DEFAULT_C_SUPPRESSED_CATEGORIES: + _global_error_suppressions[category] = True + if _SEARCH_KERNEL_FILE.search(line): + for category in _DEFAULT_KERNEL_SUPPRESSED_CATEGORIES: + _global_error_suppressions[category] = True + + +def ResetNolintSuppressions(): + """Resets the set of NOLINT suppressions to empty.""" + _error_suppressions.clear() + _global_error_suppressions.clear() + + +def IsErrorSuppressedByNolint(category, linenum): + """Returns true if the specified error category is suppressed on this line. + + Consults the global error_suppressions map populated by + ParseNolintSuppressions/ProcessGlobalSuppresions/ResetNolintSuppressions. + + Args: + category: str, the category of the error. + linenum: int, the current line number. + Returns: + bool, True iff the error should be suppressed due to a NOLINT comment or + global suppression. + """ + return (_global_error_suppressions.get(category, False) or + linenum in _error_suppressions.get(category, set()) or + linenum in _error_suppressions.get(None, set())) + + +def Match(pattern, s): + """Matches the string with the pattern, caching the compiled regexp.""" + # The regexp compilation caching is inlined in both Match and Search for + # performance reasons; factoring it out into a separate function turns out + # to be noticeably expensive. + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].match(s) + + +def ReplaceAll(pattern, rep, s): + """Replaces instances of pattern in a string with a replacement. + + The compiled regex is kept in a cache shared by Match and Search. + + Args: + pattern: regex pattern + rep: replacement text + s: search string + + Returns: + string with replacements made (or original string if no replacements) + """ + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].sub(rep, s) + + +def Search(pattern, s): + """Searches the string for the pattern, caching the compiled regexp.""" + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].search(s) + + +def _IsSourceExtension(s): + """File extension (excluding dot) matches a source file extension.""" + return s in GetNonHeaderExtensions() + + +class _IncludeState(object): + """Tracks line numbers for includes, and the order in which includes appear. + + include_list contains list of lists of (header, line number) pairs. + It's a lists of lists rather than just one flat list to make it + easier to update across preprocessor boundaries. + + Call CheckNextIncludeOrder() once for each header in the file, passing + in the type constants defined above. Calls in an illegal order will + raise an _IncludeError with an appropriate error message. + + """ + # self._section will move monotonically through this set. If it ever + # needs to move backwards, CheckNextIncludeOrder will raise an error. + _INITIAL_SECTION = 0 + _MY_H_SECTION = 1 + _C_SECTION = 2 + _CPP_SECTION = 3 + _OTHER_H_SECTION = 4 + + _TYPE_NAMES = { + _C_SYS_HEADER: 'C system header', + _CPP_SYS_HEADER: 'C++ system header', + _LIKELY_MY_HEADER: 'header this file implements', + _POSSIBLE_MY_HEADER: 'header this file may implement', + _OTHER_HEADER: 'other header', + } + _SECTION_NAMES = { + _INITIAL_SECTION: "... nothing. (This can't be an error.)", + _MY_H_SECTION: 'a header this file implements', + _C_SECTION: 'C system header', + _CPP_SECTION: 'C++ system header', + _OTHER_H_SECTION: 'other header', + } + + def __init__(self): + self.include_list = [[]] + self._section = None + self._last_header = None + self.ResetSection('') + + def FindHeader(self, header): + """Check if a header has already been included. + + Args: + header: header to check. + Returns: + Line number of previous occurrence, or -1 if the header has not + been seen before. + """ + for section_list in self.include_list: + for f in section_list: + if f[0] == header: + return f[1] + return -1 + + def ResetSection(self, directive): + """Reset section checking for preprocessor directive. + + Args: + directive: preprocessor directive (e.g. "if", "else"). + """ + # The name of the current section. + self._section = self._INITIAL_SECTION + # The path of last found header. + self._last_header = '' + + # Update list of includes. Note that we never pop from the + # include list. + if directive in ('if', 'ifdef', 'ifndef'): + self.include_list.append([]) + elif directive in ('else', 'elif'): + self.include_list[-1] = [] + + def SetLastHeader(self, header_path): + self._last_header = header_path + + def CanonicalizeAlphabeticalOrder(self, header_path): + """Returns a path canonicalized for alphabetical comparison. + + - replaces "-" with "_" so they both cmp the same. + - removes '-inl' since we don't require them to be after the main header. + - lowercase everything, just in case. + + Args: + header_path: Path to be canonicalized. + + Returns: + Canonicalized path. + """ + return header_path.replace('-inl.h', '.h').replace('-', '_').lower() + + def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path): + """Check if a header is in alphabetical order with the previous header. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + header_path: Canonicalized header to be checked. + + Returns: + Returns true if the header is in alphabetical order. + """ + # If previous section is different from current section, _last_header will + # be reset to empty string, so it's always less than current header. + # + # If previous line was a blank line, assume that the headers are + # intentionally sorted the way they are. + if (self._last_header > header_path and + Match(r'^\s*#\s*include\b', clean_lines.elided[linenum - 1])): + return False + return True + + def CheckNextIncludeOrder(self, header_type): + """Returns a non-empty error message if the next header is out of order. + + This function also updates the internal state to be ready to check + the next include. + + Args: + header_type: One of the _XXX_HEADER constants defined above. + + Returns: + The empty string if the header is in the right order, or an + error message describing what's wrong. + + """ + error_message = ('Found %s after %s' % + (self._TYPE_NAMES[header_type], + self._SECTION_NAMES[self._section])) + + last_section = self._section + + if header_type == _C_SYS_HEADER: + if self._section <= self._C_SECTION: + self._section = self._C_SECTION + else: + self._last_header = '' + return error_message + elif header_type == _CPP_SYS_HEADER: + if self._section <= self._CPP_SECTION: + self._section = self._CPP_SECTION + else: + self._last_header = '' + return error_message + elif header_type == _LIKELY_MY_HEADER: + if self._section <= self._MY_H_SECTION: + self._section = self._MY_H_SECTION + else: + self._section = self._OTHER_H_SECTION + elif header_type == _POSSIBLE_MY_HEADER: + if self._section <= self._MY_H_SECTION: + self._section = self._MY_H_SECTION + else: + # This will always be the fallback because we're not sure + # enough that the header is associated with this file. + self._section = self._OTHER_H_SECTION + else: + assert header_type == _OTHER_HEADER + self._section = self._OTHER_H_SECTION + + if last_section != self._section: + self._last_header = '' + + return '' + + +class _CppLintState(object): + """Maintains module-wide state..""" + + def __init__(self): + self.verbose_level = 1 # global setting. + self.error_count = 0 # global count of reported errors + # filters to apply when emitting error messages + self.filters = _DEFAULT_FILTERS[:] + # backup of filter list. Used to restore the state after each file. + self._filters_backup = self.filters[:] + self.counting = 'total' # In what way are we counting errors? + self.errors_by_category = {} # string to int dict storing error counts + + # output format: + # "emacs" - format that emacs can parse (default) + # "eclipse" - format that eclipse can parse + # "vs7" - format that Microsoft Visual Studio 7 can parse + # "junit" - format that Jenkins, Bamboo, etc can parse + self.output_format = 'emacs' + + # For JUnit output, save errors and failures until the end so that they + # can be written into the XML + self._junit_errors = [] + self._junit_failures = [] + + def SetOutputFormat(self, output_format): + """Sets the output format for errors.""" + self.output_format = output_format + + def SetVerboseLevel(self, level): + """Sets the module's verbosity, and returns the previous setting.""" + last_verbose_level = self.verbose_level + self.verbose_level = level + return last_verbose_level + + def SetCountingStyle(self, counting_style): + """Sets the module's counting options.""" + self.counting = counting_style + + def SetFilters(self, filters): + """Sets the error-message filters. + + These filters are applied when deciding whether to emit a given + error message. + + Args: + filters: A string of comma-separated filters (eg "+whitespace/indent"). + Each filter should start with + or -; else we die. + + Raises: + ValueError: The comma-separated filters did not all start with '+' or '-'. + E.g. "-,+whitespace,-whitespace/indent,whitespace/badfilter" + """ + # Default filters always have less priority than the flag ones. + self.filters = _DEFAULT_FILTERS[:] + self.AddFilters(filters) + + def AddFilters(self, filters): + """ Adds more filters to the existing list of error-message filters. """ + for filt in filters.split(','): + clean_filt = filt.strip() + if clean_filt: + self.filters.append(clean_filt) + for filt in self.filters: + if not (filt.startswith('+') or filt.startswith('-')): + raise ValueError('Every filter in --filters must start with + or -' + ' (%s does not)' % filt) + + def BackupFilters(self): + """ Saves the current filter list to backup storage.""" + self._filters_backup = self.filters[:] + + def RestoreFilters(self): + """ Restores filters previously backed up.""" + self.filters = self._filters_backup[:] + + def ResetErrorCounts(self): + """Sets the module's error statistic back to zero.""" + self.error_count = 0 + self.errors_by_category = {} + + def IncrementErrorCount(self, category): + """Bumps the module's error statistic.""" + self.error_count += 1 + if self.counting in ('toplevel', 'detailed'): + if self.counting != 'detailed': + category = category.split('/')[0] + if category not in self.errors_by_category: + self.errors_by_category[category] = 0 + self.errors_by_category[category] += 1 + + def PrintErrorCounts(self): + """Print a summary of errors by category, and the total.""" + for category, count in sorted(iteritems(self.errors_by_category)): + self.PrintInfo('Category \'%s\' errors found: %d\n' % + (category, count)) + if self.error_count > 0: + self.PrintInfo('Total errors found: %d\n' % self.error_count) + + def PrintInfo(self, message): + if not _quiet and self.output_format != 'junit': + sys.stderr.write(message) + + def PrintError(self, message): + if self.output_format == 'junit': + self._junit_errors.append(message) + else: + sys.stderr.write(message) + + def AddJUnitFailure(self, filename, linenum, message, category, confidence): + self._junit_failures.append((filename, linenum, message, category, + confidence)) + + def FormatJUnitXML(self): + num_errors = len(self._junit_errors) + num_failures = len(self._junit_failures) + + testsuite = xml.etree.ElementTree.Element('testsuite') + testsuite.attrib['name'] = 'cpplint' + testsuite.attrib['errors'] = str(num_errors) + testsuite.attrib['failures'] = str(num_failures) + + if num_errors == 0 and num_failures == 0: + testsuite.attrib['tests'] = str(1) + xml.etree.ElementTree.SubElement(testsuite, 'testcase', name='passed') + + else: + testsuite.attrib['tests'] = str(num_errors + num_failures) + if num_errors > 0: + testcase = xml.etree.ElementTree.SubElement(testsuite, 'testcase') + testcase.attrib['name'] = 'errors' + error = xml.etree.ElementTree.SubElement(testcase, 'error') + error.text = '\n'.join(self._junit_errors) + if num_failures > 0: + # Group failures by file + failed_file_order = [] + failures_by_file = {} + for failure in self._junit_failures: + failed_file = failure[0] + if failed_file not in failed_file_order: + failed_file_order.append(failed_file) + failures_by_file[failed_file] = [] + failures_by_file[failed_file].append(failure) + # Create a testcase for each file + for failed_file in failed_file_order: + failures = failures_by_file[failed_file] + testcase = xml.etree.ElementTree.SubElement(testsuite, 'testcase') + testcase.attrib['name'] = failed_file + failure = xml.etree.ElementTree.SubElement(testcase, 'failure') + template = '{0}: {1} [{2}] [{3}]' + texts = [template.format(f[1], f[2], f[3], f[4]) for f in failures] + failure.text = '\n'.join(texts) + + xml_decl = '\n' + return xml_decl + xml.etree.ElementTree.tostring(testsuite, 'utf-8').decode('utf-8') + + +_cpplint_state = _CppLintState() + + +def _OutputFormat(): + """Gets the module's output format.""" + return _cpplint_state.output_format + + +def _SetOutputFormat(output_format): + """Sets the module's output format.""" + _cpplint_state.SetOutputFormat(output_format) + + +def _VerboseLevel(): + """Returns the module's verbosity setting.""" + return _cpplint_state.verbose_level + + +def _SetVerboseLevel(level): + """Sets the module's verbosity, and returns the previous setting.""" + return _cpplint_state.SetVerboseLevel(level) + + +def _SetCountingStyle(level): + """Sets the module's counting options.""" + _cpplint_state.SetCountingStyle(level) + + +def _Filters(): + """Returns the module's list of output filters, as a list.""" + return _cpplint_state.filters + + +def _SetFilters(filters): + """Sets the module's error-message filters. + + These filters are applied when deciding whether to emit a given + error message. + + Args: + filters: A string of comma-separated filters (eg "whitespace/indent"). + Each filter should start with + or -; else we die. + """ + _cpplint_state.SetFilters(filters) + +def _AddFilters(filters): + """Adds more filter overrides. + + Unlike _SetFilters, this function does not reset the current list of filters + available. + + Args: + filters: A string of comma-separated filters (eg "whitespace/indent"). + Each filter should start with + or -; else we die. + """ + _cpplint_state.AddFilters(filters) + +def _BackupFilters(): + """ Saves the current filter list to backup storage.""" + _cpplint_state.BackupFilters() + +def _RestoreFilters(): + """ Restores filters previously backed up.""" + _cpplint_state.RestoreFilters() + +class _FunctionState(object): + """Tracks current function name and the number of lines in its body.""" + + _NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc. + _TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER. + + def __init__(self): + self.in_a_function = False + self.lines_in_function = 0 + self.current_function = '' + + def Begin(self, function_name): + """Start analyzing function body. + + Args: + function_name: The name of the function being tracked. + """ + self.in_a_function = True + self.lines_in_function = 0 + self.current_function = function_name + + def Count(self): + """Count line in current function body.""" + if self.in_a_function: + self.lines_in_function += 1 + + def Check(self, error, filename, linenum): + """Report if too many lines in function body. + + Args: + error: The function to call with any errors found. + filename: The name of the current file. + linenum: The number of the line to check. + """ + if not self.in_a_function: + return + + if Match(r'T(EST|est)', self.current_function): + base_trigger = self._TEST_TRIGGER + else: + base_trigger = self._NORMAL_TRIGGER + trigger = base_trigger * 2**_VerboseLevel() + + if self.lines_in_function > trigger: + error_level = int(math.log(self.lines_in_function / base_trigger, 2)) + # 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ... + if error_level > 5: + error_level = 5 + error(filename, linenum, 'readability/fn_size', error_level, + 'Small and focused functions are preferred:' + ' %s has %d non-comment lines' + ' (error triggered by exceeding %d lines).' % ( + self.current_function, self.lines_in_function, trigger)) + + def End(self): + """Stop analyzing function body.""" + self.in_a_function = False + + +class _IncludeError(Exception): + """Indicates a problem with the include order in a file.""" + pass + + +class FileInfo(object): + """Provides utility functions for filenames. + + FileInfo provides easy access to the components of a file's path + relative to the project root. + """ + + def __init__(self, filename): + self._filename = filename + + def FullName(self): + """Make Windows paths like Unix.""" + return os.path.abspath(self._filename).replace('\\', '/') + + def RepositoryName(self): + r"""FullName after removing the local path to the repository. + + If we have a real absolute path name here we can try to do something smart: + detecting the root of the checkout and truncating /path/to/checkout from + the name so that we get header guards that don't include things like + "C:\Documents and Settings\..." or "/home/username/..." in them and thus + people on different computers who have checked the source out to different + locations won't see bogus errors. + """ + fullname = self.FullName() + + if os.path.exists(fullname): + project_dir = os.path.dirname(fullname) + + # If the user specified a repository path, it exists, and the file is + # contained in it, use the specified repository path + if _repository: + repo = FileInfo(_repository).FullName() + root_dir = project_dir + while os.path.exists(root_dir): + # allow case insensitive compare on Windows + if os.path.normcase(root_dir) == os.path.normcase(repo): + return os.path.relpath(fullname, root_dir).replace('\\', '/') + one_up_dir = os.path.dirname(root_dir) + if one_up_dir == root_dir: + break + root_dir = one_up_dir + + if os.path.exists(os.path.join(project_dir, ".svn")): + # If there's a .svn file in the current directory, we recursively look + # up the directory tree for the top of the SVN checkout + root_dir = project_dir + one_up_dir = os.path.dirname(root_dir) + while os.path.exists(os.path.join(one_up_dir, ".svn")): + root_dir = os.path.dirname(root_dir) + one_up_dir = os.path.dirname(one_up_dir) + + prefix = os.path.commonprefix([root_dir, project_dir]) + return fullname[len(prefix) + 1:] + + # Not SVN <= 1.6? Try to find a git, hg, or svn top level directory by + # searching up from the current path. + root_dir = current_dir = os.path.dirname(fullname) + while current_dir != os.path.dirname(current_dir): + if (os.path.exists(os.path.join(current_dir, ".git")) or + os.path.exists(os.path.join(current_dir, ".hg")) or + os.path.exists(os.path.join(current_dir, ".svn"))): + root_dir = current_dir + current_dir = os.path.dirname(current_dir) + + if (os.path.exists(os.path.join(root_dir, ".git")) or + os.path.exists(os.path.join(root_dir, ".hg")) or + os.path.exists(os.path.join(root_dir, ".svn"))): + prefix = os.path.commonprefix([root_dir, project_dir]) + return fullname[len(prefix) + 1:] + + # Don't know what to do; header guard warnings may be wrong... + return fullname + + def Split(self): + """Splits the file into the directory, basename, and extension. + + For 'chrome/browser/browser.cc', Split() would + return ('chrome/browser', 'browser', '.cc') + + Returns: + A tuple of (directory, basename, extension). + """ + + googlename = self.RepositoryName() + project, rest = os.path.split(googlename) + return (project,) + os.path.splitext(rest) + + def BaseName(self): + """File base name - text after the final slash, before the final period.""" + return self.Split()[1] + + def Extension(self): + """File extension - text following the final period, includes that period.""" + return self.Split()[2] + + def NoExtension(self): + """File has no source file extension.""" + return '/'.join(self.Split()[0:2]) + + def IsSource(self): + """File has a source file extension.""" + return _IsSourceExtension(self.Extension()[1:]) + + +def _ShouldPrintError(category, confidence, linenum): + """If confidence >= verbose, category passes filter and is not suppressed.""" + + # There are three ways we might decide not to print an error message: + # a "NOLINT(category)" comment appears in the source, + # the verbosity level isn't high enough, or the filters filter it out. + if IsErrorSuppressedByNolint(category, linenum): + return False + + if confidence < _cpplint_state.verbose_level: + return False + + is_filtered = False + for one_filter in _Filters(): + if one_filter.startswith('-'): + if category.startswith(one_filter[1:]): + is_filtered = True + elif one_filter.startswith('+'): + if category.startswith(one_filter[1:]): + is_filtered = False + else: + assert False # should have been checked for in SetFilter. + if is_filtered: + return False + + return True + + +def Error(filename, linenum, category, confidence, message): + """Logs the fact we've found a lint error. + + We log where the error was found, and also our confidence in the error, + that is, how certain we are this is a legitimate style regression, and + not a misidentification or a use that's sometimes justified. + + False positives can be suppressed by the use of + "cpplint(category)" comments on the offending line. These are + parsed into _error_suppressions. + + Args: + filename: The name of the file containing the error. + linenum: The number of the line containing the error. + category: A string used to describe the "category" this bug + falls under: "whitespace", say, or "runtime". Categories + may have a hierarchy separated by slashes: "whitespace/indent". + confidence: A number from 1-5 representing a confidence score for + the error, with 5 meaning that we are certain of the problem, + and 1 meaning that it could be a legitimate construct. + message: The error message. + """ + if _ShouldPrintError(category, confidence, linenum): + _cpplint_state.IncrementErrorCount(category) + if _cpplint_state.output_format == 'vs7': + _cpplint_state.PrintError('%s(%s): warning: %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + elif _cpplint_state.output_format == 'eclipse': + sys.stderr.write('%s:%s: warning: %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + elif _cpplint_state.output_format == 'junit': + _cpplint_state.AddJUnitFailure(filename, linenum, message, category, + confidence) + else: + final_message = '%s:%s: %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence) + sys.stderr.write(final_message) + +# Matches standard C++ escape sequences per 2.13.2.3 of the C++ standard. +_RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile( + r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)') +# Match a single C style comment on the same line. +_RE_PATTERN_C_COMMENTS = r'/\*(?:[^*]|\*(?!/))*\*/' +# Matches multi-line C style comments. +# This RE is a little bit more complicated than one might expect, because we +# have to take care of space removals tools so we can handle comments inside +# statements better. +# The current rule is: We only clear spaces from both sides when we're at the +# end of the line. Otherwise, we try to remove spaces from the right side, +# if this doesn't work we try on left side but only if there's a non-character +# on the right. +_RE_PATTERN_CLEANSE_LINE_C_COMMENTS = re.compile( + r'(\s*' + _RE_PATTERN_C_COMMENTS + r'\s*$|' + + _RE_PATTERN_C_COMMENTS + r'\s+|' + + r'\s+' + _RE_PATTERN_C_COMMENTS + r'(?=\W)|' + + _RE_PATTERN_C_COMMENTS + r')') + + +def IsCppString(line): + """Does line terminate so, that the next symbol is in string constant. + + This function does not consider single-line nor multi-line comments. + + Args: + line: is a partial line of code starting from the 0..n. + + Returns: + True, if next character appended to 'line' is inside a + string constant. + """ + + line = line.replace(r'\\', 'XX') # after this, \\" does not match to \" + return ((line.count('"') - line.count(r'\"') - line.count("'\"'")) & 1) == 1 + + +def CleanseRawStrings(raw_lines): + """Removes C++11 raw strings from lines. + + Before: + static const char kData[] = R"( + multi-line string + )"; + + After: + static const char kData[] = "" + (replaced by blank line) + ""; + + Args: + raw_lines: list of raw lines. + + Returns: + list of lines with C++11 raw strings replaced by empty strings. + """ + + delimiter = None + lines_without_raw_strings = [] + for line in raw_lines: + if delimiter: + # Inside a raw string, look for the end + end = line.find(delimiter) + if end >= 0: + # Found the end of the string, match leading space for this + # line and resume copying the original lines, and also insert + # a "" on the last line. + leading_space = Match(r'^(\s*)\S', line) + line = leading_space.group(1) + '""' + line[end + len(delimiter):] + delimiter = None + else: + # Haven't found the end yet, append a blank line. + line = '""' + + # Look for beginning of a raw string, and replace them with + # empty strings. This is done in a loop to handle multiple raw + # strings on the same line. + while delimiter is None: + # Look for beginning of a raw string. + # See 2.14.15 [lex.string] for syntax. + # + # Once we have matched a raw string, we check the prefix of the + # line to make sure that the line is not part of a single line + # comment. It's done this way because we remove raw strings + # before removing comments as opposed to removing comments + # before removing raw strings. This is because there are some + # cpplint checks that requires the comments to be preserved, but + # we don't want to check comments that are inside raw strings. + matched = Match(r'^(.*?)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line) + if (matched and + not Match(r'^([^\'"]|\'(\\.|[^\'])*\'|"(\\.|[^"])*")*//', + matched.group(1))): + delimiter = ')' + matched.group(2) + '"' + + end = matched.group(3).find(delimiter) + if end >= 0: + # Raw string ended on same line + line = (matched.group(1) + '""' + + matched.group(3)[end + len(delimiter):]) + delimiter = None + else: + # Start of a multi-line raw string + line = matched.group(1) + '""' + else: + break + + lines_without_raw_strings.append(line) + + # TODO(unknown): if delimiter is not None here, we might want to + # emit a warning for unterminated string. + return lines_without_raw_strings + + +def FindNextMultiLineCommentStart(lines, lineix): + """Find the beginning marker for a multiline comment.""" + while lineix < len(lines): + if lines[lineix].strip().startswith('/*'): + # Only return this marker if the comment goes beyond this line + if lines[lineix].strip().find('*/', 2) < 0: + return lineix + lineix += 1 + return len(lines) + + +def FindNextMultiLineCommentEnd(lines, lineix): + """We are inside a comment, find the end marker.""" + while lineix < len(lines): + if lines[lineix].strip().endswith('*/'): + return lineix + lineix += 1 + return len(lines) + + +def RemoveMultiLineCommentsFromRange(lines, begin, end): + """Clears a range of lines for multi-line comments.""" + # Having // dummy comments makes the lines non-empty, so we will not get + # unnecessary blank line warnings later in the code. + for i in range(begin, end): + lines[i] = '/**/' + + +def RemoveMultiLineComments(filename, lines, error): + """Removes multiline (c-style) comments from lines.""" + lineix = 0 + while lineix < len(lines): + lineix_begin = FindNextMultiLineCommentStart(lines, lineix) + if lineix_begin >= len(lines): + return + lineix_end = FindNextMultiLineCommentEnd(lines, lineix_begin) + if lineix_end >= len(lines): + error(filename, lineix_begin + 1, 'readability/multiline_comment', 5, + 'Could not find end of multi-line comment') + return + RemoveMultiLineCommentsFromRange(lines, lineix_begin, lineix_end + 1) + lineix = lineix_end + 1 + + +def CleanseComments(line): + """Removes //-comments and single-line C-style /* */ comments. + + Args: + line: A line of C++ source. + + Returns: + The line with single-line comments removed. + """ + commentpos = line.find('//') + if commentpos != -1 and not IsCppString(line[:commentpos]): + line = line[:commentpos].rstrip() + # get rid of /* ... */ + return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line) + + +class CleansedLines(object): + """Holds 4 copies of all lines with different preprocessing applied to them. + + 1) elided member contains lines without strings and comments. + 2) lines member contains lines without comments. + 3) raw_lines member contains all the lines without processing. + 4) lines_without_raw_strings member is same as raw_lines, but with C++11 raw + strings removed. + All these members are of , and of the same length. + """ + + def __init__(self, lines): + self.elided = [] + self.lines = [] + self.raw_lines = lines + self.num_lines = len(lines) + self.lines_without_raw_strings = CleanseRawStrings(lines) + for linenum in range(len(self.lines_without_raw_strings)): + self.lines.append(CleanseComments( + self.lines_without_raw_strings[linenum])) + elided = self._CollapseStrings(self.lines_without_raw_strings[linenum]) + self.elided.append(CleanseComments(elided)) + + def NumLines(self): + """Returns the number of lines represented.""" + return self.num_lines + + @staticmethod + def _CollapseStrings(elided): + """Collapses strings and chars on a line to simple "" or '' blocks. + + We nix strings first so we're not fooled by text like '"http://"' + + Args: + elided: The line being processed. + + Returns: + The line with collapsed strings. + """ + if _RE_PATTERN_INCLUDE.match(elided): + return elided + + # Remove escaped characters first to make quote/single quote collapsing + # basic. Things that look like escaped characters shouldn't occur + # outside of strings and chars. + elided = _RE_PATTERN_CLEANSE_LINE_ESCAPES.sub('', elided) + + # Replace quoted strings and digit separators. Both single quotes + # and double quotes are processed in the same loop, otherwise + # nested quotes wouldn't work. + collapsed = '' + while True: + # Find the first quote character + match = Match(r'^([^\'"]*)([\'"])(.*)$', elided) + if not match: + collapsed += elided + break + head, quote, tail = match.groups() + + if quote == '"': + # Collapse double quoted strings + second_quote = tail.find('"') + if second_quote >= 0: + collapsed += head + '""' + elided = tail[second_quote + 1:] + else: + # Unmatched double quote, don't bother processing the rest + # of the line since this is probably a multiline string. + collapsed += elided + break + else: + # Found single quote, check nearby text to eliminate digit separators. + # + # There is no special handling for floating point here, because + # the integer/fractional/exponent parts would all be parsed + # correctly as long as there are digits on both sides of the + # separator. So we are fine as long as we don't see something + # like "0.'3" (gcc 4.9.0 will not allow this literal). + if Search(r'\b(?:0[bBxX]?|[1-9])[0-9a-fA-F]*$', head): + match_literal = Match(r'^((?:\'?[0-9a-zA-Z_])*)(.*)$', "'" + tail) + collapsed += head + match_literal.group(1).replace("'", '') + elided = match_literal.group(2) + else: + second_quote = tail.find('\'') + if second_quote >= 0: + collapsed += head + "''" + elided = tail[second_quote + 1:] + else: + # Unmatched single quote + collapsed += elided + break + + return collapsed + + +def FindEndOfExpressionInLine(line, startpos, stack): + """Find the position just after the end of current parenthesized expression. + + Args: + line: a CleansedLines line. + startpos: start searching at this position. + stack: nesting stack at startpos. + + Returns: + On finding matching end: (index just after matching end, None) + On finding an unclosed expression: (-1, None) + Otherwise: (-1, new stack at end of this line) + """ + for i in xrange(startpos, len(line)): + char = line[i] + if char in '([{': + # Found start of parenthesized expression, push to expression stack + stack.append(char) + elif char == '<': + # Found potential start of template argument list + if i > 0 and line[i - 1] == '<': + # Left shift operator + if stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + elif i > 0 and Search(r'\boperator\s*$', line[0:i]): + # operator<, don't add to stack + continue + else: + # Tentative start of template argument list + stack.append('<') + elif char in ')]}': + # Found end of parenthesized expression. + # + # If we are currently expecting a matching '>', the pending '<' + # must have been an operator. Remove them from expression stack. + while stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + if ((stack[-1] == '(' and char == ')') or + (stack[-1] == '[' and char == ']') or + (stack[-1] == '{' and char == '}')): + stack.pop() + if not stack: + return (i + 1, None) + else: + # Mismatched parentheses + return (-1, None) + elif char == '>': + # Found potential end of template argument list. + + # Ignore "->" and operator functions + if (i > 0 and + (line[i - 1] == '-' or Search(r'\boperator\s*$', line[0:i - 1]))): + continue + + # Pop the stack if there is a matching '<'. Otherwise, ignore + # this '>' since it must be an operator. + if stack: + if stack[-1] == '<': + stack.pop() + if not stack: + return (i + 1, None) + elif char == ';': + # Found something that look like end of statements. If we are currently + # expecting a '>', the matching '<' must have been an operator, since + # template argument list should not contain statements. + while stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + + # Did not find end of expression or unbalanced parentheses on this line + return (-1, stack) + + +def CloseExpression(clean_lines, linenum, pos): + """If input points to ( or { or [ or <, finds the position that closes it. + + If lines[linenum][pos] points to a '(' or '{' or '[' or '<', finds the + linenum/pos that correspond to the closing of the expression. + + TODO(unknown): cpplint spends a fair bit of time matching parentheses. + Ideally we would want to index all opening and closing parentheses once + and have CloseExpression be just a simple lookup, but due to preprocessor + tricks, this is not so easy. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: A position on the line. + + Returns: + A tuple (line, linenum, pos) pointer *past* the closing brace, or + (line, len(lines), -1) if we never find a close. Note we ignore + strings and comments when matching; and the line we return is the + 'cleansed' line at linenum. + """ + + line = clean_lines.elided[linenum] + if (line[pos] not in '({[<') or Match(r'<[<=]', line[pos:]): + return (line, clean_lines.NumLines(), -1) + + # Check first line + (end_pos, stack) = FindEndOfExpressionInLine(line, pos, []) + if end_pos > -1: + return (line, linenum, end_pos) + + # Continue scanning forward + while stack and linenum < clean_lines.NumLines() - 1: + linenum += 1 + line = clean_lines.elided[linenum] + (end_pos, stack) = FindEndOfExpressionInLine(line, 0, stack) + if end_pos > -1: + return (line, linenum, end_pos) + + # Did not find end of expression before end of file, give up + return (line, clean_lines.NumLines(), -1) + + +def FindStartOfExpressionInLine(line, endpos, stack): + """Find position at the matching start of current expression. + + This is almost the reverse of FindEndOfExpressionInLine, but note + that the input position and returned position differs by 1. + + Args: + line: a CleansedLines line. + endpos: start searching at this position. + stack: nesting stack at endpos. + + Returns: + On finding matching start: (index at matching start, None) + On finding an unclosed expression: (-1, None) + Otherwise: (-1, new stack at beginning of this line) + """ + i = endpos + while i >= 0: + char = line[i] + if char in ')]}': + # Found end of expression, push to expression stack + stack.append(char) + elif char == '>': + # Found potential end of template argument list. + # + # Ignore it if it's a "->" or ">=" or "operator>" + if (i > 0 and + (line[i - 1] == '-' or + Match(r'\s>=\s', line[i - 1:]) or + Search(r'\boperator\s*$', line[0:i]))): + i -= 1 + else: + stack.append('>') + elif char == '<': + # Found potential start of template argument list + if i > 0 and line[i - 1] == '<': + # Left shift operator + i -= 1 + else: + # If there is a matching '>', we can pop the expression stack. + # Otherwise, ignore this '<' since it must be an operator. + if stack and stack[-1] == '>': + stack.pop() + if not stack: + return (i, None) + elif char in '([{': + # Found start of expression. + # + # If there are any unmatched '>' on the stack, they must be + # operators. Remove those. + while stack and stack[-1] == '>': + stack.pop() + if not stack: + return (-1, None) + if ((char == '(' and stack[-1] == ')') or + (char == '[' and stack[-1] == ']') or + (char == '{' and stack[-1] == '}')): + stack.pop() + if not stack: + return (i, None) + else: + # Mismatched parentheses + return (-1, None) + elif char == ';': + # Found something that look like end of statements. If we are currently + # expecting a '<', the matching '>' must have been an operator, since + # template argument list should not contain statements. + while stack and stack[-1] == '>': + stack.pop() + if not stack: + return (-1, None) + + i -= 1 + + return (-1, stack) + + +def ReverseCloseExpression(clean_lines, linenum, pos): + """If input points to ) or } or ] or >, finds the position that opens it. + + If lines[linenum][pos] points to a ')' or '}' or ']' or '>', finds the + linenum/pos that correspond to the opening of the expression. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: A position on the line. + + Returns: + A tuple (line, linenum, pos) pointer *at* the opening brace, or + (line, 0, -1) if we never find the matching opening brace. Note + we ignore strings and comments when matching; and the line we + return is the 'cleansed' line at linenum. + """ + line = clean_lines.elided[linenum] + if line[pos] not in ')}]>': + return (line, 0, -1) + + # Check last line + (start_pos, stack) = FindStartOfExpressionInLine(line, pos, []) + if start_pos > -1: + return (line, linenum, start_pos) + + # Continue scanning backward + while stack and linenum > 0: + linenum -= 1 + line = clean_lines.elided[linenum] + (start_pos, stack) = FindStartOfExpressionInLine(line, len(line) - 1, stack) + if start_pos > -1: + return (line, linenum, start_pos) + + # Did not find start of expression before beginning of file, give up + return (line, 0, -1) + + +def CheckForCopyright(filename, lines, error): + """Logs an error if no Copyright message appears at the top of the file.""" + + # We'll say it should occur by line 10. Don't forget there's a + # dummy line at the front. + for line in range(1, min(len(lines), 11)): + if re.search(r'Copyright', lines[line], re.I): break + else: # means no copyright line was found + error(filename, 0, 'legal/copyright', 5, + 'No copyright message found. ' + 'You should have a line: "Copyright [year] "') + + +def GetIndentLevel(line): + """Return the number of leading spaces in line. + + Args: + line: A string to check. + + Returns: + An integer count of leading spaces, possibly zero. + """ + indent = Match(r'^( *)\S', line) + if indent: + return len(indent.group(1)) + else: + return 0 + + +def GetHeaderGuardCPPVariable(filename): + """Returns the CPP variable that should be used as a header guard. + + Args: + filename: The name of a C++ header file. + + Returns: + The CPP variable that should be used as a header guard in the + named file. + + """ + + # Restores original filename in case that cpplint is invoked from Emacs's + # flymake. + filename = re.sub(r'_flymake\.h$', '.h', filename) + filename = re.sub(r'/\.flymake/([^/]*)$', r'/\1', filename) + # Replace 'c++' with 'cpp'. + filename = filename.replace('C++', 'cpp').replace('c++', 'cpp') + + fileinfo = FileInfo(filename) + file_path_from_root = fileinfo.RepositoryName() + if _root: + suffix = os.sep + # On Windows using directory separator will leave us with + # "bogus escape error" unless we properly escape regex. + if suffix == '\\': + suffix += '\\' + file_path_from_root = re.sub('^' + _root + suffix, '', file_path_from_root) + return re.sub(r'[^a-zA-Z0-9]', '_', file_path_from_root).upper() + '_' + + +def CheckForHeaderGuard(filename, clean_lines, error): + """Checks that the file contains a header guard. + + Logs an error if no #ifndef header guard is present. For other + headers, checks that the full pathname is used. + + Args: + filename: The name of the C++ header file. + clean_lines: A CleansedLines instance containing the file. + error: The function to call with any errors found. + """ + + # Don't check for header guards if there are error suppression + # comments somewhere in this file. + # + # Because this is silencing a warning for a nonexistent line, we + # only support the very specific NOLINT(build/header_guard) syntax, + # and not the general NOLINT or NOLINT(*) syntax. + raw_lines = clean_lines.lines_without_raw_strings + for i in raw_lines: + if Search(r'//\s*NOLINT\(build/header_guard\)', i): + return + + # Allow pragma once instead of header guards + for i in raw_lines: + if Search(r'^\s*#pragma\s+once', i): + return + + cppvar = GetHeaderGuardCPPVariable(filename) + + ifndef = '' + ifndef_linenum = 0 + define = '' + endif = '' + endif_linenum = 0 + for linenum, line in enumerate(raw_lines): + linesplit = line.split() + if len(linesplit) >= 2: + # find the first occurrence of #ifndef and #define, save arg + if not ifndef and linesplit[0] == '#ifndef': + # set ifndef to the header guard presented on the #ifndef line. + ifndef = linesplit[1] + ifndef_linenum = linenum + if not define and linesplit[0] == '#define': + define = linesplit[1] + # find the last occurrence of #endif, save entire line + if line.startswith('#endif'): + endif = line + endif_linenum = linenum + + if not ifndef or not define or ifndef != define: + error(filename, 0, 'build/header_guard', 5, + 'No #ifndef header guard found, suggested CPP variable is: %s' % + cppvar) + return + + # The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__ + # for backward compatibility. + if ifndef != cppvar: + error_level = 0 + if ifndef != cppvar + '_': + error_level = 5 + + ParseNolintSuppressions(filename, raw_lines[ifndef_linenum], ifndef_linenum, + error) + error(filename, ifndef_linenum, 'build/header_guard', error_level, + '#ifndef header guard has wrong style, please use: %s' % cppvar) + + # Check for "//" comments on endif line. + ParseNolintSuppressions(filename, raw_lines[endif_linenum], endif_linenum, + error) + match = Match(r'#endif\s*//\s*' + cppvar + r'(_)?\b', endif) + if match: + if match.group(1) == '_': + # Issue low severity warning for deprecated double trailing underscore + error(filename, endif_linenum, 'build/header_guard', 0, + '#endif line should be "#endif // %s"' % cppvar) + return + + # Didn't find the corresponding "//" comment. If this file does not + # contain any "//" comments at all, it could be that the compiler + # only wants "/**/" comments, look for those instead. + no_single_line_comments = True + for i in xrange(1, len(raw_lines) - 1): + line = raw_lines[i] + if Match(r'^(?:(?:\'(?:\.|[^\'])*\')|(?:"(?:\.|[^"])*")|[^\'"])*//', line): + no_single_line_comments = False + break + + if no_single_line_comments: + match = Match(r'#endif\s*/\*\s*' + cppvar + r'(_)?\s*\*/', endif) + if match: + if match.group(1) == '_': + # Low severity warning for double trailing underscore + error(filename, endif_linenum, 'build/header_guard', 0, + '#endif line should be "#endif /* %s */"' % cppvar) + return + + # Didn't find anything + error(filename, endif_linenum, 'build/header_guard', 5, + '#endif line should be "#endif // %s"' % cppvar) + + +def CheckHeaderFileIncluded(filename, include_state, error): + """Logs an error if a source file does not include its header.""" + + # Do not check test files + fileinfo = FileInfo(filename) + if Search(_TEST_FILE_SUFFIX, fileinfo.BaseName()): + return + + for ext in GetHeaderExtensions(): + basefilename = filename[0:len(filename) - len(fileinfo.Extension())] + headerfile = basefilename + '.' + ext + if not os.path.exists(headerfile): + continue + headername = FileInfo(headerfile).RepositoryName() + first_include = None + for section_list in include_state.include_list: + for f in section_list: + if headername in f[0] or f[0] in headername: + return + if not first_include: + first_include = f[1] + + error(filename, first_include, 'build/include', 5, + '%s should include its header file %s' % (fileinfo.RepositoryName(), + headername)) + + +def CheckForBadCharacters(filename, lines, error): + """Logs an error for each line containing bad characters. + + Two kinds of bad characters: + + 1. Unicode replacement characters: These indicate that either the file + contained invalid UTF-8 (likely) or Unicode replacement characters (which + it shouldn't). Note that it's possible for this to throw off line + numbering if the invalid UTF-8 occurred adjacent to a newline. + + 2. NUL bytes. These are problematic for some tools. + + Args: + filename: The name of the current file. + lines: An array of strings, each representing a line of the file. + error: The function to call with any errors found. + """ + for linenum, line in enumerate(lines): + if unicode_escape_decode('\ufffd') in line: + error(filename, linenum, 'readability/utf8', 5, + 'Line contains invalid UTF-8 (or Unicode replacement character).') + if '\0' in line: + error(filename, linenum, 'readability/nul', 5, 'Line contains NUL byte.') + + +def CheckForNewlineAtEOF(filename, lines, error): + """Logs an error if there is no newline char at the end of the file. + + Args: + filename: The name of the current file. + lines: An array of strings, each representing a line of the file. + error: The function to call with any errors found. + """ + + # The array lines() was created by adding two newlines to the + # original file (go figure), then splitting on \n. + # To verify that the file ends in \n, we just have to make sure the + # last-but-two element of lines() exists and is empty. + if len(lines) < 3 or lines[-2]: + error(filename, len(lines) - 2, 'whitespace/ending_newline', 5, + 'Could not find a newline character at the end of the file.') + + +def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error): + """Logs an error if we see /* ... */ or "..." that extend past one line. + + /* ... */ comments are legit inside macros, for one line. + Otherwise, we prefer // comments, so it's ok to warn about the + other. Likewise, it's ok for strings to extend across multiple + lines, as long as a line continuation character (backslash) + terminates each line. Although not currently prohibited by the C++ + style guide, it's ugly and unnecessary. We don't do well with either + in this lint program, so we warn about both. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Remove all \\ (escaped backslashes) from the line. They are OK, and the + # second (escaped) slash may trigger later \" detection erroneously. + line = line.replace('\\\\', '') + + if line.count('/*') > line.count('*/'): + error(filename, linenum, 'readability/multiline_comment', 5, + 'Complex multi-line /*...*/-style comment found. ' + 'Lint may give bogus warnings. ' + 'Consider replacing these with //-style comments, ' + 'with #if 0...#endif, ' + 'or with more clearly structured multi-line comments.') + + if (line.count('"') - line.count('\\"')) % 2: + error(filename, linenum, 'readability/multiline_string', 5, + 'Multi-line string ("...") found. This lint script doesn\'t ' + 'do well with such strings, and may give bogus warnings. ' + 'Use C++11 raw strings or concatenation instead.') + + +# (non-threadsafe name, thread-safe alternative, validation pattern) +# +# The validation pattern is used to eliminate false positives such as: +# _rand(); // false positive due to substring match. +# ->rand(); // some member function rand(). +# ACMRandom rand(seed); // some variable named rand. +# ISAACRandom rand(); // another variable named rand. +# +# Basically we require the return value of these functions to be used +# in some expression context on the same line by matching on some +# operator before the function name. This eliminates constructors and +# member function calls. +_UNSAFE_FUNC_PREFIX = r'(?:[-+*/=%^&|(<]\s*|>\s+)' +_THREADING_LIST = ( + ('asctime(', 'asctime_r(', _UNSAFE_FUNC_PREFIX + r'asctime\([^)]+\)'), + ('ctime(', 'ctime_r(', _UNSAFE_FUNC_PREFIX + r'ctime\([^)]+\)'), + ('getgrgid(', 'getgrgid_r(', _UNSAFE_FUNC_PREFIX + r'getgrgid\([^)]+\)'), + ('getgrnam(', 'getgrnam_r(', _UNSAFE_FUNC_PREFIX + r'getgrnam\([^)]+\)'), + ('getlogin(', 'getlogin_r(', _UNSAFE_FUNC_PREFIX + r'getlogin\(\)'), + ('getpwnam(', 'getpwnam_r(', _UNSAFE_FUNC_PREFIX + r'getpwnam\([^)]+\)'), + ('getpwuid(', 'getpwuid_r(', _UNSAFE_FUNC_PREFIX + r'getpwuid\([^)]+\)'), + ('gmtime(', 'gmtime_r(', _UNSAFE_FUNC_PREFIX + r'gmtime\([^)]+\)'), + ('localtime(', 'localtime_r(', _UNSAFE_FUNC_PREFIX + r'localtime\([^)]+\)'), + ('rand(', 'rand_r(', _UNSAFE_FUNC_PREFIX + r'rand\(\)'), + ('strtok(', 'strtok_r(', + _UNSAFE_FUNC_PREFIX + r'strtok\([^)]+\)'), + ('ttyname(', 'ttyname_r(', _UNSAFE_FUNC_PREFIX + r'ttyname\([^)]+\)'), + ) + + +def CheckPosixThreading(filename, clean_lines, linenum, error): + """Checks for calls to thread-unsafe functions. + + Much code has been originally written without consideration of + multi-threading. Also, engineers are relying on their old experience; + they have learned posix before threading extensions were added. These + tests guide the engineers to use thread-safe functions (when using + posix directly). + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + for single_thread_func, multithread_safe_func, pattern in _THREADING_LIST: + # Additional pattern matching check to confirm that this is the + # function we are looking for + if Search(pattern, line): + error(filename, linenum, 'runtime/threadsafe_fn', 2, + 'Consider using ' + multithread_safe_func + + '...) instead of ' + single_thread_func + + '...) for improved thread safety.') + + +def CheckVlogArguments(filename, clean_lines, linenum, error): + """Checks that VLOG() is only used for defining a logging level. + + For example, VLOG(2) is correct. VLOG(INFO), VLOG(WARNING), VLOG(ERROR), and + VLOG(FATAL) are not. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + if Search(r'\bVLOG\((INFO|ERROR|WARNING|DFATAL|FATAL)\)', line): + error(filename, linenum, 'runtime/vlog', 5, + 'VLOG() should be used with numeric verbosity level. ' + 'Use LOG() if you want symbolic severity levels.') + +# Matches invalid increment: *count++, which moves pointer instead of +# incrementing a value. +_RE_PATTERN_INVALID_INCREMENT = re.compile( + r'^\s*\*\w+(\+\+|--);') + + +def CheckInvalidIncrement(filename, clean_lines, linenum, error): + """Checks for invalid increment *count++. + + For example following function: + void increment_counter(int* count) { + *count++; + } + is invalid, because it effectively does count++, moving pointer, and should + be replaced with ++*count, (*count)++ or *count += 1. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + if _RE_PATTERN_INVALID_INCREMENT.match(line): + error(filename, linenum, 'runtime/invalid_increment', 5, + 'Changing pointer instead of value (or unused value of operator*).') + + +def IsMacroDefinition(clean_lines, linenum): + if Search(r'^#define', clean_lines[linenum]): + return True + + if linenum > 0 and Search(r'\\$', clean_lines[linenum - 1]): + return True + + return False + + +def IsForwardClassDeclaration(clean_lines, linenum): + return Match(r'^\s*(\btemplate\b)*.*class\s+\w+;\s*$', clean_lines[linenum]) + + +class _BlockInfo(object): + """Stores information about a generic block of code.""" + + def __init__(self, linenum, seen_open_brace): + self.starting_linenum = linenum + self.seen_open_brace = seen_open_brace + self.open_parentheses = 0 + self.inline_asm = _NO_ASM + self.check_namespace_indentation = False + + def CheckBegin(self, filename, clean_lines, linenum, error): + """Run checks that applies to text up to the opening brace. + + This is mostly for checking the text after the class identifier + and the "{", usually where the base class is specified. For other + blocks, there isn't much to check, so we always pass. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + pass + + def CheckEnd(self, filename, clean_lines, linenum, error): + """Run checks that applies to text after the closing brace. + + This is mostly used for checking end of namespace comments. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + pass + + def IsBlockInfo(self): + """Returns true if this block is a _BlockInfo. + + This is convenient for verifying that an object is an instance of + a _BlockInfo, but not an instance of any of the derived classes. + + Returns: + True for this class, False for derived classes. + """ + return self.__class__ == _BlockInfo + + +class _ExternCInfo(_BlockInfo): + """Stores information about an 'extern "C"' block.""" + + def __init__(self, linenum): + _BlockInfo.__init__(self, linenum, True) + + +class _ClassInfo(_BlockInfo): + """Stores information about a class.""" + + def __init__(self, name, class_or_struct, clean_lines, linenum): + _BlockInfo.__init__(self, linenum, False) + self.name = name + self.is_derived = False + self.check_namespace_indentation = True + if class_or_struct == 'struct': + self.access = 'public' + self.is_struct = True + else: + self.access = 'private' + self.is_struct = False + + # Remember initial indentation level for this class. Using raw_lines here + # instead of elided to account for leading comments. + self.class_indent = GetIndentLevel(clean_lines.raw_lines[linenum]) + + # Try to find the end of the class. This will be confused by things like: + # class A { + # } *x = { ... + # + # But it's still good enough for CheckSectionSpacing. + self.last_line = 0 + depth = 0 + for i in range(linenum, clean_lines.NumLines()): + line = clean_lines.elided[i] + depth += line.count('{') - line.count('}') + if not depth: + self.last_line = i + break + + def CheckBegin(self, filename, clean_lines, linenum, error): + # Look for a bare ':' + if Search('(^|[^:]):($|[^:])', clean_lines.elided[linenum]): + self.is_derived = True + + def CheckEnd(self, filename, clean_lines, linenum, error): + # If there is a DISALLOW macro, it should appear near the end of + # the class. + seen_last_thing_in_class = False + for i in xrange(linenum - 1, self.starting_linenum, -1): + match = Search( + r'\b(DISALLOW_COPY_AND_ASSIGN|DISALLOW_IMPLICIT_CONSTRUCTORS)\(' + + self.name + r'\)', + clean_lines.elided[i]) + if match: + if seen_last_thing_in_class: + error(filename, i, 'readability/constructors', 3, + match.group(1) + ' should be the last thing in the class') + break + + if not Match(r'^\s*$', clean_lines.elided[i]): + seen_last_thing_in_class = True + + # Check that closing brace is aligned with beginning of the class. + # Only do this if the closing brace is indented by only whitespaces. + # This means we will not check single-line class definitions. + indent = Match(r'^( *)\}', clean_lines.elided[linenum]) + if indent and len(indent.group(1)) != self.class_indent: + if self.is_struct: + parent = 'struct ' + self.name + else: + parent = 'class ' + self.name + error(filename, linenum, 'whitespace/indent', 3, + 'Closing brace should be aligned with beginning of %s' % parent) + + +class _NamespaceInfo(_BlockInfo): + """Stores information about a namespace.""" + + def __init__(self, name, linenum): + _BlockInfo.__init__(self, linenum, False) + self.name = name or '' + self.check_namespace_indentation = True + + def CheckEnd(self, filename, clean_lines, linenum, error): + """Check end of namespace comments.""" + line = clean_lines.raw_lines[linenum] + + # Check how many lines is enclosed in this namespace. Don't issue + # warning for missing namespace comments if there aren't enough + # lines. However, do apply checks if there is already an end of + # namespace comment and it's incorrect. + # + # TODO(unknown): We always want to check end of namespace comments + # if a namespace is large, but sometimes we also want to apply the + # check if a short namespace contained nontrivial things (something + # other than forward declarations). There is currently no logic on + # deciding what these nontrivial things are, so this check is + # triggered by namespace size only, which works most of the time. + if (linenum - self.starting_linenum < 10 + and not Match(r'^\s*};*\s*(//|/\*).*\bnamespace\b', line)): + return + + # Look for matching comment at end of namespace. + # + # Note that we accept C style "/* */" comments for terminating + # namespaces, so that code that terminate namespaces inside + # preprocessor macros can be cpplint clean. + # + # We also accept stuff like "// end of namespace ." with the + # period at the end. + # + # Besides these, we don't accept anything else, otherwise we might + # get false negatives when existing comment is a substring of the + # expected namespace. + if self.name: + # Named namespace + if not Match((r'^\s*};*\s*(//|/\*).*\bnamespace\s+' + + re.escape(self.name) + r'[\*/\.\\\s]*$'), + line): + error(filename, linenum, 'readability/namespace', 5, + 'Namespace should be terminated with "// namespace %s"' % + self.name) + else: + # Anonymous namespace + if not Match(r'^\s*};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$', line): + # If "// namespace anonymous" or "// anonymous namespace (more text)", + # mention "// anonymous namespace" as an acceptable form + if Match(r'^\s*}.*\b(namespace anonymous|anonymous namespace)\b', line): + error(filename, linenum, 'readability/namespace', 5, + 'Anonymous namespace should be terminated with "// namespace"' + ' or "// anonymous namespace"') + else: + error(filename, linenum, 'readability/namespace', 5, + 'Anonymous namespace should be terminated with "// namespace"') + + +class _PreprocessorInfo(object): + """Stores checkpoints of nesting stacks when #if/#else is seen.""" + + def __init__(self, stack_before_if): + # The entire nesting stack before #if + self.stack_before_if = stack_before_if + + # The entire nesting stack up to #else + self.stack_before_else = [] + + # Whether we have already seen #else or #elif + self.seen_else = False + + +class NestingState(object): + """Holds states related to parsing braces.""" + + def __init__(self): + # Stack for tracking all braces. An object is pushed whenever we + # see a "{", and popped when we see a "}". Only 3 types of + # objects are possible: + # - _ClassInfo: a class or struct. + # - _NamespaceInfo: a namespace. + # - _BlockInfo: some other type of block. + self.stack = [] + + # Top of the previous stack before each Update(). + # + # Because the nesting_stack is updated at the end of each line, we + # had to do some convoluted checks to find out what is the current + # scope at the beginning of the line. This check is simplified by + # saving the previous top of nesting stack. + # + # We could save the full stack, but we only need the top. Copying + # the full nesting stack would slow down cpplint by ~10%. + self.previous_stack_top = [] + + # Stack of _PreprocessorInfo objects. + self.pp_stack = [] + + def SeenOpenBrace(self): + """Check if we have seen the opening brace for the innermost block. + + Returns: + True if we have seen the opening brace, False if the innermost + block is still expecting an opening brace. + """ + return (not self.stack) or self.stack[-1].seen_open_brace + + def InNamespaceBody(self): + """Check if we are currently one level inside a namespace body. + + Returns: + True if top of the stack is a namespace block, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _NamespaceInfo) + + def InExternC(self): + """Check if we are currently one level inside an 'extern "C"' block. + + Returns: + True if top of the stack is an extern block, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _ExternCInfo) + + def InClassDeclaration(self): + """Check if we are currently one level inside a class or struct declaration. + + Returns: + True if top of the stack is a class/struct, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _ClassInfo) + + def InAsmBlock(self): + """Check if we are currently one level inside an inline ASM block. + + Returns: + True if the top of the stack is a block containing inline ASM. + """ + return self.stack and self.stack[-1].inline_asm != _NO_ASM + + def InTemplateArgumentList(self, clean_lines, linenum, pos): + """Check if current position is inside template argument list. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: position just after the suspected template argument. + Returns: + True if (linenum, pos) is inside template arguments. + """ + while linenum < clean_lines.NumLines(): + # Find the earliest character that might indicate a template argument + line = clean_lines.elided[linenum] + match = Match(r'^[^{};=\[\]\.<>]*(.)', line[pos:]) + if not match: + linenum += 1 + pos = 0 + continue + token = match.group(1) + pos += len(match.group(0)) + + # These things do not look like template argument list: + # class Suspect { + # class Suspect x; } + if token in ('{', '}', ';'): return False + + # These things look like template argument list: + # template + # template + # template + # template + if token in ('>', '=', '[', ']', '.'): return True + + # Check if token is an unmatched '<'. + # If not, move on to the next character. + if token != '<': + pos += 1 + if pos >= len(line): + linenum += 1 + pos = 0 + continue + + # We can't be sure if we just find a single '<', and need to + # find the matching '>'. + (_, end_line, end_pos) = CloseExpression(clean_lines, linenum, pos - 1) + if end_pos < 0: + # Not sure if template argument list or syntax error in file + return False + linenum = end_line + pos = end_pos + return False + + def UpdatePreprocessor(self, line): + """Update preprocessor stack. + + We need to handle preprocessors due to classes like this: + #ifdef SWIG + struct ResultDetailsPageElementExtensionPoint { + #else + struct ResultDetailsPageElementExtensionPoint : public Extension { + #endif + + We make the following assumptions (good enough for most files): + - Preprocessor condition evaluates to true from #if up to first + #else/#elif/#endif. + + - Preprocessor condition evaluates to false from #else/#elif up + to #endif. We still perform lint checks on these lines, but + these do not affect nesting stack. + + Args: + line: current line to check. + """ + if Match(r'^\s*#\s*(if|ifdef|ifndef)\b', line): + # Beginning of #if block, save the nesting stack here. The saved + # stack will allow us to restore the parsing state in the #else case. + self.pp_stack.append(_PreprocessorInfo(copy.deepcopy(self.stack))) + elif Match(r'^\s*#\s*(else|elif)\b', line): + # Beginning of #else block + if self.pp_stack: + if not self.pp_stack[-1].seen_else: + # This is the first #else or #elif block. Remember the + # whole nesting stack up to this point. This is what we + # keep after the #endif. + self.pp_stack[-1].seen_else = True + self.pp_stack[-1].stack_before_else = copy.deepcopy(self.stack) + + # Restore the stack to how it was before the #if + self.stack = copy.deepcopy(self.pp_stack[-1].stack_before_if) + else: + # TODO(unknown): unexpected #else, issue warning? + pass + elif Match(r'^\s*#\s*endif\b', line): + # End of #if or #else blocks. + if self.pp_stack: + # If we saw an #else, we will need to restore the nesting + # stack to its former state before the #else, otherwise we + # will just continue from where we left off. + if self.pp_stack[-1].seen_else: + # Here we can just use a shallow copy since we are the last + # reference to it. + self.stack = self.pp_stack[-1].stack_before_else + # Drop the corresponding #if + self.pp_stack.pop() + else: + # TODO(unknown): unexpected #endif, issue warning? + pass + + # TODO(unknown): Update() is too long, but we will refactor later. + def Update(self, filename, clean_lines, linenum, error): + """Update nesting state with current line. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Remember top of the previous nesting stack. + # + # The stack is always pushed/popped and not modified in place, so + # we can just do a shallow copy instead of copy.deepcopy. Using + # deepcopy would slow down cpplint by ~28%. + if self.stack: + self.previous_stack_top = self.stack[-1] + else: + self.previous_stack_top = None + + # Update pp_stack + self.UpdatePreprocessor(line) + + # Count parentheses. This is to avoid adding struct arguments to + # the nesting stack. + if self.stack: + inner_block = self.stack[-1] + depth_change = line.count('(') - line.count(')') + inner_block.open_parentheses += depth_change + + # Also check if we are starting or ending an inline assembly block. + if inner_block.inline_asm in (_NO_ASM, _END_ASM): + if (depth_change != 0 and + inner_block.open_parentheses == 1 and + _MATCH_ASM.match(line)): + # Enter assembly block + inner_block.inline_asm = _INSIDE_ASM + else: + # Not entering assembly block. If previous line was _END_ASM, + # we will now shift to _NO_ASM state. + inner_block.inline_asm = _NO_ASM + elif (inner_block.inline_asm == _INSIDE_ASM and + inner_block.open_parentheses == 0): + # Exit assembly block + inner_block.inline_asm = _END_ASM + + # Consume namespace declaration at the beginning of the line. Do + # this in a loop so that we catch same line declarations like this: + # namespace proto2 { namespace bridge { class MessageSet; } } + while True: + # Match start of namespace. The "\b\s*" below catches namespace + # declarations even if it weren't followed by a whitespace, this + # is so that we don't confuse our namespace checker. The + # missing spaces will be flagged by CheckSpacing. + namespace_decl_match = Match(r'^\s*namespace\b\s*([:\w]+)?(.*)$', line) + if not namespace_decl_match: + break + + new_namespace = _NamespaceInfo(namespace_decl_match.group(1), linenum) + self.stack.append(new_namespace) + + line = namespace_decl_match.group(2) + if line.find('{') != -1: + new_namespace.seen_open_brace = True + line = line[line.find('{') + 1:] + + # Look for a class declaration in whatever is left of the line + # after parsing namespaces. The regexp accounts for decorated classes + # such as in: + # class LOCKABLE API Object { + # }; + class_decl_match = Match( + r'^(\s*(?:template\s*<[\w\s<>,:=]*>\s*)?' + r'(class|struct)\s+(?:[A-Z_]+\s+)*(\w+(?:::\w+)*))' + r'(.*)$', line) + if (class_decl_match and + (not self.stack or self.stack[-1].open_parentheses == 0)): + # We do not want to accept classes that are actually template arguments: + # template , + # template class Ignore3> + # void Function() {}; + # + # To avoid template argument cases, we scan forward and look for + # an unmatched '>'. If we see one, assume we are inside a + # template argument list. + end_declaration = len(class_decl_match.group(1)) + if not self.InTemplateArgumentList(clean_lines, linenum, end_declaration): + self.stack.append(_ClassInfo( + class_decl_match.group(3), class_decl_match.group(2), + clean_lines, linenum)) + line = class_decl_match.group(4) + + # If we have not yet seen the opening brace for the innermost block, + # run checks here. + if not self.SeenOpenBrace(): + self.stack[-1].CheckBegin(filename, clean_lines, linenum, error) + + # Update access control if we are inside a class/struct + if self.stack and isinstance(self.stack[-1], _ClassInfo): + classinfo = self.stack[-1] + access_match = Match( + r'^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?' + r':(?:[^:]|$)', + line) + if access_match: + classinfo.access = access_match.group(2) + + # Check that access keywords are indented +1 space. Skip this + # check if the keywords are not preceded by whitespaces. + indent = access_match.group(1) + if (len(indent) != classinfo.class_indent + 1 and + Match(r'^\s*$', indent)): + if classinfo.is_struct: + parent = 'struct ' + classinfo.name + else: + parent = 'class ' + classinfo.name + slots = '' + if access_match.group(3): + slots = access_match.group(3) + error(filename, linenum, 'whitespace/indent', 3, + '%s%s: should be indented +1 space inside %s' % ( + access_match.group(2), slots, parent)) + + # Consume braces or semicolons from what's left of the line + while True: + # Match first brace, semicolon, or closed parenthesis. + matched = Match(r'^[^{;)}]*([{;)}])(.*)$', line) + if not matched: + break + + token = matched.group(1) + if token == '{': + # If namespace or class hasn't seen a opening brace yet, mark + # namespace/class head as complete. Push a new block onto the + # stack otherwise. + if not self.SeenOpenBrace(): + self.stack[-1].seen_open_brace = True + elif Match(r'^extern\s*"[^"]*"\s*\{', line): + self.stack.append(_ExternCInfo(linenum)) + else: + self.stack.append(_BlockInfo(linenum, True)) + if _MATCH_ASM.match(line): + self.stack[-1].inline_asm = _BLOCK_ASM + + elif token == ';' or token == ')': + # If we haven't seen an opening brace yet, but we already saw + # a semicolon, this is probably a forward declaration. Pop + # the stack for these. + # + # Similarly, if we haven't seen an opening brace yet, but we + # already saw a closing parenthesis, then these are probably + # function arguments with extra "class" or "struct" keywords. + # Also pop these stack for these. + if not self.SeenOpenBrace(): + self.stack.pop() + else: # token == '}' + # Perform end of block checks and pop the stack. + if self.stack: + self.stack[-1].CheckEnd(filename, clean_lines, linenum, error) + self.stack.pop() + line = matched.group(2) + + def InnermostClass(self): + """Get class info on the top of the stack. + + Returns: + A _ClassInfo object if we are inside a class, or None otherwise. + """ + for i in range(len(self.stack), 0, -1): + classinfo = self.stack[i - 1] + if isinstance(classinfo, _ClassInfo): + return classinfo + return None + + def CheckCompletedBlocks(self, filename, error): + """Checks that all classes and namespaces have been completely parsed. + + Call this when all lines in a file have been processed. + Args: + filename: The name of the current file. + error: The function to call with any errors found. + """ + # Note: This test can result in false positives if #ifdef constructs + # get in the way of brace matching. See the testBuildClass test in + # cpplint_unittest.py for an example of this. + for obj in self.stack: + if isinstance(obj, _ClassInfo): + error(filename, obj.starting_linenum, 'build/class', 5, + 'Failed to find complete declaration of class %s' % + obj.name) + elif isinstance(obj, _NamespaceInfo): + error(filename, obj.starting_linenum, 'build/namespaces', 5, + 'Failed to find complete declaration of namespace %s' % + obj.name) + + +def CheckForNonStandardConstructs(filename, clean_lines, linenum, + nesting_state, error): + r"""Logs an error if we see certain non-ANSI constructs ignored by gcc-2. + + Complain about several constructs which gcc-2 accepts, but which are + not standard C++. Warning about these in lint is one way to ease the + transition to new compilers. + - put storage class first (e.g. "static const" instead of "const static"). + - "%lld" instead of %qd" in printf-type functions. + - "%1$d" is non-standard in printf-type functions. + - "\%" is an undefined character escape sequence. + - text after #endif is not allowed. + - invalid inner-style forward declaration. + - >? and ?= and )\?=?\s*(\w+|[+-]?\d+)(\.\d*)?', + line): + error(filename, linenum, 'build/deprecated', 3, + '>? and ))?' + # r'\s*const\s*' + type_name + '\s*&\s*\w+\s*;' + error(filename, linenum, 'runtime/member_string_references', 2, + 'const string& members are dangerous. It is much better to use ' + 'alternatives, such as pointers or simple constants.') + + # Everything else in this function operates on class declarations. + # Return early if the top of the nesting stack is not a class, or if + # the class head is not completed yet. + classinfo = nesting_state.InnermostClass() + if not classinfo or not classinfo.seen_open_brace: + return + + # The class may have been declared with namespace or classname qualifiers. + # The constructor and destructor will not have those qualifiers. + base_classname = classinfo.name.split('::')[-1] + + # Look for single-argument constructors that aren't marked explicit. + # Technically a valid construct, but against style. + explicit_constructor_match = Match( + r'\s+(?:inline\s+)?(explicit\s+)?(?:inline\s+)?%s\s*' + r'\(((?:[^()]|\([^()]*\))*)\)' + % re.escape(base_classname), + line) + + if explicit_constructor_match: + is_marked_explicit = explicit_constructor_match.group(1) + + if not explicit_constructor_match.group(2): + constructor_args = [] + else: + constructor_args = explicit_constructor_match.group(2).split(',') + + # collapse arguments so that commas in template parameter lists and function + # argument parameter lists don't split arguments in two + i = 0 + while i < len(constructor_args): + constructor_arg = constructor_args[i] + while (constructor_arg.count('<') > constructor_arg.count('>') or + constructor_arg.count('(') > constructor_arg.count(')')): + constructor_arg += ',' + constructor_args[i + 1] + del constructor_args[i + 1] + constructor_args[i] = constructor_arg + i += 1 + + variadic_args = [arg for arg in constructor_args if '&&...' in arg] + defaulted_args = [arg for arg in constructor_args if '=' in arg] + noarg_constructor = (not constructor_args or # empty arg list + # 'void' arg specifier + (len(constructor_args) == 1 and + constructor_args[0].strip() == 'void')) + onearg_constructor = ((len(constructor_args) == 1 and # exactly one arg + not noarg_constructor) or + # all but at most one arg defaulted + (len(constructor_args) >= 1 and + not noarg_constructor and + len(defaulted_args) >= len(constructor_args) - 1) or + # variadic arguments with zero or one argument + (len(constructor_args) <= 2 and + len(variadic_args) >= 1)) + initializer_list_constructor = bool( + onearg_constructor and + Search(r'\bstd\s*::\s*initializer_list\b', constructor_args[0])) + copy_constructor = bool( + onearg_constructor and + Match(r'(const\s+)?%s(\s*<[^>]*>)?(\s+const)?\s*(?:<\w+>\s*)?&' + % re.escape(base_classname), constructor_args[0].strip())) + + if (not is_marked_explicit and + onearg_constructor and + not initializer_list_constructor and + not copy_constructor): + if defaulted_args or variadic_args: + error(filename, linenum, 'runtime/explicit', 5, + 'Constructors callable with one argument ' + 'should be marked explicit.') + else: + error(filename, linenum, 'runtime/explicit', 5, + 'Single-parameter constructors should be marked explicit.') + elif is_marked_explicit and not onearg_constructor: + if noarg_constructor: + error(filename, linenum, 'runtime/explicit', 5, + 'Zero-parameter constructors should not be marked explicit.') + + +def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error): + """Checks for the correctness of various spacing around function calls. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Since function calls often occur inside if/for/while/switch + # expressions - which have their own, more liberal conventions - we + # first see if we should be looking inside such an expression for a + # function call, to which we can apply more strict standards. + fncall = line # if there's no control flow construct, look at whole line + for pattern in (r'\bif\s*\((.*)\)\s*{', + r'\bfor\s*\((.*)\)\s*{', + r'\bwhile\s*\((.*)\)\s*[{;]', + r'\bswitch\s*\((.*)\)\s*{'): + match = Search(pattern, line) + if match: + fncall = match.group(1) # look inside the parens for function calls + break + + # Except in if/for/while/switch, there should never be space + # immediately inside parens (eg "f( 3, 4 )"). We make an exception + # for nested parens ( (a+b) + c ). Likewise, there should never be + # a space before a ( when it's a function argument. I assume it's a + # function argument when the char before the whitespace is legal in + # a function name (alnum + _) and we're not starting a macro. Also ignore + # pointers and references to arrays and functions coz they're too tricky: + # we use a very simple way to recognize these: + # " (something)(maybe-something)" or + # " (something)(maybe-something," or + # " (something)[something]" + # Note that we assume the contents of [] to be short enough that + # they'll never need to wrap. + if ( # Ignore control structures. + not Search(r'\b(if|for|while|switch|return|new|delete|catch|sizeof)\b', + fncall) and + # Ignore pointers/references to functions. + not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and + # Ignore pointers/references to arrays. + not Search(r' \([^)]+\)\[[^\]]+\]', fncall)): + if Search(r'\w\s*\(\s(?!\s*\\$)', fncall): # a ( used for a fn call + error(filename, linenum, 'whitespace/parens', 4, + 'Extra space after ( in function call') + elif Search(r'\(\s+(?!(\s*\\)|\()', fncall): + error(filename, linenum, 'whitespace/parens', 2, + 'Extra space after (') + if (Search(r'\w\s+\(', fncall) and + not Search(r'_{0,2}asm_{0,2}\s+_{0,2}volatile_{0,2}\s+\(', fncall) and + not Search(r'#\s*define|typedef|using\s+\w+\s*=', fncall) and + not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall) and + not Search(r'\bcase\s+\(', fncall)): + # TODO(unknown): Space after an operator function seem to be a common + # error, silence those for now by restricting them to highest verbosity. + if Search(r'\boperator_*\b', line): + error(filename, linenum, 'whitespace/parens', 0, + 'Extra space before ( in function call') + else: + error(filename, linenum, 'whitespace/parens', 4, + 'Extra space before ( in function call') + # If the ) is followed only by a newline or a { + newline, assume it's + # part of a control statement (if/while/etc), and don't complain + if Search(r'[^)]\s+\)\s*[^{\s]', fncall): + # If the closing parenthesis is preceded by only whitespaces, + # try to give a more descriptive error message. + if Search(r'^\s+\)', fncall): + error(filename, linenum, 'whitespace/parens', 2, + 'Closing ) should be moved to the previous line') + else: + error(filename, linenum, 'whitespace/parens', 2, + 'Extra space before )') + + +def IsBlankLine(line): + """Returns true if the given line is blank. + + We consider a line to be blank if the line is empty or consists of + only white spaces. + + Args: + line: A line of a string. + + Returns: + True, if the given line is blank. + """ + return not line or line.isspace() + + +def CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, + error): + is_namespace_indent_item = ( + len(nesting_state.stack) > 1 and + nesting_state.stack[-1].check_namespace_indentation and + isinstance(nesting_state.previous_stack_top, _NamespaceInfo) and + nesting_state.previous_stack_top == nesting_state.stack[-2]) + + if ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, + clean_lines.elided, line): + CheckItemIndentationInNamespace(filename, clean_lines.elided, + line, error) + + +def CheckForFunctionLengths(filename, clean_lines, linenum, + function_state, error): + """Reports for long function bodies. + + For an overview why this is done, see: + https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions + + Uses a simplistic algorithm assuming other style guidelines + (especially spacing) are followed. + Only checks unindented functions, so class members are unchecked. + Trivial bodies are unchecked, so constructors with huge initializer lists + may be missed. + Blank/comment lines are not counted so as to avoid encouraging the removal + of vertical space and comments just to get through a lint check. + NOLINT *on the last line of a function* disables this check. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + function_state: Current function name and lines in body so far. + error: The function to call with any errors found. + """ + lines = clean_lines.lines + line = lines[linenum] + joined_line = '' + + starting_func = False + regexp = r'(\w(\w|::|\*|\&|\s)*)\(' # decls * & space::name( ... + match_result = Match(regexp, line) + if match_result: + # If the name is all caps and underscores, figure it's a macro and + # ignore it, unless it's TEST or TEST_F. + function_name = match_result.group(1).split()[-1] + if function_name == 'TEST' or function_name == 'TEST_F' or ( + not Match(r'[A-Z_]+$', function_name)): + starting_func = True + + if starting_func: + body_found = False + for start_linenum in range(linenum, clean_lines.NumLines()): + start_line = lines[start_linenum] + joined_line += ' ' + start_line.lstrip() + if Search(r'(;|})', start_line): # Declarations and trivial functions + body_found = True + break # ... ignore + elif Search(r'{', start_line): + body_found = True + function = Search(r'((\w|:)*)\(', line).group(1) + if Match(r'TEST', function): # Handle TEST... macros + parameter_regexp = Search(r'(\(.*\))', joined_line) + if parameter_regexp: # Ignore bad syntax + function += parameter_regexp.group(1) + else: + function += '()' + function_state.Begin(function) + break + if not body_found: + # No body for the function (or evidence of a non-function) was found. + error(filename, linenum, 'readability/fn_size', 5, + 'Lint failed to find start of function body.') + elif Match(r'^\}\s*$', line): # function end + function_state.Check(error, filename, linenum) + function_state.End() + elif not Match(r'^\s*$', line): + function_state.Count() # Count non-blank/non-comment lines. + + +_RE_PATTERN_TODO = re.compile(r'^//(\s*)TODO(\(.+?\))?:?(\s|$)?') + + +def CheckComment(line, filename, linenum, next_line_start, error): + """Checks for common mistakes in comments. + + Args: + line: The line in question. + filename: The name of the current file. + linenum: The number of the line to check. + next_line_start: The first non-whitespace column of the next line. + error: The function to call with any errors found. + """ + commentpos = line.find('//') + if commentpos != -1: + # Check if the // may be in quotes. If so, ignore it + if re.sub(r'\\.', '', line[0:commentpos]).count('"') % 2 == 0: + # Allow one space for new scopes, two spaces otherwise: + if (not (Match(r'^.*{ *//', line) and next_line_start == commentpos) and + ((commentpos >= 1 and + line[commentpos-1] not in string.whitespace) or + (commentpos >= 2 and + line[commentpos-2] not in string.whitespace))): + error(filename, linenum, 'whitespace/comments', 2, + 'At least two spaces is best between code and comments') + + # Checks for common mistakes in TODO comments. + comment = line[commentpos:] + match = _RE_PATTERN_TODO.match(comment) + if match: + # One whitespace is correct; zero whitespace is handled elsewhere. + leading_whitespace = match.group(1) + if len(leading_whitespace) > 1: + error(filename, linenum, 'whitespace/todo', 2, + 'Too many spaces before TODO') + + username = match.group(2) + if not username: + error(filename, linenum, 'readability/todo', 2, + 'Missing username in TODO; it should look like ' + '"// TODO(my_username): Stuff."') + + middle_whitespace = match.group(3) + # Comparisons made explicit for correctness -- pylint: disable=g-explicit-bool-comparison + if middle_whitespace != ' ' and middle_whitespace != '': + error(filename, linenum, 'whitespace/todo', 2, + 'TODO(my_username) should be followed by a space') + + # If the comment contains an alphanumeric character, there + # should be a space somewhere between it and the // unless + # it's a /// or //! Doxygen comment. + if (Match(r'//[^ ]*\w', comment) and + not Match(r'(///|//\!)(\s+|$)', comment)): + error(filename, linenum, 'whitespace/comments', 4, + 'Should have a space between // and comment') + + +def CheckAccess(filename, clean_lines, linenum, nesting_state, error): + """Checks for improper use of DISALLOW* macros. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] # get rid of comments and strings + + matched = Match((r'\s*(DISALLOW_COPY_AND_ASSIGN|' + r'DISALLOW_IMPLICIT_CONSTRUCTORS)'), line) + if not matched: + return + if nesting_state.stack and isinstance(nesting_state.stack[-1], _ClassInfo): + if nesting_state.stack[-1].access != 'private': + error(filename, linenum, 'readability/constructors', 3, + '%s must be in the private: section' % matched.group(1)) + + else: + # Found DISALLOW* macro outside a class declaration, or perhaps it + # was used inside a function when it should have been part of the + # class declaration. We could issue a warning here, but it + # probably resulted in a compiler error already. + pass + + +def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): + """Checks for the correctness of various spacing issues in the code. + + Things we check for: spaces around operators, spaces after + if/for/while/switch, no spaces around parens in function calls, two + spaces between code and comment, don't start a block with a blank + line, don't end a function with a blank line, don't add a blank line + after public/protected/private, don't have too many blank lines in a row. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + + # Don't use "elided" lines here, otherwise we can't check commented lines. + # Don't want to use "raw" either, because we don't want to check inside C++11 + # raw strings, + raw = clean_lines.lines_without_raw_strings + line = raw[linenum] + + # Before nixing comments, check if the line is blank for no good + # reason. This includes the first line after a block is opened, and + # blank lines at the end of a function (ie, right before a line like '}' + # + # Skip all the blank line checks if we are immediately inside a + # namespace body. In other words, don't issue blank line warnings + # for this block: + # namespace { + # + # } + # + # A warning about missing end of namespace comments will be issued instead. + # + # Also skip blank line checks for 'extern "C"' blocks, which are formatted + # like namespaces. + if (IsBlankLine(line) and + not nesting_state.InNamespaceBody() and + not nesting_state.InExternC()): + elided = clean_lines.elided + prev_line = elided[linenum - 1] + prevbrace = prev_line.rfind('{') + # TODO(unknown): Don't complain if line before blank line, and line after, + # both start with alnums and are indented the same amount. + # This ignores whitespace at the start of a namespace block + # because those are not usually indented. + if prevbrace != -1 and prev_line[prevbrace:].find('}') == -1: + # OK, we have a blank line at the start of a code block. Before we + # complain, we check if it is an exception to the rule: The previous + # non-empty line has the parameters of a function header that are indented + # 4 spaces (because they did not fit in a 80 column line when placed on + # the same line as the function name). We also check for the case where + # the previous line is indented 6 spaces, which may happen when the + # initializers of a constructor do not fit into a 80 column line. + exception = False + if Match(r' {6}\w', prev_line): # Initializer list? + # We are looking for the opening column of initializer list, which + # should be indented 4 spaces to cause 6 space indentation afterwards. + search_position = linenum-2 + while (search_position >= 0 + and Match(r' {6}\w', elided[search_position])): + search_position -= 1 + exception = (search_position >= 0 + and elided[search_position][:5] == ' :') + else: + # Search for the function arguments or an initializer list. We use a + # simple heuristic here: If the line is indented 4 spaces; and we have a + # closing paren, without the opening paren, followed by an opening brace + # or colon (for initializer lists) we assume that it is the last line of + # a function header. If we have a colon indented 4 spaces, it is an + # initializer list. + exception = (Match(r' {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)', + prev_line) + or Match(r' {4}:', prev_line)) + + if not exception: + error(filename, linenum, 'whitespace/blank_line', 2, + 'Redundant blank line at the start of a code block ' + 'should be deleted.') + # Ignore blank lines at the end of a block in a long if-else + # chain, like this: + # if (condition1) { + # // Something followed by a blank line + # + # } else if (condition2) { + # // Something else + # } + if linenum + 1 < clean_lines.NumLines(): + next_line = raw[linenum + 1] + if (next_line + and Match(r'\s*}', next_line) + and next_line.find('} else ') == -1): + error(filename, linenum, 'whitespace/blank_line', 3, + 'Redundant blank line at the end of a code block ' + 'should be deleted.') + + matched = Match(r'\s*(public|protected|private):', prev_line) + if matched: + error(filename, linenum, 'whitespace/blank_line', 3, + 'Do not leave a blank line after "%s:"' % matched.group(1)) + + # Next, check comments + next_line_start = 0 + if linenum + 1 < clean_lines.NumLines(): + next_line = raw[linenum + 1] + next_line_start = len(next_line) - len(next_line.lstrip()) + CheckComment(line, filename, linenum, next_line_start, error) + + # get rid of comments and strings + line = clean_lines.elided[linenum] + + # You shouldn't have spaces before your brackets, except maybe after + # 'delete []' or 'return []() {};' + if Search(r'\w\s+\[', line) and not Search(r'(?:delete|return)\s+\[', line): + error(filename, linenum, 'whitespace/braces', 5, + 'Extra space before [') + + # In range-based for, we wanted spaces before and after the colon, but + # not around "::" tokens that might appear. + if (Search(r'for *\(.*[^:]:[^: ]', line) or + Search(r'for *\(.*[^: ]:[^:]', line)): + error(filename, linenum, 'whitespace/forcolon', 2, + 'Missing space around colon in range-based for loop') + + +def CheckOperatorSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing around operators. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Don't try to do spacing checks for operator methods. Do this by + # replacing the troublesome characters with something else, + # preserving column position for all other characters. + # + # The replacement is done repeatedly to avoid false positives from + # operators that call operators. + while True: + match = Match(r'^(.*\boperator\b)(\S+)(\s*\(.*)$', line) + if match: + line = match.group(1) + ('_' * len(match.group(2))) + match.group(3) + else: + break + + # We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )". + # Otherwise not. Note we only check for non-spaces on *both* sides; + # sometimes people put non-spaces on one side when aligning ='s among + # many lines (not that this is behavior that I approve of...) + if ((Search(r'[\w.]=', line) or + Search(r'=[\w.]', line)) + and not Search(r'\b(if|while|for) ', line) + # Operators taken from [lex.operators] in C++11 standard. + and not Search(r'(>=|<=|==|!=|&=|\^=|\|=|\+=|\*=|\/=|\%=)', line) + and not Search(r'operator=', line)): + error(filename, linenum, 'whitespace/operators', 4, + 'Missing spaces around =') + + # It's ok not to have spaces around binary operators like + - * /, but if + # there's too little whitespace, we get concerned. It's hard to tell, + # though, so we punt on this one for now. TODO. + + # You should always have whitespace around binary operators. + # + # Check <= and >= first to avoid false positives with < and >, then + # check non-include lines for spacing around < and >. + # + # If the operator is followed by a comma, assume it's be used in a + # macro context and don't do any checks. This avoids false + # positives. + # + # Note that && is not included here. This is because there are too + # many false positives due to RValue references. + match = Search(r'[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]', line) + if match: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around %s' % match.group(1)) + elif not Match(r'#.*include', line): + # Look for < that is not surrounded by spaces. This is only + # triggered if both sides are missing spaces, even though + # technically should should flag if at least one side is missing a + # space. This is done to avoid some false positives with shifts. + match = Match(r'^(.*[^\s<])<[^\s=<,]', line) + if match: + (_, _, end_pos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + if end_pos <= -1: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around <') + + # Look for > that is not surrounded by spaces. Similar to the + # above, we only trigger if both sides are missing spaces to avoid + # false positives with shifts. + match = Match(r'^(.*[^-\s>])>[^\s=>,]', line) + if match: + (_, _, start_pos) = ReverseCloseExpression( + clean_lines, linenum, len(match.group(1))) + if start_pos <= -1: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around >') + + # We allow no-spaces around << when used like this: 10<<20, but + # not otherwise (particularly, not when used as streams) + # + # We also allow operators following an opening parenthesis, since + # those tend to be macros that deal with operators. + match = Search(r'(operator|[^\s(<])(?:L|UL|LL|ULL|l|ul|ll|ull)?<<([^\s,=<])', line) + if (match and not (match.group(1).isdigit() and match.group(2).isdigit()) and + not (match.group(1) == 'operator' and match.group(2) == ';')): + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around <<') + + # We allow no-spaces around >> for almost anything. This is because + # C++11 allows ">>" to close nested templates, which accounts for + # most cases when ">>" is not followed by a space. + # + # We still warn on ">>" followed by alpha character, because that is + # likely due to ">>" being used for right shifts, e.g.: + # value >> alpha + # + # When ">>" is used to close templates, the alphanumeric letter that + # follows would be part of an identifier, and there should still be + # a space separating the template type and the identifier. + # type> alpha + match = Search(r'>>[a-zA-Z_]', line) + if match: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around >>') + + # There shouldn't be space around unary operators + match = Search(r'(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])', line) + if match: + error(filename, linenum, 'whitespace/operators', 4, + 'Extra space for operator %s' % match.group(1)) + + +def CheckParenthesisSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing around parentheses. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # No spaces after an if, while, switch, or for + match = Search(r' (if\(|for\(|while\(|switch\()', line) + if match: + error(filename, linenum, 'whitespace/parens', 5, + 'Missing space before ( in %s' % match.group(1)) + + # For if/for/while/switch, the left and right parens should be + # consistent about how many spaces are inside the parens, and + # there should either be zero or one spaces inside the parens. + # We don't want: "if ( foo)" or "if ( foo )". + # Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed. + match = Search(r'\b(if|for|while|switch)\s*' + r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$', + line) + if match: + if len(match.group(2)) != len(match.group(4)): + if not (match.group(3) == ';' and + len(match.group(2)) == 1 + len(match.group(4)) or + not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)): + error(filename, linenum, 'whitespace/parens', 5, + 'Mismatching spaces inside () in %s' % match.group(1)) + if len(match.group(2)) not in [0, 1]: + error(filename, linenum, 'whitespace/parens', 5, + 'Should have zero or one spaces inside ( and ) in %s' % + match.group(1)) + + +def CheckCommaSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing near commas and semicolons. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + raw = clean_lines.lines_without_raw_strings + line = clean_lines.elided[linenum] + + # You should always have a space after a comma (either as fn arg or operator) + # + # This does not apply when the non-space character following the + # comma is another comma, since the only time when that happens is + # for empty macro arguments. + # + # We run this check in two passes: first pass on elided lines to + # verify that lines contain missing whitespaces, second pass on raw + # lines to confirm that those missing whitespaces are not due to + # elided comments. + if (Search(r',[^,\s]', ReplaceAll(r'\boperator\s*,\s*\(', 'F(', line)) and + Search(r',[^,\s]', raw[linenum])): + error(filename, linenum, 'whitespace/comma', 3, + 'Missing space after ,') + + # You should always have a space after a semicolon + # except for few corner cases + # TODO(unknown): clarify if 'if (1) { return 1;}' is requires one more + # space after ; + if Search(r';[^\s};\\)/]', line): + error(filename, linenum, 'whitespace/semicolon', 3, + 'Missing space after ;') + + +def _IsType(clean_lines, nesting_state, expr): + """Check if expression looks like a type name, returns true if so. + + Args: + clean_lines: A CleansedLines instance containing the file. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + expr: The expression to check. + Returns: + True, if token looks like a type. + """ + # Keep only the last token in the expression + last_word = Match(r'^.*(\b\S+)$', expr) + if last_word: + token = last_word.group(1) + else: + token = expr + + # Match native types and stdint types + if _TYPES.match(token): + return True + + # Try a bit harder to match templated types. Walk up the nesting + # stack until we find something that resembles a typename + # declaration for what we are looking for. + typename_pattern = (r'\b(?:typename|class|struct)\s+' + re.escape(token) + + r'\b') + block_index = len(nesting_state.stack) - 1 + while block_index >= 0: + if isinstance(nesting_state.stack[block_index], _NamespaceInfo): + return False + + # Found where the opening brace is. We want to scan from this + # line up to the beginning of the function, minus a few lines. + # template + # class C + # : public ... { // start scanning here + last_line = nesting_state.stack[block_index].starting_linenum + + next_block_start = 0 + if block_index > 0: + next_block_start = nesting_state.stack[block_index - 1].starting_linenum + first_line = last_line + while first_line >= next_block_start: + if clean_lines.elided[first_line].find('template') >= 0: + break + first_line -= 1 + if first_line < next_block_start: + # Didn't find any "template" keyword before reaching the next block, + # there are probably no template things to check for this block + block_index -= 1 + continue + + # Look for typename in the specified range + for i in xrange(first_line, last_line + 1, 1): + if Search(typename_pattern, clean_lines.elided[i]): + return True + block_index -= 1 + + return False + + +def CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error): + """Checks for horizontal spacing near commas. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Except after an opening paren, or after another opening brace (in case of + # an initializer list, for instance), you should have spaces before your + # braces when they are delimiting blocks, classes, namespaces etc. + # And since you should never have braces at the beginning of a line, + # this is an easy test. Except that braces used for initialization don't + # follow the same rule; we often don't want spaces before those. + match = Match(r'^(.*[^ ({>]){', line) + + if match: + # Try a bit harder to check for brace initialization. This + # happens in one of the following forms: + # Constructor() : initializer_list_{} { ... } + # Constructor{}.MemberFunction() + # Type variable{}; + # FunctionCall(type{}, ...); + # LastArgument(..., type{}); + # LOG(INFO) << type{} << " ..."; + # map_of_type[{...}] = ...; + # ternary = expr ? new type{} : nullptr; + # OuterTemplate{}> + # + # We check for the character following the closing brace, and + # silence the warning if it's one of those listed above, i.e. + # "{.;,)<>]:". + # + # To account for nested initializer list, we allow any number of + # closing braces up to "{;,)<". We can't simply silence the + # warning on first sight of closing brace, because that would + # cause false negatives for things that are not initializer lists. + # Silence this: But not this: + # Outer{ if (...) { + # Inner{...} if (...){ // Missing space before { + # }; } + # + # There is a false negative with this approach if people inserted + # spurious semicolons, e.g. "if (cond){};", but we will catch the + # spurious semicolon with a separate check. + leading_text = match.group(1) + (endline, endlinenum, endpos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + trailing_text = '' + if endpos > -1: + trailing_text = endline[endpos:] + for offset in xrange(endlinenum + 1, + min(endlinenum + 3, clean_lines.NumLines() - 1)): + trailing_text += clean_lines.elided[offset] + # We also suppress warnings for `uint64_t{expression}` etc., as the style + # guide recommends brace initialization for integral types to avoid + # overflow/truncation. + if (not Match(r'^[\s}]*[{.;,)<>\]:]', trailing_text) + and not _IsType(clean_lines, nesting_state, leading_text)): + error(filename, linenum, 'whitespace/braces', 5, + 'Missing space before {') + + # Make sure '} else {' has spaces. + if Search(r'}else', line): + error(filename, linenum, 'whitespace/braces', 5, + 'Missing space before else') + + # You shouldn't have a space before a semicolon at the end of the line. + # There's a special case for "for" since the style guide allows space before + # the semicolon there. + if Search(r':\s*;\s*$', line): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Semicolon defining empty statement. Use {} instead.') + elif Search(r'^\s*;\s*$', line): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Line contains only semicolon. If this should be an empty statement, ' + 'use {} instead.') + elif (Search(r'\s+;\s*$', line) and + not Search(r'\bfor\b', line)): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Extra space before last semicolon. If this should be an empty ' + 'statement, use {} instead.') + + +def IsDecltype(clean_lines, linenum, column): + """Check if the token ending on (linenum, column) is decltype(). + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: the number of the line to check. + column: end column of the token to check. + Returns: + True if this token is decltype() expression, False otherwise. + """ + (text, _, start_col) = ReverseCloseExpression(clean_lines, linenum, column) + if start_col < 0: + return False + if Search(r'\bdecltype\s*$', text[0:start_col]): + return True + return False + +def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error): + """Checks for additional blank line issues related to sections. + + Currently the only thing checked here is blank line before protected/private. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + class_info: A _ClassInfo objects. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Skip checks if the class is small, where small means 25 lines or less. + # 25 lines seems like a good cutoff since that's the usual height of + # terminals, and any class that can't fit in one screen can't really + # be considered "small". + # + # Also skip checks if we are on the first line. This accounts for + # classes that look like + # class Foo { public: ... }; + # + # If we didn't find the end of the class, last_line would be zero, + # and the check will be skipped by the first condition. + if (class_info.last_line - class_info.starting_linenum <= 24 or + linenum <= class_info.starting_linenum): + return + + matched = Match(r'\s*(public|protected|private):', clean_lines.lines[linenum]) + if matched: + # Issue warning if the line before public/protected/private was + # not a blank line, but don't do this if the previous line contains + # "class" or "struct". This can happen two ways: + # - We are at the beginning of the class. + # - We are forward-declaring an inner class that is semantically + # private, but needed to be public for implementation reasons. + # Also ignores cases where the previous line ends with a backslash as can be + # common when defining classes in C macros. + prev_line = clean_lines.lines[linenum - 1] + if (not IsBlankLine(prev_line) and + not Search(r'\b(class|struct)\b', prev_line) and + not Search(r'\\$', prev_line)): + # Try a bit harder to find the beginning of the class. This is to + # account for multi-line base-specifier lists, e.g.: + # class Derived + # : public Base { + end_class_head = class_info.starting_linenum + for i in range(class_info.starting_linenum, linenum): + if Search(r'\{\s*$', clean_lines.lines[i]): + end_class_head = i + break + if end_class_head < linenum - 1: + error(filename, linenum, 'whitespace/blank_line', 3, + '"%s:" should be preceded by a blank line' % matched.group(1)) + + +def GetPreviousNonBlankLine(clean_lines, linenum): + """Return the most recent non-blank line and its line number. + + Args: + clean_lines: A CleansedLines instance containing the file contents. + linenum: The number of the line to check. + + Returns: + A tuple with two elements. The first element is the contents of the last + non-blank line before the current line, or the empty string if this is the + first non-blank line. The second is the line number of that line, or -1 + if this is the first non-blank line. + """ + + prevlinenum = linenum - 1 + while prevlinenum >= 0: + prevline = clean_lines.elided[prevlinenum] + if not IsBlankLine(prevline): # if not a blank line... + return (prevline, prevlinenum) + prevlinenum -= 1 + return ('', -1) + + +def CheckBraces(filename, clean_lines, linenum, error): + """Looks for misplaced braces (e.g. at the end of line). + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + line = clean_lines.elided[linenum] # get rid of comments and strings + + if Match(r'\s*{\s*$', line): + # We allow an open brace to start a line in the case where someone is using + # braces in a block to explicitly create a new scope, which is commonly used + # to control the lifetime of stack-allocated variables. Braces are also + # used for brace initializers inside function calls. We don't detect this + # perfectly: we just don't complain if the last non-whitespace character on + # the previous non-blank line is ',', ';', ':', '(', '{', or '}', or if the + # previous line starts a preprocessor block. We also allow a brace on the + # following line if it is part of an array initialization and would not fit + # within the 80 character limit of the preceding line. + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if (not Search(r'[,;:}{(]\s*$', prevline) and + not Match(r'\s*#', prevline) and + not (GetLineWidth(prevline) > _line_length - 2 and '[]' in prevline)): + error(filename, linenum, 'whitespace/braces', 4, + '{ should almost always be at the end of the previous line') + + # An else clause should be on the same line as the preceding closing brace. + if Match(r'\s*else\b\s*(?:if\b|\{|$)', line): + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if Match(r'\s*}\s*$', prevline): + error(filename, linenum, 'whitespace/newline', 4, + 'An else should appear on the same line as the preceding }') + + # If braces come on one side of an else, they should be on both. + # However, we have to worry about "else if" that spans multiple lines! + if Search(r'else if\s*\(', line): # could be multi-line if + brace_on_left = bool(Search(r'}\s*else if\s*\(', line)) + # find the ( after the if + pos = line.find('else if') + pos = line.find('(', pos) + if pos > 0: + (endline, _, endpos) = CloseExpression(clean_lines, linenum, pos) + brace_on_right = endline[endpos:].find('{') != -1 + if brace_on_left != brace_on_right: # must be brace after if + error(filename, linenum, 'readability/braces', 5, + 'If an else has a brace on one side, it should have it on both') + elif Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line): + error(filename, linenum, 'readability/braces', 5, + 'If an else has a brace on one side, it should have it on both') + + # Likewise, an else should never have the else clause on the same line + if Search(r'\belse [^\s{]', line) and not Search(r'\belse if\b', line): + error(filename, linenum, 'whitespace/newline', 4, + 'Else clause should never be on same line as else (use 2 lines)') + + # In the same way, a do/while should never be on one line + if Match(r'\s*do [^\s{]', line): + error(filename, linenum, 'whitespace/newline', 4, + 'do/while clauses should not be on a single line') + + # Check single-line if/else bodies. The style guide says 'curly braces are not + # required for single-line statements'. We additionally allow multi-line, + # single statements, but we reject anything with more than one semicolon in + # it. This means that the first semicolon after the if should be at the end of + # its line, and the line after that should have an indent level equal to or + # lower than the if. We also check for ambiguous if/else nesting without + # braces. + if_else_match = Search(r'\b(if\s*\(|else\b)', line) + if if_else_match and not Match(r'\s*#', line): + if_indent = GetIndentLevel(line) + endline, endlinenum, endpos = line, linenum, if_else_match.end() + if_match = Search(r'\bif\s*\(', line) + if if_match: + # This could be a multiline if condition, so find the end first. + pos = if_match.end() - 1 + (endline, endlinenum, endpos) = CloseExpression(clean_lines, linenum, pos) + # Check for an opening brace, either directly after the if or on the next + # line. If found, this isn't a single-statement conditional. + if (not Match(r'\s*{', endline[endpos:]) + and not (Match(r'\s*$', endline[endpos:]) + and endlinenum < (len(clean_lines.elided) - 1) + and Match(r'\s*{', clean_lines.elided[endlinenum + 1]))): + while (endlinenum < len(clean_lines.elided) + and ';' not in clean_lines.elided[endlinenum][endpos:]): + endlinenum += 1 + endpos = 0 + if endlinenum < len(clean_lines.elided): + endline = clean_lines.elided[endlinenum] + # We allow a mix of whitespace and closing braces (e.g. for one-liner + # methods) and a single \ after the semicolon (for macros) + endpos = endline.find(';') + if not Match(r';[\s}]*(\\?)$', endline[endpos:]): + # Semicolon isn't the last character, there's something trailing. + # Output a warning if the semicolon is not contained inside + # a lambda expression. + if not Match(r'^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}]*\}\s*\)*[;,]\s*$', + endline): + error(filename, linenum, 'readability/braces', 4, + 'If/else bodies with multiple statements require braces') + elif endlinenum < len(clean_lines.elided) - 1: + # Make sure the next line is dedented + next_line = clean_lines.elided[endlinenum + 1] + next_indent = GetIndentLevel(next_line) + # With ambiguous nested if statements, this will error out on the + # if that *doesn't* match the else, regardless of whether it's the + # inner one or outer one. + if (if_match and Match(r'\s*else\b', next_line) + and next_indent != if_indent): + error(filename, linenum, 'readability/braces', 4, + 'Else clause should be indented at the same level as if. ' + 'Ambiguous nested if/else chains require braces.') + elif next_indent > if_indent: + error(filename, linenum, 'readability/braces', 4, + 'If/else bodies with multiple statements require braces') + + +def CheckTrailingSemicolon(filename, clean_lines, linenum, error): + """Looks for redundant trailing semicolon. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + line = clean_lines.elided[linenum] + + # Block bodies should not be followed by a semicolon. Due to C++11 + # brace initialization, there are more places where semicolons are + # required than not, so we use a whitelist approach to check these + # rather than a blacklist. These are the places where "};" should + # be replaced by just "}": + # 1. Some flavor of block following closing parenthesis: + # for (;;) {}; + # while (...) {}; + # switch (...) {}; + # Function(...) {}; + # if (...) {}; + # if (...) else if (...) {}; + # + # 2. else block: + # if (...) else {}; + # + # 3. const member function: + # Function(...) const {}; + # + # 4. Block following some statement: + # x = 42; + # {}; + # + # 5. Block at the beginning of a function: + # Function(...) { + # {}; + # } + # + # Note that naively checking for the preceding "{" will also match + # braces inside multi-dimensional arrays, but this is fine since + # that expression will not contain semicolons. + # + # 6. Block following another block: + # while (true) {} + # {}; + # + # 7. End of namespaces: + # namespace {}; + # + # These semicolons seems far more common than other kinds of + # redundant semicolons, possibly due to people converting classes + # to namespaces. For now we do not warn for this case. + # + # Try matching case 1 first. + match = Match(r'^(.*\)\s*)\{', line) + if match: + # Matched closing parenthesis (case 1). Check the token before the + # matching opening parenthesis, and don't warn if it looks like a + # macro. This avoids these false positives: + # - macro that defines a base class + # - multi-line macro that defines a base class + # - macro that defines the whole class-head + # + # But we still issue warnings for macros that we know are safe to + # warn, specifically: + # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P + # - TYPED_TEST + # - INTERFACE_DEF + # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED: + # + # We implement a whitelist of safe macros instead of a blacklist of + # unsafe macros, even though the latter appears less frequently in + # google code and would have been easier to implement. This is because + # the downside for getting the whitelist wrong means some extra + # semicolons, while the downside for getting the blacklist wrong + # would result in compile errors. + # + # In addition to macros, we also don't want to warn on + # - Compound literals + # - Lambdas + # - alignas specifier with anonymous structs + # - decltype + closing_brace_pos = match.group(1).rfind(')') + opening_parenthesis = ReverseCloseExpression( + clean_lines, linenum, closing_brace_pos) + if opening_parenthesis[2] > -1: + line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]] + macro = Search(r'\b([A-Z_][A-Z0-9_]*)\s*$', line_prefix) + func = Match(r'^(.*\])\s*$', line_prefix) + if ((macro and + macro.group(1) not in ( + 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST', + 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED', + 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or + (func and not Search(r'\boperator\s*\[\s*\]', func.group(1))) or + Search(r'\b(?:struct|union)\s+alignas\s*$', line_prefix) or + Search(r'\bdecltype$', line_prefix) or + Search(r'\s+=\s*$', line_prefix)): + match = None + if (match and + opening_parenthesis[1] > 1 and + Search(r'\]\s*$', clean_lines.elided[opening_parenthesis[1] - 1])): + # Multi-line lambda-expression + match = None + + else: + # Try matching cases 2-3. + match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line) + if not match: + # Try matching cases 4-6. These are always matched on separate lines. + # + # Note that we can't simply concatenate the previous line to the + # current line and do a single match, otherwise we may output + # duplicate warnings for the blank line case: + # if (cond) { + # // blank line + # } + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if prevline and Search(r'[;{}]\s*$', prevline): + match = Match(r'^(\s*)\{', line) + + # Check matching closing brace + if match: + (endline, endlinenum, endpos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + if endpos > -1 and Match(r'^\s*;', endline[endpos:]): + # Current {} pair is eligible for semicolon check, and we have found + # the redundant semicolon, output warning here. + # + # Note: because we are scanning forward for opening braces, and + # outputting warnings for the matching closing brace, if there are + # nested blocks with trailing semicolons, we will get the error + # messages in reversed order. + + # We need to check the line forward for NOLINT + raw_lines = clean_lines.raw_lines + ParseNolintSuppressions(filename, raw_lines[endlinenum-1], endlinenum-1, + error) + ParseNolintSuppressions(filename, raw_lines[endlinenum], endlinenum, + error) + + error(filename, endlinenum, 'readability/braces', 4, + "You don't need a ; after a }") + + +def CheckEmptyBlockBody(filename, clean_lines, linenum, error): + """Look for empty loop/conditional body with only a single semicolon. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + # Search for loop keywords at the beginning of the line. Because only + # whitespaces are allowed before the keywords, this will also ignore most + # do-while-loops, since those lines should start with closing brace. + # + # We also check "if" blocks here, since an empty conditional block + # is likely an error. + line = clean_lines.elided[linenum] + matched = Match(r'\s*(for|while|if)\s*\(', line) + if matched: + # Find the end of the conditional expression. + (end_line, end_linenum, end_pos) = CloseExpression( + clean_lines, linenum, line.find('(')) + + # Output warning if what follows the condition expression is a semicolon. + # No warning for all other cases, including whitespace or newline, since we + # have a separate check for semicolons preceded by whitespace. + if end_pos >= 0 and Match(r';', end_line[end_pos:]): + if matched.group(1) == 'if': + error(filename, end_linenum, 'whitespace/empty_conditional_body', 5, + 'Empty conditional bodies should use {}') + else: + error(filename, end_linenum, 'whitespace/empty_loop_body', 5, + 'Empty loop bodies should use {} or continue') + + # Check for if statements that have completely empty bodies (no comments) + # and no else clauses. + if end_pos >= 0 and matched.group(1) == 'if': + # Find the position of the opening { for the if statement. + # Return without logging an error if it has no brackets. + opening_linenum = end_linenum + opening_line_fragment = end_line[end_pos:] + # Loop until EOF or find anything that's not whitespace or opening {. + while not Search(r'^\s*\{', opening_line_fragment): + if Search(r'^(?!\s*$)', opening_line_fragment): + # Conditional has no brackets. + return + opening_linenum += 1 + if opening_linenum == len(clean_lines.elided): + # Couldn't find conditional's opening { or any code before EOF. + return + opening_line_fragment = clean_lines.elided[opening_linenum] + # Set opening_line (opening_line_fragment may not be entire opening line). + opening_line = clean_lines.elided[opening_linenum] + + # Find the position of the closing }. + opening_pos = opening_line_fragment.find('{') + if opening_linenum == end_linenum: + # We need to make opening_pos relative to the start of the entire line. + opening_pos += end_pos + (closing_line, closing_linenum, closing_pos) = CloseExpression( + clean_lines, opening_linenum, opening_pos) + if closing_pos < 0: + return + + # Now construct the body of the conditional. This consists of the portion + # of the opening line after the {, all lines until the closing line, + # and the portion of the closing line before the }. + if (clean_lines.raw_lines[opening_linenum] != + CleanseComments(clean_lines.raw_lines[opening_linenum])): + # Opening line ends with a comment, so conditional isn't empty. + return + if closing_linenum > opening_linenum: + # Opening line after the {. Ignore comments here since we checked above. + bodylist = list(opening_line[opening_pos+1:]) + # All lines until closing line, excluding closing line, with comments. + bodylist.extend(clean_lines.raw_lines[opening_linenum+1:closing_linenum]) + # Closing line before the }. Won't (and can't) have comments. + bodylist.append(clean_lines.elided[closing_linenum][:closing_pos-1]) + body = '\n'.join(bodylist) + else: + # If statement has brackets and fits on a single line. + body = opening_line[opening_pos+1:closing_pos-1] + + # Check if the body is empty + if not _EMPTY_CONDITIONAL_BODY_PATTERN.search(body): + return + # The body is empty. Now make sure there's not an else clause. + current_linenum = closing_linenum + current_line_fragment = closing_line[closing_pos:] + # Loop until EOF or find anything that's not whitespace or else clause. + while Search(r'^\s*$|^(?=\s*else)', current_line_fragment): + if Search(r'^(?=\s*else)', current_line_fragment): + # Found an else clause, so don't log an error. + return + current_linenum += 1 + if current_linenum == len(clean_lines.elided): + break + current_line_fragment = clean_lines.elided[current_linenum] + + # The body is empty and there's no else clause until EOF or other code. + error(filename, end_linenum, 'whitespace/empty_if_body', 4, + ('If statement had no body and no else clause')) + + +def FindCheckMacro(line): + """Find a replaceable CHECK-like macro. + + Args: + line: line to search on. + Returns: + (macro name, start position), or (None, -1) if no replaceable + macro is found. + """ + for macro in _CHECK_MACROS: + i = line.find(macro) + if i >= 0: + # Find opening parenthesis. Do a regular expression match here + # to make sure that we are matching the expected CHECK macro, as + # opposed to some other macro that happens to contain the CHECK + # substring. + matched = Match(r'^(.*\b' + macro + r'\s*)\(', line) + if not matched: + continue + return (macro, len(matched.group(1))) + return (None, -1) + + +def CheckCheck(filename, clean_lines, linenum, error): + """Checks the use of CHECK and EXPECT macros. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + # Decide the set of replacement macros that should be suggested + lines = clean_lines.elided + (check_macro, start_pos) = FindCheckMacro(lines[linenum]) + if not check_macro: + return + + # Find end of the boolean expression by matching parentheses + (last_line, end_line, end_pos) = CloseExpression( + clean_lines, linenum, start_pos) + if end_pos < 0: + return + + # If the check macro is followed by something other than a + # semicolon, assume users will log their own custom error messages + # and don't suggest any replacements. + if not Match(r'\s*;', last_line[end_pos:]): + return + + if linenum == end_line: + expression = lines[linenum][start_pos + 1:end_pos - 1] + else: + expression = lines[linenum][start_pos + 1:] + for i in xrange(linenum + 1, end_line): + expression += lines[i] + expression += last_line[0:end_pos - 1] + + # Parse expression so that we can take parentheses into account. + # This avoids false positives for inputs like "CHECK((a < 4) == b)", + # which is not replaceable by CHECK_LE. + lhs = '' + rhs = '' + operator = None + while expression: + matched = Match(r'^\s*(<<|<<=|>>|>>=|->\*|->|&&|\|\||' + r'==|!=|>=|>|<=|<|\()(.*)$', expression) + if matched: + token = matched.group(1) + if token == '(': + # Parenthesized operand + expression = matched.group(2) + (end, _) = FindEndOfExpressionInLine(expression, 0, ['(']) + if end < 0: + return # Unmatched parenthesis + lhs += '(' + expression[0:end] + expression = expression[end:] + elif token in ('&&', '||'): + # Logical and/or operators. This means the expression + # contains more than one term, for example: + # CHECK(42 < a && a < b); + # + # These are not replaceable with CHECK_LE, so bail out early. + return + elif token in ('<<', '<<=', '>>', '>>=', '->*', '->'): + # Non-relational operator + lhs += token + expression = matched.group(2) + else: + # Relational operator + operator = token + rhs = matched.group(2) + break + else: + # Unparenthesized operand. Instead of appending to lhs one character + # at a time, we do another regular expression match to consume several + # characters at once if possible. Trivial benchmark shows that this + # is more efficient when the operands are longer than a single + # character, which is generally the case. + matched = Match(r'^([^-=!<>()&|]+)(.*)$', expression) + if not matched: + matched = Match(r'^(\s*\S)(.*)$', expression) + if not matched: + break + lhs += matched.group(1) + expression = matched.group(2) + + # Only apply checks if we got all parts of the boolean expression + if not (lhs and operator and rhs): + return + + # Check that rhs do not contain logical operators. We already know + # that lhs is fine since the loop above parses out && and ||. + if rhs.find('&&') > -1 or rhs.find('||') > -1: + return + + # At least one of the operands must be a constant literal. This is + # to avoid suggesting replacements for unprintable things like + # CHECK(variable != iterator) + # + # The following pattern matches decimal, hex integers, strings, and + # characters (in that order). + lhs = lhs.strip() + rhs = rhs.strip() + match_constant = r'^([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')$' + if Match(match_constant, lhs) or Match(match_constant, rhs): + # Note: since we know both lhs and rhs, we can provide a more + # descriptive error message like: + # Consider using CHECK_EQ(x, 42) instead of CHECK(x == 42) + # Instead of: + # Consider using CHECK_EQ instead of CHECK(a == b) + # + # We are still keeping the less descriptive message because if lhs + # or rhs gets long, the error message might become unreadable. + error(filename, linenum, 'readability/check', 2, + 'Consider using %s instead of %s(a %s b)' % ( + _CHECK_REPLACEMENT[check_macro][operator], + check_macro, operator)) + + +def CheckAltTokens(filename, clean_lines, linenum, error): + """Check alternative keywords being used in boolean expressions. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Avoid preprocessor lines + if Match(r'^\s*#', line): + return + + # Last ditch effort to avoid multi-line comments. This will not help + # if the comment started before the current line or ended after the + # current line, but it catches most of the false positives. At least, + # it provides a way to workaround this warning for people who use + # multi-line comments in preprocessor macros. + # + # TODO(unknown): remove this once cpplint has better support for + # multi-line comments. + if line.find('/*') >= 0 or line.find('*/') >= 0: + return + + for match in _ALT_TOKEN_REPLACEMENT_PATTERN.finditer(line): + error(filename, linenum, 'readability/alt_tokens', 2, + 'Use operator %s instead of %s' % ( + _ALT_TOKEN_REPLACEMENT[match.group(1)], match.group(1))) + + +def GetLineWidth(line): + """Determines the width of the line in column positions. + + Args: + line: A string, which may be a Unicode string. + + Returns: + The width of the line in column positions, accounting for Unicode + combining characters and wide characters. + """ + if isinstance(line, unicode): + width = 0 + for uc in unicodedata.normalize('NFC', line): + if unicodedata.east_asian_width(uc) in ('W', 'F'): + width += 2 + elif not unicodedata.combining(uc): + width += 1 + return width + else: + return len(line) + + +def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, + error): + """Checks rules from the 'C++ style rules' section of cppguide.html. + + Most of these rules are hard to test (naming, comment style), but we + do what we can. In particular we check for 2-space indents, line lengths, + tab usage, spaces inside code, etc. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + file_extension: The extension (without the dot) of the filename. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + + # Don't use "elided" lines here, otherwise we can't check commented lines. + # Don't want to use "raw" either, because we don't want to check inside C++11 + # raw strings, + raw_lines = clean_lines.lines_without_raw_strings + line = raw_lines[linenum] + prev = raw_lines[linenum - 1] if linenum > 0 else '' + + if line.find('\t') != -1: + error(filename, linenum, 'whitespace/tab', 1, + 'Tab found; better to use spaces') + + # One or three blank spaces at the beginning of the line is weird; it's + # hard to reconcile that with 2-space indents. + # NOTE: here are the conditions rob pike used for his tests. Mine aren't + # as sophisticated, but it may be worth becoming so: RLENGTH==initial_spaces + # if(RLENGTH > 20) complain = 0; + # if(match($0, " +(error|private|public|protected):")) complain = 0; + # if(match(prev, "&& *$")) complain = 0; + # if(match(prev, "\\|\\| *$")) complain = 0; + # if(match(prev, "[\",=><] *$")) complain = 0; + # if(match($0, " <<")) complain = 0; + # if(match(prev, " +for \\(")) complain = 0; + # if(prevodd && match(prevprev, " +for \\(")) complain = 0; + scope_or_label_pattern = r'\s*\w+\s*:\s*\\?$' + classinfo = nesting_state.InnermostClass() + initial_spaces = 0 + cleansed_line = clean_lines.elided[linenum] + while initial_spaces < len(line) and line[initial_spaces] == ' ': + initial_spaces += 1 + # There are certain situations we allow one space, notably for + # section labels, and also lines containing multi-line raw strings. + # We also don't check for lines that look like continuation lines + # (of lines ending in double quotes, commas, equals, or angle brackets) + # because the rules for how to indent those are non-trivial. + if (not Search(r'[",=><] *$', prev) and + (initial_spaces == 1 or initial_spaces == 3) and + not Match(scope_or_label_pattern, cleansed_line) and + not (clean_lines.raw_lines[linenum] != line and + Match(r'^\s*""', line))): + error(filename, linenum, 'whitespace/indent', 3, + 'Weird number of spaces at line-start. ' + 'Are you using a 2-space indent?') + + if line and line[-1].isspace(): + error(filename, linenum, 'whitespace/end_of_line', 4, + 'Line ends in whitespace. Consider deleting these extra spaces.') + + # Check if the line is a header guard. + is_header_guard = False + if file_extension in GetHeaderExtensions(): + cppvar = GetHeaderGuardCPPVariable(filename) + if (line.startswith('#ifndef %s' % cppvar) or + line.startswith('#define %s' % cppvar) or + line.startswith('#endif // %s' % cppvar)): + is_header_guard = True + # #include lines and header guards can be long, since there's no clean way to + # split them. + # + # URLs can be long too. It's possible to split these, but it makes them + # harder to cut&paste. + # + # The "$Id:...$" comment may also get very long without it being the + # developers fault. + # + # Doxygen documentation copying can get pretty long when using an overloaded + # function declaration + if (not line.startswith('#include') and not is_header_guard and + not Match(r'^\s*//.*http(s?)://\S*$', line) and + not Match(r'^\s*//\s*[^\s]*$', line) and + not Match(r'^// \$Id:.*#[0-9]+ \$$', line) and + not Match(r'^\s*/// [@\\](copydoc|copydetails|copybrief) .*$', line)): + line_width = GetLineWidth(line) + if line_width > _line_length: + error(filename, linenum, 'whitespace/line_length', 2, + 'Lines should be <= %i characters long' % _line_length) + + if (cleansed_line.count(';') > 1 and + # allow simple single line lambdas + not Match(r'^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}\n\r]*\}', + line) and + # for loops are allowed two ;'s (and may run over two lines). + cleansed_line.find('for') == -1 and + (GetPreviousNonBlankLine(clean_lines, linenum)[0].find('for') == -1 or + GetPreviousNonBlankLine(clean_lines, linenum)[0].find(';') != -1) and + # It's ok to have many commands in a switch case that fits in 1 line + not ((cleansed_line.find('case ') != -1 or + cleansed_line.find('default:') != -1) and + cleansed_line.find('break;') != -1)): + error(filename, linenum, 'whitespace/newline', 0, + 'More than one command on the same line') + + # Some more style checks + CheckBraces(filename, clean_lines, linenum, error) + CheckTrailingSemicolon(filename, clean_lines, linenum, error) + CheckEmptyBlockBody(filename, clean_lines, linenum, error) + CheckAccess(filename, clean_lines, linenum, nesting_state, error) + CheckSpacing(filename, clean_lines, linenum, nesting_state, error) + CheckOperatorSpacing(filename, clean_lines, linenum, error) + CheckParenthesisSpacing(filename, clean_lines, linenum, error) + CheckCommaSpacing(filename, clean_lines, linenum, error) + CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error) + CheckSpacingForFunctionCall(filename, clean_lines, linenum, error) + CheckCheck(filename, clean_lines, linenum, error) + CheckAltTokens(filename, clean_lines, linenum, error) + classinfo = nesting_state.InnermostClass() + if classinfo: + CheckSectionSpacing(filename, clean_lines, classinfo, linenum, error) + + +_RE_PATTERN_INCLUDE = re.compile(r'^\s*#\s*include\s*([<"])([^>"]*)[>"].*$') +# Matches the first component of a filename delimited by -s and _s. That is: +# _RE_FIRST_COMPONENT.match('foo').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo.cc').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo-bar_baz.cc').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo_bar-baz.cc').group(0) == 'foo' +_RE_FIRST_COMPONENT = re.compile(r'^[^-_.]+') + + +def _DropCommonSuffixes(filename): + """Drops common suffixes like _test.cc or -inl.h from filename. + + For example: + >>> _DropCommonSuffixes('foo/foo-inl.h') + 'foo/foo' + >>> _DropCommonSuffixes('foo/bar/foo.cc') + 'foo/bar/foo' + >>> _DropCommonSuffixes('foo/foo_internal.h') + 'foo/foo' + >>> _DropCommonSuffixes('foo/foo_unusualinternal.h') + 'foo/foo_unusualinternal' + + Args: + filename: The input filename. + + Returns: + The filename with the common suffix removed. + """ + for suffix in itertools.chain( + ('%s.%s' % (test_suffix.lstrip('_'), ext) + for test_suffix, ext in itertools.product(_test_suffixes, GetNonHeaderExtensions())), + ('%s.%s' % (suffix, ext) + for suffix, ext in itertools.product(['inl', 'imp', 'internal'], GetHeaderExtensions()))): + if (filename.endswith(suffix) and len(filename) > len(suffix) and + filename[-len(suffix) - 1] in ('-', '_')): + return filename[:-len(suffix) - 1] + return os.path.splitext(filename)[0] + + +def _ClassifyInclude(fileinfo, include, is_system): + """Figures out what kind of header 'include' is. + + Args: + fileinfo: The current file cpplint is running over. A FileInfo instance. + include: The path to a #included file. + is_system: True if the #include used <> rather than "". + + Returns: + One of the _XXX_HEADER constants. + + For example: + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'stdio.h', True) + _C_SYS_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'string', True) + _CPP_SYS_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', False) + _LIKELY_MY_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo_unknown_extension.cc'), + ... 'bar/foo_other_ext.h', False) + _POSSIBLE_MY_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/bar.h', False) + _OTHER_HEADER + """ + # This is a list of all standard c++ header files, except + # those already checked for above. + is_cpp_h = include in _CPP_HEADERS + + # Headers with C++ extensions shouldn't be considered C system headers + if is_system and os.path.splitext(include)[1] in ['.hpp', '.hxx', '.h++']: + is_system = False + + if is_system: + if is_cpp_h: + return _CPP_SYS_HEADER + else: + return _C_SYS_HEADER + + # If the target file and the include we're checking share a + # basename when we drop common extensions, and the include + # lives in . , then it's likely to be owned by the target file. + target_dir, target_base = ( + os.path.split(_DropCommonSuffixes(fileinfo.RepositoryName()))) + include_dir, include_base = os.path.split(_DropCommonSuffixes(include)) + target_dir_pub = os.path.normpath(target_dir + '/../public') + target_dir_pub = target_dir_pub.replace('\\', '/') + if target_base == include_base and ( + include_dir == target_dir or + include_dir == target_dir_pub): + return _LIKELY_MY_HEADER + + # If the target and include share some initial basename + # component, it's possible the target is implementing the + # include, so it's allowed to be first, but we'll never + # complain if it's not there. + target_first_component = _RE_FIRST_COMPONENT.match(target_base) + include_first_component = _RE_FIRST_COMPONENT.match(include_base) + if (target_first_component and include_first_component and + target_first_component.group(0) == + include_first_component.group(0)): + return _POSSIBLE_MY_HEADER + + return _OTHER_HEADER + + + +def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): + """Check rules that are applicable to #include lines. + + Strings on #include lines are NOT removed from elided line, to make + certain tasks easier. However, to prevent false positives, checks + applicable to #include lines in CheckLanguage must be put here. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + include_state: An _IncludeState instance in which the headers are inserted. + error: The function to call with any errors found. + """ + fileinfo = FileInfo(filename) + line = clean_lines.lines[linenum] + + # "include" should use the new style "foo/bar.h" instead of just "bar.h" + # Only do this check if the included header follows google naming + # conventions. If not, assume that it's a 3rd party API that + # requires special include conventions. + # + # We also make an exception for Lua headers, which follow google + # naming convention but not the include convention. + match = Match(r'#include\s*"([^/]+\.h)"', line) + if match and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1)): + error(filename, linenum, 'build/include_subdir', 4, + 'Include the directory when naming .h files') + + # we shouldn't include a file more than once. actually, there are a + # handful of instances where doing so is okay, but in general it's + # not. + match = _RE_PATTERN_INCLUDE.search(line) + if match: + include = match.group(2) + is_system = (match.group(1) == '<') + duplicate_line = include_state.FindHeader(include) + if duplicate_line >= 0: + error(filename, linenum, 'build/include', 4, + '"%s" already included at %s:%s' % + (include, filename, duplicate_line)) + return + + for extension in GetNonHeaderExtensions(): + if (include.endswith('.' + extension) and + os.path.dirname(fileinfo.RepositoryName()) != os.path.dirname(include)): + error(filename, linenum, 'build/include', 4, + 'Do not include .' + extension + ' files from other packages') + return + + if not _THIRD_PARTY_HEADERS_PATTERN.match(include): + include_state.include_list[-1].append((include, linenum)) + + # We want to ensure that headers appear in the right order: + # 1) for foo.cc, foo.h (preferred location) + # 2) c system files + # 3) cpp system files + # 4) for foo.cc, foo.h (deprecated location) + # 5) other google headers + # + # We classify each include statement as one of those 5 types + # using a number of techniques. The include_state object keeps + # track of the highest type seen, and complains if we see a + # lower type after that. + error_message = include_state.CheckNextIncludeOrder( + _ClassifyInclude(fileinfo, include, is_system)) + if error_message: + error(filename, linenum, 'build/include_order', 4, + '%s. Should be: %s.h, c system, c++ system, other.' % + (error_message, fileinfo.BaseName())) + canonical_include = include_state.CanonicalizeAlphabeticalOrder(include) + if not include_state.IsInAlphabeticalOrder( + clean_lines, linenum, canonical_include): + error(filename, linenum, 'build/include_alpha', 4, + 'Include "%s" not in alphabetical order' % include) + include_state.SetLastHeader(canonical_include) + + + +def _GetTextInside(text, start_pattern): + r"""Retrieves all the text between matching open and close parentheses. + + Given a string of lines and a regular expression string, retrieve all the text + following the expression and between opening punctuation symbols like + (, [, or {, and the matching close-punctuation symbol. This properly nested + occurrences of the punctuations, so for the text like + printf(a(), b(c())); + a call to _GetTextInside(text, r'printf\(') will return 'a(), b(c())'. + start_pattern must match string having an open punctuation symbol at the end. + + Args: + text: The lines to extract text. Its comments and strings must be elided. + It can be single line and can span multiple lines. + start_pattern: The regexp string indicating where to start extracting + the text. + Returns: + The extracted text. + None if either the opening string or ending punctuation could not be found. + """ + # TODO(unknown): Audit cpplint.py to see what places could be profitably + # rewritten to use _GetTextInside (and use inferior regexp matching today). + + # Give opening punctuations to get the matching close-punctuations. + matching_punctuation = {'(': ')', '{': '}', '[': ']'} + closing_punctuation = set(itervalues(matching_punctuation)) + + # Find the position to start extracting text. + match = re.search(start_pattern, text, re.M) + if not match: # start_pattern not found in text. + return None + start_position = match.end(0) + + assert start_position > 0, ( + 'start_pattern must ends with an opening punctuation.') + assert text[start_position - 1] in matching_punctuation, ( + 'start_pattern must ends with an opening punctuation.') + # Stack of closing punctuations we expect to have in text after position. + punctuation_stack = [matching_punctuation[text[start_position - 1]]] + position = start_position + while punctuation_stack and position < len(text): + if text[position] == punctuation_stack[-1]: + punctuation_stack.pop() + elif text[position] in closing_punctuation: + # A closing punctuation without matching opening punctuations. + return None + elif text[position] in matching_punctuation: + punctuation_stack.append(matching_punctuation[text[position]]) + position += 1 + if punctuation_stack: + # Opening punctuations left without matching close-punctuations. + return None + # punctuations match. + return text[start_position:position - 1] + + +# Patterns for matching call-by-reference parameters. +# +# Supports nested templates up to 2 levels deep using this messy pattern: +# < (?: < (?: < [^<>]* +# > +# | [^<>] )* +# > +# | [^<>] )* +# > +_RE_PATTERN_IDENT = r'[_a-zA-Z]\w*' # =~ [[:alpha:]][[:alnum:]]* +_RE_PATTERN_TYPE = ( + r'(?:const\s+)?(?:typename\s+|class\s+|struct\s+|union\s+|enum\s+)?' + r'(?:\w|' + r'\s*<(?:<(?:<[^<>]*>|[^<>])*>|[^<>])*>|' + r'::)+') +# A call-by-reference parameter ends with '& identifier'. +_RE_PATTERN_REF_PARAM = re.compile( + r'(' + _RE_PATTERN_TYPE + r'(?:\s*(?:\bconst\b|[*]))*\s*' + r'&\s*' + _RE_PATTERN_IDENT + r')\s*(?:=[^,()]+)?[,)]') +# A call-by-const-reference parameter either ends with 'const& identifier' +# or looks like 'const type& identifier' when 'type' is atomic. +_RE_PATTERN_CONST_REF_PARAM = ( + r'(?:.*\s*\bconst\s*&\s*' + _RE_PATTERN_IDENT + + r'|const\s+' + _RE_PATTERN_TYPE + r'\s*&\s*' + _RE_PATTERN_IDENT + r')') +# Stream types. +_RE_PATTERN_REF_STREAM_PARAM = ( + r'(?:.*stream\s*&\s*' + _RE_PATTERN_IDENT + r')') + + +def CheckLanguage(filename, clean_lines, linenum, file_extension, + include_state, nesting_state, error): + """Checks rules from the 'C++ language rules' section of cppguide.html. + + Some of these rules are hard to test (function overloading, using + uint32 inappropriately), but we do the best we can. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + file_extension: The extension (without the dot) of the filename. + include_state: An _IncludeState instance in which the headers are inserted. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # If the line is empty or consists of entirely a comment, no need to + # check it. + line = clean_lines.elided[linenum] + if not line: + return + + match = _RE_PATTERN_INCLUDE.search(line) + if match: + CheckIncludeLine(filename, clean_lines, linenum, include_state, error) + return + + # Reset include state across preprocessor directives. This is meant + # to silence warnings for conditional includes. + match = Match(r'^\s*#\s*(if|ifdef|ifndef|elif|else|endif)\b', line) + if match: + include_state.ResetSection(match.group(1)) + + + # Perform other checks now that we are sure that this is not an include line + CheckCasts(filename, clean_lines, linenum, error) + CheckGlobalStatic(filename, clean_lines, linenum, error) + CheckPrintf(filename, clean_lines, linenum, error) + + if file_extension in GetHeaderExtensions(): + # TODO(unknown): check that 1-arg constructors are explicit. + # How to tell it's a constructor? + # (handled in CheckForNonStandardConstructs for now) + # TODO(unknown): check that classes declare or disable copy/assign + # (level 1 error) + pass + + # Check if people are using the verboten C basic types. The only exception + # we regularly allow is "unsigned short port" for port. + if Search(r'\bshort port\b', line): + if not Search(r'\bunsigned short port\b', line): + error(filename, linenum, 'runtime/int', 4, + 'Use "unsigned short" for ports, not "short"') + else: + match = Search(r'\b(short|long(?! +double)|long long)\b', line) + if match: + error(filename, linenum, 'runtime/int', 4, + 'Use int16/int64/etc, rather than the C type %s' % match.group(1)) + + # Check if some verboten operator overloading is going on + # TODO(unknown): catch out-of-line unary operator&: + # class X {}; + # int operator&(const X& x) { return 42; } // unary operator& + # The trick is it's hard to tell apart from binary operator&: + # class Y { int operator&(const Y& x) { return 23; } }; // binary operator& + if Search(r'\boperator\s*&\s*\(\s*\)', line): + error(filename, linenum, 'runtime/operator', 4, + 'Unary operator& is dangerous. Do not use it.') + + # Check for suspicious usage of "if" like + # } if (a == b) { + if Search(r'\}\s*if\s*\(', line): + error(filename, linenum, 'readability/braces', 4, + 'Did you mean "else if"? If not, start a new line for "if".') + + # Check for potential format string bugs like printf(foo). + # We constrain the pattern not to pick things like DocidForPrintf(foo). + # Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str()) + # TODO(unknown): Catch the following case. Need to change the calling + # convention of the whole function to process multiple line to handle it. + # printf( + # boy_this_is_a_really_long_variable_that_cannot_fit_on_the_prev_line); + printf_args = _GetTextInside(line, r'(?i)\b(string)?printf\s*\(') + if printf_args: + match = Match(r'([\w.\->()]+)$', printf_args) + if match and match.group(1) != '__VA_ARGS__': + function_name = re.search(r'\b((?:string)?printf)\s*\(', + line, re.I).group(1) + error(filename, linenum, 'runtime/printf', 4, + 'Potential format string bug. Do %s("%%s", %s) instead.' + % (function_name, match.group(1))) + + # Check for potential memset bugs like memset(buf, sizeof(buf), 0). + match = Search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line) + if match and not Match(r"^''|-?[0-9]+|0x[0-9A-Fa-f]$", match.group(2)): + error(filename, linenum, 'runtime/memset', 4, + 'Did you mean "memset(%s, 0, %s)"?' + % (match.group(1), match.group(2))) + + if Search(r'\busing namespace\b', line): + if Search(r'\bliterals\b', line): + error(filename, linenum, 'build/namespaces_literals', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') + else: + error(filename, linenum, 'build/namespaces', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') + + # Detect variable-length arrays. + match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) + if (match and match.group(2) != 'return' and match.group(2) != 'delete' and + match.group(3).find(']') == -1): + # Split the size using space and arithmetic operators as delimiters. + # If any of the resulting tokens are not compile time constants then + # report the error. + tokens = re.split(r'\s|\+|\-|\*|\/|<<|>>]', match.group(3)) + is_const = True + skip_next = False + for tok in tokens: + if skip_next: + skip_next = False + continue + + if Search(r'sizeof\(.+\)', tok): continue + if Search(r'arraysize\(\w+\)', tok): continue + + tok = tok.lstrip('(') + tok = tok.rstrip(')') + if not tok: continue + if Match(r'\d+', tok): continue + if Match(r'0[xX][0-9a-fA-F]+', tok): continue + if Match(r'k[A-Z0-9]\w*', tok): continue + if Match(r'(.+::)?k[A-Z0-9]\w*', tok): continue + if Match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): continue + # A catch all for tricky sizeof cases, including 'sizeof expression', + # 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)' + # requires skipping the next token because we split on ' ' and '*'. + if tok.startswith('sizeof'): + skip_next = True + continue + is_const = False + break + if not is_const: + error(filename, linenum, 'runtime/arrays', 1, + 'Do not use variable-length arrays. Use an appropriately named ' + "('k' followed by CamelCase) compile-time constant for the size.") + + # Check for use of unnamed namespaces in header files. Registration + # macros are typically OK, so we allow use of "namespace {" on lines + # that end with backslashes. + if (file_extension in GetHeaderExtensions() + and Search(r'\bnamespace\s*{', line) + and line[-1] != '\\'): + error(filename, linenum, 'build/namespaces', 4, + 'Do not use unnamed namespaces in header files. See ' + 'https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces' + ' for more information.') + + +def CheckGlobalStatic(filename, clean_lines, linenum, error): + """Check for unsafe global or static objects. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Match two lines at a time to support multiline declarations + if linenum + 1 < clean_lines.NumLines() and not Search(r'[;({]', line): + line += clean_lines.elided[linenum + 1].strip() + + # Check for people declaring static/global STL strings at the top level. + # This is dangerous because the C++ language does not guarantee that + # globals with constructors are initialized before the first access, and + # also because globals can be destroyed when some threads are still running. + # TODO(unknown): Generalize this to also find static unique_ptr instances. + # TODO(unknown): File bugs for clang-tidy to find these. + match = Match( + r'((?:|static +)(?:|const +))(?::*std::)?string( +const)? +' + r'([a-zA-Z0-9_:]+)\b(.*)', + line) + + # Remove false positives: + # - String pointers (as opposed to values). + # string *pointer + # const string *pointer + # string const *pointer + # string *const pointer + # + # - Functions and template specializations. + # string Function(... + # string Class::Method(... + # + # - Operators. These are matched separately because operator names + # cross non-word boundaries, and trying to match both operators + # and functions at the same time would decrease accuracy of + # matching identifiers. + # string Class::operator*() + if (match and + not Search(r'\bstring\b(\s+const)?\s*[\*\&]\s*(const\s+)?\w', line) and + not Search(r'\boperator\W', line) and + not Match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)*\s*\(([^"]|$)', match.group(4))): + if Search(r'\bconst\b', line): + error(filename, linenum, 'runtime/string', 4, + 'For a static/global string constant, use a C style string ' + 'instead: "%schar%s %s[]".' % + (match.group(1), match.group(2) or '', match.group(3))) + else: + error(filename, linenum, 'runtime/string', 4, + 'Static/global string variables are not permitted.') + + if (Search(r'\b([A-Za-z0-9_]*_)\(\1\)', line) or + Search(r'\b([A-Za-z0-9_]*_)\(CHECK_NOTNULL\(\1\)\)', line)): + error(filename, linenum, 'runtime/init', 4, + 'You seem to be initializing a member variable with itself.') + + +def CheckPrintf(filename, clean_lines, linenum, error): + """Check for printf related issues. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # When snprintf is used, the second argument shouldn't be a literal. + match = Search(r'snprintf\s*\(([^,]*),\s*([0-9]*)\s*,', line) + if match and match.group(2) != '0': + # If 2nd arg is zero, snprintf is used to calculate size. + error(filename, linenum, 'runtime/printf', 3, + 'If you can, use sizeof(%s) instead of %s as the 2nd arg ' + 'to snprintf.' % (match.group(1), match.group(2))) + + # Check if some verboten C functions are being used. + if Search(r'\bsprintf\s*\(', line): + error(filename, linenum, 'runtime/printf', 5, + 'Never use sprintf. Use snprintf instead.') + match = Search(r'\b(strcpy|strcat)\s*\(', line) + if match: + error(filename, linenum, 'runtime/printf', 4, + 'Almost always, snprintf is better than %s' % match.group(1)) + + +def IsDerivedFunction(clean_lines, linenum): + """Check if current line contains an inherited function. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line contains a function with "override" + virt-specifier. + """ + # Scan back a few lines for start of current function + for i in xrange(linenum, max(-1, linenum - 10), -1): + match = Match(r'^([^()]*\w+)\(', clean_lines.elided[i]) + if match: + # Look for "override" after the matching closing parenthesis + line, _, closing_paren = CloseExpression( + clean_lines, i, len(match.group(1))) + return (closing_paren >= 0 and + Search(r'\boverride\b', line[closing_paren:])) + return False + + +def IsOutOfLineMethodDefinition(clean_lines, linenum): + """Check if current line contains an out-of-line method definition. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line contains an out-of-line method definition. + """ + # Scan back a few lines for start of current function + for i in xrange(linenum, max(-1, linenum - 10), -1): + if Match(r'^([^()]*\w+)\(', clean_lines.elided[i]): + return Match(r'^[^()]*\w+::\w+\(', clean_lines.elided[i]) is not None + return False + + +def IsInitializerList(clean_lines, linenum): + """Check if current line is inside constructor initializer list. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line appears to be inside constructor initializer + list, False otherwise. + """ + for i in xrange(linenum, 1, -1): + line = clean_lines.elided[i] + if i == linenum: + remove_function_body = Match(r'^(.*)\{\s*$', line) + if remove_function_body: + line = remove_function_body.group(1) + + if Search(r'\s:\s*\w+[({]', line): + # A lone colon tend to indicate the start of a constructor + # initializer list. It could also be a ternary operator, which + # also tend to appear in constructor initializer lists as + # opposed to parameter lists. + return True + if Search(r'\}\s*,\s*$', line): + # A closing brace followed by a comma is probably the end of a + # brace-initialized member in constructor initializer list. + return True + if Search(r'[{};]\s*$', line): + # Found one of the following: + # - A closing brace or semicolon, probably the end of the previous + # function. + # - An opening brace, probably the start of current class or namespace. + # + # Current line is probably not inside an initializer list since + # we saw one of those things without seeing the starting colon. + return False + + # Got to the beginning of the file without seeing the start of + # constructor initializer list. + return False + + +def CheckForNonConstReference(filename, clean_lines, linenum, + nesting_state, error): + """Check for non-const references. + + Separate from CheckLanguage since it scans backwards from current + line, instead of scanning forward. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # Do nothing if there is no '&' on current line. + line = clean_lines.elided[linenum] + if '&' not in line: + return + + # If a function is inherited, current function doesn't have much of + # a choice, so any non-const references should not be blamed on + # derived function. + if IsDerivedFunction(clean_lines, linenum): + return + + # Don't warn on out-of-line method definitions, as we would warn on the + # in-line declaration, if it isn't marked with 'override'. + if IsOutOfLineMethodDefinition(clean_lines, linenum): + return + + # Long type names may be broken across multiple lines, usually in one + # of these forms: + # LongType + # ::LongTypeContinued &identifier + # LongType:: + # LongTypeContinued &identifier + # LongType< + # ...>::LongTypeContinued &identifier + # + # If we detected a type split across two lines, join the previous + # line to current line so that we can match const references + # accordingly. + # + # Note that this only scans back one line, since scanning back + # arbitrary number of lines would be expensive. If you have a type + # that spans more than 2 lines, please use a typedef. + if linenum > 1: + previous = None + if Match(r'\s*::(?:[\w<>]|::)+\s*&\s*\S', line): + # previous_line\n + ::current_line + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+[\w<>])\s*$', + clean_lines.elided[linenum - 1]) + elif Match(r'\s*[a-zA-Z_]([\w<>]|::)+\s*&\s*\S', line): + # previous_line::\n + current_line + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+::)\s*$', + clean_lines.elided[linenum - 1]) + if previous: + line = previous.group(1) + line.lstrip() + else: + # Check for templated parameter that is split across multiple lines + endpos = line.rfind('>') + if endpos > -1: + (_, startline, startpos) = ReverseCloseExpression( + clean_lines, linenum, endpos) + if startpos > -1 and startline < linenum: + # Found the matching < on an earlier line, collect all + # pieces up to current line. + line = '' + for i in xrange(startline, linenum + 1): + line += clean_lines.elided[i].strip() + + # Check for non-const references in function parameters. A single '&' may + # found in the following places: + # inside expression: binary & for bitwise AND + # inside expression: unary & for taking the address of something + # inside declarators: reference parameter + # We will exclude the first two cases by checking that we are not inside a + # function body, including one that was just introduced by a trailing '{'. + # TODO(unknown): Doesn't account for 'catch(Exception& e)' [rare]. + if (nesting_state.previous_stack_top and + not (isinstance(nesting_state.previous_stack_top, _ClassInfo) or + isinstance(nesting_state.previous_stack_top, _NamespaceInfo))): + # Not at toplevel, not within a class, and not within a namespace + return + + # Avoid initializer lists. We only need to scan back from the + # current line for something that starts with ':'. + # + # We don't need to check the current line, since the '&' would + # appear inside the second set of parentheses on the current line as + # opposed to the first set. + if linenum > 0: + for i in xrange(linenum - 1, max(0, linenum - 10), -1): + previous_line = clean_lines.elided[i] + if not Search(r'[),]\s*$', previous_line): + break + if Match(r'^\s*:\s+\S', previous_line): + return + + # Avoid preprocessors + if Search(r'\\\s*$', line): + return + + # Avoid constructor initializer lists + if IsInitializerList(clean_lines, linenum): + return + + # We allow non-const references in a few standard places, like functions + # called "swap()" or iostream operators like "<<" or ">>". Do not check + # those function parameters. + # + # We also accept & in static_assert, which looks like a function but + # it's actually a declaration expression. + whitelisted_functions = (r'(?:[sS]wap(?:<\w:+>)?|' + r'operator\s*[<>][<>]|' + r'static_assert|COMPILE_ASSERT' + r')\s*\(') + if Search(whitelisted_functions, line): + return + elif not Search(r'\S+\([^)]*$', line): + # Don't see a whitelisted function on this line. Actually we + # didn't see any function name on this line, so this is likely a + # multi-line parameter list. Try a bit harder to catch this case. + for i in xrange(2): + if (linenum > i and + Search(whitelisted_functions, clean_lines.elided[linenum - i - 1])): + return + + decls = ReplaceAll(r'{[^}]*}', ' ', line) # exclude function body + for parameter in re.findall(_RE_PATTERN_REF_PARAM, decls): + if (not Match(_RE_PATTERN_CONST_REF_PARAM, parameter) and + not Match(_RE_PATTERN_REF_STREAM_PARAM, parameter)): + error(filename, linenum, 'runtime/references', 2, + 'Is this a non-const reference? ' + 'If so, make const or use a pointer: ' + + ReplaceAll(' *<', '<', parameter)) + + +def CheckCasts(filename, clean_lines, linenum, error): + """Various cast related checks. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Check to see if they're using an conversion function cast. + # I just try to capture the most common basic types, though there are more. + # Parameterless conversion functions, such as bool(), are allowed as they are + # probably a member operator declaration or default constructor. + match = Search( + r'(\bnew\s+(?:const\s+)?|\S<\s*(?:const\s+)?)?\b' + r'(int|float|double|bool|char|int32|uint32|int64|uint64)' + r'(\([^)].*)', line) + expecting_function = ExpectingFunctionArgs(clean_lines, linenum) + if match and not expecting_function: + matched_type = match.group(2) + + # matched_new_or_template is used to silence two false positives: + # - New operators + # - Template arguments with function types + # + # For template arguments, we match on types immediately following + # an opening bracket without any spaces. This is a fast way to + # silence the common case where the function type is the first + # template argument. False negative with less-than comparison is + # avoided because those operators are usually followed by a space. + # + # function // bracket + no space = false positive + # value < double(42) // bracket + space = true positive + matched_new_or_template = match.group(1) + + # Avoid arrays by looking for brackets that come after the closing + # parenthesis. + if Match(r'\([^()]+\)\s*\[', match.group(3)): + return + + # Other things to ignore: + # - Function pointers + # - Casts to pointer types + # - Placement new + # - Alias declarations + matched_funcptr = match.group(3) + if (matched_new_or_template is None and + not (matched_funcptr and + (Match(r'\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(', + matched_funcptr) or + matched_funcptr.startswith('(*)'))) and + not Match(r'\s*using\s+\S+\s*=\s*' + matched_type, line) and + not Search(r'new\(\S+\)\s*' + matched_type, line)): + error(filename, linenum, 'readability/casting', 4, + 'Using deprecated casting style. ' + 'Use static_cast<%s>(...) instead' % + matched_type) + + if not expecting_function: + CheckCStyleCast(filename, clean_lines, linenum, 'static_cast', + r'\((int|float|double|bool|char|u?int(16|32|64))\)', error) + + # This doesn't catch all cases. Consider (const char * const)"hello". + # + # (char *) "foo" should always be a const_cast (reinterpret_cast won't + # compile). + if CheckCStyleCast(filename, clean_lines, linenum, 'const_cast', + r'\((char\s?\*+\s?)\)\s*"', error): + pass + else: + # Check pointer casts for other than string constants + CheckCStyleCast(filename, clean_lines, linenum, 'reinterpret_cast', + r'\((\w+\s?\*+\s?)\)', error) + + # In addition, we look for people taking the address of a cast. This + # is dangerous -- casts can assign to temporaries, so the pointer doesn't + # point where you think. + # + # Some non-identifier character is required before the '&' for the + # expression to be recognized as a cast. These are casts: + # expression = &static_cast(temporary()); + # function(&(int*)(temporary())); + # + # This is not a cast: + # reference_type&(int* function_param); + match = Search( + r'(?:[^\w]&\(([^)*][^)]*)\)[\w(])|' + r'(?:[^\w]&(static|dynamic|down|reinterpret)_cast\b)', line) + if match: + # Try a better error message when the & is bound to something + # dereferenced by the casted pointer, as opposed to the casted + # pointer itself. + parenthesis_error = False + match = Match(r'^(.*&(?:static|dynamic|down|reinterpret)_cast\b)<', line) + if match: + _, y1, x1 = CloseExpression(clean_lines, linenum, len(match.group(1))) + if x1 >= 0 and clean_lines.elided[y1][x1] == '(': + _, y2, x2 = CloseExpression(clean_lines, y1, x1) + if x2 >= 0: + extended_line = clean_lines.elided[y2][x2:] + if y2 < clean_lines.NumLines() - 1: + extended_line += clean_lines.elided[y2 + 1] + if Match(r'\s*(?:->|\[)', extended_line): + parenthesis_error = True + + if parenthesis_error: + error(filename, linenum, 'readability/casting', 4, + ('Are you taking an address of something dereferenced ' + 'from a cast? Wrapping the dereferenced expression in ' + 'parentheses will make the binding more obvious')) + else: + error(filename, linenum, 'runtime/casting', 4, + ('Are you taking an address of a cast? ' + 'This is dangerous: could be a temp var. ' + 'Take the address before doing the cast, rather than after')) + + +def CheckCStyleCast(filename, clean_lines, linenum, cast_type, pattern, error): + """Checks for a C-style cast by looking for the pattern. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + cast_type: The string for the C++ cast to recommend. This is either + reinterpret_cast, static_cast, or const_cast, depending. + pattern: The regular expression used to find C-style casts. + error: The function to call with any errors found. + + Returns: + True if an error was emitted. + False otherwise. + """ + line = clean_lines.elided[linenum] + match = Search(pattern, line) + if not match: + return False + + # Exclude lines with keywords that tend to look like casts + context = line[0:match.start(1) - 1] + if Match(r'.*\b(?:sizeof|alignof|alignas|[_A-Z][_A-Z0-9]*)\s*$', context): + return False + + # Try expanding current context to see if we one level of + # parentheses inside a macro. + if linenum > 0: + for i in xrange(linenum - 1, max(0, linenum - 5), -1): + context = clean_lines.elided[i] + context + if Match(r'.*\b[_A-Z][_A-Z0-9]*\s*\((?:\([^()]*\)|[^()])*$', context): + return False + + # operator++(int) and operator--(int) + if context.endswith(' operator++') or context.endswith(' operator--'): + return False + + # A single unnamed argument for a function tends to look like old style cast. + # If we see those, don't issue warnings for deprecated casts. + remainder = line[match.end(0):] + if Match(r'^\s*(?:;|const\b|throw\b|final\b|override\b|[=>{),]|->)', + remainder): + return False + + # At this point, all that should be left is actual casts. + error(filename, linenum, 'readability/casting', 4, + 'Using C-style cast. Use %s<%s>(...) instead' % + (cast_type, match.group(1))) + + return True + + +def ExpectingFunctionArgs(clean_lines, linenum): + """Checks whether where function type arguments are expected. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + + Returns: + True if the line at 'linenum' is inside something that expects arguments + of function types. + """ + line = clean_lines.elided[linenum] + return (Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or + (linenum >= 2 and + (Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\((?:\S+,)?\s*$', + clean_lines.elided[linenum - 1]) or + Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\(\s*$', + clean_lines.elided[linenum - 2]) or + Search(r'\bstd::m?function\s*\<\s*$', + clean_lines.elided[linenum - 1])))) + + +_HEADERS_CONTAINING_TEMPLATES = ( + ('', ('deque',)), + ('', ('unary_function', 'binary_function', + 'plus', 'minus', 'multiplies', 'divides', 'modulus', + 'negate', + 'equal_to', 'not_equal_to', 'greater', 'less', + 'greater_equal', 'less_equal', + 'logical_and', 'logical_or', 'logical_not', + 'unary_negate', 'not1', 'binary_negate', 'not2', + 'bind1st', 'bind2nd', + 'pointer_to_unary_function', + 'pointer_to_binary_function', + 'ptr_fun', + 'mem_fun_t', 'mem_fun', 'mem_fun1_t', 'mem_fun1_ref_t', + 'mem_fun_ref_t', + 'const_mem_fun_t', 'const_mem_fun1_t', + 'const_mem_fun_ref_t', 'const_mem_fun1_ref_t', + 'mem_fun_ref', + )), + ('', ('numeric_limits',)), + ('', ('list',)), + ('', ('map', 'multimap',)), + ('', ('allocator',)), + ('', ('queue', 'priority_queue',)), + ('', ('set', 'multiset',)), + ('', ('stack',)), + ('', ('char_traits', 'basic_string',)), + ('', ('tuple',)), + ('', ('pair',)), + ('', ('vector',)), + + # gcc extensions. + # Note: std::hash is their hash, ::hash is our hash + ('', ('hash_map', 'hash_multimap',)), + ('', ('hash_set', 'hash_multiset',)), + ('', ('slist',)), + ) + +_HEADERS_MAYBE_TEMPLATES = ( + ('', ('copy', 'max', 'min', 'min_element', 'sort', + 'transform', + )), + ('', ('swap',)), + ) + +_RE_PATTERN_STRING = re.compile(r'\bstring\b') + +_re_pattern_headers_maybe_templates = [] +for _header, _templates in _HEADERS_MAYBE_TEMPLATES: + for _template in _templates: + # Match max(..., ...), max(..., ...), but not foo->max, foo.max or + # type::max(). + _re_pattern_headers_maybe_templates.append( + (re.compile(r'[^>.]\b' + _template + r'(<.*?>)?\([^\)]'), + _template, + _header)) + +# Other scripts may reach in and modify this pattern. +_re_pattern_templates = [] +for _header, _templates in _HEADERS_CONTAINING_TEMPLATES: + for _template in _templates: + _re_pattern_templates.append( + (re.compile(r'(\<|\b)' + _template + r'\s*\<'), + _template + '<>', + _header)) + + +def FilesBelongToSameModule(filename_cc, filename_h): + """Check if these two filenames belong to the same module. + + The concept of a 'module' here is a as follows: + foo.h, foo-inl.h, foo.cc, foo_test.cc and foo_unittest.cc belong to the + same 'module' if they are in the same directory. + some/path/public/xyzzy and some/path/internal/xyzzy are also considered + to belong to the same module here. + + If the filename_cc contains a longer path than the filename_h, for example, + '/absolute/path/to/base/sysinfo.cc', and this file would include + 'base/sysinfo.h', this function also produces the prefix needed to open the + header. This is used by the caller of this function to more robustly open the + header file. We don't have access to the real include paths in this context, + so we need this guesswork here. + + Known bugs: tools/base/bar.cc and base/bar.h belong to the same module + according to this implementation. Because of this, this function gives + some false positives. This should be sufficiently rare in practice. + + Args: + filename_cc: is the path for the source (e.g. .cc) file + filename_h: is the path for the header path + + Returns: + Tuple with a bool and a string: + bool: True if filename_cc and filename_h belong to the same module. + string: the additional prefix needed to open the header file. + """ + fileinfo_cc = FileInfo(filename_cc) + if not fileinfo_cc.Extension().lstrip('.') in GetNonHeaderExtensions(): + return (False, '') + + fileinfo_h = FileInfo(filename_h) + if not fileinfo_h.Extension().lstrip('.') in GetHeaderExtensions(): + return (False, '') + + filename_cc = filename_cc[:-(len(fileinfo_cc.Extension()))] + matched_test_suffix = Search(_TEST_FILE_SUFFIX, fileinfo_cc.BaseName()) + if matched_test_suffix: + filename_cc = filename_cc[:-len(matched_test_suffix.group(1))] + + filename_cc = filename_cc.replace('/public/', '/') + filename_cc = filename_cc.replace('/internal/', '/') + + filename_h = filename_h[:-(len(fileinfo_h.Extension()))] + if filename_h.endswith('-inl'): + filename_h = filename_h[:-len('-inl')] + filename_h = filename_h.replace('/public/', '/') + filename_h = filename_h.replace('/internal/', '/') + + files_belong_to_same_module = filename_cc.endswith(filename_h) + common_path = '' + if files_belong_to_same_module: + common_path = filename_cc[:-len(filename_h)] + return files_belong_to_same_module, common_path + + +def UpdateIncludeState(filename, include_dict, io=codecs): + """Fill up the include_dict with new includes found from the file. + + Args: + filename: the name of the header to read. + include_dict: a dictionary in which the headers are inserted. + io: The io factory to use to read the file. Provided for testability. + + Returns: + True if a header was successfully added. False otherwise. + """ + headerfile = None + try: + headerfile = io.open(filename, 'r', 'utf8', 'replace') + except IOError: + return False + linenum = 0 + for line in headerfile: + linenum += 1 + clean_line = CleanseComments(line) + match = _RE_PATTERN_INCLUDE.search(clean_line) + if match: + include = match.group(2) + include_dict.setdefault(include, linenum) + return True + + +def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, + io=codecs): + """Reports for missing stl includes. + + This function will output warnings to make sure you are including the headers + necessary for the stl containers and functions that you use. We only give one + reason to include a header. For example, if you use both equal_to<> and + less<> in a .h file, only one (the latter in the file) of these will be + reported as a reason to include the . + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + include_state: An _IncludeState instance. + error: The function to call with any errors found. + io: The IO factory to use to read the header file. Provided for unittest + injection. + """ + required = {} # A map of header name to linenumber and the template entity. + # Example of required: { '': (1219, 'less<>') } + + for linenum in range(clean_lines.NumLines()): + line = clean_lines.elided[linenum] + if not line or line[0] == '#': + continue + + # String is special -- it is a non-templatized type in STL. + matched = _RE_PATTERN_STRING.search(line) + if matched: + # Don't warn about strings in non-STL namespaces: + # (We check only the first match per line; good enough.) + prefix = line[:matched.start()] + if prefix.endswith('std::') or not prefix.endswith('::'): + required[''] = (linenum, 'string') + + for pattern, template, header in _re_pattern_headers_maybe_templates: + if pattern.search(line): + required[header] = (linenum, template) + + # The following function is just a speed up, no semantics are changed. + if not '<' in line: # Reduces the cpu time usage by skipping lines. + continue + + for pattern, template, header in _re_pattern_templates: + if pattern.search(line): + required[header] = (linenum, template) + + # The policy is that if you #include something in foo.h you don't need to + # include it again in foo.cc. Here, we will look at possible includes. + # Let's flatten the include_state include_list and copy it into a dictionary. + include_dict = dict([item for sublist in include_state.include_list + for item in sublist]) + + # Did we find the header for this file (if any) and successfully load it? + header_found = False + + # Use the absolute path so that matching works properly. + abs_filename = FileInfo(filename).FullName() + + # For Emacs's flymake. + # If cpplint is invoked from Emacs's flymake, a temporary file is generated + # by flymake and that file name might end with '_flymake.cc'. In that case, + # restore original file name here so that the corresponding header file can be + # found. + # e.g. If the file name is 'foo_flymake.cc', we should search for 'foo.h' + # instead of 'foo_flymake.h' + abs_filename = re.sub(r'_flymake\.cc$', '.cc', abs_filename) + + # include_dict is modified during iteration, so we iterate over a copy of + # the keys. + header_keys = list(include_dict.keys()) + for header in header_keys: + (same_module, common_path) = FilesBelongToSameModule(abs_filename, header) + fullpath = common_path + header + if same_module and UpdateIncludeState(fullpath, include_dict, io): + header_found = True + + # If we can't find the header file for a .cc, assume it's because we don't + # know where to look. In that case we'll give up as we're not sure they + # didn't include it in the .h file. + # TODO(unknown): Do a better job of finding .h files so we are confident that + # not having the .h file means there isn't one. + if not header_found: + for extension in GetNonHeaderExtensions(): + if filename.endswith('.' + extension): + return + + # All the lines have been processed, report the errors found. + for required_header_unstripped in sorted(required, key=required.__getitem__): + template = required[required_header_unstripped][1] + if required_header_unstripped.strip('<>"') not in include_dict: + error(filename, required[required_header_unstripped][0], + 'build/include_what_you_use', 4, + 'Add #include ' + required_header_unstripped + ' for ' + template) + + +_RE_PATTERN_EXPLICIT_MAKEPAIR = re.compile(r'\bmake_pair\s*<') + + +def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error): + """Check that make_pair's template arguments are deduced. + + G++ 4.6 in C++11 mode fails badly if make_pair's template arguments are + specified explicitly, and such use isn't intended in any case. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + match = _RE_PATTERN_EXPLICIT_MAKEPAIR.search(line) + if match: + error(filename, linenum, 'build/explicit_make_pair', + 4, # 4 = high confidence + 'For C++11-compatibility, omit template arguments from make_pair' + ' OR use pair directly OR if appropriate, construct a pair directly') + + +def CheckRedundantVirtual(filename, clean_lines, linenum, error): + """Check if line contains a redundant "virtual" function-specifier. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Look for "virtual" on current line. + line = clean_lines.elided[linenum] + virtual = Match(r'^(.*)(\bvirtual\b)(.*)$', line) + if not virtual: return + + # Ignore "virtual" keywords that are near access-specifiers. These + # are only used in class base-specifier and do not apply to member + # functions. + if (Search(r'\b(public|protected|private)\s+$', virtual.group(1)) or + Match(r'^\s+(public|protected|private)\b', virtual.group(3))): + return + + # Ignore the "virtual" keyword from virtual base classes. Usually + # there is a column on the same line in these cases (virtual base + # classes are rare in google3 because multiple inheritance is rare). + if Match(r'^.*[^:]:[^:].*$', line): return + + # Look for the next opening parenthesis. This is the start of the + # parameter list (possibly on the next line shortly after virtual). + # TODO(unknown): doesn't work if there are virtual functions with + # decltype() or other things that use parentheses, but csearch suggests + # that this is rare. + end_col = -1 + end_line = -1 + start_col = len(virtual.group(2)) + for start_line in xrange(linenum, min(linenum + 3, clean_lines.NumLines())): + line = clean_lines.elided[start_line][start_col:] + parameter_list = Match(r'^([^(]*)\(', line) + if parameter_list: + # Match parentheses to find the end of the parameter list + (_, end_line, end_col) = CloseExpression( + clean_lines, start_line, start_col + len(parameter_list.group(1))) + break + start_col = 0 + + if end_col < 0: + return # Couldn't find end of parameter list, give up + + # Look for "override" or "final" after the parameter list + # (possibly on the next few lines). + for i in xrange(end_line, min(end_line + 3, clean_lines.NumLines())): + line = clean_lines.elided[i][end_col:] + match = Search(r'\b(override|final)\b', line) + if match: + error(filename, linenum, 'readability/inheritance', 4, + ('"virtual" is redundant since function is ' + 'already declared as "%s"' % match.group(1))) + + # Set end_col to check whole lines after we are done with the + # first line. + end_col = 0 + if Search(r'[^\w]\s*$', line): + break + + +def CheckRedundantOverrideOrFinal(filename, clean_lines, linenum, error): + """Check if line contains a redundant "override" or "final" virt-specifier. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Look for closing parenthesis nearby. We need one to confirm where + # the declarator ends and where the virt-specifier starts to avoid + # false positives. + line = clean_lines.elided[linenum] + declarator_end = line.rfind(')') + if declarator_end >= 0: + fragment = line[declarator_end:] + else: + if linenum > 1 and clean_lines.elided[linenum - 1].rfind(')') >= 0: + fragment = line + else: + return + + # Check that at most one of "override" or "final" is present, not both + if Search(r'\boverride\b', fragment) and Search(r'\bfinal\b', fragment): + error(filename, linenum, 'readability/inheritance', 4, + ('"override" is redundant since function is ' + 'already declared as "final"')) + + + + +# Returns true if we are at a new block, and it is directly +# inside of a namespace. +def IsBlockInNameSpace(nesting_state, is_forward_declaration): + """Checks that the new block is directly in a namespace. + + Args: + nesting_state: The _NestingState object that contains info about our state. + is_forward_declaration: If the class is a forward declared class. + Returns: + Whether or not the new block is directly in a namespace. + """ + if is_forward_declaration: + return len(nesting_state.stack) >= 1 and ( + isinstance(nesting_state.stack[-1], _NamespaceInfo)) + + + return (len(nesting_state.stack) > 1 and + nesting_state.stack[-1].check_namespace_indentation and + isinstance(nesting_state.stack[-2], _NamespaceInfo)) + + +def ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, + raw_lines_no_comments, linenum): + """This method determines if we should apply our namespace indentation check. + + Args: + nesting_state: The current nesting state. + is_namespace_indent_item: If we just put a new class on the stack, True. + If the top of the stack is not a class, or we did not recently + add the class, False. + raw_lines_no_comments: The lines without the comments. + linenum: The current line number we are processing. + + Returns: + True if we should apply our namespace indentation check. Currently, it + only works for classes and namespaces inside of a namespace. + """ + + is_forward_declaration = IsForwardClassDeclaration(raw_lines_no_comments, + linenum) + + if not (is_namespace_indent_item or is_forward_declaration): + return False + + # If we are in a macro, we do not want to check the namespace indentation. + if IsMacroDefinition(raw_lines_no_comments, linenum): + return False + + return IsBlockInNameSpace(nesting_state, is_forward_declaration) + + +# Call this method if the line is directly inside of a namespace. +# If the line above is blank (excluding comments) or the start of +# an inner namespace, it cannot be indented. +def CheckItemIndentationInNamespace(filename, raw_lines_no_comments, linenum, + error): + line = raw_lines_no_comments[linenum] + if Match(r'^\s+', line): + error(filename, linenum, 'runtime/indentation_namespace', 4, + 'Do not indent within a namespace') + + +def ProcessLine(filename, file_extension, clean_lines, line, + include_state, function_state, nesting_state, error, + extra_check_functions=None): + """Processes a single line in the file. + + Args: + filename: Filename of the file that is being processed. + file_extension: The extension (dot not included) of the file. + clean_lines: An array of strings, each representing a line of the file, + with comments stripped. + line: Number of line being processed. + include_state: An _IncludeState instance in which the headers are inserted. + function_state: A _FunctionState instance which counts function lines, etc. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: A callable to which errors are reported, which takes 4 arguments: + filename, line number, error level, and message + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + raw_lines = clean_lines.raw_lines + ParseNolintSuppressions(filename, raw_lines[line], line, error) + nesting_state.Update(filename, clean_lines, line, error) + CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, + error) + if nesting_state.InAsmBlock(): return + CheckForFunctionLengths(filename, clean_lines, line, function_state, error) + CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error) + CheckStyle(filename, clean_lines, line, file_extension, nesting_state, error) + CheckLanguage(filename, clean_lines, line, file_extension, include_state, + nesting_state, error) + CheckForNonConstReference(filename, clean_lines, line, nesting_state, error) + CheckForNonStandardConstructs(filename, clean_lines, line, + nesting_state, error) + CheckVlogArguments(filename, clean_lines, line, error) + CheckPosixThreading(filename, clean_lines, line, error) + CheckInvalidIncrement(filename, clean_lines, line, error) + CheckMakePairUsesDeduction(filename, clean_lines, line, error) + CheckRedundantVirtual(filename, clean_lines, line, error) + CheckRedundantOverrideOrFinal(filename, clean_lines, line, error) + if extra_check_functions: + for check_fn in extra_check_functions: + check_fn(filename, clean_lines, line, error) + +def FlagCxx11Features(filename, clean_lines, linenum, error): + """Flag those c++11 features that we only allow in certain places. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) + + # Flag unapproved C++ TR1 headers. + if include and include.group(1).startswith('tr1/'): + error(filename, linenum, 'build/c++tr1', 5, + ('C++ TR1 headers such as <%s> are unapproved.') % include.group(1)) + + # Flag unapproved C++11 headers. + if include and include.group(1) in ('cfenv', + 'condition_variable', + 'fenv.h', + 'future', + 'mutex', + 'thread', + 'chrono', + 'ratio', + 'regex', + 'system_error', + ): + error(filename, linenum, 'build/c++11', 5, + ('<%s> is an unapproved C++11 header.') % include.group(1)) + + # The only place where we need to worry about C++11 keywords and library + # features in preprocessor directives is in macro definitions. + if Match(r'\s*#', line) and not Match(r'\s*#\s*define\b', line): return + + # These are classes and free functions. The classes are always + # mentioned as std::*, but we only catch the free functions if + # they're not found by ADL. They're alphabetical by header. + for top_name in ( + # type_traits + 'alignment_of', + 'aligned_union', + ): + if Search(r'\bstd::%s\b' % top_name, line): + error(filename, linenum, 'build/c++11', 5, + ('std::%s is an unapproved C++11 class or function. Send c-style ' + 'an example of where it would make your code more readable, and ' + 'they may let you use it.') % top_name) + + +def FlagCxx14Features(filename, clean_lines, linenum, error): + """Flag those C++14 features that we restrict. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) + + # Flag unapproved C++14 headers. + if include and include.group(1) in ('scoped_allocator', 'shared_mutex'): + error(filename, linenum, 'build/c++14', 5, + ('<%s> is an unapproved C++14 header.') % include.group(1)) + + +def ProcessFileData(filename, file_extension, lines, error, + extra_check_functions=None): + """Performs lint checks and reports any errors to the given error function. + + Args: + filename: Filename of the file that is being processed. + file_extension: The extension (dot not included) of the file. + lines: An array of strings, each representing a line of the file, with the + last element being empty if the file is terminated with a newline. + error: A callable to which errors are reported, which takes 4 arguments: + filename, line number, error level, and message + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + lines = (['// marker so line numbers and indices both start at 1'] + lines + + ['// marker so line numbers end in a known way']) + + include_state = _IncludeState() + function_state = _FunctionState() + nesting_state = NestingState() + + ResetNolintSuppressions() + + CheckForCopyright(filename, lines, error) + ProcessGlobalSuppresions(lines) + RemoveMultiLineComments(filename, lines, error) + clean_lines = CleansedLines(lines) + + if file_extension in GetHeaderExtensions(): + CheckForHeaderGuard(filename, clean_lines, error) + + for line in range(clean_lines.NumLines()): + ProcessLine(filename, file_extension, clean_lines, line, + include_state, function_state, nesting_state, error, + extra_check_functions) + FlagCxx11Features(filename, clean_lines, line, error) + nesting_state.CheckCompletedBlocks(filename, error) + + CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) + + # Check that the .cc file has included its header if it exists. + if _IsSourceExtension(file_extension): + CheckHeaderFileIncluded(filename, include_state, error) + + # We check here rather than inside ProcessLine so that we see raw + # lines rather than "cleaned" lines. + CheckForBadCharacters(filename, lines, error) + + CheckForNewlineAtEOF(filename, lines, error) + +def ProcessConfigOverrides(filename): + """ Loads the configuration files and processes the config overrides. + + Args: + filename: The name of the file being processed by the linter. + + Returns: + False if the current |filename| should not be processed further. + """ + + abs_filename = os.path.abspath(filename) + cfg_filters = [] + keep_looking = True + while keep_looking: + abs_path, base_name = os.path.split(abs_filename) + if not base_name: + break # Reached the root directory. + + cfg_file = os.path.join(abs_path, "CPPLINT.cfg") + abs_filename = abs_path + if not os.path.isfile(cfg_file): + continue + + try: + with open(cfg_file) as file_handle: + for line in file_handle: + line, _, _ = line.partition('#') # Remove comments. + if not line.strip(): + continue + + name, _, val = line.partition('=') + name = name.strip() + val = val.strip() + if name == 'set noparent': + keep_looking = False + elif name == 'filter': + cfg_filters.append(val) + elif name == 'exclude_files': + # When matching exclude_files pattern, use the base_name of + # the current file name or the directory name we are processing. + # For example, if we are checking for lint errors in /foo/bar/baz.cc + # and we found the .cfg file at /foo/CPPLINT.cfg, then the config + # file's "exclude_files" filter is meant to be checked against "bar" + # and not "baz" nor "bar/baz.cc". + if base_name: + pattern = re.compile(val) + if pattern.match(base_name): + _cpplint_state.PrintInfo('Ignoring "%s": file excluded by ' + '"%s". File path component "%s" matches pattern "%s"\n' % + (filename, cfg_file, base_name, val)) + return False + elif name == 'linelength': + global _line_length + try: + _line_length = int(val) + except ValueError: + _cpplint_state.PrintError('Line length must be numeric.') + elif name == 'extensions': + global _valid_extensions + try: + extensions = [ext.strip() for ext in val.split(',')] + _valid_extensions = set(extensions) + except ValueError: + sys.stderr.write('Extensions should be a comma-separated list of values;' + 'for example: extensions=hpp,cpp\n' + 'This could not be parsed: "%s"' % (val,)) + elif name == 'headers': + global _header_extensions + try: + extensions = [ext.strip() for ext in val.split(',')] + _header_extensions = set(extensions) + except ValueError: + sys.stderr.write('Extensions should be a comma-separated list of values;' + 'for example: extensions=hpp,cpp\n' + 'This could not be parsed: "%s"' % (val,)) + elif name == 'root': + global _root + _root = val + else: + _cpplint_state.PrintError( + 'Invalid configuration option (%s) in file %s\n' % + (name, cfg_file)) + + except IOError: + _cpplint_state.PrintError( + "Skipping config file '%s': Can't open for reading\n" % cfg_file) + keep_looking = False + + # Apply all the accumulated filters in reverse order (top-level directory + # config options having the least priority). + for cfg_filter in reversed(cfg_filters): + _AddFilters(cfg_filter) + + return True + + +def ProcessFile(filename, vlevel, extra_check_functions=None): + """Does google-lint on a single file. + + Args: + filename: The name of the file to parse. + + vlevel: The level of errors to report. Every error of confidence + >= verbose_level will be reported. 0 is a good default. + + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + + _SetVerboseLevel(vlevel) + _BackupFilters() + + if not ProcessConfigOverrides(filename): + _RestoreFilters() + return + + lf_lines = [] + crlf_lines = [] + try: + # Support the UNIX convention of using "-" for stdin. Note that + # we are not opening the file with universal newline support + # (which codecs doesn't support anyway), so the resulting lines do + # contain trailing '\r' characters if we are reading a file that + # has CRLF endings. + # If after the split a trailing '\r' is present, it is removed + # below. + if filename == '-': + lines = codecs.StreamReaderWriter(sys.stdin, + codecs.getreader('utf8'), + codecs.getwriter('utf8'), + 'replace').read().split('\n') + else: + lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n') + + # Remove trailing '\r'. + # The -1 accounts for the extra trailing blank line we get from split() + for linenum in range(len(lines) - 1): + if lines[linenum].endswith('\r'): + lines[linenum] = lines[linenum].rstrip('\r') + crlf_lines.append(linenum + 1) + else: + lf_lines.append(linenum + 1) + + except IOError: + _cpplint_state.PrintError( + "Skipping input '%s': Can't open for reading\n" % filename) + _RestoreFilters() + return + + # Note, if no dot is found, this will give the entire filename as the ext. + file_extension = filename[filename.rfind('.') + 1:] + + # When reading from stdin, the extension is unknown, so no cpplint tests + # should rely on the extension. + if filename != '-' and file_extension not in GetAllExtensions(): + _cpplint_state.PrintError('Ignoring %s; not a valid file name ' + '(%s)\n' % (filename, ', '.join(GetAllExtensions()))) + else: + ProcessFileData(filename, file_extension, lines, Error, + extra_check_functions) + + # If end-of-line sequences are a mix of LF and CR-LF, issue + # warnings on the lines with CR. + # + # Don't issue any warnings if all lines are uniformly LF or CR-LF, + # since critique can handle these just fine, and the style guide + # doesn't dictate a particular end of line sequence. + # + # We can't depend on os.linesep to determine what the desired + # end-of-line sequence should be, since that will return the + # server-side end-of-line sequence. + if lf_lines and crlf_lines: + # Warn on every line with CR. An alternative approach might be to + # check whether the file is mostly CRLF or just LF, and warn on the + # minority, we bias toward LF here since most tools prefer LF. + for linenum in crlf_lines: + Error(filename, linenum, 'whitespace/newline', 1, + 'Unexpected \\r (^M) found; better to use only \\n') + + _cpplint_state.PrintInfo('Done processing %s\n' % filename) + _RestoreFilters() + + +def PrintUsage(message): + """Prints a brief usage string and exits, optionally with an error message. + + Args: + message: The optional error message. + """ + sys.stderr.write(_USAGE) + + if message: + sys.exit('\nFATAL ERROR: ' + message) + else: + sys.exit(0) + + +def PrintCategories(): + """Prints a list of all the error-categories used by error messages. + + These are the categories used to filter messages via --filter. + """ + sys.stderr.write(''.join(' %s\n' % cat for cat in _ERROR_CATEGORIES)) + sys.exit(0) + + +def ParseArguments(args): + """Parses the command line arguments. + + This may set the output format and verbosity level as side-effects. + + Args: + args: The command line arguments: + + Returns: + The list of filenames to lint. + """ + try: + (opts, filenames) = getopt.getopt(args, '', ['help', 'output=', 'verbose=', + 'counting=', + 'filter=', + 'root=', + 'repository=', + 'linelength=', + 'extensions=', + 'exclude=', + 'headers=', + 'quiet', + 'recursive']) + except getopt.GetoptError: + PrintUsage('Invalid arguments.') + + verbosity = _VerboseLevel() + output_format = _OutputFormat() + filters = '' + counting_style = '' + recursive = False + + for (opt, val) in opts: + if opt == '--help': + PrintUsage(None) + elif opt == '--output': + if val not in ('emacs', 'vs7', 'eclipse', 'junit'): + PrintUsage('The only allowed output formats are emacs, vs7, eclipse ' + 'and junit.') + output_format = val + elif opt == '--verbose': + verbosity = int(val) + elif opt == '--filter': + filters = val + if not filters: + PrintCategories() + elif opt == '--counting': + if val not in ('total', 'toplevel', 'detailed'): + PrintUsage('Valid counting options are total, toplevel, and detailed') + counting_style = val + elif opt == '--root': + global _root + _root = val + elif opt == '--repository': + global _repository + _repository = val + elif opt == '--linelength': + global _line_length + try: + _line_length = int(val) + except ValueError: + PrintUsage('Line length must be digits.') + elif opt == '--exclude': + global _excludes + if not _excludes: + _excludes = set() + _excludes.update(glob.glob(val)) + elif opt == '--extensions': + global _valid_extensions + try: + _valid_extensions = set(val.split(',')) + except ValueError: + PrintUsage('Extensions must be comma seperated list.') + elif opt == '--headers': + global _header_extensions + try: + _header_extensions = set(val.split(',')) + except ValueError: + PrintUsage('Extensions must be comma seperated list.') + elif opt == '--recursive': + recursive = True + elif opt == '--quiet': + global _quiet + _quiet = True + + if not filenames: + PrintUsage('No files were specified.') + + if recursive: + filenames = _ExpandDirectories(filenames) + + if _excludes: + filenames = _FilterExcludedFiles(filenames) + + _SetOutputFormat(output_format) + _SetVerboseLevel(verbosity) + _SetFilters(filters) + _SetCountingStyle(counting_style) + + return filenames + +def _ExpandDirectories(filenames): + """Searches a list of filenames and replaces directories in the list with + all files descending from those directories. Files with extensions not in + the valid extensions list are excluded. + + Args: + filenames: A list of files or directories + + Returns: + A list of all files that are members of filenames or descended from a + directory in filenames + """ + expanded = set() + for filename in filenames: + if not os.path.isdir(filename): + expanded.add(filename) + continue + + for root, _, files in os.walk(filename): + for loopfile in files: + fullname = os.path.join(root, loopfile) + if fullname.startswith('.' + os.path.sep): + fullname = fullname[len('.' + os.path.sep):] + expanded.add(fullname) + + filtered = [] + for filename in expanded: + if os.path.splitext(filename)[1][1:] in GetAllExtensions(): + filtered.append(filename) + + return filtered + +def _FilterExcludedFiles(filenames): + """Filters out files listed in the --exclude command line switch. File paths + in the switch are evaluated relative to the current working directory + """ + exclude_paths = [os.path.abspath(f) for f in _excludes] + return [f for f in filenames if os.path.abspath(f) not in exclude_paths] + +def main(): + filenames = ParseArguments(sys.argv[1:]) + backup_err = sys.stderr + try: + # Change stderr to write with replacement characters so we don't die + # if we try to print something containing non-ASCII characters. + sys.stderr = codecs.StreamReader(sys.stderr, 'replace') + + _cpplint_state.ResetErrorCounts() + for filename in filenames: + ProcessFile(filename, _cpplint_state.verbose_level) + _cpplint_state.PrintErrorCounts() + + if _cpplint_state.output_format == 'junit': + sys.stderr.write(_cpplint_state.FormatJUnitXML()) + + finally: + sys.stderr = backup_err + + sys.exit(_cpplint_state.error_count > 0) + + +if __name__ == '__main__': + main() diff --git a/class-repo-public/labs/lab01/3081Lab_GitBasics.md b/class-repo-public/labs/lab01/3081Lab_GitBasics.md new file mode 100644 index 0000000..269c1d7 --- /dev/null +++ b/class-repo-public/labs/lab01/3081Lab_GitBasics.md @@ -0,0 +1,163 @@ +# 3081 Lab01 GitHub Basics + +## What You Will Learn +1. Basic Linux commands, for example _mkdir_, _cd_, _mv_, and _ls_. +1. How to configure github for your cselabs account. +2. How to _clone_ (create a local copy of an existing repo), _pull_ (get updates), and _push_ (make updates). +3. How to configure the _.gitignore_ file so that _push_ ignores files not necessary to track. +4. The required directory structure and file nomenclature for course assignments. +5. How to initiate and read automated testing of course assignments. + +_For additional information about Linux, see http://www-users.cs.umn.edu/~larson/repo-website-resources/website/examples/csresources/linux.html_ + +### Configuring GitHub + +Below, a number of commands are shown that should be typed into the command line. These are preceded by a percent sign "%" which is NOT to be typed in. + +Log into your cselabs account. + +`% git --version` This will establish if it is installed. +``` +% git config --global user.name << "your name" >> +% git config --global user.email << your email address >> +% git config --global core.editor << your editor choice here >>. +``` + +Note that your name appears between double quotes since it has spaces in it. Your email address doesn't, so it doesn't need to be in quotes. If you would like `emacs -nw` as your editor (emacs such that it doesn't open a new window but opens in the terminal) then you'll want double quotes around that 2 word phrase. Also note that if you want to use gedit, you probably need to use `gedit -w -s`. + +`% git config --list` to see that Git has set this up correctly. + +### Cloning the Read-Only 3081 Course Repository + +Computer Science courses are setup for github by first establishing a GitHub organization (e.g. _umn-csci-3081F17_), which contains a repository for each student, as well as a read-only course repository (e.g. _class-repo-public_). Course materials will be distributed via the read-only repository. + +> It is probably a good idea to copy any content you will be using from the class repository, into your individual workspace. This is because as we add content, it might cause merge conflicts with files that you added. Keep in mind that the way that github is used in a class is very different from how you would use it at work. You would never have 150 people at work writing the same exact code and then trying to hide it from each other. As the semester progresses, we will point out some ways in which a repository for work differs from how it is used here. + +At the top level of your cselabs account or within a directory of your choice, create a 3081 directory with any name you prefer, then move to that directory. You will clone both the class and individual repo into this directory. + +``` +% (optional) cd +% mkdir 3081_F17 +% cd 3081_F17 +% git clone https://github.umn.edu/umn-csci-3081F17/class-repo-public +% ls +``` + +**_What just happened?_** You made a new directory (i.e. folder) in your account with `mkdir`. You changed that to your working directory with `cd`. You copied the class repo locally to your machine with `clone`. You listed the contents of the working directory with `ls`, which should list the newly created directory with the name of the repository which it contains. These directories function exactly like all other linux directories. There are hidden files that track changes to the repository. + +### Cloning and Configuring Your Student Course Repository + +Follow the same process and clone your individual repository. + +``` +% git clone https://github.umn.edu/umn-csci-3081F17/repo- +``` + +Next, move the source code for lab01 from the class repo to your individual repo. Where it has __, you **replace with your UMN userID**, e.g. _lars1050_, without the "<" and ">" symbols. + +``` +% cd repo- +$ ls -a +``` +**_What just happened?_** You made your class repo the working directory with `cd`. You displayed (i.e. listed) the contents of the working directory with `ls`, including all hidden files with `-a`. Notice the directory _.git_, which is how files are tracked. This is contained at the top level of the repository. If you want to look inside that directory type `% ls .git`. + +> It is **very important** that you never modify, delete, or move this directory, AND never copy it from one repo to another, as that will really mess things up. + +``` +% mkdir labs +% cp -r ../class-repo-public/lab01 labs/. +% ls -a +``` +**_What just happened?_** You created a new directory with `mkdir` that will contain folders with all labs for this course. You copied the entire directory (i.e. folder) _lab01_ contained in the class repo into the _labs_ directory of your individual repo, maintaining the name _lab01_ with the use of `labs/.`. Notice the `../class-repo-public`. The `..` means to navigate up one level in the directory structure. The single `.` means look inside the current directory. The list of content for this directory does not contain the _.git_ directory. + +### Executing Lab Code and Pushing Results + +The code provided for lab01 will create an executable by compiling the provided C++ files using a _makefile_. Running the executable will generate 2 files that will be added to your lab01 directory. A _makefile_ manages the compilation process, which will be explored further in next week's lab. + +``` +% cd labs/lab01 +% make +% ls +``` + +**_What just happened?_** You made _lab01_ your working directory with `cd`. You executed the makefile (named _makefile_) with `make`, which created object files with the extension _.o_, and the executable _lab01.out_. These files are displayed with `ls`. + +``` +% ./lab01.out +% ls +``` +**_What just happened?_** You ran the executable with `./lab01.out`, which generated 2 files _private.pvt_ and _shared.md_. In the next part of this lab, you will setup the _.gitignore_ file so that the former file is not added to the repo, but the latter is. + +Before telling git what not to track, look at what it has been tracking. + +``` +% git status +``` + +You will see the directories and files that have been added to this repository. + +### Configuring and Using _.gitignore_ + +There are a lot of files that should not be tracked in the repository. You typically don't want object and executable files included in your repo, because these are consequences of the local compilation. There are common files or libraries that should not be included, because it is wasteful to have multiple copies and to spend bandwidth moving them around. There might also be files that are for you only, such as notes about the project or todo lists. The _.gitignore_ file stores file extensions that serve as a filter for git. Any file with that extension will not be tracked. + +Navigate back to the top level of your individual repository, confirmiing you are in the right place. + +``` +% cd ../.. +% pwd +``` + +If you are in the right place, using your favorite text editor, create a .gitignore file, e.g. `xemacs .gitignore &`, which will open xemacs in a new window. Edit the .gitignore file to remove your executable and build folders. + +Add these lines to the file: +``` +# Extensions to not include in repo +*.o +*.out +*.pvt +``` + +Save the file. + +**What just happened?** You added a comment by prefacing the first line with `#`. You indicated to not track any and all files with the use of `*` that has a specific file extension (e.g. `*.o`). If you have a one-off, you can include that specific file. You can also add a directory name, which will ignore all contents of the directory. + +Look at the results: +``` +% git status +``` + +Now you will see that the indicated files in _.gitignore_ are no longer tracked. + +### Add Changes to Repo both Locally and on the Server + +You need to _stage_ all changes to the repository, which prepares those items to be permanently part of the repository. When you _commit_ those changes, they are saved to your local repository, which lives in your cselabs account (or your personal computer if that is what you are working on). When you _push_ those changes, they will be copied to the repo on the server. + +``` +% git add * +% git commit -m "Adding lab01 results." +% git push +``` + +**What just happened?** All of the tracked changes were stages with `git add *`. You could have only staged certain files by replacing _*_ with the filename. Note that `add` does not mean you are adding a new file or a new directory, it means you are adding a change (any change). Those staged changes were committed to your local repository and tagged with the message that follows `-m`, then pushed to the server. + +>Always use good comments in the commit. If you need to restore a previous state of the repo that is several commits back, you will have a much easier time figuring out which commit is relevant when you use comments like _"adding robot class definition"_, as opposed to _"more class stuff"_. + +>Always verify that the changes have been uploaded to the server by looking at the repo through the web interface of github. + +### Reading the Feedback + +Pushing to the server triggers the automated grading system. Soon your repo will contain a feedback file, letting you know if you have passed all tests of the assignment. These tests will make sure you have the right directory structure, filenames, and that everything is behaving as indicated in the requirements. + +Watch your github account via the web interface to see when the feedback file is ready (hit refresh to check contents). You can look at it through the web, but it is important to pull in that file to your local repo, so as not to cause merge conflicts. + +``` +% git pull +% cd labs/lab01 +% ls +``` + +> ALWAYS, ALWAYS, ALWAYS perform a _pull_ before making changes to a repository. Each time you sit down to work on a lab or project iteration, it would be an excellent habit to perform a _pull_ on both the class repo and your individual repo. + +THIS LAB IS COMPLETE. + +Congratulations! diff --git a/class-repo-public/labs/lab01/main.cpp b/class-repo-public/labs/lab01/main.cpp new file mode 100644 index 0000000..e0fbcdf --- /dev/null +++ b/class-repo-public/labs/lab01/main.cpp @@ -0,0 +1,67 @@ +// adapted from http://umich.edu/~eecs381/handouts/filestreams.pdf + +// This follows the Google Style Guide: +// https://google.github.io/styleguide/cppguide.html +// Style correctness verified by cpplint (pip install cpplint) + +// Copyright 2017 " + +#include +#include +#include + +int main() { + + // instantiate an output stream object with filename "private.pvt" + // If the file exists, it will be deleted and recreated. + std::string fname = "private.pvt"; + std::ofstream privateFile(fname.c_str()); + if (!privateFile.is_open()) { + // Something went wrong - report and abort. + std::cout << "ERROR: output file " << fname << " could not be opened." + << std::endl; + return 1; + } + + // Populate the file + privateFile << "The list of things I need to do" << std::endl; + privateFile << std::endl; + for (int i = 1; i < 11; i++) { + privateFile << "Do item #" << i << "." << std::endl; + } + + privateFile.close(); + + // instantiate an output stream object with filename "shared.md" + // If the file exists, it will be deleted and recreated. + fname = "shared.md"; + std::ofstream sharedFile(fname.c_str()); + if ( !sharedFile.is_open() ) { + // Something went wrong. Report and Abort. + std::cout << "ERROR: output file " << fname << " could not be opened." + << std::endl; + return 1; + } + + // Populate the file + sharedFile << + "# This is a heading for documentation of my lab." << std::endl << + "Lab01 Github Basics creates two files." << std::endl << + "The private file will not be tracked via git, but this will." << std::endl; + sharedFile << std::endl << + "If you want to learn more about Markdown, which is an " << std::endl << + "html-like language for creating the readme.md file that " << std::endl << + "every repo should have and probably most directories too," << std::endl << + "see " << + "https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet"; + sharedFile << std::endl; + + sharedFile.close(); + + // Report to the user all was successful + std::cout << + "The private.pvt and shared.md files should now be in your directory!"; + std::cout << std::endl; + + return 0; +} diff --git a/class-repo-public/labs/lab01/makefile b/class-repo-public/labs/lab01/makefile new file mode 100644 index 0000000..0b18f5d --- /dev/null +++ b/class-repo-public/labs/lab01/makefile @@ -0,0 +1,15 @@ +# CSCI3081 Lab01 makefile + +CC = g++ +DEBUG = -g +CFLAGS = -Wall -c $(DEBUG) +LFLAGS = -Wall $(DEBUG) + +all: main.o + $(CC) $(LFLAGS) main.o -o lab01.out + +main.o : main.cpp + $(CC) $(CFLAGS) main.cpp + +clean: + \rm *.o *.*~ lab01.out diff --git a/class-repo-public/labs/lab02/.gitignore b/class-repo-public/labs/lab02/.gitignore new file mode 100644 index 0000000..e2ebb55 --- /dev/null +++ b/class-repo-public/labs/lab02/.gitignore @@ -0,0 +1 @@ +duck \ No newline at end of file diff --git a/class-repo-public/labs/lab02/CompilingClasses.md b/class-repo-public/labs/lab02/CompilingClasses.md new file mode 100644 index 0000000..b4351ce --- /dev/null +++ b/class-repo-public/labs/lab02/CompilingClasses.md @@ -0,0 +1,190 @@ +## Lab 2 - Compiling and Linking + +> A reminder that in Dr. Larson's section, attendance is required for you to receive credit for the lab. + +In this lab, you are going to learn how to compile the source code and link the executable. The source code is in .cpp and .h files. The object code is in the .o files. Executables are either .out or have no extension. + +In addition to practicing compilation, you will define classes and subclasses. In class exercises, we have demonstrated the basics on class definitions. The syntax of a class and subclass definition is also provided within this document. + +Deliverables: +- main.cpp +- DecoyDuck.cpp +- DecoyDuck.h + +In this lab, you will write and edit some C++ code, build it, and run it. Please follow the instructions below. The given source code will help you get started. + +### Getting Started and Words of Advice + +The starting code base for this lab exercise is available in the class repo: +https://github.umn.edu/umn-csci-3081F17/class-repo-public. You do not want to edit within the class repo or it will cause merge conflicts. Copy the code to your +individual repo and work within that. Please follow the same directory structure +such that lab02 is within the labs folder. + +At each push, an automatic feedback file will be pushed to your repo. +> ** Don't forget to _pull_ the feedback before making more edits to your repo or you will cause merge conflicts. ** + +The automated feedback encompasses a majority, if not all, the tests that will be used to grade your lab. Please make sure you review the feedback from your final submission to ensure it passes all the tests. Minor fixable errors can result in 0 points for the lab because automated grading fails. You are responsible for ensuring your code passes all tests in the feedback. + +> ** We will not reconsider your lab grade after the due date if the feedback file gave you information that you ignored. ** + +Text editors designed for coding are essential. There are many good options available for free and on the cselabs machines. Instructions below use emacs, but +feel free to use your favorite. + +### Step 1: Update Your Repositories + + _If you did not do last week's lab, refer to lab01 for directions on how to set up git for both the class repo and your individual repo._ + +1. Open a terminal window. +3. Navigate to the directory of your 3081 class repo. +1. `pull` to get lab02 base code +1. Copy the directory into your _labs_ directory in your individual class repo. + + ``` mv labs/lab02 /labs/. + ``` + +1. Navigate into the lab02 directory in your individual repo: + +#### Step 2: Compile the Source Code + +1. Compile the .cpp files into object files. + ```  + % g++ -c -o main.o main.cpp + % g++ -c -o Duck.o Duck.cpp + % g++ -c -o MallardDuck.o MallardDuck.cpp + % g++ -c -o RubberDuck.o RubberDuck.cpp + ``` + ** What does all this mean?** + - `g++` : call to the gnu c++ compiler + - `-c` : a flag to indicate compilation, as opposed to linking + - `-o` : output the results of compilation into the file following this flag + - `main.cpp` : the last file is the one to be compiled + + _If `-c` is not provided, the compiler will attempt to compile and link. If `-o` with an output file is not provided, the compiler will use the name of the .cpp file for the .o filename._ + + Notice that each file is independently compiled, even though there are interdependencies among them. If you look in the .cpp file, there will be a `#include file.h` statement which essentially inserts the text of at the location of the #include statement. Compilation will create symbol tables for all classes, variables, functions declared within those header files, but with no associated address for the definition of those elements. When the files are _linked_, those dependencies will be resolved and the definitions of the various objects will be included in the complete program. + +### Step 3: Linking to Generate the Executable + +1. Link the object files into an executable:  + + ``` + % g++ -o duck main.o Duck.o MallardDuck.o RubberDuck.o + ``` + + ** What does all this mean?** + - `g++` : call to the gnu c++ compiler + - `-o` : output the executable into the file following this flag + - list of files : all the files that need to be linked. + + _Notice there was no need to tell the compiler that we are in the linking phase. If `-o` with an output file is not provided, the compiler will create the executable `a.out`._ + +1. Run the program:  + + ```% ./duck``` + +_HINT: Instead of typing all the filenames, for a shorthand_ + +```g++ -o duck *.o``` + +### Step 4: Review Class Syntax + +First, let's review the syntax for class definitions in C++. There is a lot going on this simple class definition for the interface of a generic duck from which we will inherit other classes. +``` +#ifndef LAB2_DUCK_H_ +#define LAB2_DUCK_H_ +... +#endif +``` + +This serves as a guard for the header file. If there are interdependencies among files such that the compiler is instructed to include this several times, this safeguard communicates to the compiler that this code has already been incorporated. It makes compilation more efficient, and when you have circular references and declarations, it is necessary. + +##### Duck.h + +```C++ +class Duck { +public: + Duck(); + virtual void performQuack(); + virtual std::string getDescription(); +}; +``` + +- `Duck() is the constructor with no passed parameters.` +- 'virtual' indicates this can be overwritten by the subclass. When we study polymorphism, the need for `virtual` will become clearer. +- `public:` gives direct access to members and methods outside the class. +- '};' : Don't forget to conclude your definition + + +##### Duck.cpp + +```C++ +#include "Duck.h" +``` +Don't forget to include the Duck.h file - of course the compiler will let you know if you forget it when it tries to search for the _Duck_ class for inheritance. + +```C++ +class RubberDuck : public Duck { +public: + RubberDuck(); + + std::string getDescription(); + void performQuack(); +}; +``` +- `class RubberDuck : public Duck` is the declaration of a new class RubberDuck that inherits from the class Duck. `public` indicates that RubberDuck will have full access to those elements in the Duck class. +- Notice that all methods of the Duck class are restated here because they will all be overwritten by RubberDuck. Definitions of these functions are in the `RubberDuck.cpp` file. + +##### main.cpp + +```C++ +#include "MallardDuck.h" +#include "RubberDuck.h" +``` +Notice that we are not including Duck.h. Can you guess why we don't need it, even though we are referencing it below? + +```C++ +using std::cout; +using std::endl; + +int main() { + + // Create Ducks + Duck jane; + MallardDuck maloy; + RubberDuck lemon; +``` + +These are the instantiations of the different types of ducks. The beauty of subclasses comes into play when we can treat all types of ducks identically, so that the code chooses the appropriate method for the given type. This is polymorphism, which requires knowledge of pointers and dynamic allocation - soon to be covered. + +### Step 5: Create a New Class DecoyDuck + +Your task is to add another duck subclass to this simulator, namely the decoy duck. Here is the specification: + +
      +
    • Name the class DecoyDuck +
    • The description is "a plastic decoy duck" +
    • It is silent, thus it says '...' +
+ +1. Create both a DecoyDuck.h and DecoyDuck.cpp file in your favorite editor (e.g. `atom& DecoyDuck.h`). +1. Define the class as a subclass of _Duck_ +2. Declare the needed methods in the .h file as described in the specification. +3. Define the needed methods in the .cpp file. +4. Modify _main.cpp_ to include a DecoyDuck in the simulation. + + >Note that you do not need to recompile any of the other files. It is only the linking process that must be redone. This makes compilation far more efficient, which doesn't seem like much of a time savings in our little program, but imagine a project in which the compilation takes hours! + +1. Compile your new duck and link it all together. + +### Step 6 : Add MysteryDuck class. + +Create _MysteryDuck_ class that inherits from _Duck_. Create a constructor in which the user passes in the description and sound. Add this to _main.cpp_. Compile and link as before. Currently, Duck does not have any members. You will need variables of type string in which to save the description and sound, then use in the methods. Don't forget to hide your members from objects outside the class. + + +### Step 7 : Push to the repository + +``` +git add * +git commit -m “adding decoy and mystery duck classes” +git push +``` diff --git a/class-repo-public/labs/lab02/Duck.cpp b/class-repo-public/labs/lab02/Duck.cpp new file mode 100644 index 0000000..761a881 --- /dev/null +++ b/class-repo-public/labs/lab02/Duck.cpp @@ -0,0 +1,25 @@ +// +// Duck.cpp +// CSCI3081-Lab1 +// +// Created by Sarit Ghildayal on 1/24/15. +// Copyright (c) 2015 Sarit Ghildayal. All rights reserved. +// + +#include +#include "Duck.h" + +using std::cout; +using std::endl; + +Duck::Duck() { +} + +void Duck::performQuack() { + cout << "QUACK!!" << endl; +} + +std::string Duck::getDescription() { + std::string descriptionText = "a plain duck"; + return descriptionText; +} diff --git a/class-repo-public/labs/lab02/Duck.h b/class-repo-public/labs/lab02/Duck.h new file mode 100644 index 0000000..7084ff4 --- /dev/null +++ b/class-repo-public/labs/lab02/Duck.h @@ -0,0 +1,21 @@ +// +// Duck.h +// CSCI3081-Lab1 +// +// Created by Sarit Ghildayal on 1/24/15. +// Copyright (c) 2015 Sarit Ghildayal. All rights reserved. +// + +#ifndef LAB02_DUCK_H_ +#define LAB02_DUCK_H_ + +#include + +class Duck { +public: + Duck(); + virtual void performQuack(); + virtual std::string getDescription(); +}; + +#endif diff --git a/class-repo-public/labs/lab02/MallardDuck.cpp b/class-repo-public/labs/lab02/MallardDuck.cpp new file mode 100644 index 0000000..59fc088 --- /dev/null +++ b/class-repo-public/labs/lab02/MallardDuck.cpp @@ -0,0 +1,23 @@ +// +// MallardDuck.cpp +// CSCI3081-Lab1 +// +// Created by Sarit Ghildayal on 1/24/15. +// Copyright (c) 2015 Sarit Ghildayal. All rights reserved. +// + +#include +#include +#include "MallardDuck.h" + +using std::cout; +using std::endl; +using std::string; + +MallardDuck::MallardDuck() : Duck() { +} + +string MallardDuck::getDescription() { + string descriptionText = "a mallard duck"; + return descriptionText; +} diff --git a/class-repo-public/labs/lab02/MallardDuck.h b/class-repo-public/labs/lab02/MallardDuck.h new file mode 100644 index 0000000..824f1d1 --- /dev/null +++ b/class-repo-public/labs/lab02/MallardDuck.h @@ -0,0 +1,21 @@ +// +// MallardDuck.h +// CSCI3081-Lab1 +// +// Created by Sarit Ghildayal on 1/24/15. +// Copyright (c) 2015 Sarit Ghildayal. All rights reserved. +// + +#ifndef LAB02_MALLARDDUCK_H_ +#define LAB02_MALLARDDUCK_H_ + +#include +#include "Duck.h" + +class MallardDuck : public Duck { +public: + MallardDuck(); + std::string getDescription(); +}; + +#endif diff --git a/class-repo-public/labs/lab02/RubberDuck.cpp b/class-repo-public/labs/lab02/RubberDuck.cpp new file mode 100644 index 0000000..d5daaec --- /dev/null +++ b/class-repo-public/labs/lab02/RubberDuck.cpp @@ -0,0 +1,27 @@ +// +// RubberDuck.cpp +// CSCI3081-Lab1 +// +// Created by Seth Johnson on 1/24/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#include +#include +#include "RubberDuck.h" + +using std::cout; +using std::endl; +using std::string; + +RubberDuck::RubberDuck(){ +} + +string RubberDuck::getDescription() { + string descriptionText = "a yellow rubber ducky"; + return descriptionText; +} + +void RubberDuck::performQuack() { + cout << "SQUEAK!!" << endl; +} diff --git a/class-repo-public/labs/lab02/RubberDuck.h b/class-repo-public/labs/lab02/RubberDuck.h new file mode 100644 index 0000000..11b906b --- /dev/null +++ b/class-repo-public/labs/lab02/RubberDuck.h @@ -0,0 +1,22 @@ +// +// RubberDuck.h +// CSCI3081-Lab1 +// +// Created by Seth Johnson on 1/24/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#ifndef LAB02_RUBBERDUCK_H_ +#define LAB02_RUBBERDUCK_H_ + +#include +#include "Duck.h" + +class RubberDuck : public Duck { +public: + RubberDuck(); + std::string getDescription(); + void performQuack(); +}; + +#endif diff --git a/class-repo-public/labs/lab02/ducks b/class-repo-public/labs/lab02/ducks new file mode 100755 index 0000000..3cb189c Binary files /dev/null and b/class-repo-public/labs/lab02/ducks differ diff --git a/class-repo-public/labs/lab02/main.cpp b/class-repo-public/labs/lab02/main.cpp new file mode 100644 index 0000000..2c9fbcc --- /dev/null +++ b/class-repo-public/labs/lab02/main.cpp @@ -0,0 +1,57 @@ +// +// main.cpp +// CSCI3081-Lab1 +// +// Created by Sarit Ghildayal on 1/24/15. +// Copyright (c) 2015 Sarit Ghildayal. All rights reserved. +// + +#include +#include "MallardDuck.h" +#include "RubberDuck.h" + +using std::cout; +using std::endl; + +int main() { + + // Create Ducks + Duck jane; + MallardDuck maloy; + RubberDuck lemon; + + cout << endl; + + // Introduce a generic duck + cout << "This" << " is " << jane.getDescription() << " who says "; + jane.performQuack(); + cout << endl; + + // Introduce a mallard duck + cout << "This" << " is " << maloy.getDescription() << " who says " ; + maloy.performQuack(); + cout << endl; + + // Introduce a rubber duck + cout << "This" << " is " << lemon.getDescription() << " who says " ; + lemon.performQuack(); + cout << endl; + + // Introduce a decoy duck + + + // I had to type the same thing over and over. + // I think I can do better by wrapping it in a loop! + // BUT WAIT, look at the output - it doesn't work! More on this later ... + Duck ducks[3]; + ducks[0] = jane; + ducks[1] = maloy; + ducks[2] = lemon; + for (int i=0;i<3;i++) { + cout << "This is " << ducks[i].getDescription() << " who says "; + ducks[i].performQuack(); + } + cout << endl; + + return 0; +} diff --git a/class-repo-public/labs/lab02/readme.md b/class-repo-public/labs/lab02/readme.md new file mode 100644 index 0000000..dca8858 --- /dev/null +++ b/class-repo-public/labs/lab02/readme.md @@ -0,0 +1,198 @@ +## Lab 2 - Compiling and Linking + +> A reminder that in Dr. Larson's section, attendance is required for you to receive credit for the lab. + +In this lab, you are going to learn how to compile the source code and link the executable. The source code is in .cpp and .h files. The object code is in the .o files. Executables are either .out or have no extension. + +In addition to practicing compilation, you will define classes and subclasses. In class exercises, we have demonstrated the basics on class definitions. The syntax of a class and subclass definition is also provided within this document. + +Deliverables: +- main.cpp +- DecoyDuck.cpp +- DecoyDuck.h +- MysteryDuck.cpp +- MysteryDuck.h + +In this lab, you will write and edit some C++ code, build it, and run it. Please follow the instructions below. The given source code will help you get started. + +### Getting Started and Words of Advice + +The starting code base for this lab exercise is available in the class repo: +https://github.umn.edu/umn-csci-3081F17/class-repo-public. You do not want to edit within the class repo or it will cause merge conflicts. Copy the code to your +individual repo and work within that. Please follow the same directory structure +such that lab02 is within the labs folder. + +At each push, an automatic feedback file will be pushed to your repo. +> Don't forget to _pull_ the feedback before making more edits to your repo or you will cause merge conflicts. + +The automated feedback encompasses a majority, if not all, the tests that will be used to grade your lab. Please make sure you review the feedback from your final submission to ensure it passes all the tests. Minor fixable errors can result in 0 points for the lab because automated grading fails. You are responsible for ensuring your code passes all tests in the feedback. + +> We will not reconsider your lab grade after the due date if the feedback file gave you information that you ignored. + +Text editors designed for coding are essential. There are many good options available for free and on the cselabs machines. + +### Step 1: Update Your Repositories + + _If you did not do last week's lab, refer to lab01 for directions on how to set up git for both the class repo and your individual repo._ + +1. Open a terminal window. +3. Navigate to the directory of your 3081 class repo. +1. `pull` to get lab02 base code +1. Copy the directory into your _labs_ directory in your individual class repo. + + ``` mv labs/lab02 /labs/.``` + +1. Navigate into the lab02 directory in your individual repo: + +#### Step 2: Compile the Source Code + +1. Compile the .cpp files into object files. + ```  + % g++ -c -o main.o main.cpp + % g++ -c -o Duck.o Duck.cpp + % g++ -c -o MallardDuck.o MallardDuck.cpp + % g++ -c -o RubberDuck.o RubberDuck.cpp + ``` + What does all this mean? + - `g++` : call to the gnu c++ compiler + - `-c` : a flag to indicate compilation, as opposed to linking + - `-o` : output the results of compilation into the file following this flag + - `main.cpp` : the last file is the one to be compiled + - If `-c` is not provided, the compiler will attempt to compile AND link. + - If `-o` with an output file is not provided, the compiler will use the name of the .cpp file for the .o filename. + + Notice that each file is independently compiled, even though there are interdependencies among them. If you look in the .cpp file, there will be a `#include file.h` statement which essentially inserts the text of at the location of the #include statement. Compilation will create symbol tables for all classes, variables, functions declared within those header files, but with no associated address for the definition of those elements. When the files are _linked_, those dependencies will be resolved and the definitions of the various objects will be included in the complete program. + +### Step 2: Linking to Generate the Executable + +1. Link the object files into an executable:  + + ```% g++ -o duck main.o Duck.o MallardDuck.o RubberDuck.o``` + + * What does all this mean? * + - `g++` : call to the gnu c++ compiler + - `-o` : output the executable into the file following this flag + - list of files : all the files that need to be linked. + -If `-o` and output file are not provided, the compiler will create the executable `a.out` + + _Notice there was no need to tell the compiler that we are in the linking phase._ + + > HINT: A shortcut for linking is `% g++ -o duck *.o` + +1. Run the program:  + + ```% ./duck``` + +### Step 3: Add DecoyDuck to Current Project + +First, let's review the syntax for class definitions in C++. This is a simple class definition that we can think of as defining an interface for the subclasses which will inherit from _Duck_. +``` +#ifndef LAB02_DUCK_H_ +#define LAB02_DUCK_H_ +... +#endif +``` +This serves as a guard for the header file. If there are interdependencies among files such that the compiler is instructed to include this several times, this safeguard communicates to the compiler that this code has already been incorporated. It makes compilation more efficient, and when you have circular references or definitions within the header, it is necessary. + +##### Duck.h + +```C++ +class Duck { +public: + Duck(); + virtual void performQuack(); + virtual std::string getDescription(); +}; +``` + +- `Duck()` is the constructor with no passed parameters. +- 'virtual' indicates this can be overwritten by the subclass. When we study polymorphism, the need for `virtual` will become clearer. +- `public:` gives direct access to members and methods outside the class. +- '};' : Don't forget to conclude your definition with a semi-colon + + +##### Duck.cpp + +```C++ +#include "Duck.h" +``` +Don't forget to include the Duck.h file - of course the compiler will let you know if you forget it when it tries to search for the _Duck_ class for inheritance. + +```C++ +class RubberDuck : public Duck { +public: + RubberDuck(); + + std::string getDescription(); + void performQuack(); +}; +``` +- `class RubberDuck : public Duck` is the declaration of a new class RubberDuck that inherits from the class Duck. `public` indicates that RubberDuck will have full access to those elements in the Duck class. +- Notice that all methods of the Duck class are restated here because they will all be overwritten by RubberDuck. Definitions of these functions are in the `RubberDuck.cpp` file. + +##### main.cpp + +```C++ +#include "MallardDuck.h" +#include "RubberDuck.h" +``` +Notice that we are not including Duck.h. Can you guess why we don't need it, even though we are referencing it below? + +```C++ +using std::cout; +using std::endl; + +int main() { + + // Create Ducks + Duck jane; + MallardDuck maloy; + RubberDuck lemon; +``` + +These are the instantiations of the different types of ducks. The beauty of subclasses comes into play when we can treat all types of ducks identically, so that the code chooses the appropriate method for the given type. This is polymorphism, which requires knowledge of pointers and dynamic allocation - soon to be covered. + +```C++ +Duck ducks[3]; +ducks[0] = jane; +ducks[1] = maloy; +ducks[2] = lemon; +for (int i=0;i<3;i++) { + cout << "This is " << ducks[0].getDescription() << " who says "; + ducks[0].performQuack(); +} +``` + +Above is an attempt to use polymorphism in C++, such that we refer to objects of different duck types in one structure using the same interface. It doesn't work because we aren't using dynamic allocation. + +#### Step 1: Create a New Class + +Your task is to add another duck subclass to this simulator, namely the decoy duck. Here is the specification: + +
      +
    • Name the class DecoyDuck +
    • The description is "a plastic decoy duck" +
    • It is silent, thus it says '...' +
+ +1. Create both a DecoyDuck.h and DecoyDuck.cpp file in your favorite editor (e.g. `atom& DecoyDuck.h`). +1. Define the class as a subclass of _Duck_ +2. Declare the needed methods in the .h file as described in the specification. +3. Define the needed methods in the .cpp file. +4. Modify _main.cpp_ to include a DecoyDuck in the simulation. + +>Note that you do not need to recompile any of the other files. It is only the linking process that must be redone. This makes compilation far more efficient, which doesn't seem like much of a time savings in our little program, but imagine a project in which the compilation takes hours! + +1. Compile your new duck and link it all together. + +### Step 4 : Add MysteryDuck class. + +Create _MysteryDuck_ class that inherits from _Duck_. Create a constructor in which the user passes in the description and sound. Add this to _main.cpp_. Compile and link as before. Currently, Duck does not have any members. You will need variables of type string in which to save the description and sound, then use in the methods. Don't forget to hide your members from objects outside the class. + +### Step 5 : Push to the repository + +``` +git add * +git commit -m “adding decoy and mystery duck classes” +git push +``` diff --git a/class-repo-public/labs/lab03/DecoyDuck.cpp b/class-repo-public/labs/lab03/DecoyDuck.cpp new file mode 100644 index 0000000..648d73c --- /dev/null +++ b/class-repo-public/labs/lab03/DecoyDuck.cpp @@ -0,0 +1,28 @@ +// +// DecoyDuck.cpp +// +// Created by Seth Johnson on 1/24/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#include +#include +#include "DecoyDuck.h" +#include "MuteQuack.h" + +using std::cout; +using std::endl; +using std::string; + +DecoyDuck::DecoyDuck() { + setQuackBehavior(new MuteQuack()); +} + +DecoyDuck::~DecoyDuck() {} + +string DecoyDuck::getDescription() { + string descriptionText = "a plastic decoy duck"; + return descriptionText; +} + + diff --git a/class-repo-public/labs/lab03/DecoyDuck.h b/class-repo-public/labs/lab03/DecoyDuck.h new file mode 100644 index 0000000..c8c0ff2 --- /dev/null +++ b/class-repo-public/labs/lab03/DecoyDuck.h @@ -0,0 +1,22 @@ +// +// DecoyDuck.h +// +// Created by Seth Johnson on 1/24/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#ifndef DECOYDUCK_H +#define DECOYDUCK_H + +#include +#include "Duck.h" + +class DecoyDuck : public Duck { +public: + DecoyDuck(); + ~DecoyDuck(); + + std::string getDescription(); +}; + +#endif diff --git a/class-repo-public/labs/lab03/Duck.cpp b/class-repo-public/labs/lab03/Duck.cpp new file mode 100644 index 0000000..2cfdf53 --- /dev/null +++ b/class-repo-public/labs/lab03/Duck.cpp @@ -0,0 +1,33 @@ +// +// Duck.cpp +// +// Created by Sarit Ghildayal on 1/24/15. +// Copyright (c) 2015 Sarit Ghildayal. All rights reserved. +// + +#include "Duck.h" + +#include + +#include "QuackBehavior.h" +#include "Quack.h" + +using std::cout; +using std::endl; + +Duck::Duck() { + m_quackBehavior = NULL; + setQuackBehavior(new Quack()); + setName("Duck"); +} + +void Duck::performQuack() { + m_quackBehavior->quack(); +} + +void Duck::setQuackBehavior(QuackBehavior* behavior) { + if (m_quackBehavior != NULL) + delete m_quackBehavior; + + m_quackBehavior = behavior; +} \ No newline at end of file diff --git a/class-repo-public/labs/lab03/Duck.h b/class-repo-public/labs/lab03/Duck.h new file mode 100644 index 0000000..cdb0a42 --- /dev/null +++ b/class-repo-public/labs/lab03/Duck.h @@ -0,0 +1,35 @@ +// +// Duck.h +// +// Created by Sarit Ghildayal on 1/24/15. +// Copyright (c) 2015 Sarit Ghildayal. All rights reserved. +// + +#ifndef DUCK_H +#define DUCK_H + +#include + +class QuackBehavior; + + +class Duck { +public: + Duck(); + virtual ~Duck() {}; + + virtual void performQuack(); + + virtual std::string getDescription() = 0; + + std::string getName() {return m_name;} + void setName(std::string name ) {m_name = name;} + void setQuackBehavior(QuackBehavior * behavior); +protected: + std::string m_name; + + QuackBehavior * m_quackBehavior; + +}; + +#endif diff --git a/class-repo-public/labs/lab03/MallardDuck.cpp b/class-repo-public/labs/lab03/MallardDuck.cpp new file mode 100644 index 0000000..a279c7c --- /dev/null +++ b/class-repo-public/labs/lab03/MallardDuck.cpp @@ -0,0 +1,27 @@ +// +// MallardDuck.cpp +// +// Created by Sarit Ghildayal on 1/24/15. +// Copyright (c) 2015 Sarit Ghildayal. All rights reserved. +// + +#include +#include +#include "MallardDuck.h" + +#include "Quack.h" + +using std::cout; +using std::endl; +using std::string; + +MallardDuck::MallardDuck() { + +} + +MallardDuck::~MallardDuck() {} + +string MallardDuck::getDescription() { + string descriptionText = "a mallard duck"; + return descriptionText; +} diff --git a/class-repo-public/labs/lab03/MallardDuck.h b/class-repo-public/labs/lab03/MallardDuck.h new file mode 100644 index 0000000..fb2c88d --- /dev/null +++ b/class-repo-public/labs/lab03/MallardDuck.h @@ -0,0 +1,22 @@ +// +// MallardDuck.h +// +// Created by Sarit Ghildayal on 1/24/15. +// Copyright (c) 2015 Sarit Ghildayal. All rights reserved. +// + +#ifndef MALLARDDUCK_H +#define MALLARDDUCK_H + +#include +#include "Duck.h" + +class MallardDuck : public Duck { +public: + MallardDuck(); + ~MallardDuck(); + + std::string getDescription(); +}; + +#endif diff --git a/class-repo-public/labs/lab03/MuteQuack.cpp b/class-repo-public/labs/lab03/MuteQuack.cpp new file mode 100644 index 0000000..b302b5c --- /dev/null +++ b/class-repo-public/labs/lab03/MuteQuack.cpp @@ -0,0 +1,17 @@ +// +// MuteQuack.cpp +// +// Created by Seth Johnson on 2/5/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#include "MuteQuack.h" + +#include + +using std::cout; +using std::endl; + +void MuteQuack::quack() { + cout << "..." << endl; +} \ No newline at end of file diff --git a/class-repo-public/labs/lab03/MuteQuack.h b/class-repo-public/labs/lab03/MuteQuack.h new file mode 100644 index 0000000..9b5d341 --- /dev/null +++ b/class-repo-public/labs/lab03/MuteQuack.h @@ -0,0 +1,20 @@ +// +// MuteQuack.h +// +// Created by Seth Johnson on 2/5/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#ifndef MUTEQUACK_H +#define MUTEQUACK_H + +#include "QuackBehavior.h" + +class MuteQuack : public QuackBehavior { +public: + MuteQuack() {}; + ~MuteQuack() {}; + void quack(); +}; + +#endif diff --git a/class-repo-public/labs/lab03/Quack.cpp b/class-repo-public/labs/lab03/Quack.cpp new file mode 100644 index 0000000..71029b9 --- /dev/null +++ b/class-repo-public/labs/lab03/Quack.cpp @@ -0,0 +1,16 @@ +// +// Quack.cpp +// +// Created by Seth Johnson on 2/4/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#include +#include "Quack.h" + +using std::cout; +using std::endl; + +void Quack::quack() { + cout << "QUACK!!" << endl; +} \ No newline at end of file diff --git a/class-repo-public/labs/lab03/Quack.h b/class-repo-public/labs/lab03/Quack.h new file mode 100644 index 0000000..5a0a1fa --- /dev/null +++ b/class-repo-public/labs/lab03/Quack.h @@ -0,0 +1,20 @@ +// +// Quack.h +// +// Created by Seth Johnson on 2/4/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#ifndef QUACK_H +#define QUACK_H + +#include "QuackBehavior.h" + +class Quack : public QuackBehavior { +public: + Quack() {}; + ~Quack() {}; + void quack(); +}; + +#endif diff --git a/class-repo-public/labs/lab03/QuackBehavior.cpp b/class-repo-public/labs/lab03/QuackBehavior.cpp new file mode 100644 index 0000000..544c63b --- /dev/null +++ b/class-repo-public/labs/lab03/QuackBehavior.cpp @@ -0,0 +1,8 @@ +// +// QuackBehavior.cpp +// +// Created by Seth Johnson on 2/4/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#include "QuackBehavior.h" diff --git a/class-repo-public/labs/lab03/QuackBehavior.h b/class-repo-public/labs/lab03/QuackBehavior.h new file mode 100644 index 0000000..80138fd --- /dev/null +++ b/class-repo-public/labs/lab03/QuackBehavior.h @@ -0,0 +1,19 @@ +// +// QuackBehavior.h +// +// Created by Seth Johnson on 2/4/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#ifndef QUACKBEHAVIOR_H +#define QUACKBEHAVIOR_H + +#include + +class QuackBehavior { +public: + QuackBehavior() {}; + virtual ~QuackBehavior() {} + virtual void quack() = 0; +}; +#endif /* defined(__Minimal_Duck__QuackBehavior__) */ diff --git a/class-repo-public/labs/lab03/README.md b/class-repo-public/labs/lab03/README.md new file mode 100644 index 0000000..075ce2e --- /dev/null +++ b/class-repo-public/labs/lab03/README.md @@ -0,0 +1,155 @@ +#### Lab Session 3: Introduction to Makefiles + +**Due date:** Sun, Sept. 23 at 10:00 pm + +In this lab, you will learn about Makefiles by writing a _makefile_ for the duck simulator. A makefile automates the process of compilation and linking and it is essential for large-scale projects. In IDE's, there are project files that provide the same functionality as a makefile, and sometimes those generate a makefile that is not obvious to the user. Makefiles can be very simple or very complex, depending on the needs of the project and skill of the writer. Here you will be creating the most basic makefile. + +You have already used a makefile perhaps without realizing it. In the first lab, you created an executable by typing 'make' at the command prompt. This was a call to the file named _makefile_ included in that directory. A 'make' command starts by searching for a makefile (named either _makefile_ or _Makefile_). If no argument is passed, it will try to create the first _target_ listed in the file, which is described below. If you entered 'make main.o', it would look for a _target_ with the name _main.o_ and follow the directions for creating that target, which happens to be a file, but that is not always the case. The targets _all_ and _clean_ are standard targets in makefiles that compile and link all files needed for the executable, and remove all object files and executable for a clean start, respectively. + +There are directions below for creating a makefile for the included code. At the bottom of the file is a quick guide to makefile commands. You might also find the following resources helpful: + +- http://mrbook.org/blog/tutorials/make/ +- http://www.cs.umd.edu/class/fall2002/cmsc214/Tutorial/makefile.html +- https://www.cs.swarthmore.edu/~newhall/unixhelp/howto_makefiles.html +- https://www.cprogramming.com/tutorial/makefiles.html +- https://www.gnu.org/software/make/manual/make.html (this is massive and speaks to the complexity of the language) + + ##### Getting Started and Words of Advice + +The starting code base for this lab exercise is available in the class repo: +https://github.umn.edu/umn-csci-3081F17/class-repo-public. You do not want to edit within the class repo or it will cause merge conflicts. Copy the code to your individual repo and work within that. Please follow the same directory structure such that lab03 is within the labs folder. + +At each push, an automatic feedback file will be pushed to your repo. +> Don't forget to _pull_ the feedback before making more edits to your repo or you will cause merge conflicts. + +The automated feedback encompasses a majority, if not all, the tests that will be used to grade your lab. Please make sure you review the feedback from your final submission to ensure it passes all the tests. Minor fixable errors can result in 0 points for the lab because automated grading fails. You are responsible for ensuring your code passes all tests in the feedback. + +> We will not reconsider your lab grade after the due date if the feedback file gave you information that you ignored. + + +##### Step 1: Update Your Repositories + +1. Open a terminal window. +3. Navigate to the directory of your 3081 class repo. +1. `git pull` to get lab03 base code +1. Copy the directory into your _labs_ directory in your individual class repo. + + ``` cp -R public-class-repo/lab03 /labs/. + ``` + +1. Navigate into the lab03 directory in your individual repo: + +#### Instructions + +A Makefile is a build system. The main difference between a script file and a Makefile is that a Makefile keeps track of file modification timestamps, which means that it only compiles those files that currently don't exist or have undergone change since the last compilation. Some consider the Makefile to be a glorified script but to call it that makes it sound like it is simple to do. Makefiles can be quite complex. + +##### Step 2: Create Your Makefile with _all_ and _clean_ targets + +1. Create your Makefile. +2. _emacs& makefile_ (or _atom& makefile_) + +3. Write the all target, which will perform the linking to create the executable **_ducks_** (_see “Targets” section in Makefile Cheat Sheet below_). Do not create any dependencies at this time, thus you need only have _all:_ and on the next line TAB !!**It must be a tab, not spaces**!! and the command to link everything together, which will start with 'g++'. Recall that '-o ' sends the results to the listed and linking incorporates all the object files. Making visible compiler and linking warnings is a good idea! In all your commands for building and linking add the flag -Wall for viewing all warnings. +> Have the _all_ target the first, and therefore the default when you make with no arguments. + +4. Write the clean target to remove all object files and the executable. +5. At the OS command prompt, +``` +make clean +make +``` +This will generate an error because the object files do not exist. + +6. Create a target file.o for all relevant files in your directory. Try make again. Still does not work! Keep going to fix ... + +> If you want to make comments in your makefile, use the pound # symbol. + +##### Step 3: Adding Dependencies + +The files listed after _target:_ are the dependencies, which are used in two ways. 1) The file has to exist, and if it does not exist, the makefile will search for a target of the same name to create it, and 2) If the file does exist, it will look at the timestamp to see if it has changed since the last creation of the _target_, and recreate the dependency if necessary. + +Think about why you are still seeing an error when you make. If you don't know, ask a neighbor. + +1. Add dependencies to your _all_ target. +2. make and now your executable should be created. Pay attention to what is being echoed out to your shell, which will show you each of the commands in your makefile being executed. Try it out with ./ducks +3. Modify one of the files in your dependencies for _all_ (any change will do). +4. make again and observe that this file, and only this file, is recompiled. +5. make clean. Observe that the object files and executable are now gone. +6. make MallardDuck.o and observe that it made that target, and only that target. +7. make all and observe that everything else is being compiled, and the executable is being created. + +> Sometimes the dependencies are not listed correctly or do not work the way we think they should. If you make changes to a file, but it doesn't seem to be having any impact on the executable, make might not recognize that it needs to recompile. You can remedy this with make clean then make to force recompilation. + + +##### Step 4: Improve Your Makefile + +We just made you do a lot of extra work by typing in the same command over and over for each object file. As you can imagine, there is a better way with the use of variables. + +1. First, copy your makefile so that you have a backup: cp makefile makefile.old +2. Modify your makefile to add a variable for the compiler (i.e. CXX), compiler flags (CXXFLAGS), and linking flags (LDFLAGS). Don't forget to include -Wall. Variables appear first in the makefile, before the _all_ target. (*See “Macros” section in Makefile Cheat Sheet below.*) +3. Add a variable OBJS and set it equal to a list of object files (e.g. MallardDuck.o) separated by a space (not commas). +4. Use your new compiling variables in ONE of the commands for the targets to ensure it is working. You can reference a makefile variable in a command with '$(_varname_)'. +5. Use your linking variables in all (note that CXX is used for both compiling and linking but the flags are specific to the process.) +6. Use your OBJS variable in the dependencies list. +7. Test your results with make clean and 'make' + +##### Step4.2: Even Better (optional)... + +There is still a lot of redundancy in your makefile. You can greatly reduce it with the use of wildcards and symbols. Look through this stackoverflow post for some pointers on how to incorporate these into your makefile: + +https://stackoverflow.com/questions/3220277/what-do-the-makefile-symbols-and-mean + + +##### Step 5: Submit + +1. Clean and build your program with your makefile +2. Run your program +3. ```./ducks``` +4. Commit and check in results: +``` +git add . +git commit -m “finishing lab03” +git push +``` + +
+ +### Makefile Cheat Sheet + +##### Targets + +A Makefile target works in this way: +``` +target: dependencies +[tab] system command +``` +A target is usually a file, but not always. This definition tells you to construct the target when the dependencies are newer than the target with the system command. For example, to compile our duck simulator you could enter the following in your Makefile: + +``` +Duck.o: Duck.h Duck.cpp + g++ -c Duck.cpp +``` + +##### Dummy Targets + +There are times when you do not want to create a target, but instead run a few commands. For example, you may want to “clean” your directory of files that come from the compilation process. This could be done with the following: +``` +clean: + rm *.o ducks +``` + +##### Macros + +A macro is a “variable” can be defined in a Makefile. Macros are defined as = pairs and can be called by either \$(VARIABLE\_NAME) or \${VARIABLE\_NAME}. For example, we can define compiler and compiler flags macros: + +``` +CXX = g++ +CXXFLAGS = -Wall -c -std=c++11 +``` + +and it might be used in a target command like this +``` +$(CXX) $(CXXFLAGS) -o main.o main.cpp +``` + + +(Makefile conventions say variable names should consist of all caps andunderscores) diff --git a/class-repo-public/labs/lab03/RubberDuck.cpp b/class-repo-public/labs/lab03/RubberDuck.cpp new file mode 100644 index 0000000..8cd6dc3 --- /dev/null +++ b/class-repo-public/labs/lab03/RubberDuck.cpp @@ -0,0 +1,28 @@ +// +// RubberDuck.cpp +// +// Created by Seth Johnson on 1/24/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#include +#include +#include "RubberDuck.h" +#include "Squeak.h" + +using std::cout; +using std::endl; +using std::string; + +RubberDuck::RubberDuck() { + setQuackBehavior(new Squeak()); +} + +RubberDuck::~RubberDuck() {} + +string RubberDuck::getDescription() { + string descriptionText = "a yellow rubber ducky"; + return descriptionText; +} + + diff --git a/class-repo-public/labs/lab03/RubberDuck.h b/class-repo-public/labs/lab03/RubberDuck.h new file mode 100644 index 0000000..812cecf --- /dev/null +++ b/class-repo-public/labs/lab03/RubberDuck.h @@ -0,0 +1,22 @@ +// +// RubberDuck.h +// +// Created by Seth Johnson on 1/24/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#ifndef RUBBERDUCK_H +#define RUBBERDUCK_H + +#include +#include "Duck.h" + +class RubberDuck : public Duck { +public: + RubberDuck(); + ~RubberDuck(); + + std::string getDescription(); +}; + +#endif diff --git a/class-repo-public/labs/lab03/Squeak.cpp b/class-repo-public/labs/lab03/Squeak.cpp new file mode 100644 index 0000000..46a901e --- /dev/null +++ b/class-repo-public/labs/lab03/Squeak.cpp @@ -0,0 +1,17 @@ +// +// Squeak.cpp +// +// Created by Seth Johnson on 2/5/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#include "Squeak.h" + +#include + +using std::cout; +using std::endl; + +void Squeak::quack() { + cout << "SQUEAK!!" << endl; +} \ No newline at end of file diff --git a/class-repo-public/labs/lab03/Squeak.h b/class-repo-public/labs/lab03/Squeak.h new file mode 100644 index 0000000..9e8ecf5 --- /dev/null +++ b/class-repo-public/labs/lab03/Squeak.h @@ -0,0 +1,20 @@ +// +// Squeak.h +// +// Created by Seth Johnson on 2/5/15. +// Copyright (c) 2015 Seth Johnson. All rights reserved. +// + +#ifndef SQUEAK_H +#define SQUEAK_H + +#include "QuackBehavior.h" + +class Squeak : public QuackBehavior { +public: + Squeak() {}; + ~Squeak() {} + void quack(); +}; + +#endif diff --git a/class-repo-public/labs/lab03/main.cpp b/class-repo-public/labs/lab03/main.cpp new file mode 100644 index 0000000..0a824bb --- /dev/null +++ b/class-repo-public/labs/lab03/main.cpp @@ -0,0 +1,70 @@ +// +// main.cpp +// +// Created by Sarit Ghildayal on 1/24/15. +// Copyright (c) 2015 Sarit Ghildayal. All rights reserved. +// + +#include +#include "MallardDuck.h" +#include "RubberDuck.h" +#include "DecoyDuck.h" +#include "MuteQuack.h" + +#include + +using std::cout; +using std::endl; +using std::vector; + +void introduceDuck(Duck* duck); +void shushDuck(Duck* duck); + +int main(int argc, const char * argv[]) { + + // Create Ducks + Duck * steve = new MallardDuck(); + Duck * charles = new RubberDuck(); + Duck * edmund = new DecoyDuck(); + + steve->setName("Steve"); + charles->setName("Charles"); + edmund->setName("Edmund"); + + Duck ** duckArray = new Duck* [10]; // Make room for 10 ducks + int duckCount = 0; // This will increase by one everytime we add a + duckArray[duckCount++] = steve; + duckArray[duckCount++] = charles; + //duckArray[duckCount++] = edmund; + + + // Introduce all the ducks + cout << "\nHere we have " << duckCount << " ducks." << endl; + for (int i = 0; i < duckCount; i++) { + Duck* current_duck = duckArray[i]; + introduceDuck(current_duck); + } + + // Tell steve to be quiet + cout << endl; + shushDuck(steve); + + // Re-introduce all the ducks + cout << "\nHere we have " << duckCount << " ducks." << endl; + for (int i = 0; i < duckCount; i++) { + Duck* current_duck = duckArray[i]; + introduceDuck(current_duck); + } + + return 0; +} + +void introduceDuck(Duck* duck) { + cout << duck->getName() << " is " << duck->getDescription() << " who says: "; + duck->performQuack(); +} + +void shushDuck(Duck* duck) { + cout << "\"Shhhh, " << duck->getName() << "!\"" << endl; + duck->setQuackBehavior(new MuteQuack()); +} \ No newline at end of file diff --git a/class-repo-public/labs/lab04/Makefile b/class-repo-public/labs/lab04/Makefile new file mode 100755 index 0000000..291fa40 --- /dev/null +++ b/class-repo-public/labs/lab04/Makefile @@ -0,0 +1,19 @@ +### CSci-3081W Project Support Code Makefile ### + +# This is the main Makefile for the project. It provides easy access +# to building and testing the whole project, which requires running +# make in subdirectories. + +.PHONY: proj01 clean + +# Build everything that can be built for this project +all: proj01 + +# Build the bin/proj01 executable by running make in the project's src directory +proj01: + $(MAKE) -C src all + + +# Clean everything that has been for a fresh start +clean: + $(MAKE) -C src clean diff --git a/class-repo-public/labs/lab04/README.md b/class-repo-public/labs/lab04/README.md new file mode 100755 index 0000000..b883fad --- /dev/null +++ b/class-repo-public/labs/lab04/README.md @@ -0,0 +1,193 @@ +# Proof-of-Concept for the RobotSim project for 3081W + +The base code for the robot simulator project of 3081W was bourn from this proof-of-concept application written by Dan Keefe. There are 2 primary components: the _robot\_viewer_ and _robot\_land_. Robots live in robot land and are quite oblivious to being viewed by the viewer. The viewer is in control of everything in that it sends a message to robot_land to update, meaning move the robots the appropriate amount given how much time has passed since the last update. It then gets the appropriate positional information from each robot and draws these to the screen. All the while, the system is listening for keyboard and mouse activity by the user. If any action occurs, the associated callback is executed. + +After reading below and looking through the code, you might want to circle back to the explanation above to better understand how this works. + +
+ +## Mechanics of the System + +### Resources + +- libsimple_graphics : https://github.umn.edu/umn-csci-3081F17/libSimpleGraphics/ +- nanogui : https://github.com/wjakob/nanogui +- nanogui documentation: https://nanogui.readthedocs.io/en/latest/ +- nanovg : https://github.com/memononen/NanoVG +- Google Style Guide : https://google.github.io/styleguide/cppguide.html +- cpplint : https://github.com/google/styleguide/tree/gh-pages/cpplint + +### libSimpleGraphics and /project/f17c3081 + +The robot viewer is a subclass of GraphicsApp, which can be found in the libSimpleGraphics repo. When building the simulator, the local object files are linked with libSimpleGraphics located at `/project/f17c3081`, which is publicly accessible on all cselabs machines. If you want to compile on your own platform, you will have to create a similar directory and change the makefile to reflect your local drive (i.e. edit the CS3081DIR variable in src/Makefile to point to the directory where libSimpleGraphics has been installed.) + +> **Do not submit to your repo any of the code from libSimpleGraphics. Also, do not submit a makefile in which you are linking to your personal local directory - it must refer to the cselabs directory.** + +### Directory Structure + +Makefile: +- make all and make clean for the whole project (calls make recursively in subdirs). + +src/: +- the main source code where you will find robot_viewer.h/cc and robot_land.h/cc + +src/Makefile: +- builds the project source, assumes it should build all .cc and .cpp files in the current directory +- **creates an executable in build/bin/exename** +- uses auto dependency generation to get dependencies from #includes + +build/: +- created by the Makefiles + +build/bin: +- **all exes generated for the project go in here** + +build/obj: +- all .o's and .d's (from make depend) go in here + +
+ +## Getting Started + +1. _pull_ the class repo. +2. cp the lab to your personal repo +3. If you are working from your local machine, modify the `CS3081DIR` variable in `src/Makefile` so that it points to your local installation of libSimpleGraphics. Ignore this step if working on a CSE labs machine. +4. At the top level from the OS command prompt: `make` +5. Navigate to `build/bin` +6. At command prompt: `./robotviewer` + +If you have trouble compiling, it might be that your account is behind on the gcc version. At the prompt, type `gcc --version` and if it says 4.x, you need to load gcc: `module load /soft/gcc/7.1`. + +
+ +## Code explanation + +### class GraphicsApp + +The GraphicsApp makes use of the nanogui, nanovg, and openGL libraries to create a window with 2D graphics and a GUI. As with most graphics applications, there is a main loop in which keyboard and mouse events are handled, then all objects in the graphics window are drawn. The loop is executed at the frame rate until the application is closed. The actual loop is hidden inside the library code, but it behaves something like this: + +```C++ +while (Active(window)) { // ie user has not clicked 'x' to close the window + handleKeyMouseEvents(); + clearScreen() // start with a fresh screen/buffer + drawContents(); + swapBuffers(window); // double-buffered system +} +``` + +This loop is initiated by a call to nanogui::mainloop(), which you can find in GraphicsApp::Run(). + +```C++ +void GraphicsApp::Run() { + glfwSetTime(0.0); + nanogui::mainloop(); +} +``` + +Once that mainloop() is initiated, at the frame rate, GraphicsApp::draw() will be called. Notice the **two very important function calls `UpdateSimulation()` and `DrawUsingNanoVG()`**. + +```C++ +void GraphicsApp::draw(NVGcontext *ctx) { + + double now = glfwGetTime(); + UpdateSimulation(now - last_draw_time_); + last_draw_time_ = now; + + // Draw the user interface using the nanogui library, which is built upon nanovg + Screen::draw(ctx); + + // Draw other vector graphics using the nanovg library + DrawUsingNanoVG(ctx); +} +``` + +### class RobotViewer in robot_viewer.h / .cc + +RobotViewer is a subclass of GraphicsApp. In this application, it is instantiated in main() with the name _app_. Notice in the constructor of RobotViewer, you see the instantiation of robot land. The simulator gets started with a call to app->Run(), which calls nanogui::mainloop(): + +```C++ +int main(int argc, char **argv) { + + csci3081::InitGraphics(); + + csci3081::RobotViewer \*app = new csci3081::RobotViewer(); + app->Run(); + + csci3081::ShutdownGraphics(); + return 0; +} +``` + +Recall that in the draw() function of the GraphicsApp, there is a call to UpdateSimulation() and DrawUsingNanoVG(). **Look at these function definitions in _robot\_viewer.cc._** All that is happening in Update is a call to robot land to update based on how much time has passed. In the draw function, each object of robot land is being drawn. Notice that the graphics commands for drawing each of the objects begins with `nvg` for nanovg. The header file `https://github.com/memononen/nanovg/blob/master/src/nanovg.h` is pretty well documented. Take a moment to look at this header file and read about some of the function calls that you see in drawing the various graphics objects (e.g. DrawRobot, DrawObstacle). + +(The keyboard and mouse event processing is discussed below.) + +### class RobotLand + +Robot land has no awareness of robot viewer, despite the fact that it was instantiated inside of robot viewer. You can think about robots in robot land constantly moving around (even though they move in discrete steps), but periodically the robot viewer takes a snapshot of the situation and displays it on the screen. Robot viewer interacts with robot land through getters, which get information from each robot and each obstacle that is relevant to drawing them in the graphics window. + +Currently, robot land consists of one stationary obstacle and two robots going around in circles. No robot class exists, but if it did, it would consist of a position, velocity, color, and size. The color and size is fixed. The position and velocity are determined by the circular pattern they are following. A call to circle_x() and circle_y() with a time value will generate the current position of the robot. Notice that the RobotLand::advance_time() sets the current time of the simulation. When the robot viewer needs to draw the objects, circle_x and circle_y are called to calculate and return the current position of the robot. + +### Keyboard and Mouse Event Handling + +Events are handled inside robot viewer, because this manages all aspects of the GUI. Look in robot_viewer.cc to see the various events to be handled (e.g. OnKeyDown(), which handles the down arrow key). The name gives an indication of which event it is responding to. When that event occurs, this is the function that is called. You never see the call to this function, but know that it does happen. Currently, for the most part, events are handled by printing messages to a terminal, but they really come in handy when you want the user to interact with the graphics window. For example, you might want the functionality that wherever the user clicks the mouse, a robot is created at that position. + +The menu you see in the application with a reset and pause button was a custom menu made for this application. If you downloaded the nanogui examples, you saw how complex these menus can be. It is quite easy to add buttons with various functionality to the menu. Let's trace that process through the code ... + +It starts in the RobotViewer constructor: + +```C++ +nanogui::FormHelper *gui = new nanogui::FormHelper(this); + +nanogui::ref window = + gui->addWindow(Eigen::Vector2i(10, 10), "Simulation Controls"); +pause_btn_ = + gui->addButton("Pause", std::bind(&RobotViewer::OnPauseBtnPressed, this)); +gui->addButton("Restart", std::bind(&RobotViewer::OnRestartBtnPressed, this)); +``` + +You start with the instantiation of FormHelper, then add the various components. The parameters being passed to the `addButton` are the name that appears on the button and the _callback_ function, meaning the one that is called when the button is pressed (which is registered via a mouse click). Again, all of this is going on behind the scenes and you will not see an explicit call to the callback function. + +Next, look at each of the functions passed to the "Restart" and "Pause" buttons: + +```C++ +void RobotViewer::OnRestartBtnPressed() { + robot_land_->set_time(0.0); } + +void RobotViewer::OnPauseBtnPressed() { + paused_ = !paused_; + if (paused_) { + pause_btn_->setCaption("Play"); + } else { + pause_btn_->setCaption("Pause"); + } +} +``` + +At this point, you should have a general understanding of the framework for the graphics application. If you are really not understanding it, read through again or talk to a peer. + +> Notice the special form of the comments. They are following doxygen formatting, which when compiled for doxygen, generates the documentation in the form of a pdf or web pages. + +
+ +## Your Assignment + +1. Add a robot class to the application. Define the robot class in the 2 files _robot.h_ and _robot.cc_ + - Use the robot class to save the size, color, position, and velocity. + - Instantiate 2 robots as members of the class RobotLand when RobotLand is instantiated (i.e. use composition). + - Modify the position and velocity of each robot when time is advanced. + - Create getters for the robot class, which will be called from the getters of RobotLand (which are called by RobotViewer). + > RECOMMENDATION: Create your robot class and test its functionality before trying to incorporate it into the graphics application. To test your robot class, create an instance of the class then use setters and getters to check that the member variables and methods are working properly. You can instantiate your robot temporarily in main(), commenting out the graphics. Then **gradually** incorporate the functionality of the robot class into the graphics. + +2. Add a button to the GUI to change the color of a robot - either robot. + - Create an OnColorChangeBtnPressed() function that changes a robot color. + - Instantiate the button in the RobotViewer constructor, passing it the function name. + +3. If you get this far, do something for fun! Maybe you want to ... + - Add a different kind of object. + - Move the robot in a different pattern. + - Have the robot bounce off of other objects. + - Add more functionality to the GUI by adding more buttons. + +4. And if you get this far, use cpplint to see if it is Google Style Guide compliant: + `cpplint --repository=. robot_land.cc` diff --git a/class-repo-public/labs/lab04/src/Makefile b/class-repo-public/labs/lab04/src/Makefile new file mode 100755 index 0000000..a87b203 --- /dev/null +++ b/class-repo-public/labs/lab04/src/Makefile @@ -0,0 +1,151 @@ +### CSci-3081W Project Support Code Makefile ### + +# File History: This combines Prof. Keefe's Makefiles from past years +# with TA John Harwell's 3081W Makefiles from Fall 2016, which introduced +# auto-dependency generation and several other exciting features. + + +### Section 0: Change this when compiling on non-CSELabs machines ### + +# Path to pre-installed cs3081 support libraries (Google Test, libsimple_graphics, nanogui, ...) +CS3081DIR = /project/f17c3081 + + +### Section I: Definitions ### + +# Root of the source tree for the project (e.g., could be just . or ./src) +SRCDIR = . + +# Output directories for the build process +BUILDDIR = ../build +BINDIR = $(BUILDDIR)/bin +OBJDIR = $(BUILDDIR)/obj/src + +# The name of the executable to create +EXEFILE = $(BINDIR)/robotviewer + +# The list of files to compile for this project. Defaults to all +# of the .cpp and .cc files in the source directory. (We use both .cpp +# and .cc in order to support two different popular naming conventions.) +SRCFILES = $(wildcard $(SRCDIR)/*.cpp) $(wildcard $(SRCDIR)/*.cc) + +# For each of the source files found above, replace .cpp (or .cc) with +# .o in order to generate the list of .o files make should create. +OBJFILES = $(notdir $(patsubst %.cpp,%.o,$(patsubst %.cc,%.o,$(SRCFILES)))) + + + +# Add -Idirname to add directories to the compiler search path for finding .h files +INCLUDEDIRS = -I.. -I$(SRCDIR) -I$(CS3081DIR)/include + +# Add -Ldirname to add directories to the linker search path for finding libraries +LIBDIRS = -L$(CS3081DIR)/lib + +# Add -llibname to link with external libraries +LIBS = -lsimple_graphics -lnanogui -Wl,-rpath,$(CS3081DIR)/lib + + + +# The command to run for the C++ compiler and linker +CXX = g++ + +# Arguments to pass to the C++ compiler. +# -c is required, it tells the compiler to output a .o file +# Optionally include -g to turn on debugging or include -O or -O2 to turn on optimizations instead +# Optionally include -Wall to turn on most warnings +CXXFLAGS = -g -Wall -std=c++14 -c $(INCLUDEDIRS) + +# Arguments to pass to the C++ linker, such as -L, but not -lfoo, which should go in LDLIBS +LDFLAGS = $(LIBDIRS) + +# Library names to pass to the C++ linker, such as -lfoo +LDLIBS = $(LIBS) + + + + +### Section II: Rules ### + + +# This is a list of "phony targets" -- targets that do not specify the name of a file. +# Rather they specify the name of a recipe to run whenever make is envoked with the target name. +.PHONY: clean all $(BINDIR) $(OBJDIR) + + +# The default target which will be run if the user just types "make" +all: $(EXEFILE) + +# This rule says that each .o file in $(OBJDIR)/ depends on the +# presence of the $(OBJDIR)/ directory. +$(addprefix $(OBJDIR)/, $(OBJFILES)): | $(OBJDIR) + +# And, this rule provides a recipe for creating that objdir. The same rule applies +# to the bindir, where the exe will be output. +$(OBJDIR) $(BINDIR): + @mkdir -p $@ + + + +# COMPILING (USING A PATTERN RULE): +# Since every .cpp (or .cc) file must be compiled into a .o, we will write this +# recipe using a pattern rule. Using this recipe, any file that matches the pattern +# $(SRCDIR)/filename.cpp can be turned into $(OBJDIR)/filename.o. +$(OBJDIR)/%.o: $(SRCDIR)/%.cpp + @echo "==== Auto-Generating Dependencies for $<. ====" + $(call make-depend-cxx,$<,$@,$(subst .o,.d,$@)) + @echo "==== Compiling $< into $@. ====" + $(CXX) $(CXXFLAGS) $(CXXLIBDIRS) -c -o $@ $< + +# The same thing will also work for files with a .cc extension +$(OBJDIR)/%.o: $(SRCDIR)/%.cc + @echo "==== Auto-Generating Dependencies for $<. ====" + $(call make-depend-cxx,$<,$@,$(subst .o,.d,$@)) + @echo "==== Compiling $< into $@. ====" + $(CXX) $(CXXFLAGS) $(CXXLIBDIRS) -c -o $@ $< + +# WITH AUTO-GENERATED DEPENDENCIES: +# Note that there are actually two steps to the compiling recipe above. The second +# step should be familiar, it just calls g++ to compile the .cpp into a .o. But, +# the first step is an advanced topic. It calls the custom function make-depend-cxx() +# defined below, which calls the g++ compiler with special flags (the -M* parts) +# that tell g++ to output a make-compatable list of all of the .h files included +# by the specified C++ source file. All of these .h files should be listed as +# dependencies of the .cpp file that is being compiled because if any of the included +# .h files change, our .cpp file will need to be recompiled in order to stay up to +# date. So, we want to be thorough and capture all of these dependencies in our +# Makefile. We could do this manually. For each .cpp file we would need to add a +# rule to this Makefile that lists dependies, and would look something like this: +# file1.o: file1.cpp file1.h file2.h mydir/file3.h myotherdir/file4.h +# where all of the .h files are either included by file1.cpp or by each other. +# It's tedious and error prone to try to track down all these dependencies manually. +# So, instead, we ask the compiler to generate a list of dependencies for us and +# save it out to a new text file with a .d extension. This text file lists the +# dependencies using the same Makefile syntax we would use if we wrote them down +# manually. Read more about auto-dependency generation here: +# http://make.mad-scientist.net/papers/advanced-auto-dependency-generation +# usage: $(call make-depend,source-file,object-file,depend-file) +make-depend-cxx=$(CXX) -MM -MF $3 -MP -MT $2 $(CXXFLAGS) $1 + +# Once the make-depend-cxx() function auto-generates the .d text file of additional +# dependency rules, we need to load it into make, as if those rules were actually +# written in this file. This is done with make's own "include" command, which +# enables us to include one Makefile within another. +-include $(addprefix $(OBJDIR)/,$(OBJFILES:.o=.d)) + + + +# LINKING: +# This rule is for the linking step of building a program. The dependencies mean that +# the executable target that we are building depends upon all of the .o files that are +# generated by the compiler as well as the $(BINDIR), which must exist so we can +# output the exe there. The recipe that follows calls g++ to tell it to link all the +# .o files into an executable program. +$(EXEFILE): $(addprefix $(OBJDIR)/, $(OBJFILES)) | $(BINDIR) + @echo "==== Linking $@. ====" + $(CXX) $(LDFLAGS) $(addprefix $(OBJDIR)/, $(OBJFILES)) -o $@ $(LDLIBS) + + +# Clean up the project, removing ALL files generated during a build. +clean: + @rm -rf $(OBJDIR) + @rm -rf $(EXEFILE) diff --git a/class-repo-public/labs/lab04/src/main.cc b/class-repo-public/labs/lab04/src/main.cc new file mode 100755 index 0000000..012be93 --- /dev/null +++ b/class-repo-public/labs/lab04/src/main.cc @@ -0,0 +1,25 @@ +/** + * @file main.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/robot_viewer.h" + +/******************************************************************************* + * Non-Member Functions + ******************************************************************************/ +int main(int argc, char **argv) { + csci3081::InitGraphics(); + + // RobotViewer is a subclass of GraphicsApp, found in libSimpleGraphics + // Run() is a function defined in the GraphicsApp class definition. + // Run() calls nanogui::mainloop(). + csci3081::RobotViewer *app = new csci3081::RobotViewer(); + app->Run(); + csci3081::ShutdownGraphics(); + return 0; +} diff --git a/class-repo-public/labs/lab04/src/robot_land.cc b/class-repo-public/labs/lab04/src/robot_land.cc new file mode 100755 index 0000000..d7d544d --- /dev/null +++ b/class-repo-public/labs/lab04/src/robot_land.cc @@ -0,0 +1,93 @@ +/** + * @file robot_land.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/robot_land.h" + +/******************************************************************************* + * Member Functions + ******************************************************************************/ + +// Hard coded for 2 robots moving in circular patterns +int RobotLand::get_num_robots(void) { return 2; } + +// Hard coded for now... +// Once the robot class is added, this getter should call the robot getters +bool RobotLand::get_robot_pos(int id, double *x_pos, double *y_pos) { + if (id == 0) { + *x_pos = circle_x(sim_time_); + *y_pos = circle_y(sim_time_); + return true; + } else if (id == 1) { + // make this robot slower + double t = 0.75 * sim_time_; + *x_pos = circle_x(t); + *y_pos = circle_y(t); + return true; + } else { + return false; + } +} + +// Hard coded for now... +// Once the robot class is added, this getter should call the robot getters +// Notice the use of "id" -- how will you handle this in your robot class?? +bool RobotLand::get_robot_vel(int id, double *x_vel, double *y_vel) { + double xnow, ynow, xprev, yprev; + double delta = 0.1; + if (id == 0) { + xnow = circle_x(sim_time_); + ynow = circle_y(sim_time_); + xprev = circle_x(sim_time_ - delta); + yprev = circle_y(sim_time_ - delta); + + *x_vel = xnow - xprev; + *y_vel = ynow - yprev; + return true; + } else if (id == 1) { + // make this robot slower + double t = 0.75 * sim_time_; + xnow = circle_x(t); + ynow = circle_y(t); + xprev = circle_x(t - delta); + yprev = circle_y(t - delta); + + *x_vel = xnow - xprev; + *y_vel = ynow - yprev; + return true; + } else { + return false; + } +} + +// Hard coded for now... +double RobotLand::get_robot_radius(int id) { return 50; } + +// Hard coded for now... in radians +double RobotLand::get_robot_sensor_angle(int id) { return 2.0; } + +// Hard coded for now... +double RobotLand::get_robot_sensor_distance(int id) { + return 3.0 * get_robot_radius(id); +} + +// Hard coded for now... +int RobotLand::get_num_obstacles() { return 1; } + +// Hard coded for now... +bool RobotLand::get_obstacle_pos(int id, double *x_pos, double *y_pos) { + if (id == 0) { + *x_pos = 200; + *y_pos = 300; + return true; + } + return false; +} + +// Hard coded for now... +double RobotLand::get_obstacle_radius(int id) { return 75; } diff --git a/class-repo-public/labs/lab04/src/robot_land.h b/class-repo-public/labs/lab04/src/robot_land.h new file mode 100755 index 0000000..810f3a8 --- /dev/null +++ b/class-repo-public/labs/lab04/src/robot_land.h @@ -0,0 +1,173 @@ +/** + * @file robot_land.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ROBOT_LAND_H_ +#define SRC_ROBOT_LAND_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief The main class for the simulation of a 2D world with many robots running + * around. + * + * RobotViewer or Tests call \ref set_time and \ref advance_time to control the + * simulation and use the get*() functions to read out the current state of the + * simulation (i.e., the current positions and orientations of robots and + * obstacles). + * + * For now, RobotLand is hard coded to run a simulation of two robots running + * around in a circle. You can see their sensors, but they don't yet respond + * to each other or to obstacles. + */ +class RobotLand { + public: + RobotLand(void) { sim_time_ = 0.0; } + + /** + * @brief Set the simulation time for \ref RobotLand. + * + * This function will be used mainly for testing purposes, as time flows in a + * steady forward direction in general simulation. + * + * @param[in] t The new simulation time. + */ + void set_time(double t) { + sim_time_ = t; + std::cout << "Setting simulation time to " << sim_time_ << std::endl; + } + + /** + * @brief Advance the simulation by the specified # of steps. + * + * @param[in] dt The # of steps to increment by. + */ + + // Once the robot class is created, this should call a robot method to + // advance its position and set the velocity based on dt + void advance_time(double dt) { + sim_time_ += dt; + std::cout << "Advancing simulation time to " << sim_time_ << std::endl; + } + + /** + * @brief Get the current simulation time. + */ + double get_current_time(void) { return sim_time_; } + + /* + * @brief Get the current # of robots in the arena. Currently a stub. + * + * @todo: Actually implement this. + */ + int get_num_robots(void); + + /** + * @brief Get the current position of the specified robot. Currently a stub. + * + * @todo: Actually implement this. + * + * @param[in] id The ID of the robot. + * @param[out] x_pos The X position of the robot. + * @param[out] y_pos The Y position of the robot. + * + * @return @todo: What does this mean? + */ + bool get_robot_pos(int id, double *x_pos, double *y_pos); + + /** + * @brief Get the current velocity of the specified robot. Currently a stub. + * + * @todo Actually implement this. + * + * @param[in] id The ID of the robot. + * @param[out] x_vel The X component of velocity. + * @param[out] y_vel The Y component of velocity. + * + * @return @todo what does this mean? + */ + bool get_robot_vel(int id, double *x_vel, double *y_vel); + + /** + * @brief Get the radius of the specified robot. Currently a stub. + * + * @todo: Actually implement this. + * + * @param[in] id The ID of the robot. + * + * @return The robot's radius. + */ + double get_robot_radius(int id); + + /** + * @brief Get the angle of the specified robots sensor, in radians. Currently + * a stub. + * + * @todo: Actually implement this. + * + * @param id The ID of the robot. + * + * @return The sensor angle in radians, + */ + double get_robot_sensor_angle(int id); + + /** + * @brief Get the distance of a specified robot's sensor. Currently a stub. + * + * @todo: Actually implement this. + * + * @param[in] id The ID of the robot. + * + * @return The sensor distance. + */ + double get_robot_sensor_distance(int id); + + /** + * @brief Get the # of obstacles currently in RobotLand. Currently a stub. + * + * @todo: Actually implement this. + * + * @return The # of obstacles. + */ + int get_num_obstacles(void); + + /** + * @brief Get the position of the specified obstacle. Currently a stub. + * + * @todo: Actually implement this. + * + * @param[in] id The ID of the obstacle. + * @param[out] x_pos The X component of the position. + * @param[out] y_pos The Y component of the position. + * + * @return @todo What does this mean? + */ + bool get_obstacle_pos(int id, double *x_pos, double *y_pos); + + /** + * @brief Get the radius of the specified obstacle. + * + * @param[in] id The ID of the obstacle. + * + * @return The obstacle's radius. + */ + double get_obstacle_radius(int id); + + private: + // Hard coding these robots to move in a circle + double circle_x(double t) { return 512 + 200.0 * cos(t); } + double circle_y(double t) { return 350 + 200.0 * sin(t); } + + double sim_time_; +}; + +#endif // SRC_ROBOT_LAND_H_ diff --git a/class-repo-public/labs/lab04/src/robot_viewer.cc b/class-repo-public/labs/lab04/src/robot_viewer.cc new file mode 100755 index 0000000..9bfd684 --- /dev/null +++ b/class-repo-public/labs/lab04/src/robot_viewer.cc @@ -0,0 +1,256 @@ +/** + * @file robot_viewer.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include +#include "src/robot_viewer.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Constructors/Destructor + ******************************************************************************/ +RobotViewer::RobotViewer() + : csci3081::GraphicsApp(1024, 768, "Robot Simulation") { + + // Create a menu with 2 buttons for pausing and restarting the simulation. + nanogui::FormHelper *gui = new nanogui::FormHelper(this); + nanogui::ref window = + gui->addWindow(Eigen::Vector2i(10, 10), "Simulation Controls"); + pause_btn_ = + gui->addButton("Pause", std::bind(&RobotViewer::OnPauseBtnPressed, this)); + gui->addButton("Restart", std::bind(&RobotViewer::OnRestartBtnPressed, this)); + + performLayout(); + paused_ = false; + + // Instantiate RobotLand, which holds 2 robots and an obstacle. + robot_land_ = new RobotLand(); +} + +RobotViewer::~RobotViewer() { delete robot_land_; } + +/******************************************************************************* + * Member Functions + ******************************************************************************/ +void RobotViewer::UpdateSimulation(double dt) { + + // This is called at each pass through nanogui::mainloop. + // It sends the time change dt to robot_land_ to advance the simulation. + if (!paused_) { + robot_land_->advance_time(dt); + } +} + +// The handlers for the menu buttons ... +void RobotViewer::OnRestartBtnPressed() { robot_land_->set_time(0.0); } + +void RobotViewer::OnPauseBtnPressed() { + paused_ = !paused_; + if (paused_) { + pause_btn_->setCaption("Play"); + } else { + pause_btn_->setCaption("Pause"); + } +} + +// The standard mouse and keyboard event callback functions +void RobotViewer::OnMouseMove(int x, int y) { + std::cout << "Mouse moved to (" << x << ", " << y << ")" << std::endl; +} + +void RobotViewer::OnLeftMouseDown(int x, int y) { + std::cout << "Left mouse button DOWN (" << x << ", " << y << ")" << std::endl; +} + +void RobotViewer::OnLeftMouseUp(int x, int y) { + std::cout << "Left mouse button UP (" << x << ", " << y << ")" << std::endl; +} + +void RobotViewer::OnRightMouseDown(int x, int y) { + std::cout << "Right mouse button DOWN (" << x << ", " << y << ")" + << std::endl; +} + +void RobotViewer::OnRightMouseUp(int x, int y) { + std::cout << "Right mouse button UP (" << x << ", " << y << ")" << std::endl; +} + +void RobotViewer::OnKeyDown(const char *c, int modifiers) { + std::cout << "Key DOWN (" << c << ") modifiers=" << modifiers << std::endl; +} + +void RobotViewer::OnKeyUp(const char *c, int modifiers) { + std::cout << "Key UP (" << c << ") modifiers=" << modifiers << std::endl; +} + +void RobotViewer::OnSpecialKeyDown(int key, int scancode, int modifiers) { + std::cout << "Special Key DOWN key=" << key << " scancode=" << scancode + << " modifiers=" << modifiers << std::endl; +} + +void RobotViewer::OnSpecialKeyUp(int key, int scancode, int modifiers) { + std::cout << "Special Key UP key=" << key << " scancode=" << scancode + << " modifiers=" << modifiers << std::endl; +} + +// this function requires an active nanovg drawing context (ctx), +// so it should probably only be called from within DrawUsingNanoVG() +void RobotViewer::DrawRobot(NVGcontext *ctx, int id, double xpos, double ypos, + double xvel, double yvel, double rad) { + // translate and rotate all graphics calls that follow so that they are + // centered + // at the position and heading for this robot + + // ********* + // IMPORTANT: the nvgSave(ctx) saves the current origin of the coordinate + // system. When you translate and rotate, it modifies the origin. Any + // subsequent reference to {x,y} is relative to that origin. + // The nvgSave needs to be paired with nvgRestore, as that is what returns + // the origin to its saved position. Notice these can be nested. + // ********* + + // Put the origin of the coordinate system at the center of the robot, and + // orient the x-axis to align with the velocity (i.e. heading) of the robot. + nvgSave(ctx); + nvgTranslate(ctx, xpos, ypos); + double angle = std::atan2(yvel, xvel); + nvgRotate(ctx, angle); + + // robot's circle + nvgBeginPath(ctx); + nvgCircle(ctx, 0.0, 0.0, rad); + nvgFillColor(ctx, nvgRGBA(200, 200, 200, 255)); + nvgFill(ctx); + nvgStrokeColor(ctx, nvgRGBA(0, 0, 0, 255)); + nvgStroke(ctx); + + // robot id text label + nvgSave(ctx); + nvgRotate(ctx, M_PI / 2.0); + nvgFillColor(ctx, nvgRGBA(0, 0, 0, 255)); + std::string text = "Robot " + std::to_string(id); + nvgText(ctx, 0.0, 10.0, text.c_str(), NULL); + nvgRestore(ctx); + + nvgRestore(ctx); +} + +// this function requires an active nanovg drawing context (ctx), +// so it should probably only be called from with DrawUsingNanoVG() +void RobotViewer::DrawRobotSensors(NVGcontext *ctx, int id, double xpos, + double ypos, double xvel, double yvel, + double rad, double sensor_angle, + double sensor_dist) { + // translate and rotate all graphics calls that follow so that they are + // centered at the position and heading for this robot + nvgSave(ctx); + nvgTranslate(ctx, xpos, ypos); + double angle = std::atan2(yvel, xvel); + nvgRotate(ctx, angle); + + // sensor cone outline + nvgSave(ctx); + nvgRotate(ctx, 0.5 * sensor_angle); + nvgBeginPath(ctx); + nvgMoveTo(ctx, 0.0, 0.0); + nvgLineTo(ctx, sensor_dist, 0.0); + nvgArc(ctx, 0.0, 0.0, sensor_dist, 0.0, -sensor_angle, NVG_CCW); + nvgLineTo(ctx, 0.0, 0.0); + nvgStrokeColor(ctx, nvgRGBA(0, 0, 0, 100)); + nvgStroke(ctx); + nvgRestore(ctx); + + // blue for right sensor cone + nvgSave(ctx); + nvgRotate(ctx, 0.5 * sensor_angle); + nvgBeginPath(ctx); + nvgMoveTo(ctx, 0.0, 0.0); + nvgLineTo(ctx, sensor_dist, 0.0); + nvgArc(ctx, 0.0, 0.0, sensor_dist, 0.0, -0.5 * sensor_angle, NVG_CCW); + nvgLineTo(ctx, 0.0, 0.0); + nvgFillColor(ctx, nvgRGBA(100, 100, 255, 150)); + nvgFill(ctx); + nvgRestore(ctx); + + // yellow for left sensor cone + nvgSave(ctx); + nvgBeginPath(ctx); + nvgMoveTo(ctx, 0.0, 0.0); + nvgLineTo(ctx, sensor_dist, 0.0); + nvgArc(ctx, 0.0, 0.0, sensor_dist, 0.0, -0.5 * sensor_angle, NVG_CCW); + nvgLineTo(ctx, 0.0, 0.0); + nvgFillColor(ctx, nvgRGBA(255, 255, 100, 150)); + nvgFill(ctx); + nvgRestore(ctx); + + nvgRestore(ctx); +} + +// This function requires an active nanovg drawing context (ctx), +// so it should probably only be called from with DrawUsingNanoVG() +void RobotViewer::DrawObstacle(NVGcontext *ctx, int id, double xpos, + double ypos, double rad) { + nvgBeginPath(ctx); + nvgCircle(ctx, xpos, ypos, rad); + nvgFillColor(ctx, nvgRGBA(255, 100, 100, 255)); + nvgFill(ctx); + nvgStrokeColor(ctx, nvgRGBA(0, 0, 0, 255)); + nvgStroke(ctx); + + nvgFillColor(ctx, nvgRGBA(0, 0, 0, 255)); + std::string text = "Obstacle " + std::to_string(id); + nvgText(ctx, xpos, ypos, text.c_str(), NULL); +} + +void RobotViewer::DrawUsingNanoVG(NVGcontext *ctx) { + // initialize text rendering settings + nvgFontSize(ctx, 18.0f); + nvgFontFace(ctx, "sans-bold"); + nvgTextAlign(ctx, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); + + // Draw each obstacle in robot land + for (int i = 0; i < robot_land_->get_num_obstacles(); i++) { + double x, y; + robot_land_->get_obstacle_pos(i, &x, &y); + + DrawObstacle(ctx, i, x, y, robot_land_->get_obstacle_radius(i)); + } + + // Draw each robot in robot land + for (int i = 0; i < robot_land_->get_num_robots(); i++) { + double xpos, ypos; + robot_land_->get_robot_pos(i, &xpos, &ypos); + + double xvel, yvel; + robot_land_->get_robot_vel(i, &xvel, &yvel); + + DrawRobot(ctx, i, xpos, ypos, xvel, yvel, robot_land_->get_robot_radius(i)); + } + + // Draw the field-of-view for the robot sensors + // Sensor beams must be drawn last because they are semi transparent + for (int i = 0; i < robot_land_->get_num_robots(); i++) { + double xpos, ypos; + robot_land_->get_robot_pos(i, &xpos, &ypos); + + double xvel, yvel; + robot_land_->get_robot_vel(i, &xvel, &yvel); + + DrawRobotSensors(ctx, i, xpos, ypos, xvel, yvel, + robot_land_->get_robot_radius(i), + robot_land_->get_robot_sensor_angle(i), + robot_land_->get_robot_sensor_distance(i)); + } +} + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab04/src/robot_viewer.h b/class-repo-public/labs/lab04/src/robot_viewer.h new file mode 100755 index 0000000..c5eeb60 --- /dev/null +++ b/class-repo-public/labs/lab04/src/robot_viewer.h @@ -0,0 +1,220 @@ +/** + * @file robot_viewer.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ROBOT_VIEWER_H_ +#define SRC_ROBOT_VIEWER_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/robot_land.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief An application that uses the cs3081 SimpleGraphics library to open up + * a window that includes a few buttons for controlling the simulation and can + * be used to draw circles and other computer graphics. + * + * After constructing a new RobotViewer, call Run() to start and run the + * application. Run() will not return until the application window is closed. + * Make sure that you call cs3081::InitGraphics() before creating the + * RobotViewer app. Example: + * + * ``` + * int main(int argc, char **argv) { + * cs3081::InitGraphics(); + * RobotViewer *app = new RobotViewer(); + * app->Run(); + * cs3081::ShutdownGraphics(); + * return 0; + * } + * ``` + * + * While the window is open \ref UpdateSimulation() will be called repeatedly, + * once per frame. Fill this in to update your simulation or perform any other + * processing that should happen over time as the simulation progresses. + * + * Fill in the On*() methods as desired to respond to user input events. + * + * Fill in the Draw*() methods to draw graphics to the screen using + * either the nanovg library or raw OpenGL. + */ +class RobotViewer : public csci3081::GraphicsApp { + public: + RobotViewer(); + ~RobotViewer(); + + void UpdateSimulation(double dt); + + /** + * @brief Handle the user pressing the restart button on the GUI. + */ + void OnRestartBtnPressed(); + + /** + * @brief Handle the user pressing the pause button on the GUI. + */ + void OnPauseBtnPressed(); + + /** + * @brief Called each time the mouse moves on the screen within the GUI + * window. + * + * Origin is at the lower left of the window. + * + * @param[in] x X position of the cursor. + * @param[in] y Y position of the cursor. + */ + void OnMouseMove(int x, int y); + + /** + * @brief Called each time the left mouse button is clicked. + * + * Origin is at the lower left of the window. + * + * @param[in] x The X position of the click. + * @param[in] y The Y position of the click. + */ + void OnLeftMouseDown(int x, int y); + + /** + * @brief Called each time the left mouse button is released. + * + * Origin is at the lower left of the window. + * + * @param[in] x The X position of the release. + * @param[in] y The Y position of the release. + */ + void OnLeftMouseUp(int x, int y); + + /** + * @brief Called each time the right mouse button is clicked. + * + * Origin is at the lower left of the window. + * + * @param[in] x The X position of the click. + * @param[in] y The Y position of the click. + */ + + void OnRightMouseDown(int x, int y); + + /** + * @brief Called each time the right mouse button is released. + * + * Origin is at the lower left of the window. + * + * @param[in] x The X position of the release. + * @param[in] y The Y position of the release. + */ + void OnRightMouseUp(int x, int y); + + /** + * @brief Called each time a character key is pressed. + * + * @param[in] c Character representing a key that was pressed. + * @param[in] modifiers Any modifier keys that were also pressed. + */ + void OnKeyDown(const char *c, int modifiers); + + /** + * @brief Called each time a character key is released. + * + * @param[in] c Character representing a key that was released. + * @param[in] modifiers Any modifier keys that were held with the key. + */ + void OnKeyUp(const char *c, int modifiers); + + /** + * @brief Called each time a special (non-alphabetic) key is pressed. + * + * @param[in] key The key that was pressed. + * @param[in] scancode The scancode corresponding to the key. + * @param[in] modifiers Any modifiers that were also pressed. + */ + void OnSpecialKeyDown(int key, int scancode, int modifiers); + + /** + * @brief Called each time a special (non-alphabetic) key is released. + * + * @param[in] key The key that was released. + * @param[in] scancode The scancode corresponding to the key. + * @param[in] modifiers Any modifiers that were also pressed. + */ + void OnSpecialKeyUp(int key, int scancode, int modifiers); + + /** + * @brief Draw the arena with all robots, obstacles using nanogui. + * + * @param[in] ctx Context for nanogui. + */ + void DrawUsingNanoVG(NVGcontext *ctx); + + /** + * @brief Draw using OpenGL. This callback had to be defined, but we are doing + * all drawing with nanovg in this application, so it is empty. + */ + void DrawUsingOpenGL(void) {} + + private: + /** + * @brief Draw a robot using nanogui. + * + * @param[in] ctx The nanogui context. + * @param[in] id The ID of the robot. + * @param[in] xpos The X position of the robot. + * @param[in] ypos The Y position of the robot. + * @param[in] xvel The X velocity of the robot. + * @param[in] yvel The Y velocity of the robot. + * @param[in] rad The radius of the robot. + */ + void DrawRobot(NVGcontext *ctx, int id, double xpos, double ypos, double xvel, + double yvel, double rad); + + /** + * @brief Draw the sensors from a robot using nanogui. + * + * @param[in] ctx The nanogui context. + * @param[in] id The ID of the robot. + * @param[in] xpos The X position of the robot. + * @param[in] ypos The Y position of the robot. + * @param[in] xvel The X velocity of the robot. + * @param[in] yvel The Y velocity of the robot. + * @param[in] rad The radius of the robot. + * @param[in] sensor_angle The angle of the robot's sensor. + * @param[in] sensor_dist The range of the sensor. + */ + void DrawRobotSensors(NVGcontext *ctx, int id, double xpos, double ypos, + double xvel, double yvel, double rad, + double sensor_angle, double sensor_dist); + + /** + * @brief Draw an obostacle in the arena using nanogui. + * + * @param[in] ctx The nanogui context. + * @param[in] id The ID of the obstacle. + * @param[in] xpos The X position of the obstacle. + * @param[in] ypos The Y position of the obstacle. + * @param[in] rad The radius of the obstacle. + */ + void DrawObstacle(NVGcontext *ctx, int id, double xpos, double ypos, + double rad); + + RobotLand *robot_land_; + bool paused_; + nanogui::Button *pause_btn_; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_ROBOT_VIEWER_H_ */ diff --git a/class-repo-public/labs/lab05/README.md b/class-repo-public/labs/lab05/README.md new file mode 100755 index 0000000..fc735b6 --- /dev/null +++ b/class-repo-public/labs/lab05/README.md @@ -0,0 +1,66 @@ +# Google Style Guide Compliance + +**REMEMBER TO SIGN IN TODAY!!!** + +https://google.github.io/styleguide/cppguide.html + +A style guide sets guidelines for how files, classes, functions, and variables +should be named. It also sets guidelines for white space, line lengths, and +commenting within the code. By standardizing naming conventions and layout, it +is easier to integrate code from various programmers in a cohesive way that +is readable and understandable by everyone. Going forward, all code submitted +for this course must be compliant with the Google Style Guide. As stated before, +not everyone is going to agree on every Style aesthetically, but it is important to have +a standard that everyone programs to. + +As stated in the email, the code was recently modified to fix most of the issues +related to non-compliance. If you have not done so, you will want to clone the +latest version from the class repo and copy it into your personal repo, +overwriting the previous one. + +To fix the errors, you will first need to identify them with cpplint. The +cpplint.py file has been provided in the repo for convenience, but you can +get it direct from the repository: https://github.com/google/styleguide. If you are working on your own machine, it +is recommended that you install pip3 if not installed, then install it with + +``` +pip3 install cpplint +``` + +On the cselabs machines, you call the file directly. Place the 'cpplint.py' file +somewhere convenient to reference. For example, you might put it in your home +directory. Then you pass it a file or collection of files. For example, if +the cpplint file is in `~` and it is being called from the `iteration1` folder, +the following command will check all `.cc` and `.h` files in src. + +``` +~/cpplint.py src/*.cc src/*.h +``` + +You should get a report of all of the errors that includes the file, line +number, and a short description of the problem. If you need guidance in what +is expected, you can consult the Style Guide: +https://google.github.io/styleguide/cppguide.html. Notice that the errors range +from the placement of braces to the use of the keyword `explicit` to commenting. + +There is one issue that arises with the header guards. With the above call to +`cpplint`, the header guard needs the full path prefix of +`PROJECT_ITERATION1_SRC`, but if you use this command instead: + +``` +~/cpplint.py --repository=. src/*.cc src/*.h +``` + +the header guard should not have `PROJECT_ITERATION1` in the prefix. To change +this, open up one of the files in atom and the whole project will load. Select +the pulldown menu option "Find in Project" under "Find". + +``` +Find: PROJECT_ITERATION1_SRC +Replace with: SRC +``` +for a quick universal change throughout the code. + +As part of assessment, all files will be run through cpplint. + +If you have time left in lab, you can work on the UML assignment, or answer "Engaging the Code" questions found in the project folder. diff --git a/class-repo-public/labs/lab06/DateCrash/.DS_Store b/class-repo-public/labs/lab06/DateCrash/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/class-repo-public/labs/lab06/DateCrash/.DS_Store differ diff --git a/class-repo-public/labs/lab06/DateCrash/Date.cpp b/class-repo-public/labs/lab06/DateCrash/Date.cpp new file mode 100644 index 0000000..6aaeb8e --- /dev/null +++ b/class-repo-public/labs/lab06/DateCrash/Date.cpp @@ -0,0 +1,170 @@ +#include "Date.h" +#include "Debug.h" +#include "Exceptions.h" + +#include +#include +#include + +using namespace std ; + +bool is_valid_date (int y, int m, int d) ; + +Date::Date() { + if (DEBUG) + cout << "... calling default constructor for Date..." << endl ; + year = 1900; month = 1; day = 1; +} + +Date::Date(int y, int m, int d) { + if (DEBUG) + cout << "... calling parameterize constructor for Date..." << endl ; + + //assert ( y >= 1900 ) + //assert ( 0 <= m && m <= 12 ) + //assert ( 0 <= d && d <= 31 ) + + year = y; month = m; day = d; +} + +Date::~Date() { + if (DEBUG) + cout << "... calling destructor for Date \"" + << show() << "\" ..." << endl ; +} + +Date Date::copy() const { + Date myDateCopy(year,month,day); +} + +bool Date::equals(const Date &d) const { + assert ( d.year >= 1900 ) + assert ( 0 <= d.month && d.month <= 12 ) + assert ( 0 <= d.day && d.day <= 31 ) + + return year == d.year && month == d.month && + day == d.day ; +} + +string Date::show() const { + ostringstream s ; + + switch (month) { + case 1 : s << "January" ; break; + case 2 : s << "February" ; break; + case 3 : s << "March" ; break; + case 4 : s << "April" ; break; + case 5 : s << "May" ; break; + case 6 : s << "June" ; break; + case 7 : s << "July" ; break; + case 8 : s << "August" ; break; + case 9 : s << "September"; break; + case 10 : s << "October" ; break; + case 11 : s << "November" ; break; + case 12 : s << "December" ; break; + default : s << "ERROR: invalid month" ; + } + + s << " " << day << ", " << year ; + + return s.str() ; +} + + + + +bool is_valid_date (int y, int m, int d) { + int month_length[] = { 0, + 31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31 } ; + + if ( y >= 1900 && + m >= 1 && + m <= 12 && + d >= 1 && + d <= month_length[m] ) { + return true ; + } + else{ + return false ; + } +} + +// assert (is_valid_date (d.year, d.month, d.day)) + + + + + +void Date::day_inc(int n) { + int month_length[] = { 31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31 } ; + + assert (n >= 0) ; + + if (n + day > month_length[month]) { + ++ month ; + day = 1 ; + n = n - (month_length[month] - day) ; + } + + while (n > month_length[month]) { + ++ month ; + n = n - month_length[month] ; + } + day = day + n ; + + assert (is_valid_date(year, month, day) ) ; +} + + + +// Is this date "before" the parameter Date d ? +bool Date::before(const Date &d) const { + + if ( ! is_valid_date(d.year, d.month, d.day) ) + throw InvalidData ("Invalid Date passed to Date::before.") ; + + if (year < d.year) + return true ; + else + if (year > d.year) + return false ; + else + if (month < d.month) + return true ; + else + if (month > d.month) + return false ; + else + return day < d.day ; +} + + + +// Factories for Date + +Date mkDate(int y, int m, int d) { + if (DEBUG) + cout << "... calling mkDate to create Date ..." << endl ; + + if ( ! is_valid_date(y, m, d) ) + throw InvalidData ("Invalid Date passed to Date::before.") ; + + Date date(y,m,d) ; + + return date ; +} + +Date * mkDate_ptr(int y, int m, int d) { + if (DEBUG) + cout << "... calling mkDate to create Date ..." << endl ; + + if ( ! is_valid_date(y, m, d) ) + throw InvalidData ("Invalid Date passed to Date::before.") ; + + Date *date = new Date (y,m,d) ; + + return date ; +} + diff --git a/class-repo-public/labs/lab06/DateCrash/Date.h b/class-repo-public/labs/lab06/DateCrash/Date.h new file mode 100644 index 0000000..06e2250 --- /dev/null +++ b/class-repo-public/labs/lab06/DateCrash/Date.h @@ -0,0 +1,51 @@ +#ifndef DATE_H +#define DATE_H + +#include "Exceptions.h" + +#include + +class Date { +public: + Date() ; + Date(int, int, int) ; + ~Date() ; + + std::string show() const ; + + bool equals(const Date &) const ; + + bool before(const Date &) const ; + + void day_inc(int) ; + + Date copy() const ; + +private: + int year, month, day ; +}; + + + + + + + + + + + + + + + + + + + + + +Date mkDate(int y, int m, int d) ; +Date * mkDate_ptr(int y, int m, int d) ; + +#endif diff --git a/class-repo-public/labs/lab06/DateCrash/Debug.h b/class-repo-public/labs/lab06/DateCrash/Debug.h new file mode 100644 index 0000000..7b45f2f --- /dev/null +++ b/class-repo-public/labs/lab06/DateCrash/Debug.h @@ -0,0 +1,14 @@ +#ifndef DEBUG_H +#define DEBUG_H + +#include + +#define DEBUG 0 + +#define assert(e) if (!(e)) { \ + cout << "Invariant \"" << #e \ + << "\" on line " \ + << __LINE__ << " fails.\n" ; \ + exit(1) ;} + +#endif /* DEBUG_H */ diff --git a/class-repo-public/labs/lab06/DateCrash/Exceptions.cpp b/class-repo-public/labs/lab06/DateCrash/Exceptions.cpp new file mode 100644 index 0000000..17b9542 --- /dev/null +++ b/class-repo-public/labs/lab06/DateCrash/Exceptions.cpp @@ -0,0 +1,22 @@ +/* Exceptions.cpp + + Author: Eric Van Wyk + Date: Oct 20, 2009. +*/ + +#include "Exceptions.h" + +#include + +using namespace std ; + +InvalidData::InvalidData(string _msg) { + msg = _msg ; +} + +InvalidData::~InvalidData() { +} + +string InvalidData::get_msg() { + return msg ; +} diff --git a/class-repo-public/labs/lab06/DateCrash/Exceptions.h b/class-repo-public/labs/lab06/DateCrash/Exceptions.h new file mode 100644 index 0000000..6e3f73a --- /dev/null +++ b/class-repo-public/labs/lab06/DateCrash/Exceptions.h @@ -0,0 +1,21 @@ +#ifndef EXCEPTIONS_H +#define EXCEPTIONS_H + +/* Author: Eric Van Wyk + Date created: October 20, 2009. + */ + +#include + +class InvalidData { +public: + InvalidData (std::string) ; + ~InvalidData () ; + + std::string get_msg() ; + +private: + std::string msg ; +} ; + +#endif /* EXCEPTIONS_H */ diff --git a/class-repo-public/labs/lab06/DateCrash/Makefile b/class-repo-public/labs/lab06/DateCrash/Makefile new file mode 100644 index 0000000..cc5be8f --- /dev/null +++ b/class-repo-public/labs/lab06/DateCrash/Makefile @@ -0,0 +1,12 @@ + +date: main.cpp Date.o Exceptions.o + g++ -g -Wall -o date main.cpp Date.o Exceptions.o + +Date.o: Date.cpp Date.h Debug.h + g++ -g -Wall -c -o Date.o Date.cpp + +Exceptions.o: Exceptions.cpp Exceptions.h + g++ -g -Wall -c -o Exceptions.o Exceptions.cpp + +clean: + rm -f date *.o \ No newline at end of file diff --git a/class-repo-public/labs/lab06/DateCrash/main.cpp b/class-repo-public/labs/lab06/DateCrash/main.cpp new file mode 100644 index 0000000..a7c1e0b --- /dev/null +++ b/class-repo-public/labs/lab06/DateCrash/main.cpp @@ -0,0 +1,59 @@ +#include "Date.h" + +#include +#include + +using namespace std ; + +void doInterestingThing(int yr) { + //variables used in this method + int* array_len; + Date d1; + Date d2; + Date d3; + Date* birthday_list; + + cout << " ... A set of Dates ... " << endl; + + array_len = NULL; + d2 = Date(yr,4,19); + d3 = d2.copy(); + *array_len = 4; + + cout << "default date is: " << d1.show() << endl ; + + cout << "date d2 is: " << d2.show() << endl ; + + cout << "date d3 is: " << d3.show() << endl; + + cout << " ... Now print out 4 dates in reverse time order ... " << endl; + + birthday_list = new Date[*array_len]; + + //accumulate the all dates January 1, from 1990 to 1990+array_len + for (int i=0; i<*array_len; i++) { + birthday_list[i] = Date(1990+i,1,1); + } + + // now show the dates accumulated in reverse order + for (int i=0; i<*array_len; --i) { + cout << "date is: " << birthday_list[i].show() << endl; + } + + // now set the dates to January 2 + for (int i=0; i<=*array_len; i++) { + birthday_list[i] = Date(1990+i,1,2); + } + + cout << "the first date is: " << birthday_list[0].show() << endl; + +} + +int main() { + + doInterestingThing(1998); + + doInterestingThing(1999); + + cout << endl << "... Goodbye ..." << endl ; +} diff --git a/class-repo-public/labs/lab06/lab06.md b/class-repo-public/labs/lab06/lab06.md new file mode 100644 index 0000000..c0c3583 --- /dev/null +++ b/class-repo-public/labs/lab06/lab06.md @@ -0,0 +1,208 @@ +# Lab Session 6: gdb Backtraces +#### Due date: Sunday, October 15th at 10:00 p.m. + +
+ +In this lab, you will learn how to +- generate the automated doxygen documentation. +- use gdb backtracing to debug C++ programs + +Please follow the setup instructions below. + +#### Setup +1. Log into a computer in the lab. +2. Open a terminal window. +3. Navigate to your 3081 class repo. +4. _pull_ to get lab06 base code +5. Copy the directory into your labs directory in your individual class repo. +6. Push changes to your student repo. + +
+ +### Doxygen + +**Reference**: http://www.stack.nl/~dimitri/doxygen/manual/commands.html#cmdmainpage + +Doxygen scans header and source files for specially formated comments to assemble web page documentation, UML, and or pdfs (via latex). The configuration file was already included in the project folder of the class repo. + +Your first task is to generate the documents. + +- Navigate to /project/iteration1. +- Run the command: `$ doxygen docs/Doxyfile` +> Make sure you are in the iteration1 folder. The _docs_ folder already exists, and when you run doxygen from the iteration1 folder, that existing directory will get populated with the documentation. +- Look in `/docs` and you should now see a folder `html` +- In the html folder, open _index.html_ with your favorite browser. + +Notice that the main page is blank. Let's fix that. + +- Navigate to your `/src`. Create a file 'mainpage.h' +- Put this in the file: + +``` +/*! \mainpage CSCI3081 Robot Simulator Project + * + * \section intro_sec Introduction + * + * This is the introduction. + * + * \section install_sec Installation + * + * \subsection libsimplegraphics Installing and Using libsimplegraphics + * + * etc... + */ + ``` + +- Replace "This is the introduction" with the paragraph that you wrote and included with your UML submission. +- Save and close. +- Navigate back to `/iteration` +- Run the command `doxygen docs/Doxyfile` and see the results in index.html +- Push your changes. + +
+ +### Debugging with GDB + +1. First, build the example program with _make_. +2. Then, run the program: + ```` + ./date. + ```` +See that the program quits in a Segmentation Fault. In the following steps, we will try to diagnose the problem. + +3. Start a debugging session on an executable file by typing _gdb -YOUR-EXECUTABLE-NAME-HERE_ + ```` + gdb date + ```` + +4. Once in the debugger session (the prompt will change to (**gdb**)). Then, execute the program with _run_. +5. The program _date_ will run and crash with the following output (your output may look slightly different): + ```` + (gdb) run + Starting program: /home/it11/half0032/workspace/S11C3081-user110/lab08/DateCrash/date + ... A set of Dates ... + + Program received signal SIGSEGV, Segmentation fault. + 0x0000000000401349 in doInterestingThing (yr=1998) at main.cpp:21 + 21 *array_len = 4; + ```` + +6. Attempt to discover where (and why) in the provided code, the fault is occurring. +In this case, the line number causing the error is provided. If the location is not in the provided code, use the _gdb bt_ command (bt stands for "backtrace") to determine where the fault occurs. Type _bt_ now to see the where the execution stopped in each method: + ```` + (gdb) bt + + #0 0x0000000000401349 in doInterestingThing (yr=1998) at main.cpp:21 + #1 0x00000000004018bc in main () at main.cpp:54 + ```` + +The backtrace shows that execution stopped in the method _doInterestingThing()_. This method was called from _main()_ on line 54 of _main.cpp_. + +The above backtrace indicates that the error happened on line 21 of _main.cpp_. Edit the file, and look at line 21. Why is this causing an error? + +Fix this error by modifying the code in _main.cpp_. Recompile _date_ with _make_, and run the program again. + +There are several causes of "segentation faults" in this program. Use _gdb_ to help to identify and fix each issue until it runs correctly (see step 7). You might find the commands _up_ and _print_ particularly useful. + +7. When your program is running correctly, running should output the following: + ```` + ... A set of Dates ... + default date is: January 1, 1900 + date d2 is: April 19, 1998 + date d3 is: April 19, 1998 + ... Now print out 4 dates in reverse time order ... + date is: January 1, 1993 + date is: January 1, 1992 + date is: January 1, 1991 + date is: January 1, 1990 + the first date is: January 2, 1990 + ... A set of Dates ... + default date is: January 1, 1900 + date d2 is: April 19, 1999 + date d3 is: April 19, 1999 + ... Now print out 4 dates in reverse time order ... + date is: January 1, 1993 + date is: January 1, 1992 + date is: January 1, 1991 + date is: January 1, 1990 + the first date is: January 2, 1990 + + ... Goodbye ... + ```` + +#### Turning in Lab 6 +8. Push changes up to your student repo. + +#### Assessment +- mainpage.h exists +- generated doxygen files are not in the repo (shouldn't have to do anything, as the .gitignore already set up for this). +- date executable runs without crashing +- no object code or executable is pushed to the repo + + + +#### References +Using _gdb_ in your own programs +1. Make sure that the debugging symbols are being generated when you compile your programs. +To do this edit the _Makefile_. For all lines calling _g++_ add the flag __-g__. + ```` + g++ -g -o WHATEVER_FILES_COME_AFTER_GO_HERE + ```` +2. Then do a _make clean_ and recompile. Then you can run _gdb_ on your executable. + +#### Commands +The following is a basic set of commands for use with _gdb_. We have highlighted the ones that will probably be most useful for you. + +###### unix level commands -- entered at the $ prompt: + +| Command | Description | +|---|---| +| man gdb | to get help on gdb at the unix command level | +| g++ -g -o program filename.cpp | to compile & link with the debug (-g) option | +| gdb _ProgramName_ | to execute the debugger on executable _ProgramName_ | + + +###### Basic gdb commands -- entered after the (gdb) prompt: +| Command | Description | +|---|---| +| help | to display a list of gdb commands | +| help _command_ | to get help on a specified gdb command | +|run | to run/execute the program starting from the beginning | +| backtrace | show the current stack (which function is being executed) | +| up | move up in the backtrace stack | +| down | move down in the backtrace stack | +| continue | to resume running/executing the program | +| next | to execute the current statement and stop at the next statement | +| step | same as next, but step into a function | +| list xx | list source lines starting at line xx | +| list | to list the next source lines | +| list xx,yy | to list sources lines from line xx to line yy | +| list filename:xx | to list source lines in the specified file starting at line xx | +| quit | to quit gdb and revert to the unix command level | +| break _functionname_ | to set a breakpoint at the start of a function (set this before typing run) | +|break classname::functionname | to set a breakpoint at the start of a member function | +| break filename:xx | to set a breakpoint at line xx in the specified file | +| break xx | to set a breakpoint at line xx in the current file | +| break 1 | to set a breakpoint at the first line in the current file (declaration or executable statement) | +| info break | to list all breakpoints (including those disabled); breakpoints are numbered #1, #2, #3, etc. | +| disable xx | to disable breakpoint #xx | +| enable xx | to enable breakpoint #xx | +| print v1 | to print the value of a specified variable | +| info source | to show the name of the current source file | +| info locals | to show local variables in the current frame | +| info sources | to list the name of all source files in use | +| set variable = value | to assign a new value to a specified variable | +| (return) | to re-execute the previous gdb command; this is particularly useful if the previous gdb command was next or step | + +You can also execute most gdb commands by entering only the first letter of the command. + +* The original source for this list is [here](https://www.bgsu.edu/arts-and-sciences/computer-science/cs-documentation/using-the-gdb-debugger.html). + +##### GDB Resources +There are many places to find additional documentation on GDB: + +Command reference: (http://www.yolinux.com/TUTORIALS/GDB-Commands.html) + +Another tutorial: (http://www.cs.cmu.edu/~gilpin/tutorial/) + +Search for "gdb tutorial" on the web: (http://lmgtfy.com/?q=gdb+tutorial) diff --git a/class-repo-public/labs/lab07/.gitignore b/class-repo-public/labs/lab07/.gitignore new file mode 100755 index 0000000..8c194c3 --- /dev/null +++ b/class-repo-public/labs/lab07/.gitignore @@ -0,0 +1,52 @@ +# Compiled source # +################### +*.com +*.class +*.dll +*.exe +*.o +*.so + +# Temporary files # +################### +*.swp +*.swo +*~ + +# Packages # +############ +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite + +# OS generated files # +###################### +.DS_Store* +ehthumbs.db +Icon? +Thumbs.db + +# Files used by/generated by Emacs # +#################################### +.\#* +GPATH +GRTAGS +GTAGS +.clang_complete +compile_options.json + +# Build process +build +doc/html +doc/latex diff --git a/class-repo-public/labs/lab07/Intro.md b/class-repo-public/labs/lab07/Intro.md new file mode 100755 index 0000000..71b0194 --- /dev/null +++ b/class-repo-public/labs/lab07/Intro.md @@ -0,0 +1,103 @@ +# Lab07 Google Unit Test +#### Due date: Wednesday, October 25th at 11:55 p.m. +#### Remember to sign in today. +*** + +## Objective: +1. To master the skills of writing and running your own Google unit tests +2. Understand how to run the unittests so that you can use the project's unittests to thoroughly test your iteration 1 + requirements (i.e. priority 1, priority 2, and priority 3 coding changes) + +## Preparing for the Lab +1. Pull from the public-class-repo repository. +2. Copy the public-class-repo/labs/lab07 to your own local repository. + +## Materials: +In the lab07/src folder, we have +1. **QuackBehavior.h**, including class *QuackBehavior*, class *Quack*, class *Mute*, class *Squeak*, class *Honk*; +2. **FlyBehavior.h**, including class *FlyBehavior*, class *FlyWithWings*, class *NoFly*, class *FlyWithRocket*; +3. **Duck.h**, including class *Duck*, class *Mallard*, class *RubberDuck*. + +In the lab07/tests folder, we have three unit test files. They are Duck_Test_Example.cc, FlyBehavior_Test_Example.cc, and QuackBehavior_Test_Example.cc + +## Google Test Introduction: +Google C++ Testing Framework helps you write better C++ tests. No matter whether you work on Linux, Windows, or a Mac, if you write C++ code, Google Test can help you. For more information please refer to the following link: +https://github.com/google/googletest/blob/master/googletest/docs/Primer.md. + +For this lab, we have set up the environment on the **CSELab** computers, so you can run the tests directly on **CSELab** computers. We are using the same Google Framework as the project. You may see some source files in the lab07/src that are from the project. We do not use these but you could if you wanted to try and write a test for this code. + + +## Statements: + +1. **Test naming**: Please use TEST(classnameTest,methodname), e.g. *TEST(QuackTest, Constructor)*. + +2. **File naming**: Please use Filename_classname_unittest.cc, eg:*QuackBehavior_Quack_unittest.cc*. + +3. **Assertion type**: Unless the program should not proceed if the test fails, then use EXPECT, not ASSERT. + +4. **Comments**: If you have multiple assertions in a single test, then it would be good to add comments to the assertions fail, e.g. *EXPECT_STREQ("Quack at 10 decibels.",my_quack.quack().c_str())<< "FAIL:Quack Constructor!"*. The comment follows the test with a ** << "message goes here".** + +5. **Test scope**: Each test should only test a single method/function. In addition, if there is a lot of functionality within that function, it might even be appropriate to separate into different tests. + +6. **Test coverage**: Think carefully about what tests should be testing. + + +## Google Unit Test Examples: + +Before trying to write your own unit tests, the following are three simple examples of unit tests that have already been written for you: + +```c++ +TEST(RubberDuckTest,display){ + RubberDuck my_ralph; + string except_str="I am a Rubber Duck."; + ASSERT_STREQ(except_str.c_str(),my_ralph.display().c_str()) << "FAIL: display" ; +} +``` +In this unit test, we test the *RubberDuck* class and be more specific, we test its *display function*. + +```c++ +TEST(FlyBehaviorTest, fly) { + FlyBehavior my_flybehavior; + string expect_str="Generic Flying at 5 mph."; + EXPECT_STREQ(expect_str.c_str(),my_flybehavior.fly().c_str())<< "FAIL: fly function!"; +} +``` +In this unit test, we test the *FlyBehavior* class and be more specific, we test its *fly function*. + +```c++ +TEST(QuackTest, Constructor) { + Quack my_quack; + string expect_str="Quack at 10 decibels."; + EXPECT_STREQ(expect_str.c_str(),my_quack.quack().c_str())<<"FAIL: Quack Constructor!"; +} +``` +In this unit test, we test the *Quack* class and be more specific, we test its *constructor*. + +## How to run Google Unit Test: +1. You should be in the lab07 directory. +2. Run *make* +3. Change the directory to the /build/bin under the lab07 directory +4. Run the unittests file + + +## Your Assignments for this lab: +Now, you are ready to write your own tests! Write the following unit tests for this lab: +1. Write a unit test for class *Squeak*, it should test its constructor and the quack function. + +2. Write unit test for class *FlyWithRocket*, it should test its constructor and the fly function. + +3. Write unit test for class *Mallard*, it should test its constructor and display function. (Note: The type needs to be a pointer to a Duck to test polymorphism.) + +4. Write unit test for class *RubberDuck*, it should test its constructor and display function; + +5. Update the Makefile as needed in the /tests directory and run make from the /lab07 directory. + +6. Run the unittests file in the /lab07/build/bin directory to ensure everything passes the tests. If something fails, you will need to fix the code. + +7. Push your changes to GitHub. This should push up all new test .cc files and the Makefile changes. +``` +git add . +git commit -m “Submitting Lab07” +git push +``` + diff --git a/class-repo-public/labs/lab07/Makefile b/class-repo-public/labs/lab07/Makefile new file mode 100755 index 0000000..f4017df --- /dev/null +++ b/class-repo-public/labs/lab07/Makefile @@ -0,0 +1,28 @@ +### CSci-3081W Project Support Code Makefile ### + +# This is the main Makefile for the project. It provides easy access +# to building and testing the whole project, which requires running +# make in subdirectories. + +.PHONY: proj01 unittest docs clean + +# Build everything that can be built for this project +all: proj01 unittest + +# Build the bin/proj01 executable by running make in the project's src directory +proj01: + $(MAKE) -C src all + +# Build docs/html, docs/latex by running doxygen in the project's docs directory +docs: + @doxygen docs/Doxyfile + @open docs/html/index.html + +# Build the bin/unittest executable by running make in the project's tests directory +unittest: + $(MAKE) -C tests all + +# Clean everything that has been for a fresh start +clean: + $(MAKE) -C src clean + $(MAKE) -C tests clean diff --git a/class-repo-public/labs/lab07/src/Duck.h b/class-repo-public/labs/lab07/src/Duck.h new file mode 100644 index 0000000..04d2bc5 --- /dev/null +++ b/class-repo-public/labs/lab07/src/Duck.h @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +//#define DB_DEFAULT 10 +using namespace std; + + +//---------------------------------------------- +// THE DUCKS +#include "FlyBehavior.h" +#include "QuackBehavior.h" + +class Duck { +protected: + FlyBehavior *flyBehavior; + QuackBehavior *quackBehavior; +public: + Duck() { } + virtual string display() = 0; + string performFly() { return flyBehavior->fly(); } + string performQuack() { return quackBehavior->quack(); } + void setFlyBehavior(FlyBehavior *fb) {delete flyBehavior; flyBehavior = fb;} + void setQuackBehavior(QuackBehavior *qb) {delete quackBehavior; quackBehavior = qb;} +}; + +class Mallard : public Duck { +public: + Mallard() { + flyBehavior = new FlyWithWings(); + quackBehavior = new Quack(); + } + std::string display() { + std::string result_str="I am a Mallard."; + return result_str; + } + +}; + +class RubberDuck : public Duck { +public: + RubberDuck() { + flyBehavior = new NoFly(); + quackBehavior = new Squeak(2); + } + std::string display() { + std::string result_str="I am a Rubber Duck."; + return result_str; + } +}; diff --git a/class-repo-public/labs/lab07/src/FlyBehavior.h b/class-repo-public/labs/lab07/src/FlyBehavior.h new file mode 100755 index 0000000..594d798 --- /dev/null +++ b/class-repo-public/labs/lab07/src/FlyBehavior.h @@ -0,0 +1,73 @@ +#include +#include +#include +#include + +#define MPH_DEFAULT 5 + +using namespace std; + +//----------------------------------------------- +// FLYING + +// The FlyBehavior class is not polymorphic because we did not make this +// class abstract. Note that fly() is not a pure virtual function in this +// class (see the quack() up above that makes QuackBehavior abstract) +class FlyBehavior { +protected: + double milesPerHour; +public: + FlyBehavior() : milesPerHour(MPH_DEFAULT) {} + string fly() { + stringstream stream; + string fly_speed; + string result_str; + stream << milesPerHour; + stream >> fly_speed; + string str1="Generic Flying at"; + string str2="mph."; + result_str= str1+" "+fly_speed+" "+str2; + return result_str; + } +}; + +class FlyWithWings : public FlyBehavior { +public: + FlyWithWings() {} + string fly() { + stringstream stream; + string fly_speed; + string result_str; + stream << milesPerHour; + stream >> fly_speed; + string str1="Fly with wings at speed of"; + string str2="mph."; + result_str= str1+" "+fly_speed+" "+str2; + return result_str; + } +}; + +class NoFly : public FlyBehavior { +public: + NoFly() {} + string fly() { + string result_str="Cannot fly."; + return result_str; + } +}; + +class FlyWithRocket : public FlyBehavior { +public: + FlyWithRocket(){milesPerHour=20;} + string fly() { + stringstream stream; + string fly_speed; + string result_str; + stream << milesPerHour; + stream >> fly_speed; + string str1="Fly with wings at speed of"; + string str2="mph."; + result_str= str1+" "+fly_speed+" "+str2; + return result_str; + } +}; diff --git a/class-repo-public/labs/lab07/src/Makefile b/class-repo-public/labs/lab07/src/Makefile new file mode 100755 index 0000000..0442589 --- /dev/null +++ b/class-repo-public/labs/lab07/src/Makefile @@ -0,0 +1,151 @@ +### CSci-3081W Project Support Code Makefile ### + +# File History: This combines Prof. Keefe's Makefiles from past years +# with TA John Harwell's 3081W Makefiles from Fall 2016, which introduced +# auto-dependency generation and several other exciting features. + + +### Section 0: Change this when compiling on non-CSELabs machines ### + +# Path to pre-installed cs3081 support libraries (Google Test, libsimple_graphics, nanogui, ...) +CS3081DIR = /project/f17c3081 + + +### Section I: Definitions ### + +# Root of the source tree for the project (e.g., could be just . or ./src) +SRCDIR = . + +# Output directories for the build process +BUILDDIR = ../build +BINDIR = $(BUILDDIR)/bin +OBJDIR = $(BUILDDIR)/obj/src + +# The name of the executable to create +EXEFILE = $(BINDIR)/arenaviewer + +# The list of files to compile for this project. Defaults to all +# of the .cpp and .cc files in the source directory. (We use both .cpp +# and .cc in order to support two different popular naming conventions.) +SRCFILES = $(wildcard $(SRCDIR)/*.cpp) $(wildcard $(SRCDIR)/*.cc) + +# For each of the source files found above, replace .cpp (or .cc) with +# .o in order to generate the list of .o files make should create. +OBJFILES = $(notdir $(patsubst %.cpp,%.o,$(patsubst %.cc,%.o,$(SRCFILES)))) + + + +# Add -Idirname to add directories to the compiler search path for finding .h files +INCLUDEDIRS = -I.. -I$(SRCDIR) -isystem$(CS3081DIR)/include + +# Add -Ldirname to add directories to the linker search path for finding libraries +LIBDIRS = -L$(CS3081DIR)/lib + +# Add -llibname to link with external libraries +LIBS = -lsimple_graphics -lnanogui -Wl,-rpath,$(CS3081DIR)/lib + + + +# The command to run for the C++ compiler and linker +CXX = g++ + +# Arguments to pass to the C++ compiler. +# -c is required, it tells the compiler to output a .o file +# Optionally include -g to turn on debugging or include -O or -O2 to turn on optimizations instead +# Optionally include -Wall to turn on most warnings +CXXFLAGS = -g -W -Wall -Weffc++ -Wshadow -std=c++14 -Wsuggest-override -c $(INCLUDEDIRS) + +# Arguments to pass to the C++ linker, such as -L, but not -lfoo, which should go in LDLIBS +LDFLAGS = $(LIBDIRS) + +# Library names to pass to the C++ linker, such as -lfoo +LDLIBS = $(LIBS) + + + + +### Section II: Rules ### + + +# This is a list of "phony targets" -- targets that do not specify the name of a file. +# Rather they specify the name of a recipe to run whenever make is envoked with the target name. +.PHONY: clean all $(BINDIR) $(OBJDIR) + + +# The default target which will be run if the user just types "make" +all: $(EXEFILE) + +# This rule says that each .o file in $(OBJDIR)/ depends on the +# presence of the $(OBJDIR)/ directory. +$(addprefix $(OBJDIR)/, $(OBJFILES)): | $(OBJDIR) + +# And, this rule provides a recipe for creating that objdir. The same rule applies +# to the bindir, where the exe will be output. +$(OBJDIR) $(BINDIR): + @mkdir -p $@ + + + +# COMPILING (USING A PATTERN RULE): +# Since every .cpp (or .cc) file must be compiled into a .o, we will write this +# recipe using a pattern rule. Using this recipe, any file that matches the pattern +# $(SRCDIR)/filename.cpp can be turned into $(OBJDIR)/filename.o. +$(OBJDIR)/%.o: $(SRCDIR)/%.cpp + @echo "==== Auto-Generating Dependencies for $<. ====" + $(call make-depend-cxx,$<,$@,$(subst .o,.d,$@)) + @echo "==== Compiling $< into $@. ====" + $(CXX) $(CXXFLAGS) $(CXXLIBDIRS) -c -o $@ $< + +# The same thing will also work for files with a .cc extension +$(OBJDIR)/%.o: $(SRCDIR)/%.cc + @echo "==== Auto-Generating Dependencies for $<. ====" + $(call make-depend-cxx,$<,$@,$(subst .o,.d,$@)) + @echo "==== Compiling $< into $@. ====" + $(CXX) $(CXXFLAGS) $(CXXLIBDIRS) -c -o $@ $< + +# WITH AUTO-GENERATED DEPENDENCIES: +# Note that there are actually two steps to the compiling recipe above. The second +# step should be familiar, it just calls g++ to compile the .cpp into a .o. But, +# the first step is an advanced topic. It calls the custom function make-depend-cxx() +# defined below, which calls the g++ compiler with special flags (the -M* parts) +# that tell g++ to output a make-compatable list of all of the .h files included +# by the specified C++ source file. All of these .h files should be listed as +# dependencies of the .cpp file that is being compiled because if any of the included +# .h files change, our .cpp file will need to be recompiled in order to stay up to +# date. So, we want to be thorough and capture all of these dependencies in our +# Makefile. We could do this manually. For each .cpp file we would need to add a +# rule to this Makefile that lists dependies, and would look something like this: +# file1.o: file1.cpp file1.h file2.h mydir/file3.h myotherdir/file4.h +# where all of the .h files are either included by file1.cpp or by each other. +# It's tedious and error prone to try to track down all these dependencies manually. +# So, instead, we ask the compiler to generate a list of dependencies for us and +# save it out to a new text file with a .d extension. This text file lists the +# dependencies using the same Makefile syntax we would use if we wrote them down +# manually. Read more about auto-dependency generation here: +# http://make.mad-scientist.net/papers/advanced-auto-dependency-generation +# usage: $(call make-depend,source-file,object-file,depend-file) +make-depend-cxx=$(CXX) -MM -MF $3 -MP -MT $2 $(CXXFLAGS) $1 + +# Once the make-depend-cxx() function auto-generates the .d text file of additional +# dependency rules, we need to load it into make, as if those rules were actually +# written in this file. This is done with make's own "include" command, which +# enables us to include one Makefile within another. +-include $(addprefix $(OBJDIR)/,$(OBJFILES:.o=.d)) + + + +# LINKING: +# This rule is for the linking step of building a program. The dependencies mean that +# the executable target that we are building depends upon all of the .o files that are +# generated by the compiler as well as the $(BINDIR), which must exist so we can +# output the exe there. The recipe that follows calls g++ to tell it to link all the +# .o files into an executable program. +$(EXEFILE): $(addprefix $(OBJDIR)/, $(OBJFILES)) | $(BINDIR) + @echo "==== Linking $@. ====" + $(CXX) $(LDFLAGS) $(addprefix $(OBJDIR)/, $(OBJFILES)) -o $@ $(LDLIBS) + + +# Clean up the project, removing ALL files generated during a build. +clean: + @rm -rf $(OBJDIR) + @rm -rf $(EXEFILE) diff --git a/class-repo-public/labs/lab07/src/QuackBehavior.h b/class-repo-public/labs/lab07/src/QuackBehavior.h new file mode 100755 index 0000000..4502d62 --- /dev/null +++ b/class-repo-public/labs/lab07/src/QuackBehavior.h @@ -0,0 +1,78 @@ +#include +#include +#include +#include + +#define DB_DEFAULT 10 + +using namespace std; + +//----------------------------------------------- +// QUACKING + +// Type Polymorphic because of the the QuackBehavior class being abstract +// due to the virtual void quack() = 0 +class QuackBehavior { +protected: + double volume; +public: + QuackBehavior() : volume(DB_DEFAULT) {} + virtual string quack() = 0; +}; + +class Quack : public QuackBehavior { +public: + Quack() {} + string quack() { + stringstream stream; + string duck_volume; + string result_str; + stream << volume; + stream >> duck_volume; + string str1="Quack at"; + string str2="decibels."; + result_str= str1+" "+duck_volume+" "+str2; + return result_str; + } +}; + +class Mute : public QuackBehavior { +public: + Mute() { volume = 0; } + string quack() { + string duck_quack= "Cannot talk."; + return duck_quack; + } +}; + +class Squeak : public QuackBehavior { +public: + Squeak() {} + Squeak(int d) { volume = d; } + string quack() { + stringstream stream; + string duck_volume; + string result_str; + stream << volume; + stream >> duck_volume; + string str1="Quack at"; + string str2="decibels."; + result_str= str1+" "+duck_volume+" "+str2; + return result_str; + } +}; + +class Honk : public QuackBehavior { + Honk() {} + string quack() { + stringstream stream; + string duck_volume; + string result_str; + stream << volume; + stream >> duck_volume; + string str1="Quack at"; + string str2="decibels."; + result_str= str1+" "+duck_volume+" "+str2; + return result_str; + } +}; diff --git a/class-repo-public/labs/lab07/src/arena.cc b/class-repo-public/labs/lab07/src/arena.cc new file mode 100644 index 0000000..b4d850a --- /dev/null +++ b/class-repo-public/labs/lab07/src/arena.cc @@ -0,0 +1,208 @@ +/** + * @file arena.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include + +#include "src/arena.h" +#include "src/robot.h" +#include "src/circular_obstacle.h" +#include "src/event_collision.h" +#include "src/arena_params.h" +#include "src/recharge_station.h" +#include "src/home_base.h" +#include "src/event_recharge.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Constructors/Destructor + ******************************************************************************/ +Arena::Arena(const struct arena_params* const params) : + x_dim_(params->x_dim), y_dim_(params->y_dim), + n_robots_(1), + n_obstacles_(params->n_obstacles), + robot_(new Robot(¶ms->robot)), + recharge_station_(new RechargeStation(params->recharge_station.radius, + params->recharge_station.pos, + params->recharge_station.color)), + home_base_(new HomeBase(¶ms->home_base)), + entities_(), + mobile_entities_() { + robot_->heading_angle(37); + entities_.push_back(robot_); + mobile_entities_.push_back(robot_); + entities_.push_back(recharge_station_); + entities_.push_back(home_base_); + + for (size_t i = 0; i < n_obstacles_; ++i) { + entities_.push_back(new CircularObstacle( + params->obstacles[i].radius, + params->obstacles[i].pos, + params->obstacles[i].color)); + } /* for(i..) */ +} + +Arena::~Arena(void) { + for (auto ent : entities_) { + delete ent; + } /* for(ent..) */ +} +/******************************************************************************* + * Member Functions + ******************************************************************************/ +void Arena::Reset(void) { + for (auto ent : entities_) { + ent->Reset(); + } /* for(ent..) */ +} /* reset() */ + +std::vector Arena::obstacles(void) { + std::vector res; + for (auto ent : entities_) { + CircularObstacle* o = dynamic_cast(ent); + if (o) { + res.push_back(o); + } + } /* for(ent..) */ + return res; +} /* robots() */ + +void Arena::AdvanceTime(double dt) { + std::cout << "Advancing simulation time by " << dt << " timesteps\n"; + for (size_t i = 0; i < 1; ++i) { + UpdateEntitiesTimestep(); + } /* for(i..) */ +} /* AdvanceTime() */ + +void Arena::UpdateEntitiesTimestep(void) { + /* + * First, update the position of all entities, according to their current + * velocities. + */ + for (auto ent : entities_) { + ent->TimestepUpdate(1); + } /* for(ent..) */ + + /* + * Next, check if the robot has run out of battery + */ + if (robot_->battery_level() <=0) { + assert(0); /* not implemented yet */ + } + + /* + * Next, check if the robot has collided with the recharge station or the home + * base. These need to be before the general collisions, which can move the + * robot away from these "obstacles" before the "collisions" have been + * properly processed. + */ + + EventCollision ec; + + CheckForEntityCollision(robot_, home_base_, &ec, robot_->collision_delta()); + if (ec.collided()) { + assert(0); /* not implemented yet */ + } + + CheckForEntityCollision(robot_, recharge_station_, &ec, + robot_->collision_delta()); + if (ec.collided()) { + EventRecharge er; + robot_->Accept(&er); + } + + /* + * Finally, some pairs of entities may now be close enough to be considered + * colliding, send collision events as necessary. + * + * When something collides with an immobile entity, the immobile entity does + * not move (duh), so no need to send it a collision event. + */ + for (auto ent : mobile_entities_) { + // Check if it is out of bounds. If so, use that as point of contact. + CheckForEntityOutOfBounds(ent,&ec); + + // If not at wall, check if it is colliding with any other entities (not itself) + if (!ec.collided()) { + for (size_t i = 0; i < entities_.size(); ++i) { + if (entities_[i] == ent) { + continue; + } + CheckForEntityCollision(ent, entities_[i], &ec, ent->collision_delta()); + if (ec.collided()) { + break; + } + } /* for(i..) */ + } /* else */ + ent->Accept(&ec); + } /* for(ent..) */ +} /* UpdateEntities() */ + +void Arena::CheckForEntityOutOfBounds(const ArenaMobileEntity * const ent, + EventCollision * const event) { + // Angle of reflection should be 180-heading for walls + // Right Wall + if (ent->get_pos().x() + ent->radius() >= x_dim_) { + event->collided(true); + event->point_of_contact( Position( x_dim_, ent->get_pos().y()) ); + event->angle_of_contact( -180 ); + } + // Left Wall + else if (ent->get_pos().x() - ent->radius() <= 0) { + event->collided(true); + event->point_of_contact( Position( 0, ent->get_pos().y()) ); + event->angle_of_contact( 0 ); + } + // Bottom Wall + else if (ent->get_pos().y() + ent->radius() >= y_dim_) { + event->collided(true); + event->point_of_contact( Position( ent->get_pos().x(), y_dim_) ); + event->angle_of_contact( ent->heading_angle() ); + } + // Top Wall + else if (ent->get_pos().y() - ent->radius() <= 0) { + event->collided(true); + + event->point_of_contact( Position( 0, y_dim_) ); + event->angle_of_contact( ent->heading_angle() ); + } + else { + event->collided(false); + } +} /* entity_out_of_bounds() */ + +void Arena::CheckForEntityCollision(const ArenaEntity* const ent1, + const ArenaEntity* const ent2, EventCollision * const event, + double collision_delta) { + /* Note: this assumes circular entities */ + double ent1_x = ent1->get_pos().x(); + double ent1_y = ent1->get_pos().y(); + double ent2_x = ent2->get_pos().x(); + double ent2_y = ent2->get_pos().y(); + double dist = std::sqrt(std::pow(ent2_x - ent1_x, 2) + + std::pow(ent2_y - ent1_y, 2)); + if (dist > ent1->radius() + ent2->radius() + collision_delta) { + event->collided(false); + } else { + // Populate the collision event. + // Collided is true + // Point of contact is point along perimeter of ent1 + // Angle of contact is angle to that point of contact + event->collided(false); + /// >>>>>>> FILL THIS IN + } +} /* entities_have_collided() */ + +void Arena::Accept(const __unused EventKeypress * const e) { +} + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab07/src/arena.h b/class-repo-public/labs/lab07/src/arena.h new file mode 100644 index 0000000..689cd1b --- /dev/null +++ b/class-repo-public/labs/lab07/src/arena.h @@ -0,0 +1,153 @@ +/** + * @file robot_arena.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ARENA_H_ +#define SRC_ARENA_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include +#include +#include "src/event_keypress.h" +#include "src/event_collision.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +struct arena_params; + +/** + * @brief The main class for the simulation of a 2D world with many robots running + * around. + * + * \ref GraphicsArenaViewer or Tests call \ref AdvanceTime to control the + * simulation and use the get*() functions to read out the current state of the + * simulation (i.e., the current positions and orientations of robots and + * obstacles). + */ +class Arena { + public: + explicit Arena(const struct arena_params * const params); + ~Arena(void); + + /** + * @brief Advance the simulation by the specified # of steps. + * + * @param[in] dt The # of steps to increment by. + */ + void AdvanceTime(double dt); + + /** + * @brief Handle the key press passed along by the viewer. + * + * @param[in] an event holding the key press. + * + */ + void Accept(const EventKeypress * const e); + + /** + * @brief Reset all entities in the arena, effectively restarting the game. + */ + void Reset(void); + + /* + * @brief Get the # of robots in the arena. + */ + unsigned int n_robots(void) { return n_robots_; } + + /* + * @brief Get # of obstacles in the arena. + */ + unsigned int n_obstacles(void) { return n_obstacles_; } + + /** + * @brief Get a list of all obstacles (i.e. non-mobile entities in the arena). + */ + std::vector obstacles(void); + + /** + * @brief Get the list of all mobile entities in the arena. + */ + std::vector mobile_entities(void) + { return mobile_entities_; } + + class RechargeStation* recharge_station(void) const { + return recharge_station_; + } + + class Robot* robot(void) const { return robot_; } + class HomeBase* home_base(void) const { return home_base_; } + + private: + /** + * @brief Determine if any entities in the arena are overlapping. + * + * @return TRUE if any entities overlap, FALSE if no entities overlap. + */ + bool any_entities_overlap(void); + + /** + * @brief Determine if two entities have collided in the arena. Collision is + * defined as the difference between the extents of the two entities being less + * than a run-time parameter. + * + * @param ent1 Entity #1. + * @param ent2 Entity #2. + * @param pointer to a collision event + * + * Collision Event is populated appropriately. + */ + void CheckForEntityCollision(const class ArenaEntity* const ent1, + const class ArenaEntity* const ent2, + EventCollision * const ec, + double collision_delta); + + /** + * @brief Determine if a particular entity is gone out of the boundaries of + * the simulation. + * + * @param ent The entity to check. + * @param pointer to a collision event. + * + * Collision event is populated appropriately. + */ + void CheckForEntityOutOfBounds(const class ArenaMobileEntity* const ent, + EventCollision * const ec); + + /** + * @brief Update all entities for a single timestep + */ + void UpdateEntitiesTimestep(void); + + // Under certain circumstance, the compiler requires that the copy + // constructor is not defined. This is deleting the default copy const. + Arena& operator=(const Arena& other) = delete; + Arena(const Arena& other) = delete; + + // Dimensions of graphics window inside which robots must operate + double x_dim_; + double y_dim_; + unsigned int n_robots_; + unsigned int n_obstacles_; + + // Entities populating the arena + Robot* robot_; + RechargeStation * recharge_station_; + HomeBase * home_base_; + std::vector entities_; + std::vector mobile_entities_; +}; + +NAMESPACE_END(csci3081); + +#endif // SRC_ARENA_H_ diff --git a/class-repo-public/labs/lab07/src/arena_entity.h b/class-repo-public/labs/lab07/src/arena_entity.h new file mode 100644 index 0000000..25af274 --- /dev/null +++ b/class-repo-public/labs/lab07/src/arena_entity.h @@ -0,0 +1,80 @@ +/** + * @file arena_entity.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ARENA_ENTITY_H_ +#define SRC_ARENA_ENTITY_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include +#include "src/common.h" +#include "src/position.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief A base class representing an entity within the arena. All entities + * within the arena know how to : + * + * 1. Update themselves each timestep (i.e. in accordance with current velocity + * and position). + * + * 2. Reset themselves to a newly constructed state. This is so the user can + * click the reset button after completing a game and have things work as + * expected. + * + * Please note that here use the upper-left coordinate, which means that the + * origin point (0.0,0.0) is at the upper left. + * + * Also, all arena entities are circular. + */ +class ArenaEntity { + public: + ArenaEntity(double radius, const Position& pos, + const nanogui::Color& color) : + radius_(radius), pos_(pos), color_(color) {} + virtual ~ArenaEntity(void) {} + + /** + * @brief Perform whatever updates are needed for a particular entity after 1 + * timestep (updating position, changing color, etc.). + */ + virtual void TimestepUpdate(__unused uint dt) {} + + /** + * @brief Reset the entity to its newly constructed state. + */ + virtual void Reset(void) {} + + /** + * @brief Get the name of an entity for visualization purposes, to aid in + * debugging. + */ + virtual std::string get_name(void) const = 0; + + void set_pos(const Position& pos) { pos_ = pos; } + const Position& get_pos(void) const { return pos_; } + const nanogui::Color& get_color(void) const { return color_; } + void set_color(const nanogui::Color& color) { color_ = color; } + double radius(void) const { return radius_; } + + private: + double radius_; + Position pos_; + nanogui::Color color_; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_ARENA_ENTITY_H_ */ diff --git a/class-repo-public/labs/lab07/src/arena_immobile_entity.h b/class-repo-public/labs/lab07/src/arena_immobile_entity.h new file mode 100644 index 0000000..6082487 --- /dev/null +++ b/class-repo-public/labs/lab07/src/arena_immobile_entity.h @@ -0,0 +1,39 @@ +/** + * @file arena_immobile_entity.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ARENA_IMMOBILE_ENTITY_H_ +#define SRC_ARENA_IMMOBILE_ENTITY_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/arena_entity.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief An immobile entity in the arena. + * + * Immobile entities cannot move, and therefore do not need to override the + * \ref TimestepUpdate() function. + */ + +class ArenaImmobileEntity : public ArenaEntity { + public: + ArenaImmobileEntity(double radius, const Position& pos, + const nanogui::Color& color) : + ArenaEntity(radius, pos, color) {} +}; + +NAMESPACE_END(csci3081); + +#endif // SRC_ARENA_IMMOBILE_ENTITY_H_ diff --git a/class-repo-public/labs/lab07/src/arena_mobile_entity.cc b/class-repo-public/labs/lab07/src/arena_mobile_entity.cc new file mode 100644 index 0000000..47132a7 --- /dev/null +++ b/class-repo-public/labs/lab07/src/arena_mobile_entity.cc @@ -0,0 +1,26 @@ +/** + * @file arena_mobile_entity.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/arena_mobile_entity.h" +#include "src/robot_motion_behavior.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Member Functions + ******************************************************************************/ +void ArenaMobileEntity::TimestepUpdate(uint dt) { + RobotMotionBehavior h; + h.UpdatePosition(this, dt); +} /* TimestepUpdate() */ + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab07/src/arena_mobile_entity.h b/class-repo-public/labs/lab07/src/arena_mobile_entity.h new file mode 100644 index 0000000..26b6388 --- /dev/null +++ b/class-repo-public/labs/lab07/src/arena_mobile_entity.h @@ -0,0 +1,102 @@ +/** + * @file mobile_arena_entity.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ARENA_MOBILE_ENTITY_H_ +#define SRC_ARENA_MOBILE_ENTITY_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/arena_entity.h" +#include "src/event_base_class.h" +#include "src/event_recharge.h" +#include "src/event_collision.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief A mobile entity in the arena, capable of updating its own position + * and/or velocity when asked by the simulation. + * + * All mobile entities must have a heading angle so that their orientation can + * be properly drawn by the viwer. + */ +class ArenaMobileEntity : public ArenaEntity { + public: + ArenaMobileEntity(double radius, double collision_delta, + const Position& pos, const nanogui::Color& color) : + ArenaEntity(radius, pos, color), + collision_delta_(collision_delta) {} + + /** + * @brief Get the current heading angle for the entity. + */ + virtual double heading_angle(void) const = 0; + + /** + * @brief Set the new heading angle for the entity. + * + * This should only be called by a dedicated motion handler class, and only + * from within \ref Timestepupdate(). + */ + virtual void heading_angle(double heading_angle) = 0; + + /** + * @brief Get the current speed of an arena entity. + */ + virtual double get_speed(void) const = 0; + + /** + * @brief Set the new speed of an entity. + * + * This should only be called by a dedicated motion handler class, and only + * from within \ref Timestepupdate(). + */ + virtual void set_speed(double sp) = 0; + + /** + * @brief Accept a visit from a collision event, updating state appropriately. + */ + virtual void Accept(const EventCollision * const e) = 0; + + /** + * @brief Accept a visit from a recharge event, updating state appropriately. + * + * Note that this is robot-specific, and should be removed as a pure virtual + * function once the \ref HomeBase class is made mobile. + */ + virtual void Accept(const EventRecharge * const e) = 0; + + /** + * @brief Update an entity's position and velocity after the specified # of + * timesteps has elapsed. + * + * @param dt The # of timesteps that has elapsed since the last time + * position/velocity were updated. + */ + void TimestepUpdate(uint dt) override; + + /** + * @brief Get the minimum distance between two arena entities that will be + * considered a collision. + * + */ + double collision_delta(void) const { return collision_delta_; } + + private: + double collision_delta_; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_ARENA_MOBILE_ENTITY_H_ */ diff --git a/class-repo-public/labs/lab07/src/arena_mobile_entity_params.h b/class-repo-public/labs/lab07/src/arena_mobile_entity_params.h new file mode 100644 index 0000000..6be3c9b --- /dev/null +++ b/class-repo-public/labs/lab07/src/arena_mobile_entity_params.h @@ -0,0 +1,38 @@ +/** + * @file arena_mobile_entity_params.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ARENA_MOBILE_ENTITY_PARAMS_H_ +#define SRC_ARENA_MOBILE_ENTITY_PARAMS_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/circular_entity_params.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Structure Definitions + ******************************************************************************/ +/** + * @brief Define the parameters for a mobile entity within the arena. + */ +struct arena_mobile_entity_params : public circular_entity_params { + arena_mobile_entity_params(void) : circular_entity_params(), + collision_delta() {} + + /** + * Minimum distance between entities that qualifies as a collision. + */ + double collision_delta; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_ARENA_MOBILE_ENTITY_PARAMS_H_ */ diff --git a/class-repo-public/labs/lab07/src/arena_params.h b/class-repo-public/labs/lab07/src/arena_params.h new file mode 100644 index 0000000..18744c4 --- /dev/null +++ b/class-repo-public/labs/lab07/src/arena_params.h @@ -0,0 +1,50 @@ +/** + * @file arena_params.h + * + * @copyright 2017 3081 Staff, All rights reserved. + * + */ + +#ifndef SRC_ARENA_PARAMS_H_ +#define SRC_ARENA_PARAMS_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/robot_params.h" +#include "src/home_base_params.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Constant Definitions + ******************************************************************************/ +#define MAX_OBSTACLES 8 +/******************************************************************************* + * Structure Definitions + ******************************************************************************/ + /** + * @brief Define the parameter for the arena. + * + * This includes the arena geometry as well as the parameters for \c ALL + * entities within the arena. + */ +struct arena_params { + arena_params(void) : robot(), recharge_station(), home_base(), + obstacles(), n_obstacles(), x_dim(), y_dim() {} + + struct robot_params robot; + struct circular_entity_params recharge_station; + struct home_base_params home_base; + struct circular_entity_params obstacles[MAX_OBSTACLES]; + size_t n_obstacles; + uint x_dim; + uint y_dim; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_ARENA_PARAMS_H_ */ diff --git a/class-repo-public/labs/lab07/src/circular_entity_params.h b/class-repo-public/labs/lab07/src/circular_entity_params.h new file mode 100644 index 0000000..b6bfaf5 --- /dev/null +++ b/class-repo-public/labs/lab07/src/circular_entity_params.h @@ -0,0 +1,39 @@ +/** + * @file circular_entity_params.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_CIRCULAR_ENTITY_PARAMS_H_ +#define SRC_CIRCULAR_ENTITY_PARAMS_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/common.h" +#include "src/position.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Structure Definitions + ******************************************************************************/ +/** + * @brief Define the params for a circular entity in the arena. + * + * For now, all entities in the arena are circular. + */ +struct circular_entity_params { + circular_entity_params(void) : radius(), pos(), color() {} + double radius; + Position pos; + nanogui::Color color; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_CIRCULAR_ENTITY_PARAMS_H_ */ diff --git a/class-repo-public/labs/lab07/src/circular_obstacle.cc b/class-repo-public/labs/lab07/src/circular_obstacle.cc new file mode 100644 index 0000000..9781baa --- /dev/null +++ b/class-repo-public/labs/lab07/src/circular_obstacle.cc @@ -0,0 +1,32 @@ +/** + * @file Circular_obstacle.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/circular_obstacle.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Static Variables + ******************************************************************************/ +uint CircularObstacle::next_id_ = 0; + +/******************************************************************************* + * Constructors/Destructor + ******************************************************************************/ +CircularObstacle::CircularObstacle(double radius, const Position& pos, + const nanogui::Color& color) : + ArenaImmobileEntity(radius, pos, color), + id_(-1) { + id_ = next_id_++; +} + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab07/src/circular_obstacle.h b/class-repo-public/labs/lab07/src/circular_obstacle.h new file mode 100644 index 0000000..8b326b1 --- /dev/null +++ b/class-repo-public/labs/lab07/src/circular_obstacle.h @@ -0,0 +1,51 @@ +/** + * @file circular_obstacle.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_CIRCULAR_OBSTACLE_H_ +#define SRC_CIRCULAR_OBSTACLE_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/arena_immobile_entity.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief A class that represents are circular, immobile entity within the + * arena, which we are defining as an "obstacle". + * + */ +class CircularObstacle: public ArenaImmobileEntity { + public: + explicit CircularObstacle(double radius); + CircularObstacle(double radius, const Position& pos, + const nanogui::Color& color); + + /** + * @brief Get the name of circular obstacle, for use in visualization and + * debugging + */ + std::string get_name(void) const override { + return "Circular Obstacle" + std::to_string(id_); + } + + private: + static uint next_id_; + + int id_; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_CIRCULAR_OBSTACLE_H_ */ diff --git a/class-repo-public/labs/lab07/src/common.h b/class-repo-public/labs/lab07/src/common.h new file mode 100644 index 0000000..3fccdb4 --- /dev/null +++ b/class-repo-public/labs/lab07/src/common.h @@ -0,0 +1,24 @@ +/** + * @file common.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_COMMON_H_ +#define SRC_COMMON_H_ + +/******************************************************************************* + * Macros + ******************************************************************************/ +/** + * @brief It defines the Macros that will be used in the Project. + */ +#define NAMESPACE_BEGIN(name) namespace name { +#define NAMESPACE_END(name) } + +/* This should be placed in front of any variable defined but not used to + * satisfy the compiler - otherwise a warning is given. + */ +#define __unused __attribute__((unused)) + +#endif /* SRC_COMMON_H_ */ diff --git a/class-repo-public/labs/lab07/src/event_base_class.h b/class-repo-public/labs/lab07/src/event_base_class.h new file mode 100644 index 0000000..54777a5 --- /dev/null +++ b/class-repo-public/labs/lab07/src/event_base_class.h @@ -0,0 +1,41 @@ +/** + * @file event_base_class.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_EVENT_BASE_CLASS_H_ +#define SRC_EVENT_BASE_CLASS_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/common.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief The base event class from which all events should derive from. + */ +class EventBaseClass { + public: + EventBaseClass(void) {} + virtual ~EventBaseClass(void) {} + + /** + * @brief Each event, upon its firing, should emit a message to the user on + * stdout saying what happened, in order to aid debugging. + * + */ + virtual void EmitMessage(void) = 0; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_BASE_EVENT_H_ */ diff --git a/class-repo-public/labs/lab07/src/event_collision.cc b/class-repo-public/labs/lab07/src/event_collision.cc new file mode 100644 index 0000000..57afe03 --- /dev/null +++ b/class-repo-public/labs/lab07/src/event_collision.cc @@ -0,0 +1,36 @@ +/** + * @file event_collision.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/event_collision.h" +#include "src/arena_mobile_entity.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Constructor + ******************************************************************************/ +EventCollision::EventCollision() : + collided_(false), + point_of_contact_(0, 0), + angle_of_contact_(0) { +} + +/******************************************************************************* + * Member Functions + ******************************************************************************/ +void EventCollision::EmitMessage(void) { + printf("Collision event at point %d %d. Angle %f", + point_of_contact_.x(), point_of_contact_.y(), + angle_of_contact_); +} /* EmitMessage() */ + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab07/src/event_collision.h b/class-repo-public/labs/lab07/src/event_collision.h new file mode 100644 index 0000000..af40971 --- /dev/null +++ b/class-repo-public/labs/lab07/src/event_collision.h @@ -0,0 +1,65 @@ +/** + * @file event_collision.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_EVENT_COLLISION_H_ +#define SRC_EVENT_COLLISION_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/event_base_class.h" +#include "src/position.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief A collision event class, which is created after a + * \ref MobileArenaEntity gets within a specified distance of another entity in + * the arena, OR when an entity that was previously within the collision delta + * of other entity outside of that threshold. + */ +class EventCollision : public EventBaseClass { + public: + EventCollision(void); + void EmitMessage(void) override; + + /** + * @brief If \c TRUE, then this instance represents an active collision + * event. If \c FALSE, then it represents a previous collision event that has + * been resolved. + */ + bool collided(void) const { return collided_; } + void collided(bool c) { collided_ = c; } + + /** + * @brief The point in the arena at which the entities have collided/touched. + */ + Position point_of_contact(void) const { return point_of_contact_; } + void point_of_contact(Position p) { point_of_contact_ = p; } + + /** + * @brief The angle, in degrees, as specified on the unit circle, that the + * collision occurred at. Needed to calculate the outgoing/bounceback angle. + */ + double angle_of_contact(void) const { return angle_of_contact_; } + void angle_of_contact(double aoc) { angle_of_contact_ = aoc; } + + private: + bool collided_; + Position point_of_contact_; + double angle_of_contact_; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_COLLISION_EVENT_H_ */ diff --git a/class-repo-public/labs/lab07/src/event_command.cc b/class-repo-public/labs/lab07/src/event_command.cc new file mode 100644 index 0000000..ef27566 --- /dev/null +++ b/class-repo-public/labs/lab07/src/event_command.cc @@ -0,0 +1,22 @@ +/** + * @file actuator_cmd_event.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/event_command.h" +#include "src/robot.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Member Functions + ******************************************************************************/ + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab07/src/event_command.h b/class-repo-public/labs/lab07/src/event_command.h new file mode 100644 index 0000000..c405be5 --- /dev/null +++ b/class-repo-public/labs/lab07/src/event_command.h @@ -0,0 +1,42 @@ +/** + * @file actuator_cmd_event.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_EVENT_COMMAND_H_ +#define SRC_EVENT_COMMAND_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/event_base_class.h" +#include "src/event_commands.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief An event representing a keypress command from the user that should be + * sent to the robot. + */ +class EventCommand : public EventBaseClass { + public: + explicit EventCommand(enum event_commands cmd) : cmd_(cmd) {} + + void EmitMessage(void) override { printf("Motion cmd %d received\n", cmd_); } + enum event_commands cmd(void) const { return cmd_; } + + private: + enum event_commands cmd_; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_ACTUATOR_CMD_EVENT_H_ */ diff --git a/class-repo-public/labs/lab07/src/event_commands.h b/class-repo-public/labs/lab07/src/event_commands.h new file mode 100644 index 0000000..61685ec --- /dev/null +++ b/class-repo-public/labs/lab07/src/event_commands.h @@ -0,0 +1,36 @@ +/** + * @file actuator_commands.h + * + * @copyright 2017 John Harwell, All rights reserved. + * + */ + +#ifndef SRC_EVENT_COMMANDS_H_ +#define SRC_EVENT_COMMANDS_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/common.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Type Definitions + ******************************************************************************/ +/** + * @brief The supported commands that the robot supports: forward, turn left a + * little bit, turn right a little bit. Doesn't get much simpler than that! + */ +enum event_commands { + COM_TURN_LEFT, + COM_TURN_RIGHT, + COM_FORWARD +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_ACTUATOR_COMMANDS_H_ */ diff --git a/class-repo-public/labs/lab07/src/event_keypress.cc b/class-repo-public/labs/lab07/src/event_keypress.cc new file mode 100644 index 0000000..f7e5064 --- /dev/null +++ b/class-repo-public/labs/lab07/src/event_keypress.cc @@ -0,0 +1,44 @@ +/** + * @file event_keypress.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/event_keypress.h" +#include "src/graphics_arena_viewer.h" +#include "src/robot.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Member Functions + ******************************************************************************/ +enum event_commands EventKeypress::keypress_to_cmd(int key) { + /* + * @todo These values are likely not universal across all OS's. + * Write a wrapper for the various OS or see if nanogui has that + * functionality. + */ + switch (key) { + case 263: + return COM_TURN_LEFT; + break; + case 262: + return COM_TURN_RIGHT; + break; + case 265: + return COM_FORWARD; + break; + default: + printf("Unknown keypress: %d\n", key); + assert(0); + } /* switch() */ + } /* keypress_to_cmd() */ + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab07/src/event_keypress.h b/class-repo-public/labs/lab07/src/event_keypress.h new file mode 100644 index 0000000..9597406 --- /dev/null +++ b/class-repo-public/labs/lab07/src/event_keypress.h @@ -0,0 +1,45 @@ +/** + * @file event_keypress.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_EVENT_KEYPRESS_H_ +#define SRC_EVENT_KEYPRESS_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/event_base_class.h" +#include "src/event_commands.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief An event representing that a key on the keyboard has been + * pressed. + * + * They are sent from the graphics framework to the \ref GraphicsArenaViewer, + * where they are interpreted further + */ +class EventKeypress : public EventBaseClass { + public: + explicit EventKeypress(int key) : key_(key) {} + + void EmitMessage(void) override { printf("Keypress command received\n"); } + + private: + enum event_commands keypress_to_cmd(int key); + int key_; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_KEYPRESS_EVENT_H_ */ diff --git a/class-repo-public/labs/lab07/src/event_recharge.cc b/class-repo-public/labs/lab07/src/event_recharge.cc new file mode 100644 index 0000000..02c7eba --- /dev/null +++ b/class-repo-public/labs/lab07/src/event_recharge.cc @@ -0,0 +1,22 @@ +/** + * @file event_recharge.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/event_recharge.h" +#include "src/robot.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Member Functions + ******************************************************************************/ + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab07/src/event_recharge.h b/class-repo-public/labs/lab07/src/event_recharge.h new file mode 100644 index 0000000..07491b1 --- /dev/null +++ b/class-repo-public/labs/lab07/src/event_recharge.h @@ -0,0 +1,37 @@ +/** + * @file event_recharge.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_EVENT_RECHARGE_H_ +#define SRC_EVENT_RECHARGE_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/event_base_class.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief An event representing an encounter (really a collision) that a robot + * has with a \ref RechargeStation. + */ +class EventRecharge : public EventBaseClass { + public: + EventRecharge(void) {} + + void EmitMessage(void) override { printf("Robot Battery recharged!\n"); } +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_RECHARGE_EVENT_H_ */ diff --git a/class-repo-public/labs/lab07/src/graphics_arena_viewer.cc b/class-repo-public/labs/lab07/src/graphics_arena_viewer.cc new file mode 100644 index 0000000..61a0c54 --- /dev/null +++ b/class-repo-public/labs/lab07/src/graphics_arena_viewer.cc @@ -0,0 +1,199 @@ +/** + * @file graphics_arena_viewer.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/graphics_arena_viewer.h" +#include +#include +#include + +#include "src/robot.h" +#include "src/home_base.h" +#include "src/circular_obstacle.h" +#include "src/arena_params.h" +#include "src/event_keypress.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Constructors/Destructor + ******************************************************************************/ +GraphicsArenaViewer::GraphicsArenaViewer(const struct arena_params* const params) + : csci3081::GraphicsApp(params->x_dim, params->y_dim, "Robot Simulation"), + arena_(new Arena(params)), + paused_(false), + pause_btn_(nullptr) { + nanogui::FormHelper *gui = new nanogui::FormHelper(this); + nanogui::ref window = gui->addWindow(Eigen::Vector2i(10, 10), + "Simulation Controls"); + gui->addButton("Restart", + std::bind(&GraphicsArenaViewer::OnRestartBtnPressed, this)); + pause_btn_ = gui->addButton("Pause", + std::bind(&GraphicsArenaViewer::OnPauseBtnPressed, this)); + + last_dt = 0; + performLayout(); +} + +/******************************************************************************* + * Member Functions + ******************************************************************************/ + +// This is the primary driver for state change in the arena. +// It will be called at each iteration of nanogui::mainloop() +void GraphicsArenaViewer::UpdateSimulation(double dt) { + if (!paused_) { + if ((last_dt + dt) > .05) { + arena_->AdvanceTime(dt+last_dt); + last_dt = 0; + } else { + last_dt += dt; + } + } +} + +/******************************************************************************* + * Handlers for User Keyboard and Mouse Events + ******************************************************************************/ +void GraphicsArenaViewer::OnRestartBtnPressed() { + arena_->Reset(); +} + +void GraphicsArenaViewer::OnPauseBtnPressed() { + paused_ = !paused_; + if (paused_) { + pause_btn_->setCaption("Play"); + } else { + pause_btn_->setCaption("Pause"); + } +} + +void GraphicsArenaViewer::OnMouseMove(int x, int y) { + std::cout << "Mouse moved to (" << x << ", " << y << ")" << std::endl; +} + +void GraphicsArenaViewer::OnLeftMouseDown(int x, int y) { + std::cout << "Left mouse button DOWN (" << x << ", " << y << ")" << std::endl; +} + +void GraphicsArenaViewer::OnLeftMouseUp(int x, int y) { + std::cout << "Left mouse button UP (" << x << ", " << y << ")" << std::endl; +} + +void GraphicsArenaViewer::OnRightMouseDown(int x, int y) { + std::cout << "Right mouse button DOWN (" << x << ", " << y << ")\n"; +} + +void GraphicsArenaViewer::OnRightMouseUp(int x, int y) { + std::cout << "Right mouse button UP (" << x << ", " << y << ")" << std::endl; +} + +void GraphicsArenaViewer::OnKeyDown(const char *c, int modifiers) { + std::cout << "Key DOWN (" << c << ") modifiers=" << modifiers << std::endl; +} + +void GraphicsArenaViewer::OnKeyUp(const char *c, int modifiers) { + std::cout << "Key UP (" << c << ") modifiers=" << modifiers << std::endl; +} + +void GraphicsArenaViewer::OnSpecialKeyDown(int key, int scancode, int modifiers) { + EventKeypress e(key); + arena_->Accept(&e); + std::cout << "Special Key DOWN key=" << key << " scancode=" << scancode + << " modifiers=" << modifiers << std::endl; +} + +void GraphicsArenaViewer::OnSpecialKeyUp(int key, int scancode, int modifiers) { + std::cout << "Special Key UP key=" << key << " scancode=" << scancode + << " modifiers=" << modifiers << std::endl; +} + +/******************************************************************************* + * Drawing of Entities in Arena + ******************************************************************************/ +void GraphicsArenaViewer::DrawRobot(NVGcontext *ctx, const Robot* const robot) { + // translate and rotate all graphics calls that follow so that they are + // centered, at the position and heading for this robot + nvgSave(ctx); + nvgTranslate(ctx, robot->get_pos().x(), robot->get_pos().y()); + nvgRotate(ctx, robot->heading_angle()); + + // robot's circle + nvgBeginPath(ctx); + nvgCircle(ctx, 0.0, 0.0, robot->radius()); + nvgFillColor(ctx, nvgRGBA(static_cast(robot->get_color().r() * 255), + static_cast(robot->get_color().g() * 255), + static_cast(robot->get_color().b() * 255), + 255)); + nvgFill(ctx); + nvgStrokeColor(ctx, nvgRGBA(0, 0, 0, 255)); + nvgStroke(ctx); + + // robot id text label + nvgSave(ctx); + nvgRotate(ctx, M_PI / 2.0); + nvgFillColor(ctx, nvgRGBA(0, 0, 0, 255)); + nvgText(ctx, 0.0, 10.0, robot->get_name().c_str(), NULL); + nvgRestore(ctx); + + nvgRestore(ctx); +} + +void GraphicsArenaViewer::DrawCircularObstacle(NVGcontext *ctx, + const CircularObstacle* const obstacle) { + nvgBeginPath(ctx); + nvgCircle(ctx, obstacle->get_pos().x(), obstacle->get_pos().y(), obstacle->radius()); + nvgFillColor(ctx, nvgRGBA(static_cast(obstacle->get_color().r() * 255), + static_cast(obstacle->get_color().g() * 255), + static_cast(obstacle->get_color().b() * 255), + 255)); + nvgFill(ctx); + nvgStrokeColor(ctx, nvgRGBA(0, 0, 0, 255)); + nvgStroke(ctx); + + nvgFillColor(ctx, nvgRGBA(0, 0, 0, 255)); + nvgText(ctx, obstacle->get_pos().x(), obstacle->get_pos().y(), + obstacle->get_name().c_str(), NULL); +} + +void GraphicsArenaViewer::DrawHomeBase(NVGcontext *ctx, + const HomeBase* const home) { + nvgBeginPath(ctx); + nvgCircle(ctx, home->get_pos().x(), home->get_pos().y(), home->radius()); + nvgFillColor(ctx, nvgRGBA(static_cast(home->get_color().r() * 255), + static_cast(home->get_color().g() * 255), + static_cast(home->get_color().b() * 255), + 255)); + nvgFill(ctx); + nvgStrokeColor(ctx, nvgRGBA(0, 0, 0, 255)); + nvgStroke(ctx); + + nvgFillColor(ctx, nvgRGBA(0, 0, 0, 255)); + nvgText(ctx, home->get_pos().x(), home->get_pos().y(), + home->get_name().c_str(), NULL); +} + +void GraphicsArenaViewer::DrawUsingNanoVG(NVGcontext *ctx) { + // initialize text rendering settings + nvgFontSize(ctx, 18.0f); + nvgFontFace(ctx, "sans-bold"); + nvgTextAlign(ctx, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); + + std::vector obstacles = arena_->obstacles(); + for (size_t i = 0; i < obstacles.size(); i++) { + DrawCircularObstacle(ctx, obstacles[i]); + } /* for(i..) */ + + DrawRobot(ctx, arena_->robot()); + DrawHomeBase(ctx, arena_->home_base()); +} + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab07/src/graphics_arena_viewer.h b/class-repo-public/labs/lab07/src/graphics_arena_viewer.h new file mode 100644 index 0000000..493a924 --- /dev/null +++ b/class-repo-public/labs/lab07/src/graphics_arena_viewer.h @@ -0,0 +1,235 @@ +/** + * @file graphics_arena_viewer.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ARENA_VIEWER_H_ +#define SRC_ARENA_VIEWER_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/arena.h" +#include "src/common.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief An application that uses the cs3081 SimpleGraphics library to open up + * a window that includes a few buttons for controlling the simulation and can + * be used to draw circles and other computer graphics. + * + * After constructing a new RobotViewer, call Run() to start and run the + * application. Run() will not return until the application window is closed. + * Make sure that you call cs3081::InitGraphics() before creating the + * RobotViewer app. Example: + * + * ``` + * int main(int argc, char **argv) { + * cs3081::InitGraphics(); + * RobotViewer *app = new RobotViewer(); + * app->Run(); + * cs3081::ShutdownGraphics(); + * return 0; + * } + * ``` + * + * While the window is open \ref UpdateSimulation() will be called repeatedly, + * once per frame. Fill this in to update your simulation or perform any other + * processing that should happen over time as the simulation progresses. + * + * Fill in the On*() methods as desired to respond to user input events. + * + * Fill in the Draw*() methods to draw graphics to the screen using + * either the nanovg library or raw OpenGL. + */ +class GraphicsArenaViewer : public GraphicsApp { + + public: + explicit GraphicsArenaViewer(const struct arena_params* const params); + virtual ~GraphicsArenaViewer(void) { delete arena_; } + + /** + * @brief Informs the arena of the new time, so that it can update. + */ + void UpdateSimulation(double dt) override; + + /** + * @brief Handle the user pressing the restart button on the GUI. + */ + void OnRestartBtnPressed(); + + /** + * @brief Handle the user pressing the pause button on the GUI. + */ + void OnPauseBtnPressed(); + + /** + * @brief Called each time the mouse moves on the screen within the GUI + * window. + * + * Origin is at the lower left of the window. + * + * @param[in] x X position of the cursor. + * @param[in] y Y position of the cursor. + */ + void OnMouseMove(int x, int y) override; + + /** + * @brief Called each time the left mouse button is clicked. + * + * Origin is at the lower left of the window. + * + * @param[in] x The X position of the click. + * @param[in] y The Y position of the click. + */ + void OnLeftMouseDown(int x, int y) override; + + /** + * @brief Called each time the left mouse button is released. + * + * Origin is at the lower left of the window. + * + * @param[in] x The X position of the release. + * @param[in] y The Y position of the release. + */ + void OnLeftMouseUp(int x, int y) override; + + /** + * @brief Called each time the right mouse button is clicked. + * + * Origin is at the lower left of the window. + * + * @param[in] x The X position of the click. + * @param[in] y The Y position of the click. + */ + + void OnRightMouseDown(int x, int y) override; + + /** + * @brief Called each time the right mouse button is released. + * + * Origin is at the lower left of the window. + * + * @param[in] x The X position of the release. + * @param[in] y The Y position of the release. + */ + void OnRightMouseUp(int x, int y) override; + + /** + * @brief Called each time a character key is pressed. + * + * @param[in] c Character representing a key that was pressed. + * @param[in] modifiers Any modifier keys that were also pressed. + */ + void OnKeyDown(const char *c, int modifiers) override; + + /** + * @brief Called each time a character key is released. + * + * @param[in] c Character representing a key that was released. + * @param[in] modifiers Any modifier keys that were held with the key. + */ + void OnKeyUp(const char *c, int modifiers) override; + + /** + * @brief Called each time a special (non-alphabetic) key is pressed. + * + * @param[in] key The key that was pressed. + * @param[in] scancode The scancode corresponding to the key. + * @param[in] modifiers Any modifiers that were also pressed. + */ + void OnSpecialKeyDown(int key, int scancode, int modifiers) override; + + /** + * @brief Called each time a special (non-alphabetic) key is released. + * + * @param[in] key The key that was released. + * @param[in] scancode The scancode corresponding to the key. + * @param[in] modifiers Any modifiers that were also pressed. + */ + void OnSpecialKeyUp(int key, int scancode, int modifiers) override; + + /** + * @brief Draw the arena with all robots, obstacles using nanogui. + * + * This is the primary driver for drawing all entities in the arena. It is + * called at each iteration of \ref nanogui::mainloop(). + * + * @param[in] ctx Context for nanogui. + */ + void DrawUsingNanoVG(NVGcontext *ctx) override; + + /** + * @brief Draw using OpenGL. This callback had to be defined, but we are doing + * all drawing with nanovg in this application, so it is empty. + */ + void DrawUsingOpenGL(void) override {} + + Arena* arena(void) const { return arena_; } + + private: + /** + * @brief Draw a robot using nanogui. + * + * This function requires an active nanovg drawing context (ctx), so it should + * probably only be called from with \ref DrawUsingNanoVG(). + * + * @param[in] ctx The nanogui context. + * @param[in] robot The robot handle. + */ + void DrawRobot(NVGcontext *ctx, const class Robot* const robot); + + /** + * @brief Draw the sensors from a robot using nanogui. + * + * This function requires an active nanovg drawing context (ctx), so it should + * probably only be called from with \ref DrawUsingNanoVG(). + * + * @param[in] ctx The nanogui context. + * @param[in] robot The robot handle. + */ + //void DrawRobotSensors(NVGcontext *ctx, const class Robot* const robot); + + /** + * @brief Draw a circular in the arena using nanogui. + * + * This function requires an active nanovg drawing context (ctx), so it should + * probably only be called from with \ref DrawUsingNanoVG(). + * + * @param[in] ctx The nanogui context. + * @param[in] obstacle The obstacle handle. + */ + void DrawCircularObstacle(NVGcontext *ctx, + const class CircularObstacle* const obstacle); + + /** + * @brief Draw the home base using nanogui. + * + * @param[in] ctx The nanogui context. + * @param[in] home The home base handle. + */ + void DrawHomeBase(NVGcontext *ctx, const class HomeBase* const home); + + Arena *arena_; + bool paused_; + nanogui::Button *pause_btn_; + + double last_dt; + + // Satisfies compilers warning that the copy constructor should exist. + GraphicsArenaViewer& operator=(const GraphicsArenaViewer& other) = delete; + GraphicsArenaViewer(const GraphicsArenaViewer& other) = delete; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_ARENA_VIEWER_H_ */ diff --git a/class-repo-public/labs/lab07/src/home_base.h b/class-repo-public/labs/lab07/src/home_base.h new file mode 100644 index 0000000..d9c4f79 --- /dev/null +++ b/class-repo-public/labs/lab07/src/home_base.h @@ -0,0 +1,40 @@ +/** + * @file home_base.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_HOME_BASE_H_ +#define SRC_HOME_BASE_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/home_base_params.h" +#include "src/arena_immobile_entity.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief The goal the the robot is trying to drive to within the arena. + * + * Initially an immobile entity, it should be made to move during iteration 1. + */ +class HomeBase: public ArenaImmobileEntity { + public: + explicit HomeBase(const struct home_base_params* const params) : + ArenaImmobileEntity(params->radius, params->pos, params->color) {} + + std::string get_name(void) const override { return "Home Base"; } +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_HOME_BASE_H_ */ diff --git a/class-repo-public/labs/lab07/src/home_base_params.h b/class-repo-public/labs/lab07/src/home_base_params.h new file mode 100644 index 0000000..01338ca --- /dev/null +++ b/class-repo-public/labs/lab07/src/home_base_params.h @@ -0,0 +1,35 @@ +/** + * @file home_base_params.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_HOME_BASE_PARAMS_H_ +#define SRC_HOME_BASE_PARAMS_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/arena_mobile_entity_params.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Structure Definitions + ******************************************************************************/ +struct home_base_params : public arena_mobile_entity_params { + home_base_params(void) : + arena_mobile_entity_params(), max_speed(0), min_heading_steps(0.0), + heading_change_prob(0.0) {} + + unsigned int max_speed; + double min_heading_steps; + double heading_change_prob; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_HOME_BASE_PARAMS_H_ */ diff --git a/class-repo-public/labs/lab07/src/main.cc b/class-repo-public/labs/lab07/src/main.cc new file mode 100644 index 0000000..e480964 --- /dev/null +++ b/class-repo-public/labs/lab07/src/main.cc @@ -0,0 +1,60 @@ +/** + * @file main.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/graphics_arena_viewer.h" +#include "src/arena_params.h" + +/******************************************************************************* + * Non-Member Functions + ******************************************************************************/ +int main(int, char **) { + // Essential call to initiate the graphics window + csci3081::InitGraphics(); + + // Initialize default start values for various arena entities + csci3081::robot_params rparams; + + rparams.battery_max_charge = 100.0; + rparams.angle_delta = 10; + rparams.collision_delta = 1; + rparams.radius = 20.0; + rparams.pos = csci3081::Position(500, 500); + rparams.color = nanogui::Color(0, 0, 255, 255); /* blue */ + + csci3081::arena_params aparams; + + aparams.robot = rparams; + + aparams.recharge_station.radius = 20.0; + aparams.recharge_station.pos = {500, 300}; + aparams.recharge_station.color = nanogui::Color(0, 128, 128, 255); /* green */ + + aparams.home_base.collision_delta = 1; + aparams.home_base.radius = 20.0; + aparams.home_base.pos = {400, 400}; + aparams.home_base.color = nanogui::Color(255, 0, 0, 255); /* red */ + aparams.home_base.max_speed = 5.0; + aparams.home_base.min_heading_steps = 100; + aparams.home_base.heading_change_prob = 0.5; + + aparams.obstacles[0].radius = 30.0; + aparams.obstacles[0].pos = {200, 200}; + aparams.obstacles[0].color = nanogui::Color(255, 255, 255, 255); /* white */ + + aparams.n_obstacles = 1; + aparams.x_dim = 1024; + aparams.y_dim = 768; + + // Start up the graphics (which creates the arena). + // Run will enter the nanogui::mainloop() + csci3081::GraphicsArenaViewer *app = new csci3081::GraphicsArenaViewer(&aparams); + app->Run(); + csci3081::ShutdownGraphics(); + return 0; +} diff --git a/class-repo-public/labs/lab07/src/position.h b/class-repo-public/labs/lab07/src/position.h new file mode 100644 index 0000000..f37172c --- /dev/null +++ b/class-repo-public/labs/lab07/src/position.h @@ -0,0 +1,49 @@ +/** + * @file position.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_POSITION_H_ +#define SRC_POSITION_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/common.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief A simple representation of a position of an entity within the arena. + */ +class Position { + public: + Position(void) : x_(0), y_(0) {} + Position(int in_x, int in_y) : x_(in_x), y_(in_y) { } + + int x(void) const { return x_; } + int y(void) const { return y_; } + void x(double x) { x_ = x; } + void y(double y) { y_ = y; } + + Position& operator=(const Position& other) { + this->x_ = other.x_; + this->y_ = other.y_; + return *this; + } + + private: + int x_; + int y_; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_POSITION_H_ */ diff --git a/class-repo-public/labs/lab07/src/recharge_station.h b/class-repo-public/labs/lab07/src/recharge_station.h new file mode 100644 index 0000000..bf6e5d8 --- /dev/null +++ b/class-repo-public/labs/lab07/src/recharge_station.h @@ -0,0 +1,41 @@ +/** + * @file recharge_station.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_RECHARGE_STATION_H_ +#define SRC_RECHARGE_STATION_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/circular_obstacle.h" +#include "src/position.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ + /** + * @brief A class representing a recharge station within the arena that is used + * to recharge robot batteries. + */ +class RechargeStation: public CircularObstacle { + public: + RechargeStation(double radius, const Position& pos, + const nanogui::Color& color) : + CircularObstacle(radius, pos, color) {} + std::string get_name(void) const override { + return "Recharge Station"; + } +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_RECHARGE_STATION_H_ */ diff --git a/class-repo-public/labs/lab07/src/robot.cc b/class-repo-public/labs/lab07/src/robot.cc new file mode 100644 index 0000000..4f4d214 --- /dev/null +++ b/class-repo-public/labs/lab07/src/robot.cc @@ -0,0 +1,80 @@ +/** + * @file robot.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/robot.h" +#include "src/robot_motion_behavior.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Static Variables + ******************************************************************************/ +uint Robot::next_id_ = 0; + +/******************************************************************************* + * Constructors/Destructor + ******************************************************************************/ +Robot::Robot(const struct robot_params* const params) : + ArenaMobileEntity(params->radius, params->collision_delta, + params->pos, params->color), + id_(-1), + heading_angle_(0), + angle_delta_(params->angle_delta), + battery_(params->battery_max_charge), + motion_handler_(), + motion_behavior_(), + sensor_touch_() { + motion_handler_.heading_angle(270); + motion_handler_.speed(5); + id_ = next_id_++; +} + + +/******************************************************************************* + * Member Functions + ******************************************************************************/ +void Robot::TimestepUpdate(uint dt) { + Position old_pos = get_pos(); + + // Update heading and speed as indicated by touch sensor + motion_handler_.UpdateVelocity(sensor_touch_); + + // Use velocity and position to update position + motion_behavior_.UpdatePosition(this, dt); + + // Deplete battery as appropriate given distance and speed of movement + battery_.Deplete(old_pos, get_pos(), dt); +} /* TimestepUpdate() */ + +void Robot::Accept(__unused const EventRecharge * const e) { + battery_.EventRecharge(); +} + +void Robot::Accept(const EventCollision * const e) { + sensor_touch_.Accept(e); +} + +void Robot::EventCmd(enum event_commands cmd) { + motion_handler_.AcceptCommand(cmd); +} /* event_cmd() */ + +void Robot::Reset(void) { + battery_.Reset(); + motion_handler_.Reset(); + sensor_touch_.Reset(); +} /* Reset() */ + +void Robot::ResetBattery(void) { + battery_.Reset(); +} + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab07/src/robot.h b/class-repo-public/labs/lab07/src/robot.h new file mode 100644 index 0000000..6955ea4 --- /dev/null +++ b/class-repo-public/labs/lab07/src/robot.h @@ -0,0 +1,116 @@ +/** + * @file robot.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ROBOT_H_ +#define SRC_ROBOT_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/robot_motion_handler.h" +#include "src/robot_motion_behavior.h" +#include "src/sensor_touch.h" +#include "src/robot_battery.h" +#include "src/arena_mobile_entity.h" +#include "src/event_recharge.h" +#include "src/event_collision.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief Class representing a mobile robot within the arena. + * + * Robots have the capability of updating their own position when asked, and + * also track their own velocity and heading. They have a touch sensor for + * responding to collision events which is activated/deactivated on collision + * events. + * + */ +class Robot : public ArenaMobileEntity { + public: + explicit Robot(const struct robot_params* const params); + + /** + * @brief Reset the robot's battery to full after an encounter with the + * recharge station. + */ + void ResetBattery(void); + + /** + * @brief Reset the robot to a newly constructed state (needed for reset + * button to work in arena GUI). + */ + void Reset(void) override; + + /** + * @brief Update the robot's position and velocity after the specified + * duration has passed. + * + * @param dt The # of timesteps that have elapsed since the last update. + */ + void TimestepUpdate(unsigned int dt) override; + + /** + * @brief Accept a recharge event. + * + * This causes the robot's battery to become fully charged. + * + * @param e The recharge event. + */ + void Accept(const EventRecharge * const e) override; + + /** + * @brief Pass along a collision event (from arena) to the touch sensor. + * + * This method provides a framework in which sensors can get different types + * of information from different sources. The robot's heading will be updated + * to move it away from the incident angle at the point of contact. + * + * @param e The collision event. + */ + void Accept(const EventCollision * const e) override; + + /** + * @brief Handle user input commands to change the heading or speed. + * + * @param cmd The command to process. + */ + void EventCmd(enum event_commands cmd); + + /** + * @brief Get the current battery level of the robot. + */ + double battery_level(void) { return battery_.level(); } + double heading_angle(void) const override { return motion_handler_.heading_angle(); } + void heading_angle(double ha) override { motion_handler_.heading_angle(ha); } + double get_speed(void) const override { return motion_handler_.speed(); } + void set_speed(double sp) override { motion_handler_.speed(sp); } + std::string get_name(void) const override { + return "Robot" + std::to_string(id_); + } + + private: + static unsigned int next_id_; + + int id_; + double heading_angle_; + double angle_delta_; + RobotBattery battery_; + RobotMotionHandler motion_handler_; + RobotMotionBehavior motion_behavior_; + SensorTouch sensor_touch_; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_ROBOT_H_ */ diff --git a/class-repo-public/labs/lab07/src/robot_battery.cc b/class-repo-public/labs/lab07/src/robot_battery.cc new file mode 100644 index 0000000..e278a99 --- /dev/null +++ b/class-repo-public/labs/lab07/src/robot_battery.cc @@ -0,0 +1,31 @@ +/** + * @file robot_battery.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/robot_battery.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Member Functions + ******************************************************************************/ +double RobotBattery::Deplete(__unused Position old_pos, + __unused Position new_pos, __unused double dt) { + /* @todo deplete battery by some value based on movement and speed */ + return charge_; +} /* deplete() */ + +void Accept(__unused const EventCollision * const e) { + /* @todo deplete battery by some value -- arbitrary selected for bumping */ +} + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab07/src/robot_battery.h b/class-repo-public/labs/lab07/src/robot_battery.h new file mode 100644 index 0000000..7204f55 --- /dev/null +++ b/class-repo-public/labs/lab07/src/robot_battery.h @@ -0,0 +1,88 @@ +/** + * @file robot_battery.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ROBOT_BATTERY_H_ +#define SRC_ROBOT_BATTERY_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/common.h" +#include "src/event_collision.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Classes + ******************************************************************************/ +/** + * @brief The battery for a robot. + * @todo: implement depletion for movement and collision + */ +class RobotBattery { + public: + explicit RobotBattery(double max_charge) : charge_(max_charge), + max_charge_(max_charge) {} + + /** + * @brief All robots consume SOME power, even when just sitting there not moving. + */ + double kBASE_DEPLETION = 0.1; + + /** + * @brief The amount of energy consumed by the robot due to its linear speed + * its is directly proportional to that speed, with a scaling factor. + */ + double kLINEAR_SCALE_FACTOR = 0.01; + + /** + * @brief The amount of energy consumed by the robot due to its angular speed + * its is directly proportional to that speed, with a scaling factor. + */ + double kANGULAR_SCALE_FACTOR = 0.01; + + /** + * @brief Get the current battery level. + */ + double level(void) const { return charge_; } + + /** + * @brief Handle a recharge event by instantly restoring the robot's battery + * to its maximum value. + */ + void EventRecharge(void) { charge_ = max_charge_; } + + /** + * @brief Reset the robot's battery to its newly constructed/undepleted state. + */ + void Reset(void) { EventRecharge(); } + + /** + * @brief Calculate the new battery level based on the current linear/angular speed. + * + * @param linear_vel The current linear speed, in m/s. + * @param angular_vel The current angular speed, in rad/s. + * + * @return The updated battery level. + */ + double Deplete(__unused Position old_pos, + __unused Position new_pos, __unused double dt); + + // This is how the battery can be informed a collision occured. + // Deplete accordingly. + void Accept(const EventCollision * const e); + + private: + double charge_; + double max_charge_; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_ROBOT_BATTERY_H_ */ diff --git a/class-repo-public/labs/lab07/src/robot_motion_behavior.cc b/class-repo-public/labs/lab07/src/robot_motion_behavior.cc new file mode 100644 index 0000000..15a3d52 --- /dev/null +++ b/class-repo-public/labs/lab07/src/robot_motion_behavior.cc @@ -0,0 +1,40 @@ +/** + * @file robot_motion_behavior.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/robot_motion_behavior.h" +#include "src/position.h" +#include "src/arena_mobile_entity.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Member Functions + ******************************************************************************/ +void RobotMotionBehavior::UpdatePosition(ArenaMobileEntity * const ent, + unsigned int dt) { + // Save position for debugging purposes + Position new_pos = ent->get_pos(); + Position old_pos = ent->get_pos(); + + // Movement is always along the heading_angle (i.e. the hypotenuse) + new_pos.x(new_pos.x() + cos(ent->heading_angle()*M_PI/180.0)* + ent->get_speed()*dt); + new_pos.y(new_pos.y() + sin(ent->heading_angle()*M_PI/180.0)* + ent->get_speed()*dt); + ent->set_pos(new_pos); + + printf("Updated %s kinematics: old_pos=(%d, %d), new_pos=(%d, %d)\n", + ent->get_name().c_str(), old_pos.x(), old_pos.y(), new_pos.x(), + new_pos.y()); +} /* update_position() */ + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab07/src/robot_motion_behavior.h b/class-repo-public/labs/lab07/src/robot_motion_behavior.h new file mode 100644 index 0000000..7a60fe5 --- /dev/null +++ b/class-repo-public/labs/lab07/src/robot_motion_behavior.h @@ -0,0 +1,47 @@ +/** + * @file robot_motion_behavior.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ROBOT_MOTION_BEHAVIOR_H_ +#define SRC_ROBOT_MOTION_BEHAVIOR_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/common.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Class Definitions + ******************************************************************************/ +/** + * @brief Manages the modification to the velocity based on user + * input and collisions. + * + * Translates velocity and position to a new position. Both of these are + * straightforward, but the framework allows for more sophisticated models of + * motion in which each wheel has distinct speed. + */ +class RobotMotionBehavior { + public: + RobotMotionBehavior(void) {} + + /** + * @brief Update the position for an ArenaEntity, based on its current + * position and velocity. + * + * @param[in] ent The entitity to update. + */ + void UpdatePosition(class ArenaMobileEntity * const ent, unsigned int dt); +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_ROBOT_MOTION_BEHAVIOR_H_ */ diff --git a/class-repo-public/labs/lab07/src/robot_motion_handler.cc b/class-repo-public/labs/lab07/src/robot_motion_handler.cc new file mode 100644 index 0000000..2ad9966 --- /dev/null +++ b/class-repo-public/labs/lab07/src/robot_motion_handler.cc @@ -0,0 +1,46 @@ +/** + * @file robot_motion_handler.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/robot_motion_handler.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Constructors/Destructor + ******************************************************************************/ +RobotMotionHandler::RobotMotionHandler() : + heading_angle_(0), + speed_(0), + max_speed_(5) { +} + +/******************************************************************************* + * Member Functions + ******************************************************************************/ +void RobotMotionHandler::AcceptCommand(enum event_commands cmd) { + switch (cmd) { + case COM_TURN_LEFT: break; + case COM_TURN_RIGHT: break; + case COM_FORWARD: break; + default: + std::cerr << "FATAL: bad actuator command" << std::endl; + assert(0); + } /* switch() */ +} /* accept_command() */ + +void RobotMotionHandler::UpdateVelocity( SensorTouch st ) { + if (st.activated()) { + heading_angle_ = - st.angle_of_contact(); + } +} + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab07/src/robot_motion_handler.h b/class-repo-public/labs/lab07/src/robot_motion_handler.h new file mode 100644 index 0000000..62a6475 --- /dev/null +++ b/class-repo-public/labs/lab07/src/robot_motion_handler.h @@ -0,0 +1,81 @@ +/** + * @file actuator_handler.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ROBOT_MOTION_HANDLER_H_ +#define SRC_ROBOT_MOTION_HANDLER_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/event_commands.h" +#include "src/robot_params.h" +#include "src/arena_mobile_entity.h" +#include "src/sensor_touch.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Classes + ******************************************************************************/ +/** + * @brief The handler for the robot's actuators, which in this case are the two + * wheel actuators. Its main job is to translate the directional commands from + * the user into the appropriate differential drive wheel speeds. + * + * Manages the modification to the velocity based on user input and + * collisions. \ref RobotMotionBehavior translates velocity and position to a + * new position. Both of these are straightforward, but the framework allows + * for more sophisticated models of motion in which each wheel has distinct + * speed. + * + * For this iteration, both wheels are always going at maximum speed, and + * cannot be controlled independently. + */ +class RobotMotionHandler { + public: + RobotMotionHandler(void); + + /** + * @brief Reset the actuators to their newly constructed/un-commanded state. + */ + void Reset(void) {} + + /** + * @brief Command from user keypress via the viewer. + * + * @param cmd The command. + */ + void AcceptCommand(enum event_commands cmd); + + /** + * @brief Change the speed and direction according to the sensor readings. + * + * @param touch sensor that can be activated and contains point-of-contact. + * + */ + void UpdateVelocity(SensorTouch st); + + double speed(void) const { return speed_; } + void speed( double sp ) { speed_ = sp; } + + double heading_angle(void) const { return heading_angle_;} + void heading_angle( double ha ) { heading_angle_ = ha; } + + double max_speed(void) const { return max_speed_; } + void max_speed( double ms ) { max_speed_ = ms; } + + private: + double heading_angle_; + double speed_; + double max_speed_; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_ACTUATOR_HANDLER_H_ */ diff --git a/class-repo-public/labs/lab07/src/robot_params.h b/class-repo-public/labs/lab07/src/robot_params.h new file mode 100644 index 0000000..f42310d --- /dev/null +++ b/class-repo-public/labs/lab07/src/robot_params.h @@ -0,0 +1,39 @@ +/** + * @file robot_params.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_ROBOT_PARAMS_H_ +#define SRC_ROBOT_PARAMS_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include "src/arena_mobile_entity_params.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Structure Definitions + ******************************************************************************/ + /** + * @brief Inherits from mobile_entity_params to set various + * properties of the robot. + **/ +struct robot_params : public arena_mobile_entity_params { + robot_params(void) : + arena_mobile_entity_params(), + battery_max_charge(), + angle_delta() {} + + double battery_max_charge; + uint angle_delta; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_ROBOT_PARAMS_H_ */ diff --git a/class-repo-public/labs/lab07/src/sensor_touch.cc b/class-repo-public/labs/lab07/src/sensor_touch.cc new file mode 100644 index 0000000..6d989c8 --- /dev/null +++ b/class-repo-public/labs/lab07/src/sensor_touch.cc @@ -0,0 +1,47 @@ +/** + * @file sensor_touch.cc + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include "src/sensor_touch.h" +#include "src/arena_entity.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Constructors/Destructor + ******************************************************************************/ +SensorTouch::SensorTouch(void) : + activated_(false), + point_of_contact_(0, 0), + angle_of_contact_(0) { +} + +/******************************************************************************* + * Member Functions + ******************************************************************************/ + +void SensorTouch::Accept(const EventCollision * const e) { + // Determine if the sensor should be activated or inactivated. + if (e->collided()) { + activated_ = true; + point_of_contact_ = e->point_of_contact(); + angle_of_contact_ = e->angle_of_contact(); + } else { + activated_ = false; + } +} + +void SensorTouch::Reset(void) { + activated_ = false; +} /* reset() */ + +NAMESPACE_END(csci3081); diff --git a/class-repo-public/labs/lab07/src/sensor_touch.h b/class-repo-public/labs/lab07/src/sensor_touch.h new file mode 100644 index 0000000..6b51a6b --- /dev/null +++ b/class-repo-public/labs/lab07/src/sensor_touch.h @@ -0,0 +1,77 @@ +/** + * @file sensor_touch.h + * + * @copyright 2017 3081 Staff, All rights reserved. + */ + +#ifndef SRC_SENSOR_TOUCH_H_ +#define SRC_SENSOR_TOUCH_H_ + +/******************************************************************************* + * Includes + ******************************************************************************/ +#include +#include + +#include "src/common.h" +#include "src/event_collision.h" + +/******************************************************************************* + * Namespaces + ******************************************************************************/ +NAMESPACE_BEGIN(csci3081); + +/******************************************************************************* + * Classes + ******************************************************************************/ +class ArenaEntity; + +/** + * @brief A representation of a touch sensor. + * + * Touch or "bump" sensors are "active" when pressed. For our purposes, imagine + * a series of bump sensors around the perimeter of the robot. A collision will + * activate the sensor at a particular point of contact, which translates to an + * angle of contact. + */ +class SensorTouch { + public: + SensorTouch(); + + /** + * @brief Get the current activation reading from the sensor. + */ + bool activated(void) { return activated_; } + void activated(bool value) { activated_ = value; } + + Position point_of_contact() { return point_of_contact_; } + void point_of_contact(Position p) { + point_of_contact_ = p; + } + + double angle_of_contact(void) { return angle_of_contact_; } + void angle_of_contact(double aoc) { angle_of_contact_ = aoc; } + + /** + * @brief Compute a new reading based on the collision event. + * + * Note that collision events can report "no collision" which + * will inactivate the sensor. + * + */ + void Accept(const EventCollision * const e); + + /** + * @brief Reset the proximity sensor to its newly constructed state. + */ + void Reset(void); + + private: + bool activated_; + Position point_of_contact_; + double angle_of_contact_; +}; + +NAMESPACE_END(csci3081); + +#endif /* SRC_SENSOR_TOUCH_H_ */ diff --git a/class-repo-public/labs/lab07/tests/Duck_Test_Example.cc b/class-repo-public/labs/lab07/tests/Duck_Test_Example.cc new file mode 100755 index 0000000..2455dfc --- /dev/null +++ b/class-repo-public/labs/lab07/tests/Duck_Test_Example.cc @@ -0,0 +1,19 @@ +/* STUDENTS: DO NOT EDIT THIS FILE. INSTEAD, CREATE YOUR OWN UNIT TESTS ACCORDING +TO THE LAB DOCUMENT. +*/ + +// Google Test Framework +#include +// Project code from the ../src directory +#include "../src/Duck.h" +//#include +#include + +#ifdef FEEDBACK_TEST_01 +// Initial Test for Rubber Duck. YayYay +TEST(RubberDuckTest,display){ + RubberDuck my_ralph; + string except_str="I am a Rubber Duck."; + ASSERT_STREQ(except_str.c_str(),my_ralph.display().c_str()) << "FAIL: display" ; +} +#endif diff --git a/class-repo-public/labs/lab07/tests/FlyBehavior_Test_Example.cc b/class-repo-public/labs/lab07/tests/FlyBehavior_Test_Example.cc new file mode 100755 index 0000000..a74fb61 --- /dev/null +++ b/class-repo-public/labs/lab07/tests/FlyBehavior_Test_Example.cc @@ -0,0 +1,15 @@ +// Google Test Framework +#include +// Project code from the ../src directory +#include "../src/FlyBehavior.h" +#include + +#ifdef FEEDBACK_TEST_02 + +TEST(FlyBehaviorTest, fly) { + FlyBehavior my_flybehavior; + string expect_str="Generic Flying at 5 mph."; + EXPECT_STREQ(expect_str.c_str(),my_flybehavior.fly().c_str())<< "FAIL: fly function!"; +} + +#endif diff --git a/class-repo-public/labs/lab07/tests/Makefile b/class-repo-public/labs/lab07/tests/Makefile new file mode 100755 index 0000000..07da1ba --- /dev/null +++ b/class-repo-public/labs/lab07/tests/Makefile @@ -0,0 +1,206 @@ +### CSci-3081W Project Support Code Makefile ### + +# This Makefile is a bit different from the other project makefiles +# because it compiles both the project code in the src directory +# and the google test testing code in the tests code to create an +# executable called bin/unittest. This also assumes that google +# test project has already been compiled into a gtest_main.a library +# that is available on the system. This should be the case on the +# lab machines, but students compiling on their own machines should +# first build google test and then adjust the GTEST_DIR variable +# below to point to their local version of google test install dir. + + +# File History: This combines Prof. Keefe's Makefiles from past years +# with TA John Harwell's 3081W Makefiles from Fall 2016, which introduced +# auto-dependency generation and several other exciting features. + + +### Section 0: Change this when compiling on non-CSELabs machines ### + +# Path to pre-installed cs3081 support libraries (Google Test, lib_simple_graphics, nanogui, ...) +CS3081DIR = /project/f17c3081 + + + +### Section I: Definitions ### + +# Students: Uncomment the lines below to enable more feedback tests as +# you continue to write your code. Before turning in your assignment +# you should be able to uncomment each of the tests and successfully +# build and run the unittest executable. + +DEFINES += -DFEEDBACK_TEST_01 +DEFINES += -DFEEDBACK_TEST_02 +DEFINES += -DFEEDBACK_TEST_03 +DEFINES += -DFEEDBACK_TEST_04 + + +# Directory of source files for the project we wish to test +PROJSRCDIR = ../src + +# Directory of source files for the unit tests themselves +TESTSRCDIR = . + +# Output directories for the build process +BUILDDIR = ../build +BINDIR = $(BUILDDIR)/bin +OBJDIR = $(BUILDDIR)/obj/tests + +# The name of the executable to create +EXEFILE = $(BINDIR)/unittest + + +# Google Test includes its own main() function, so we do not want to +# compile the project's main function into our tests. We will filter +# these files out from the list of project sources. +# Also, since our tests do not require graphics, we can also filter +# out the RobotViewer source files and avoid the dependency on the +# pre-installed graphics libraries on the CSELabs machines, making it +# a bit easier to develop and test project code on non-CSELabs machines. +MAINSRCFILES = $(PROJSRCDIR)/main.cc $(PROJSRCDIR)/main.cpp $(PROJSRCDIR)/robot_viewer.cpp + +# The list of files to compile for this project. Defaults to all +# of the .cpp and .cc files in the source directory. (We use both .cpp +# and .cc in order to support two different popular naming conventions.) +PROJSRCFILES = $(filter-out $(MAINSRCFILES), $(wildcard $(PROJSRCDIR)/*.cpp) $(wildcard $(PROJSRCDIR)/*.cc)) + +# Same as above, but captures the test files, which are in a different dir. +TESTSRCFILES = $(wildcard $(TESTSRCDIR)/*.cpp) $(wildcard $(TESTSRCDIR)/*.cc) + +# For each of the source files found above, replace .cpp (or .cc) with +# .o in order to generate the list of .o files make should create. +OBJFILES = $(notdir $(patsubst %.cpp,%.o,$(patsubst %.cc,%.o,$(PROJSRCFILES)))) \ + $(notdir $(patsubst %.cpp,%.o,$(patsubst %.cc,%.o,$(TESTSRCFILES)))) + + + +# Add -Idirname to add directories to the compiler search path for finding .h files +INCLUDEDIRS = -I$(TESTSRCDIR) -I.. -isystem $(CS3081DIR)/include + +# Add -Ldirname to add directories to the linker search path for finding libraries +LIBDIRS = -L$(CS3081DIR)/lib + +# Add -llibname to link with external libraries +LIBS = -lgtest -lgtest_main -lnanogui -lsimple_graphics -lnanogui -Wl,-rpath,$(CS3081DIR)/lib + + + +# The command to run for the C++ compiler and linker +CXX = g++ + +# Arguments to pass to the C++ compiler. +# -c is required, it tells the compiler to output a .o file +# Optionally include -g to turn on debugging or include -O or -O2 to turn on optimizations instead +# Optionally include -Wall to turn on most warnings +CXXFLAGS = -g -Wall -Wextra -pthread -c $(INCLUDEDIRS) $(DEFINES) -std=c++11 + +# Arguments to pass to the C++ linker, such as -L, but not -lfoo, which should go in LDLIBS +LDFLAGS = $(LIBDIRS) -pthread + +# Library names to pass to the C++ linker, such as -lfoo +LDLIBS = $(LIBS) + + + + +### Section II: Rules ### + + +# This is a list of "phony targets" -- targets that do not specify the name of a file. +# Rather they specify the name of a recipe to run whenever make is envoked with the target name. +.PHONY: clean all $(BINDIR) $(OBJDIR) + + +# The default target which will be run if the user just types "make" +all: $(EXEFILE) + +# This rule says that each .o file in $(OBJDIR)/ depends on the +# presence of the $(OBJDIR)/ directory. +$(addprefix $(OBJDIR)/, $(OBJFILES)): | $(OBJDIR) + +# And, this rule provides a recipe for creating that objdir. The same rule applies +# to the bindir, where the exe will be output. +$(OBJDIR) $(BINDIR): + @mkdir -p $@ + + + +# COMPILING (USING A PATTERN RULE): +# Since every .cpp (or .cc) file must be compiled into a .o, we will write this +# recipe using a pattern rule. Using this recipe, any file that matches the pattern +# $(SRCDIR)/filename.cpp can be turned into $(OBJDIR)/filename.o. +$(OBJDIR)/%.o: $(PROJSRCDIR)/%.cpp + @echo "==== Auto-Generating Dependencies for $<. ====" + $(call make-depend-cxx,$<,$@,$(subst .o,.d,$@)) + @echo "==== Compiling $< into $@. ====" + $(CXX) $(CXXFLAGS) $(CXXLIBDIRS) -c -o $@ $< + +# The same thing will also work for files with a .cc extension +$(OBJDIR)/%.o: $(PROJSRCDIR)/%.cc + @echo "==== Auto-Generating Dependencies for $<. ====" + $(call make-depend-cxx,$<,$@,$(subst .o,.d,$@)) + @echo "==== Compiling $< into $@. ====" + $(CXX) $(CXXFLAGS) $(CXXLIBDIRS) -c -o $@ $< + + +# Repeat the two pattern rules above for .cc/.cpp files in the test dir +$(OBJDIR)/%.o: $(TESTSRCDIR)/%.cpp + @echo "==== Auto-Generating Dependencies for $<. ====" + $(call make-depend-cxx,$<,$@,$(subst .o,.d,$@)) + @echo "==== Compiling $< into $@. ====" + $(CXX) $(CXXFLAGS) $(CXXLIBDIRS) -c -o $@ $< + +$(OBJDIR)/%.o: $(TESTSRCDIR)/%.cc + @echo "==== Auto-Generating Dependencies for $<. ====" + $(call make-depend-cxx,$<,$@,$(subst .o,.d,$@)) + @echo "==== Compiling $< into $@. ====" + $(CXX) $(CXXFLAGS) $(CXXLIBDIRS) -c -o $@ $< + +# WITH AUTO-GENERATED DEPENDENCIES: +# Note that there are actually two steps to the compiling recipe above. The second +# step should be familiar, it just calls g++ to compile the .cpp into a .o. But, +# the first step is an advanced topic. It calls the custom function make-depend-cxx() +# defined below, which calls the g++ compiler with special flags (the -M* parts) +# that tell g++ to output a make-compatable list of all of the .h files included +# by the specified C++ source file. All of these .h files should be listed as +# dependencies of the .cpp file that is being compiled because if any of the included +# .h files change, our .cpp file will need to be recompiled in order to stay up to +# date. So, we want to be thorough and capture all of these dependencies in our +# Makefile. We could do this manually. For each .cpp file we would need to add a +# rule to this Makefile that lists dependies, and would look something like this: +# file1.o: file1.cpp file1.h file2.h mydir/file3.h myotherdir/file4.h +# where all of the .h files are either included by file1.cpp or by each other. +# It's tedious and error prone to try to track down all these dependencies manually. +# So, instead, we ask the compiler to generate a list of dependencies for us and +# save it out to a new text file with a .d extension. This text file lists the +# dependencies using the same Makefile syntax we would use if we wrote them down +# manually. Read more about auto-dependency generation here: +# http://make.mad-scientist.net/papers/advanced-auto-dependency-generation +# usage: $(call make-depend,source-file,object-file,depend-file) +make-depend-cxx=$(CXX) -MM -MF $3 -MP -MT $2 $(CXXFLAGS) $1 + +# Once the make-depend-cxx() function auto-generates the .d text file of additional +# dependency rules, we need to load it into make, as if those rules were actually +# written in this file. This is done with make's own "include" command, which +# enables us to include one Makefile within another. +-include $(addprefix $(OBJDIR)/,$(OBJFILES:.o=.d)) + + + +# LINKING: +# This rule is for the linking step of building a program. The dependencies mean that +# the executable target that we are building depends upon all of the .o files that are +# generated by the compiler as well as the $(BINDIR), which must exist so we can +# output the exe there. The recipe that follows calls g++ to tell it to link all the +# .o files into an executable program. +$(EXEFILE): $(addprefix $(OBJDIR)/, $(OBJFILES)) | $(BINDIR) + @echo "==== Linking $@. ====" + $(CXX) $(LDFLAGS) $(addprefix $(OBJDIR)/, $(OBJFILES)) -o $@ $(LDLIBS) + + + +# Clean up the project, removing ALL files generated during a build. +clean: + @rm -rf $(OBJDIR) + @rm -rf $(EXEFILE) diff --git a/class-repo-public/labs/lab07/tests/QuackBehavior_Test_Example.cc b/class-repo-public/labs/lab07/tests/QuackBehavior_Test_Example.cc new file mode 100755 index 0000000..c05ce69 --- /dev/null +++ b/class-repo-public/labs/lab07/tests/QuackBehavior_Test_Example.cc @@ -0,0 +1,19 @@ +/* STUDENTS: DO NOT EDIT THIS FILE. INSTEAD, CREATE YOUR OWN UNIT TESTS ACCORDING +TO THE LAB DOCUMENT. +*/ + +// Google Test Framework +#include +// Project code from the ../src directory +#include "../src/QuackBehavior.h" +#include + +#ifdef FEEDBACK_TEST_03 + +TEST(QuackBehaviorTest, Constructor) { + Quack my_quack; + string expect_str="Quack at 10 decibels."; + EXPECT_STREQ(expect_str.c_str(),my_quack.quack().c_str())<<"FAIL:Quack Constructor!"; +} + +#endif diff --git a/class-repo-public/labs/lab08/lab08.md b/class-repo-public/labs/lab08/lab08.md new file mode 100644 index 0000000..75e72f7 --- /dev/null +++ b/class-repo-public/labs/lab08/lab08.md @@ -0,0 +1,188 @@ +### Lab 8 : Refactoring and Branching + +In this lab, you will prepare the iteration1 code for development of iteration2. +You will maintain multiple branches and use Github _issues_ to track necessary +changes and fixes to the code. + +John Harwell has put together an informative guide on the use of git and +branching, much of which was used to create this lab. It can be found in +**_git-usage-f17.pdf_** in the class repo under _project_. You might have previously read through the section on commits. New +material on branching has been added to the bottom of the document. + +#### Branching Model + +In a general git workflow, such as most you will encounter in industry, the +_master_ branch is not the general purpose development branch. Instead a number +of other branches are used to perform development, and only pristine +(i.e. working/release/etc) versions of the code are pushed to _master_. +For this class, we will be using the following branching model, which is inspired by http://nvie.com/posts/a-successful-git-branching-model. + +##### Branches + +- **Master**: Always contains 100% functional, working code. Push to this branch +only for releases. Anytime someone clones your repository and gets your master branch, +they should *always* get a working version of code (even if it is not the most +up-to-date, which could live on your _devel_ branch). For this class moving forward, +the **Master** branch will be limited to the final submission for each iteration. +This branch always exists in a git repository, so you will never need to create it. + +- **devel** or **integration**: This is the main development branch, and also +where all feature branches branch off of or get merged into. You rarely make +changes directly in this branch, unless it is something trivial. For all +consequential development work, a feature branch should be created off of +_devel_ or _integration_ (depending on what you want to call it) to contain +the work. Branch off of _master_ at the start of a project, and never merge +back into it, except for code releases. + +**Start preparing your repo by creating the _devel_ branch.** While in the +directory of your repo: + +``` +git checkout master +git checkout -b devel +git push --all origin +``` + +This will push your newly created _devel_ branch to github. The rest of this +document will assume that you named your development branch _devel_. +You can verify that you are on the new branch with: + +``` +git branch +``` + +- **Feature Branches**: These are the branches that should contain the actual +development work. They should be named something specific (i.e. not just +feature/phase2, but feature/phase2-widgetA-scaffolding). Keep your commits on +these branches focused on the issue and make relatively few, unless the complexity +of the feature warrants more. Resist the temptation to follow multiple paths and +address multiple issues on a single feature branch. Instead, open an +issue in github to track what you have found/want to do +(See [[*Github Issues][Github Issues]]), and continue on with the work of the +feature branch. + +> A "Feature" branch is an umbrella term for many types of modifications to the +code. Other common reasons for creating a branch include refactoring, bug fixes, +formatting, documentation, and general clean-up. + +#### Github Issues + +It is important to keep a list of items that need attention in the code. Github +provides that mechanism through the use of _Issues_. In this lab, you will +create 3 issues, the associated branches, and refactor your code accordingly. +The first issue is aligning the style of _Position_ in the code with that of +_Color_. This should be a struct in its own file position.h and it should be +included in the csci3081 namespace. + +``` +Go to the github site for you repo. +Click on the Issues tab. +Click on the "New Issue" button on the upper right. +Provide an appropriate title and description. +Submit new issue. +``` + +Notice that there is a number associated with the issue. We will use this for +naming the branch created to address the issue of _Position_. + +##### Naming The Branch + +Use the categories of modification described above as part of the naming of the +branch. Also use the issue number and a short description. As a reminder, here +are the different categories of changes: +- feature - An actual feature (i.e. adding NEW functionality to the code). +- refactor - Refactoring existing code. +- bugfix - Fix a bug that was found in the code. +- style - Updating/fixing code style (i.e. making it Google style compliant, for example). +- doc - Adding/updating documentation for the code. +- chore - Doing miscellaneous grunt work, such as file moving, renaming, etc. + +A reasonable name for this issue is _refactor/01-position-as-struct_, but you +might prefer different wording. **The important part** is that it has the issue +number in the title and the name is meaningful. + +Now, make a branch for this new issue. **Confirm you are on the devel branch.** + +``` +git checkout -b refactor/01-position-as-struct +git push --all origin +``` + +Make the changes necessary to make Position a struct in its own file in the +namespace. + +... And now that you are finished, commit those changes **using the issue +number in the commit AND using the full commit message following the guidelines +provided in the git usage pdf.** + +> When you use the issue number in the message, Github recognizes and adds the +commit message as a comment to the issue creating a link between the issue +and any related commits and branches. + +``` +git add * +git commit +... enter into your editor and follow the guidelines +... the first line should be something like +... refactor(position.h): fix issue #1 position as struct +git push --set-upstream origin refactor/01-position-as-struct +``` + +If you are satisfied with the results, you now want to merge this branch with +_devel_ (not with _master_). You can do this in 2 ways. If you want to take a +trial run or if someone else is in control of merging branches, you can submit +a _pull request_. This is a request to pull in your position branch into the +devel branch. You can do this from the Github interface under Pull Requests. +Then you can _accept_ the pull request to merge the branches. +**Make sure your pull request is into the _devel_ branch - select from the +pulldown menus.** Instead, let's do this with the command line. + +``` +git checkout devel +git merge --no-ff refactor/01-position-as-struct +``` + +The _--no-ff_ option tells git not to do a fast forward merge, and actually +create a merge object, which is just a technical way of saying making it easier +to roll back changes that break things. + +> *Always* branch off of _devel_, and *always* merge back into it. + +You can now close the issue through github (notice the commit added to the issue). +And you +don't want those branches polluting your workspace, so let's delete the branch. + +``` +git branch -d refactor/01-position-as-struct +``` + +If you need to delete it from github as well, you can do: + +``` +git push origin :refactor/01-position-as-struct +``` + +Note the _:_ in the code above -- it will not work without it. + + +#### Lab Assignment + +These are the requirements of this lab: + +- Create a branch _devel_ +- Create an issue related to the struct Position +- Create a branch for Position +- refactor Position +- Merge the refactoring branch into devel +- Delete the refactoring branch + +Repeat the issue/branching for these 2 issues (except don't delete those branches +yet, so that we can see what you are doing): + +- Refactor all setters and getters in the arena entities to use the format +"set_" and "get_", for example set_angle and get_angle. + +- Fix any mismatch of types for angles and positions, both of which should be +doubles. Cast into int when needed as an integer. While you are looking at types, +make sure that heading, which is expressed in degrees for the robot, is +translated to radians in the graphics drawing functions. diff --git a/class-repo-public/project/EngagingTheCode.md b/class-repo-public/project/EngagingTheCode.md new file mode 100644 index 0000000..fa573e2 --- /dev/null +++ b/class-repo-public/project/EngagingTheCode.md @@ -0,0 +1,23 @@ +## Exercise to Engage the Code + +These are questions that ask you to explore the code in a specific way. They highlight important functionality and flow-of-control. You might want to jot down the answers for your own use as you spend time learning the code. It will probably take you a minimum of 2 hours just to understand how all the pieces fit together. We highly recommend you spend time understanding the framework before adding a single line of code. + +# Answer the Following Questions Based on Iteration1 Code + +1. What does GraphicsArenaViewer::DrawUsingNanoVG() do? +2. When does this get called? +3. How is it that the Recharge Station is being drawn - there is no command to draw it? +4. What entities exist in the Arena? + - For each entity, what is it's base (parent) class? + +5. Where does the system check for collisions (look in Arena.cc)? +6. When does this happen (i.e. where is it in flow-of-control)? +7. What is the logic for checking for colliding with walls? +8. What is the logic for checking for colliding with other entities? +9. Trace the EventCollision from the point at which it is created in Arena to +the robot sensor. What does the sensor record when a collision happens? +10. The RobotMotionHandler reads the touch sensor. What does it do with this information? +11. Identify the method (function) that updates the robot position. Trace flow-of-control +from the call UpdateSimulation in GraphicsArenaViewer. +12. What is the equation used to calculate the deltaX and deltaY for the robot +and update the position? diff --git a/class-repo-public/project/Iteration1Requirements.md b/class-repo-public/project/Iteration1Requirements.md new file mode 100644 index 0000000..5762e47 --- /dev/null +++ b/class-repo-public/project/Iteration1Requirements.md @@ -0,0 +1,414 @@ + +>Your software is a reflection of your understanding of the requirements as specified in this document. If you do not +>understand any portion of the requirements or you think that the requirements are underspecified, it is your +>responsibility to get clarification from the instructor or a TA. Please read this document carefully and review it +>prior to turning in your iteration for assessment. + +# Robot Simulator: Iteration 1 + + + +| Date | Item | Description | +|:----:|:-----|:------------| +| Wed, Oct 11, 10pm | UML diagram of base code | submit via Github | +| MonWED, Oct 25, 10pm | Preliminary code tests | Pass automated tests | +| Mon, Oct 30, 10pm | Documentation and Code Implementation | Automated tests and inspection | + + + +Your project should demonstrate thoughtful software development with good design quality, using good process. This +iteration will help you to establish those good habits. Unit tests will be provided for automated testing of +functionality. You will create design documents including a UML diagram. _doxygen_ will be used to automatically +generate code documentation. Code style will comply to the Google Style Guide. Intermediate deadlines will keep you on +track in this iterative process. + +#### Deliverables and Submission Process + +Everything will be submitted via Github. We will pull your repository at the specified due dates and grade according to the contents at that time. + +> Please verify that your submission compiles on a cselabs machine and it is complete. There are many components to be submitted - a checklist has been provided. + +The schedule is very tight, and it is important that you keep up with the project. The project will continue to build +throughout the semester, and if you get behind, it will be very difficult to catch up. For this reason, **late +assignments will not be accepted**, but you will receive partial credit for partial completion of the requirements. + +You can convert this requirements document to pdf using this tool: http://www.markdowntopdf.com/ + +
+ +## Documentation and Code Implementation Requirements + +### Overview + +The goal of this software project is to develop a rudimentary robot simulator in which robot behavior is visualized +within a graphics window. The exact use of the application is yet to be determined. It might be that it is used more +like a video game in which users control the robots. Alternatively, it might be a test environment for experimenting +with autonomous robot control using sensor feedback, or it might be a visualization of real robots operating in a remote +location. Due to the ambiguity of future use, it is important that you plan for change through good design. + +In this iteration, you will put all the pieces in place for development including libraries, base code, unit tests with +Google Test, class structure, Google style guide compliance, design documentation using UML, and doxygen +documentation. Teaching staff will also be working diligently to provide the structure and support for development, +testing, and documentation. Automation in unit testing, style compliance, in-house github testing, and documentation +will make this an easier process, however it means that you will have to develop within the constraints of these +systems. Often there is a learning curve and a lot of systems trouble-shooting to get it all working, so start early! + +In the first iteration, the robot simulator is more like a video game. Users control the robot with the arrow keys. The +objective of the game is to reach a moving home base before running out of energy. Energy is depleted as the robot moves +and even more when it bumps into obstacles, but can renew its energy by going to the charging station. + +### Functional Requirements + +You are being provided with a significant initial code base. The first iteration is focused on enhancing functionality +and getting practiced in the software development process. It is also a drop into the depths of C++ syntax. If you have +any questions about the provided code, please ask. We do not expect you to understand all the constructs in this +code. Below is an outline of the required functionality. + +The code is organized such that robot behavior is separate from robot visualization, which allows for automated unit +testing using Google Test. For example, if the robot is supposed to move from {x1,y1} to {x2, y2}, the member _position_ +should be set to {x2, y2} when complete, which is easy to check in an automated way with "getters." The visualization of +that move in the graphics window can only be tested through human inspection. The Robot class is composed of many other +classes that control various aspects of robot behavior and environment interaction. You will need to maintain this class +structure to interface with Google Tests. + +#### Robot Arena + +The robot arena has a robot, obstacle, home base, and charging station. The intent is for the robot +to move around the space in search of the home base. As it moves, its battery is depleted. +The robot can refill its battery by touching the recharge station. +If it bumps into something, it should bounce off of it. The user can change the speed and +direction of the robot using the arrow keys. If the robot reaches the home base, you win. +If it runs out of battery, you lose. All of this functionality is not yet implemented. + +#### Graphics Environment + +The graphics environment consists primarily of a single window with robots, obstacles, home base, and charging +station. All objects in the environment will be drawn as circles, which greatly simplifies collision detection. You are +welcome to add graphics enhancements, such as color, text, or decorations provided it does not interfere with required +functionality. The basic graphics window framework has been provided. You will need to get familiar with the graphics +library and provided code to learn how to draw objects of particular shape in the environment. + + +#### GUI and User Input + +A basic GUI with user buttons and input via mouse and keyboard has been provided. The user should have the following +control: + +- With UI buttons: Restart and Pause +- With left and right arrow keys, change the direction of the robot +- With up and down arrow keys, change the speed of the robot (no reverse) + +
+ +**REQUIREMENTS you need to fulfill** + +> This is just functionality. Along the way, please document the code using +Doxygen syntax and test for Google Style compliance using cpplint. + +The iterative method identifies and prioritizes feature enhancements and code refactoring +over short intervals called sprints. Below is a prioritized list of feature +enhancements, fixes, and refactoring of the code. **Complete all functionality in +one priority level before moving on to the next.** In this way, if we run out +of time, the low priority items will be dropped from the requirements. + +- Priority Level 1 + - Implement the arrow keypress handlers. Translate the keypress into appropriate + command for the robot arena and send the command to the arena, which + will send the command to robot_motion_handler_. + - Up arrow increases speed, up to a defined limit + - Down arrow decreases speed, down to 0 (no negative speeds) + - Right arrow changes the heading clockwise by some delta of your choosing + - Left arrow changes the heading counter-clockwise by some defined delta + - Bounce robot off of other entities (at angle of incidence). + - Deplete battery as robot moves. + - Recharge battery if robot collides with recharge station. + - Home base is mobile. + +- Priority Level 2 + - Implement functionality for pause and restart buttons. + - Implement functionality for win and lose. + - Fix bounce behavior off of left and right walls (it should reflect at angle of incidence.) + - Visualize battery level (can be through GUI or something on the robot) + - Replace all nanogui::Color types in Arena to separate graphics from robots. Devise a system + that will convert Arena color to nanogui color. + +- Priority Level 3 + - Limit the working dimensions inside which the robots can move (so GUI is not in arena). + - Create a Sensor base class and make SensorTouch a subclass. + - Deplete battery when robot bumps into obstacles. + - Slow the robot down when it bumps into obstacles. + - Home base randomly changes direction at random times. + - Create a total of 5 obstacles in the environment. + +
+ +### Unit Testing with Google Test + +https://github.com/google/googletest + +Unit tests are essential in a large-scale project, because the entire code base can be tested regularly, automatically, +as it undergoes development. In the strictest application of Test-Driven Development (TDD), the tests are written +_before_ the code, which helps solidify requirements and write testable code. + +While not strictly TDD, the provided code for this project iteration includes tests written in Google Test. The +functionality of the various functions and class methods must pass each of these tests (which are a subset of the +complete tests used for assessment) to receive full credit. **Note what was just stated: these are a subset of tests +used for assessment, thus also write and use your own tests.** + +The provided makefile allows you to include only the tests for which you have implemented functionality. In the makefile +uncomment collections of tests when you are ready. In addition to Google Tests, the github tests will provide a feedback +file at each push to the repo. The tests run through github will run the unit tests as part of automatic feedback. + +_More to come on testing_. + +### File Structure + +You must follow the file structure provided. It follows the conventions of code development, with some specific +requirements added as part of the Google style guide. Not being compliant with the structure will likely break +compilation and testing. Even if it doesn't, you will lose points for not maintaining the provided file structure. + +### Google Style Guide Compliance + +https://google.github.io/styleguide/cppguide.html + +Consistency in code organization, naming conventions, file structure, and formatting makes code easier to read and +integrate. There are many options with various merits, so it is up to the development team to establish these +conventions, or in the words of Kevin Wendt "The only style guide that really matters is the one your boss uses." We +decided to use the Google style guide because it is published, documented, and has automated tests. We don't agree with +every decision, but we are complying so that we are all coding to a single standard. + +To test your code for Google style compliance, install cpplint (https://github.com/cpplint/cpplint) + +Obviously, if you are doing this on your own laptop you need Python 3.x installed. + +
+ +## Documentation + +Code documentation comes in many forms for many audiences. For this project, your audience is other programmers, who +need to understand class interfaces, function parameters, function behavior, code organization, class organization, and +code logic. Self-documenting code (i.e. using good organization and naming conventions) is an efficient means of +communicating, since you have to write the code anyway. _Good_ comments - not too much, not too little - help guide +others through the logic and class interfaces. For the larger picture of code and class structure, use UML diagrams and +Doxygen-generated (automatic) web pages. + +Doxygen automatically generates documentation of class and code structure when you follow some simple conventions for +commenting within your code (_see_ http://www.stack.nl/~dimitri/doxygen/). _More on Doxygen soon_. + +All of these elements of documentation will be assessed, accounting for half of your grade for each iteration. Think +about these two extremes: 1) your code functions perfectly and you have almost no documentation - you will likely fail +on this iteration; 2) your code is not functional and will only compile when you comment most of it out, but you have +complete, great documentation - you will likely get a decent grade on this iteration. Get in the habit of creating the +documentation along with the code. + +
+ +## Assessment + +Your software **must compile on a cselabs machine or it will not be graded**. We will not grade your project unless it +compiles. As long as you provide a version that compiles, we will evaluate those aspects that are partially functional +for partial credit. In other words, comment it out if it breaks compilation. + +Your software will be assessed through automatic testing and by the TA. You will receive immediate feedback whenever you +push to github (although not now - we will let you know when those scripts are in place). At the deadline, all +submissions will be downloaded and the automated testing will no longer be available. Through both automated testing and +the TA, the process for testing will be to `make clean` then `make`. We **highly encourage** you to freshly clone your +repo on a **cselabs machine** and also `make clean` and `make` to confirm all is working well. + +This is the breakdown for point distribution: + +20% : Iteration \#1 +- 10% : UML (base code, draft) +- 10% : Preliminary Code Submission +- 40% : Final Documentation +- 40% : Final Code + +### Documentation Assessment ( 50% ) + +#### Draft of UML Diagrams ( 10% ) + +Early in the iteration, you will construct a UML diagram of the provided code. This a draft of the final product, and we anticipate that it will be revised. We will be looking for proper use of UML components, and a correct depiction of the code structure. + +#### Complete Iteration Documentation ( 40% ) + +#### Design Document and UML + +Documentation includes an overview of the design provided in mainpage.h in /src and the final UML diagram saved in /docs. The overview should be written in prose and be about 1 page of text (if it were on an 8.5x11 paper). It should highlight important design elements, such as the separation +of graphics from arena, and the composition of the robot. We will +look for a well written and well organized document that clearly articulates the design. It must stand alone in the +sense that anyone reading it should not have to look at code or other documents to understand the design. + +#### Google Style Compliance + +We will run the automated test using 'cpplint' to check compliance. There are a few additional elements for compliance +that _cpplint_ does not check for, thus TAs will inspect for those. + +#### Doxygen Documentation + +We will compile the code to generate _Doxygen_ web pages. All classes and major functions must be documented as +specified in the _Doxygen_ documentation. + +#### Readme.md BugReport.md + +The github submission must include a _bugreport_ that communicates to the TAs. It should report any requirements that are either not attempted, not complete, and/or not functional, and any known bugs in the system. Also, if you have a sense of the problem or how to fix it, please provide a brief description, as this will help TA's give partial credit to that element. + +#### Self-Documenting Code + +TAs will inspect code for good naming conventions, good code organization, and internal comments to highlight logic. + +### Code Assessment ( 50% ) + +#### Preliminary Code Submission ( 10% ) + +In the week prior to the due date of the completed iteration, we will run a subset of the automated tests. You must pass +all tests to receive full points for the preliminary submission. There will be no inspection of the code at this point +-- it is strictly a test for completed functionality. There will be transparency in this process in that you will know +what functionality will be tested, and you will see the results. Points earned at this stage are independent of the 40% +points for the final submission. + +#### Completed Iteration Code ( 40% ) + +Code functionality will be tested with automated tests through Github, which in turn run the automated unit tests of +Google Test. The provided unit tests are a subset of the final tests run for this iteration. Review the automated +feedback prior to final submission, as it will identify any issues with directory structure, compilation issues, or +failed unit tests. Assessment will also include a visual inspection of the game, as well as a visual inspection of the code. + +
+ +## Getting Started + +AFTER reading this document, pull the public class repository to obtain the base code. Copy that code to your individual +class repository on a cselabs machine while maintaining the file structure. Follow the directions for compilation and +start it up! + +> **NOTE**: Graphics code has been compiled and made available on the cselabs machines. You are linking with this code, +> thus it should not appear in your repo. If you want to develop on your personal machine, then you need to recreate +> this set-up. Do not put this code in your repo. + + +
+ +## Resources + +Graphics Libraries : +- https://github.com/wjakob/nanogui +- https://nanogui.readthedocs.io/en/latest/ +- https://github.com/memononen/NanoVG +- https://www.opengl.org/ + +Local System and Compilation: +- Repo: https://github.umn.edu/umn-csci-3081F17/class-repo-public +- Linking library: `/project/3081cf17/` +- Compilation: https://gcc.gnu.org/ +- Linux quick reference: http://www-users.cs.umn.edu/~larson/repo-website-resources/website/examples/csresources/linux.html +- Makefile Resources: http://www-users.cs.umn.edu/~larson/repo-website-resources/website/examples/csresources/cpp.html + +Testing: +- https://github.com/google/googletest +- Unit Testing: https://martinfowler.com/bliki/UnitTest.html + +Style: +- https://google.github.io/styleguide/cppguide.html +- https://github.com/google/styleguide/tree/gh-pages/cpplint +- https://www.python.org/downloads/ + +Documentation: +- http://www.stack.nl/~dimitri/doxygen/ +- https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet + +IDEs: +- Xemacs: https://www.xemacs.org/ +- vi: http://www.lagmonster.org/docs/vi.html +- Atom: https://atom.io/ +- Sublime: https://www.sublimetext.com/ +- Eclipse: https://eclipse.org/ + +
+ +### Response to Forum Inquiry: + +_There are several inconsistency issues with the latest changes introduced +by iter1-tests branch:_ + + It being the first time this code is being rolled out, this is not a surprise. We will try to address each as non-disruptively as possible. + +1. `void Robot::Accept(EventCommand * e)` was added to Robot, making the original +`void EventCmd(enum event_commands cmd)` useless. `void EventCmd(enum event_commands cmd)` can be found in the source code in +both iter1-tests and iter1-update1. I guess we ought to take out +`void EventCmd(enum event_commands cmd)?` + + My guess is that for those who have implemented the keypress, you used +_EventCmd()_ rather than _Accept()_. We can convert our tests to the _EventCmd()_ so +that you do not have to change your code. + +2. _Going off from this, I suppose this new Accept in Robot is to be called within +void Arena::Accept(const EventKeypress \*const e). But at the moment there is no +way to extract the key from EventKeypress (at least not with the source code +in iter1-update1 or iter1-tests1). Adding a getter to EventKeypress is easy, +but just want to confirm that this is what we are expected to do. I vaguely +remember this was addressed somewhere (or maybe not), so it could be my +memory glitch._ + + Yes, add a getter. + +2. _Some tests in graphics_arena_viewer_unittest.cc AND event_command_unittest.cc +are trying to call speed() on Robot. But in iter1-update1 the getter/setter for +robot speed received new names: get_speed() and set_speed(). So which is it? +Do we follow iter1-update1 or iter1-tests?_ + + I'm going to get back to you on this. I want to standardize the format of getters and setters - I will discuss this with Dr. Watters. + + +Minor issues: + +1. _robot\_params added 2 new fields: speed_delta and max\_speed. Currently +max_speed is not used at all in the base code, but I suppose it is used to initialize motion\_handler\_ _in Robot's constructor_. + + You need a limit on the speed - it is part of the requirements. + +_The purpose of angle\_delta +and speed\_delta is not very clear, should we use them to initialize +motion\_handler\_ _as well (maybe for motion handler to +increment/decrement speed/heading angle)?_ + + Yes, this is the quantity by which you modify speed and angle when a key is pressed. + +_(BTW, adding these 2 new fields means +that they should be assigned some value in main.cc too, but the base code does +not do that at the moment.)_ + + The tests are written for when you complete the iteration, not for the state of the code in master or iter1-update1 branch. + +2. _I think Position should be in the csci3081 namespace. Is there a sound +reason against doing so? It would only require minimal changes to main.cc._ + + Agreed. But since it wasn't initially, I thought I would wait to make you change it. + +3. _Unused variable orig_angle from graphics_arena_viewer_unittest.cc:38_ + + Yes. I think the intent was that if you hit reset, you have an initial direction and speed to reset the robot to. + +4. _I randomly stumbled upon this one. In robot_battery_unittest.cc, test #2 is +bat.Deplete(Position(1, 1), Position(1, 1), 1000000); and it looks like the test +expects the battery to be completely drained, even though there is no change +in position. So are we supposed to deplete the battery based on dt +(the 3rd argument) too (which is fine, as that would be some sort of standby +mode, just like cell phones)? But the requirement is "Deplete battery as +robot moves."._ + + The test for that is incorrect - the test is correctly implemented in a few tests below that. While it makes perfect sense that the battery would deplete while it is idling, we will leave the requirements that it has to be moving to deplete. The depletion should be based on speed (as calculated by the positional change and time). Doesn't matte what equation you use, but it should have the outcome that depletion increases with speed. + +_PS: It is possible to have a +double RobotBattery::Deplete(double linear_vel, double angular_vel), +we just need to calculate linear_vel and angular_vel in +void Robot::TimestepUpdate(uint dt) instead of Deplete itself. +(original question_ https://ay17.moodle.umn.edu/mod/forum/discuss.php?d=126854) + + Yes, but it's not required. + +_PS2: More robust collision handling +(ref: https://ay17.moodle.umn.edu/mod/forum/discuss.php?d=128184) +could be done using something like https://github.com/erincatto/Box2D, +as seen in 4611. But I guess it won't matter anyway in this case..._ + + That is very involved. You are welcome to make a more realistic simulation, but that really isn't necessary for our purposes. (**But do NOT use this code - do not use any code you find on the web.**) diff --git a/class-repo-public/project/Iteration2Requirements.md b/class-repo-public/project/Iteration2Requirements.md new file mode 100644 index 0000000..c213ae5 --- /dev/null +++ b/class-repo-public/project/Iteration2Requirements.md @@ -0,0 +1,336 @@ +**NOTES ON ITERATION 2** +- (11/13) TAG your devel branch with "v.2.prelim" for the preliminary submission on Wednesday, as discussed in class. See the github usage document for instructions on how to tag and push to the server. +- (11/13) Many, many edits, clarifications, and additions in response to student questions. All are marked with **EDIT** or **CLARIFICATION** or **ADDITION**. Of note, + - _SUGGESTED design_ has been added to areas in which you can make design decisions. Note why you are making those design decisions, as you will have to defend your choice in your design document, and stating that "Dr. Larson suggested this design" is not a sufficient reason. + - Every method of a non-abstract class should have tests that cover at a minimum 2 typical inputs and 1 edge case. + - You may hard code the number of Robots in the game and the initial position - have at least 5 robots in the arena. + - Keep an active bug list. Don't obsess over bugs when you still have tons to implement. +- (11/13) The requirements state to create a SuperBot class. You may instead implement a SuperBot within the class Robot. However, please do not use a flag in the Robot class that "turns on" SuperBot and uses that flag to implement one of two different pieces of code. If you want to vary behavior, consider the Strategy pattern or the Template pattern. +- (11/09) Develop in your _devel_ branch - do not merge with Master branch until time of submission. We will use tags for the preliminary submission (see the Tagging section in the git usage document: https://github.umn.edu/umn-csci-3081F17/class-repo-public/blob/master/project/git-usage-f17.pdf). And don't forget to use issues and branching to develop your code. +- (11/09) The preliminary submission requirements are 1) refactoring as defined in the lab, 2) refactoring the initialization process, and 3) writing unit tests for the sensors. +- (11/09) The preliminary submission requirements for writing unit tests will require you to create the header file for each of the sensors. Tests will fail, but that is the nature of Test-Driven Development - write your tests first to better understand the requirements and needed interfaces, then fill in the functionality that will result in the passing of the tests. You might completely change the interface prior to submitting iteration 2 - that's okay. +- (11/09) There is a lot of functionality to implement for this iteration. We highly recommend that as you come across bugs in your code, spend a little time trying to track down the problem, but do not obsess about it. Create a bug report in your github issues, and return to it **_after_** you have completed the other requirements. The only exception to this would be if the bug prevents compilation - then you either comment it out or spend more time fixing it. + + +
+>Your software is a reflection of your understanding of the requirements as specified in this document. If you do not understand any portion of the requirements or you think that the requirements are underspecified, it is your responsibility to get clarification from the instructor or a TA. Please read this document carefully and review it prior to turning in your iteration for assessment. + +# Robot Simulator: Iteration 2 + + + +| Date | Item | Description | +|:----:|:-----|:------------| +| Wed, Nov 15, 11:55pm | Preliminary code tests | Unit Tests and Refactoring | +| Tue, Nov 21, 11:55pm | Design Document, UML, and Priority 1 and 2 Code Implementation | As described below | +| Thu, Nov 30, 11:55pm | Priority 3 Code Implementation and Bug Report | As described below + + + +Your project should demonstrate thoughtful software development with good design quality, using good process. This +iteration will help you to establish those good habits. You will create design documents including a UML diagram. _doxygen_ will be used to automatically +generate code documentation. Code style will comply to the Google Style Guide. Intermediate deadlines will keep you on track in this iterative process. + +#### Deliverables and Submission Process + +Everything will be submitted via Github. We will pull your repository at the specified due dates and grade according to the contents at that time. + +> Please verify that your submission compiles on a cselabs machine and it is complete. There are many components to be submitted - a checklist has been provided. + +The schedule is very tight, and it is important that you keep up with the project. The project will continue to build +throughout the semester, and if you get behind, it will be very difficult to catch up. For this reason, **late +assignments will not be accepted**, but you will receive partial credit for partial completion of the requirements. + +You can convert this requirements document to pdf using this tool: http://www.markdowntopdf.com/ + +
+ +## Documentation and Code Implementation Requirements + +### Overview + +The goal of this software project is to develop a rudimentary robot simulator in which robot behavior is visualized +within a graphics window. The exact use of the application is yet to be determined. It might be that it is used more +like a video game in which users control the robots. Alternatively, it might be a test environment for experimenting +with autonomous robot control using sensor feedback, or it might be a visualization of real robots operating in a remote +location. Due to the ambiguity of future use, it is important that you plan for change through good design. + +In this iteration, you will continue the software development of iteration 1, write unit tests with Google Test, use the Google style guide, and maintain your UML and doxygen +documentation. Feature enhancements for this iteration include the addition of various types of sensors, and autonomous robots (in addition to the user controlled robot). + +In this iteration, the robot simulator is still like a video game, but moving towards autonomous, intelligent robot behavior. Users control a player with the arrow keys. The +objective of the game is for the player to freeze all robots at the same time before running out of energy. Energy is depleted constantly and is depleted even more when moving +or when it bumps into obstacles, but it can renew its energy by going to the charging station. Autonomous robots that use sensors to avoid objects will move around the environment interfering with play. If the user-controlled robot collides with an autonomous robot, it will freeze (i.e. stop moving), but if another autonomous robot collides with a _frozen_ robot, it will move again. A frozen robot will emit a distress call, which when detected by another autonomous robot, allows the moving robot to ignore its proximity sensor and bump into the frozen robot. Furthermore, SuperBots roam the arena and if they collide with the Player, the Player freezes for a fixed period of time. SuperBots become SuperBots by an orndinary Robot colliding with home base. + +### Functional Requirements + +It is better to continue to develop your version, because your version is familiar to you, and you likely spent time documenting the code. If your code is truly dysfunctional, contact Dr. Larson and you will be supplied with a working version, of which you can use as is or take sections of. Functional requirements are detailed below. + +#### Robot Arena + +The robot arena has a user-controlled player, autonomous robots, obstacles, home base, and a charging station. The intent is for the player +to move around the space searching for robots to freeze. As it moves, its battery is depleted. +The player can refill its battery by touching the recharge station. + +The primary addition to the environment is autonomous robots that have 2 proximity sensors, a distress call receiver (i.e a type of sensor), an entity type sensor, and a touch sensor. _Notes on sensors are at the bottom of the requirements._ + +#### Graphics Environment + +The graphics environment consists primarily of a single window with robots, obstacles, home base, and charging +station. All objects in the environment will be drawn as circles, which greatly simplifies collision detection. You are +welcome to add graphics enhancements, such as color, text, or decorations provided it does not interfere with required +functionality. + + +#### GUI and User Input + +The user should have the following control: + +- With UI buttons: Restart and Pause +- With UI buttons: Initialize game settings +- With left and right arrow keys, change the direction of the robot +- With up and down arrow keys, change the speed of the robot (no reverse) + +
+ +**FUNCTIONAL REQUIREMENTS you need to fulfill** + +> This is just functionality. Along the way, please document the code using +Doxygen syntax and test for Google Style compliance using cpplint. + +The iterative method identifies and prioritizes feature enhancements and code refactoring +over short intervals called sprints. Below is a prioritized list of feature +enhancements, fixes, and refactoring of the code. **Complete all functionality in +one priority level before moving on to the next.** In this way, if we run out +of time, the low priority items will be dropped from the requirements. + +>CREATE DEVEL BRANCH and REFACTOR as described in lab08 before you do anything! (**CLARIFICATION**: We will assess your history of branches in your repo. You should have a branch for all the bulleted points - maybe there is an exception or two for very small changes. Make your branch when you are ready to work on that issue. If you can, finish it, and merge it before moving on to a new issue and new branch - this isn't always possible and having 5-10 branches at once would not be unusaul.) + +- **Refactoring** + - As outlined in lab and in class, please refactor your code to standardize interfaces. + - Refactor the initialization process. (**CLARIFICATION**: The use of a single file to gather initialization information is a suggestion - not a requirement. You can leave initialization using the param structures in main, but this still needs to be refactored - because in Dr. Larson's opinion it is not consistent nor complete for this new set-up. Make sure the process allows for multiple entities of the same type. For example, you cannot declare all robots with the same default position. Whatever you choose, make sure it is complete and consistent across classes.) + +- **Priority Level 1 - Sensor, Event, and Robot Interfaces** + +> **ADDITION**: At the bottom of the document are descriptions and resources of +robot sensors to help you better understand the application. + + - **Write unit tests for each Sensor class as discussed below. These tests and the refactoring will be your preliminary submission.** Unit tests do not have to be Google style compliant. (**EDIT**: You may separate tests of each class into a separate file or put them all in the one file _student\_tests\_it2.cc_. All tests should be in the _tests_ folder. Every method should have, at a minimum, tests for 2 typical values and 1 edge case. You can remove the test files from iteration 1 - many are no longer relevant to the new code.) (**ADDED RESOURCE**: It might be beneficial to use Google Test Fixtures, which allows for a special Set-Up function to initialize elements to be used in a collection of tests -- see https://github.com/google/googletest/blob/master/googletest/docs/Primer.md.) + - Define an enumerated type entity_type for representing the type of entities. Use kRobot, kSuperBot, kPlayer, kHomeBase, kRechargeStation, kWall. + - (**EDIT** SUGGESTED design) Define the interface for the abstract class Sensor modeled after the observer pattern (with a little Visitor mixed in), as well as its member variables. Each sensor should Accept an event (any type of event) from the Arena directly, not from the entity to which it belongs. The sensor determines if the event activates the sensor. It will likely be necessary for the sensor to hold a link to the arena entity to which it belongs. + - Define the interface and member variables of the SensorProximity class, derived from the Sensor base class. A proximity sensor should have a range and _field of view_, such that it has a limited cone in which it can sense objects. A single cone emanating from the center of the robot, split in two, signifies the two fields of view for the two sensors. The range and field of view, expressed as an angle, are attributes of the sensor. Sensor output is the distance to an obstacle. If there is no obstacle, it should output -1. (_See the file proximity.py in class repo in Exercises folder._) + - Define the interface and member variables of the SensorDistress class, derived from the Sensor base class. The distress signal can be sensed when it is within a defined range, but the direction of the signal cannot be determined. Sensor output is 1 for a sensed call and 0 for none. + - Define the interface and member variables of the SensorEntityType class. The type of the entity emitting the signal can be sensed when it is within a defined range, but the direction of the signal cannot be determined. Sensor output is the enumerated entity type. (**CLARIFICATION**: Think of this as a physical system. Although type information is readily available within the Arena, the other sensors would not have access to this information.) + - Revise the interface and member variables as appropriate for the SensorTouch. Sensor output is 1 for a sensed collision and 0 for none (this is a change from boolean so that all sensor readings are an integer value). + - Define the interface and member variables of the following events (as appropriate): EventCollision, EventProximity, EventDistressCall, EventTypeEmit. (**EDIT**: SUGGESTED design) Events should provide information regarding the entities involved and the location of the event. + - Define an abstract MotionHandler class that is the base class for the various types of entities in the environment. + - Define the interface and member variables as appropriate for derived motion handler classes for the various entities. Name these to match other nomenclature using base class then the derived class name, for example MotionHandlerRobot. + - Rename the Robot class the Player class. (_The term "robot" implies that it is autonomous, thus the user-controlled entity is not a robot._) + - Define the interface and member variables for a Robot class that has 2 proximity sensors (**Clarification**: The robot can be composed of a single sensor that has a left and right component, rather than 2 sensors), a distress call sensor, an entity type sensor, and a touch sensor. + - Define the interface and member variables for a SuperBot class that has 2 proximity sensors, a distress call sensor, an entity type sensor, and a touch sensor. SuperBots can unfreeze all Robots and freeze the Player. Create a visual distinction between Robot and SuperBot - color would suffice. (**EDIT** _Alternatively, you can implement this inside of the Robot class - see Notes at the top of the file._) + +> (**EDIT**) NOTE: KEEP A LIST OF YOUR BUGS using either GitHub or Doxygen, as this is part of the requirements. Do not spend too much time tracking down bugs - record them and move on. When everything is implemented, then return to your bug report and fix them. You will be docked points if there are too many bugs in your code, but you will be docked more points for not fully implementing the functionality. Remember that the goal of this class is to understand and be practiced in software development, not to implement a perfect robot simulator. + +- **Priority Level 2 - Arena Sensor Interface and Functionality** + + - (**EDIT**: SUGGESTED Design) Define an interface for Arena methods modeled after the observer pattern. The Arena should have a structure for registered sensors and the methods Arena::RegisterSensor(Sensor * s) and Arena::RemoveSensor(Sensor * s) for adding and deleting to the structure. For each update loop, the Arena checks for various events in the environment and sends a notice of that event to every registered sensor. + - Implement functionality in Arena that checks for the various events and sends those events to all registered sensors (**EDIT**: SUGGESTED design). Conceptually, this is similar to the current CheckEntityCollision method. **NOTE**: Calculating the distance to an object within range of the proximity sensor has been provided in Python. In the class repo, go to Exercises and look at the file _proximity.py_. + +- **Priority Level 3 - Entity and Sensor Functionality** + - Implement functionality for the Accept method for each event of each sensor (**EDIT** Moved from Priority 2). + - Implement functionality for Player incorporating the new structure of sensors and events. The Player has a touch sensor. It's motion is controlled by the user. If Player collides with ... + - Robot: the Player should bounce - the robot will freeze. + - SuperBot: the Player should freeze for a fixed amount of time before allowed to carry on, ignoring user input while frozen. + - Recharge Station: recharge and bounce. + - Wall: bounce. + - Home Base: bounce (not a win). + - Implement functionality for Robot (**EDIT**: You may hard code the number of robots - minimum of 5 and their initial position). The Robot moves at a fixed speed at a given heading, except under the following situations: + - If it gets close to anything other than the Player and it is not sensing a distress call, then it should avoid it by adjusting its heading and slowing as some function to the distance to the obstacle, as indicated by the proximity sensor. If nothing is sensed by the proximity sensor, resume to default speed. + - If it is sensing a distress call, then it's heading should stay fixed. + - If it collide with the Player, then it should "freeze," meaning its speed goes to 0 until another Robot or SuperBot runs into it. + - If it collides with a Robot and that robot is "frozen," that should unfreeze the robot. + - If the Robot collides with home base, it becomes a SuperBot! - think about the design of this carefully - lot's of options. + - It should bounce after colliding with an entity. + - Implement functionality for the SuperBot. It is identical to a Robot with a few exceptions: + - If it detects the Player, it should not avoid collision. + - If it collides with the Player, it bounces and the Player freezes. + + - Implement functionality for the home base and recharge station. The home base should have a touch sensor so that it bounces off of other obstacles. The recharge station does not move. + - Implement win / loss. If the Player freezes all regular robots, Player wins. If all robots turn in to SuperBots, Player loses. + +
+ +### Unit Testing with Google Test + +https://github.com/google/googletest + +Unit tests are essential in a large-scale project, because the entire code base can be tested regularly, automatically, +as it undergoes development. In the strictest application of Test-Driven Development (TDD), the tests are written +_before_ the code, which helps solidify requirements and write testable code. + +Part of the requirements of this iteration are to write the tests for the Sensor classes. These tests should be in the students_tests_it2.cc file in the tests folder. + +### File Structure + +You must follow the file structure provided. It follows the conventions of code development, with some specific +requirements added as part of the Google style guide. Not being compliant with the structure will likely break +compilation and testing. Even if it doesn't, you will lose points for not maintaining the provided file structure. + +### Google Style Guide Compliance + +https://google.github.io/styleguide/cppguide.html + +Consistency in code organization, naming conventions, file structure, and formatting makes code easier to read and +integrate. There are many options with various merits, so it is up to the development team to establish these +conventions, or in the words of Kevin Wendt "The only style guide that really matters is the one your boss uses." We +decided to use the Google style guide because it is published, documented, and has automated tests. We don't agree with +every decision, but we are complying so that we are all coding to a single standard. + +
+ +## Documentation + +Code documentation comes in many forms for many audiences. For this project, your audience is other programmers, who +need to understand class interfaces, function parameters, function behavior, code organization, class organization, and +code logic. Self-documenting code (i.e. using good organization and naming conventions) is an efficient means of +communicating, since you have to write the code anyway. _Good_ comments - not too much, not too little - help guide +others through the logic and class interfaces. For the larger picture of code and class structure, use UML diagrams and +Doxygen-generated (automatic) web pages. + +Doxygen automatically generates documentation of class and code structure when you follow some simple conventions for +commenting within your code (_see_ http://www.stack.nl/~dimitri/doxygen/). + +All of these elements of documentation will be assessed, accounting for half of your grade for each iteration. Think +about these two extremes: 1) your code functions perfectly and you have almost no documentation - you will likely fail +on this iteration; 2) your code is not functional and will only compile when you comment most of it out, but you have +complete, great documentation - you will likely get a decent grade on this iteration. Get in the habit of creating the +documentation along with the code. + +
+ +## Assessment + +Your software **must compile on a cselabs machine or it will not be graded**. We will not grade your project unless it +compiles. As long as you provide a version that compiles, we will evaluate those aspects that are partially functional +for partial credit. In other words, comment it out if it breaks compilation. + +This is the breakdown for point distribution: + +20% : Iteration \#2 +- 05% : UML +- 15% : Preliminary Code Submission +- 40% : Final Documentation +- 40% : Final Code + +### Documentation Assessment ( 50% ) + +#### Complete Iteration Documentation ( 40% ) + +#### Design Document and UML + +In this iteration, a design document will be required, which discusses key design choices. In this document, you will outline a substantial design decision, describe the process of choosing the design, and justify your final choice. Design can be driven by personal preference, and very different designs might be equally justified. Therefore, the quality of your response greatly depends on the justification, not the _right_ or _wrong-ness_ of your design. + +Documentation includes an overview of the design provided in mainpage.h in /src and the final UML diagram saved in /docs. The overview should be written in prose and be about 1 page of text (if it were on an 8.5x11 paper). It should highlight important design elements, such as the separation +of graphics from arena, and the composition of the robot. We will +look for a well written and well organized document that clearly articulates the design. It must stand alone in the +sense that anyone reading it should not have to look at code or other documents to understand the design. + +#### Google Style Compliance + +We will run the automated test using 'cpplint' to check compliance. There are a few additional elements for compliance +that _cpplint_ does not check for, thus TAs will inspect for those. + +#### Doxygen Documentation + +We will compile the code to generate _Doxygen_ web pages. All classes and major functions must be documented as +specified in the _Doxygen_ documentation. + +#### BugReport.md + +The github submission must include a _bugreport_ that communicates to the TAs. It should report any requirements that are either not attempted, not complete, and/or not functional, and any known bugs in the system. Also, if you have a sense of the problem or how to fix it, please provide a brief description, as this will help TA's give partial credit to that element. + +#### Self-Documenting Code + +TAs will inspect code for good naming conventions, good code organization, and internal comments to highlight logic. + +### Code Assessment ( 50% ) + +#### Preliminary Code Submission ( 10% ) + +In the week prior to the due date of the completed iteration, you will submit a subset of the requirements. You must pass +all tests to receive full points for the preliminary submission. There will be no inspection of the code at this point +-- it is strictly a test for completed functionality. There will be transparency in this process in that you will know +what functionality will be tested, and you will see the results. Points earned at this stage are independent of the 40% +points for the final submission. + +#### Completed Iteration Code ( 40% ) + +Code functionality will be tested with unit tests and visual inspection of the game being played. + +
+ +## Getting Started + +It is best if you use your code from iteration1. If you think your code is so dysfunctional that it cannot be recovered, ask Dr. Larson for a completed version of the iteration1. + +**CREATE DEVEL BRANCH** and **REFACTOR** as described in lab08 before you do anything! + +
+ +## Resources + +Guides for Github: +- Tutorial: https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging + +- Guides on best practices: https://www.atlassian.com/git/tutorials/comparing-workflows + +- A workflow guide: http://nvie.com/posts/a-successful-git-branching-model. + +- This workflow in particular is pretty popular, but far more than needed for working on your own project: https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow + +Graphics Libraries : +- https://github.com/wjakob/nanogui +- https://nanogui.readthedocs.io/en/latest/ +- https://github.com/memononen/NanoVG +- https://www.opengl.org/ + +Local System and Compilation: +- Repo: https://github.umn.edu/umn-csci-3081F17/class-repo-public +- Linking library: `/project/3081cf17/` +- Compilation: https://gcc.gnu.org/ +- Linux quick reference: http://www-users.cs.umn.edu/~larson/repo-website-resources/website/examples/csresources/linux.html +- Makefile Resources: http://www-users.cs.umn.edu/~larson/repo-website-resources/website/examples/csresources/cpp.html + +Testing: +- https://github.com/google/googletest +- Unit Testing: https://martinfowler.com/bliki/UnitTest.html + +Style: +- https://google.github.io/styleguide/cppguide.html +- https://github.com/google/styleguide/tree/gh-pages/cpplint +- https://www.python.org/downloads/ + +Documentation: +- http://www.stack.nl/~dimitri/doxygen/ +- https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet + +IDEs: +- Xemacs: https://www.xemacs.org/ +- vi: http://www.lagmonster.org/docs/vi.html +- Atom: https://atom.io/ +- Sublime: https://www.sublimetext.com/ +- Eclipse: https://eclipse.org/ + +
+ +#### sensors + +This software is attempting to simulate physical systems with various degrees of fidelity. Each sensor is representative of a physical device with limited sensing capability. Below, I have provided some resources for you to better understand the physical device you are trying to simulate. + +- **__Proximity Sensor__**: There are a variety of ways to sense distance to an object. These sensors can output an analog signal (i.e. a voltage range that must be calibrated to relate a voltage to a distance) or a digital signal (i.e. a number that relates to the distance to the sensed object). The only information these sensors output are distance. If you use multiple sensors, you can get a sense of the direction of the object, but the individual sensor cannot provide that information. Here is a link to a sonar sensor -- notice the "cone" of the sensing region that has a range and width: https://www.pololu.com/product/723 + +- **__Contact Sensor__**: Contact sensors are on/off buttons that typically output high or low voltage for pressed and not pressed, respectively. Here is a link to a typical contact sensor: https://www.pololu.com/product/1403. And here is how you might use them on a round robot: http://robot50.net/2015/02/ - notice that any contact along the bumper will activate either one or both of the contact sensors. For our purposes, we are modeling a system that can tell where the contact is taking place along the perimeter of the robot. This would require something far more sophisticated than the image shown. + +- **__Distress Call Sensor__**: This might be accomplished with a small microphone, such as this one: https://www.pololu.com/product/1618/resources. It is omnidirectional with sensitivity that we can describe with range (i.e. the furthest it can be from the source of the sound and still detect it). There is a lot more information that could be gained from analyzing the frequency and amplitude of the sound waves, but for our purposes, when a robot gets within a certain distance of the robot, it will sense the distress call. An alternative to this is using IR beacons: https://www.pololu.com/product/702, which give directionality of a signal (maybe for the next iteration!). + +- **__Entity Type Sensor__**: There are a lot of sophisticated devices for transmitting and receiving complex information wirelessly, but for this simulation, you should think of cheap and small devices that can transmit and receive distinguishable signals. We could have a microphone for this as well, in which each type of entity emits a different sound. IR remote controls are one possibility: https://www.pololu.com/product/2777 diff --git a/class-repo-public/project/ProjectClassOverview.md b/class-repo-public/project/ProjectClassOverview.md new file mode 100755 index 0000000..45d4f3b --- /dev/null +++ b/class-repo-public/project/ProjectClassOverview.md @@ -0,0 +1,179 @@ +## main.cc + +```C++ +int main(int argc, \*\*char argv) { + + csci3081::InitGraphics(); + // Initialization of many parameter structs + + csci3081::ArenaViewer \*app = new csci3081::ArenaViewer(&aparams); + app->Run(); + + csci3081::ShutdownGraphics(); + return 0; +} + +``` + +## Arena Viewer ( GraphicsApp ) + +Associated Files +- graphics\_arena\_viewer.cc +- graphics\_arena\_viewer.h + +This creates the graphics window and the GUI. It is controlling the timing of the screen update, performing the drawing, watching for key and mouse events, and it is the keeper of the arena. + +Primary member is `Arena * arena_`. + +`UpdateSimulation` sends a message to the arena `arena_->advanceTime(1)`. It has methods to draw a robot, obstacle, and the homebase. It holds all the `OnEvent()` methods. It also contains a boolean `paused_` (which is associated with the button member which is `nanogui::Button *pause_btn_`.) + +## Arena + +Associated Files +- arena_params.h +- arena.cc +- arena.h + +Class contains all the entities within the arena: +- Robot * robot_ +- Recharge_station * recharge\_station_ +- HomeBase * home\_base\_ +- vector< Obstacles * > obstacles_ + +All entities are stored in `vector< ArenaEntity * > entities_` + +Constructor creates all these entities. + +Call to `AdvanceTime(dt)` makes things happen. This sends message to each +entity to update time by 1 for each `dt` with `UpdateEntitiesTimeStep()`, which in turn +for each entity calls `entity->TimeStepUpdate(1)`. +After all entities updated for the 1 time step, checks for events: +- robot at charging station +- robot at home base- collisions with obstacles +- collisions with walls + +
+ +## Entities in the Arena + +### Base Classes + +- arena\_entity.h `class ArenaEntity`. +- arena\_immobile\_entity.h `class ImmobileArenaEntity : ArenaEntity` +- arena\_mobile\_entity.h class `MobileArenaEntity : ArenaEntity` + - arena\_mobile\_entity.cc + - arena\_mobile\_entity\_params.h : struct used for initializing entity + +Entities generally have: +- color, position, size +- if mobile, heading and velocity too +- is_mobile() return True or False +- reset() +- TimeStepUpdate + + +### Home Base + +Associated Files +- home_base.cc +- home_base.h +- home_base_params.h : struct for initializing + +``` +class Homebase : public MobileArenaEntity +``` + +This is the target that the robot needs to get to to win. The intent is to +have this moving, and better yet, have it randomly change directions at +unpredictable times to make the game harder. + +### Charging Station + +Associated Files +- recharge\_station.h + +``` +class ChargingStation : public ImmobileArenaEntity +``` + +This is a stationary entity that has the ability to recharge the robot battery. +The robot need only collide with it to get a full charge to its battery. + + +### Circular Entities (i.e. Obstacles) + +Associated Files +- obstacle\_params.h +- obstacle.cc +- obstacle.h + +``` +class CircularObstacle : public ImmobileArenaEntity +``` + +Circular stationary objects that get in the way of the robot. + +### Robot + +This is the primary entity in the arena. It is composed of many of the classes +in the project. + +- robot.cc +- robot.h + +
+ +## Robot Members + +### Motion Handler for ArenaMobileEntity + +Associated Files +- robot\_motion\_handler.cc +- robot\_motion\_handler.h + +This manages the setting of velocity for the robot (or for any mobile arena +entity). Velocity (i.e. heading and speed) are initialized at the start, but +will change when either the user inputs a command or the robot collides with +something, which will change the direction of the robot. + +### Motion Behavior for ArenaMobileEntity + +Associated Files +- robot\_motion\_behavior.cc +- robot\_motion\_behavior.h + +This is the crucial update method for mobile arena objects. It uses the velocity of +each mobile entity to calculate a new x,y position using the setter for "this". +In iteration 1, the 2 wheels will always have equal velocity, thus the robot will always move in a straight line in the direction of its heading angle. + +```C++ +void Robot::TimestepUpdate(uint dt) { + motion_handler_.UpdateVelocity(sensor_touch_); + motion_behavior_.UpdatePosition(this, dt); +} /* TimestepUpdate() */ +``` + +#### Battery + +The battery is depleted as the robot moves, with extra depletion when running into things. It is recharged at the charging station. Robot has a member `RobotBattery battery_`. + +Associated Files +- robot\_battery.cc +- robot\_battery.h + +``` +class RobotBattery +``` + +Crucial methods are `event\_recharge()` and `deplete()`. + + +#### Touch Sensor + +Touch sensors activate or signal when touched or bumped. Any time the robot +collides with something (as determined by the Arena), an event is sent to the +sensor to set its activation. Conversely, when collision is no longer detected, +an event is sent to the sensor to deactivate it. + +- sensor\_touch.cc +- sensor\_touch.h diff --git a/class-repo-public/project/RegradeRequest.md b/class-repo-public/project/RegradeRequest.md new file mode 100644 index 0000000..b7afd1d --- /dev/null +++ b/class-repo-public/project/RegradeRequest.md @@ -0,0 +1,46 @@ +### Project Regrade Requests +#### CSCI 3081W Staff Fall 2017 + +The window for regrade requests is **__10 calendar days__** after grades are released in +moodle (i.e. weekends count). To be considered, you must submit a formal request +based on the feedback file provided with specific examples, as outlined below. +Requests in the form of "Please regrade my project, I think you made a mistake +or graded me unfairly," will not be considered. + +- All regrade requests must be submitted in writing to +csci3081f17-help@umn.edu. Do not contact individual TAs or Prof. Larson with +regrade requests – they will be ignored. +- Please include _Regrade Request_ in the subject line. +- Please include a link to the home directory of your repo. +or to the specific directory of the code/documentation in consideration. +- Please include code files indicating the line numbers, screenshots, or some +other form of documentation. + +Once we have regraded an assignment/project iteration, **we will not accept +additional regrade requests for that grade item**, and the updated grade will be +final. Please include ALL requests at one time. + +When regrading any item, there is NO guarantee that your grade will go up. It +may go down if we find other issues we initially overlooked. + +### Presenting Work for a Regrade + +Requests in the form of "Please regrade my project, I think you made a mistake +or graded me unfairly," will not be considered. + +Please include with the request the specific item on the feedback that you think +was graded incorrectly, along with specific information about why you think it +was graded incorrectly. This might include a screenshot, a file with indicated +line numbers, or directions on how to run the code to see exactly the functionality +in question. + +_Example 1_: I did not receive any points for X because my code did Y, but that +was only under Z circumstances, and as you can see in the attached screenshots, +it performs correctly in the majority of cases, so I do not think I should lose +all the points for this item. And here is the link to my repo: W. + +_Example 2_: I did not receive any points for X functionality, however that is +implemented in my code. **__Go to the attached file Y.cc and look at lines ##-##__** to see the corresponding code. And here is the link to my repo: Z. + +_Example 3_: I did not receive any points for documentation of X – perhaps you did +not see it in the docs folder. It is titled Y: here is the link to the file in my repo. diff --git a/class-repo-public/project/RemoteAccessWindows.md b/class-repo-public/project/RemoteAccessWindows.md new file mode 100644 index 0000000..61e6e30 --- /dev/null +++ b/class-repo-public/project/RemoteAccessWindows.md @@ -0,0 +1,35 @@ +## NOT CURRENTLY WORKING + +``` +Systems is doing an upgrade Friday, Oct 6. We are hoping this resolves remote access issues. +``` + +
+ +#### Remotely running the Project using Windows + +This guide will teach you how to run the project remotely when using the Windows operating system. I've only tested this on Windows 10, so you may encounter issues when on older versions of Windows. This uses the X11 window system, which is a way of remotely rendering graphics via SSH. + +#### Setup + +1. Compile the project on the CSE lab machines. See the other guides on how to compile and run the project. This guide will assume you have successfully run the project when physically on the lab machines + +2. Install [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html). Get the Windows installer for your preferred number of bits. + +3. Install [VcXSrv](https://sourceforge.net/projects/vcxsrv/). This is a server which provides the rendering on your physical machine. *Note: the popular X11 server `Xming` does not work for this project. VcXSrv is required* + +4. Set-up a PuTTY connection. Pick a [CSE Labs machine](https://cseit.umn.edu/computer-classrooms/cse-labs-unix-machine-listings) as the target for your connection and put its address in the address field. Any CSE Labs machine should do (I've been using kh1260). While setting up the connection, go to `Connection->SSH->X11` and check *Enable X11 Forwarding*. You can save this setup for future use. + +#### Running the Project + +1. Start VcXSrv on your machine. This sets up a server which will render the graphics of the project on your current machine. + +2. Open PuTTY and connect to the CSE Labs machine, using a connection with X11 Fowarding enabled. + +3. Run the project via the PuTTY terminal. A window containing project's rendered output will be created on your local machine. + +#### Troubleshooting + +After connecting, you can use a different graphical application to test whether or not the X11 system is working. Running the command `firefox` should open a firefox window. + +If Firefox X11 forwarding is working and the application is still generating errors, please verify they are not related to changes you have made in the code (and thus unrelated to the X11 forwarding). You can verify you have correct versions of X11 forwarding and OpenGL by running the command `glxinfo | grep version` in the PuTTY terminal. The X11 Server and X11 Client need to be version >1.3 and OpenGL needs to be >3.0. If any of these versions are not high enough, try a different lab machine (or lab all together). diff --git a/class-repo-public/project/UML_assignment.md b/class-repo-public/project/UML_assignment.md new file mode 100644 index 0000000..24b232b --- /dev/null +++ b/class-repo-public/project/UML_assignment.md @@ -0,0 +1,28 @@ +### UML Assignment + +**DUE: Wednesday, October 11 at 10:00 pm** + +Submit via Github +Place in folder project/iteration1 + +#### Resources + +- UML@Classroom (at lib.umn.edu) +- UML graphics tool Lucid Chart +- UML graphics tool Dia Diagram Editor (available on cselabs - module load soft/dia if not already available to you) + +#### Requirements + +**Create a UML class diagram** of the provided base code with the intent of providing a clear and accurate description of the classes and how they relate. Include inheritance, composition, and aggregation if appropriate. Also indicate associations in which classes interact in a fundamental manner, for example the event classes have associations with other classes. Provide only significant class members and methods. Do not include setters and getters. + +There are no absolute rules about creating a UML diagram, however the **class boxes and connectors should follow the standards.** Class boxes have the class name appearing in a top section, members in the middle section, and methods in the bottom section. Use open triangles for inheritance, filled in diamonds for composition, open diamonds for aggregation, and lines with and without arrows for associations. + +In addition to the typical "box" diagrams of classes, **annotate the document with important information**. For example, you might indicate critical data being shared, or identify a 1-to-many relationship, or perhaps provide a brief description of the type of association between classes. This should just be a few words to highlight the important aspects, not paragraphs. + +Also, **write one paragraph** that describes the software generally. It should provide a sense of the purpose of the software, as well as an overview of a few key elements of the code structure (e.g. the graphics viewer and arena classes). If you use some text that was provided with the code, then be sure and cite your source. + +The audience is programmers (including yourself). This diagram should have sufficient detail to help someone with no knowledge of this software to better understand the framework, but not so much detail that it is hard to separate the very important elements from the insignificant details. The UML diagram that you create will be shared with your peers as part of an in-class exercise. + +#### Assessment + +This is a writing-in-the-discipline assignment, and as such, it will be assessed based on typical criteria for writing. This includes good organization, clarity of ideas, a demonstration of understanding, depth of thought, and correctness. The most important thing is to clearly highlight the organization of classes, so pay close attention to the layout that you are presenting to the user. If you have a bunch of crossed lines, you probably need to reorganize the layout. diff --git a/class-repo-public/project/git-usage-f17.pdf b/class-repo-public/project/git-usage-f17.pdf new file mode 100644 index 0000000..61fc246 Binary files /dev/null and b/class-repo-public/project/git-usage-f17.pdf differ diff --git a/class-repo-public/project/iteration1/.gitignore b/class-repo-public/project/iteration1/.gitignore new file mode 100755 index 0000000..8c194c3 --- /dev/null +++ b/class-repo-public/project/iteration1/.gitignore @@ -0,0 +1,52 @@ +# Compiled source # +################### +*.com +*.class +*.dll +*.exe +*.o +*.so + +# Temporary files # +################### +*.swp +*.swo +*~ + +# Packages # +############ +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite + +# OS generated files # +###################### +.DS_Store* +ehthumbs.db +Icon? +Thumbs.db + +# Files used by/generated by Emacs # +#################################### +.\#* +GPATH +GRTAGS +GTAGS +.clang_complete +compile_options.json + +# Build process +build +doc/html +doc/latex diff --git a/class-repo-public/project/iteration1/Makefile b/class-repo-public/project/iteration1/Makefile new file mode 100755 index 0000000..c56f10c --- /dev/null +++ b/class-repo-public/project/iteration1/Makefile @@ -0,0 +1,23 @@ +### CSci-3081W Project Support Code Makefile ### + +# This is the main Makefile for the project. It provides easy access +# to building and testing the whole project, which requires running +# make in subdirectories. + +.PHONY: proj01 docs clean + +# Build everything that can be built for this project +all: proj01 + +# Build the bin/proj01 executable by running make in the project's src directory +proj01: + $(MAKE) -C src all + +# Build docs/html, docs/latex by running doxygen in the project's docs directory +docs: + @doxygen docs/Doxyfile + @open docs/html/index.html + +# Clean everything that has been for a fresh start +clean: + $(MAKE) -C src clean diff --git a/class-repo-public/project/iteration1/README.md b/class-repo-public/project/iteration1/README.md new file mode 100755 index 0000000..b16e65f --- /dev/null +++ b/class-repo-public/project/iteration1/README.md @@ -0,0 +1,180 @@ +# CSCI3081: Robot Simulator + +The robot simulator consists of 2 primary components: the _graphics\_area\_viewer_ and _arena_. Robots and other similar entities live in the arena and are quite oblivious to being viewed by the viewer. The viewer is in control of everything in that it sends a message to the arena to update, meaning move the arena entities the appropriate amount given how much time has passed since the last update. It then gets the appropriate positional information from each entity and draws these to the screen. All the while, the system is listening for keyboard and mouse activity by the user. If any action occurs, the associated callback is executed. + +
+#### EDITS: + +- Arrow keys handles with OnSpecialKeyDown not OnKeyDown. + +
+ +Iteration 1 Requirements are in a separate document. This is a general overview +of the framework. + +An overview of classes is provided in _ProjectClassOverview.md_ + +
+ +## Mechanics of the System + +### Resources + +- libsimple_graphics : https://github.umn.edu/umn-csci-3081F17/libSimpleGraphics/ +- nanogui : https://github.com/wjakob/nanogui +- nanogui documentation: https://nanogui.readthedocs.io/en/latest/ +- nanovg : https://github.com/memononen/NanoVG +- Google Style Guide : https://google.github.io/styleguide/cppguide.html +- cpplint : https://github.com/google/styleguide/tree/gh-pages/cpplint +- doxygen : http://www.stack.nl/~dimitri/doxygen/ +- Google Test : https://github.com/google/googletest + +### libSimpleGraphics and /project/f17c3081 + +The arena viewer is a subclass of GraphicsApp, which can be found in the libSimpleGraphics repo. When building the simulator, the local object files are linked with libSimpleGraphics located at `/project/f17c3081`, which is publicly accessible on all cselabs machines. If you want to compile on your own platform, you will have to create a similar directory and change the makefile to reflect your local drive (i.e. edit the CS3081DIR variable in src/Makefile to point to the directory where libSimpleGraphics has been installed.) + +> **Do not submit to your repo any of the code from libSimpleGraphics. Also, do not submit a makefile in which you are linking to your personal local directory - it must refer to the cselabs directory.** + +### Directory Structure + +Makefile: +- make all and make clean for the whole project (calls make recursively in subdirs). + +src/: +- the main source code where you will find robot_viewer.h/cc and robot_land.h/cc + +src/Makefile: +- builds the project source, assumes it should build all .cc and .cpp files in the current directory +- **creates an executable in build/bin/exename** +- uses auto dependency generation to get dependencies from #includes + +build/: +- created by the Makefiles + +build/bin: +- **all exes generated for the project go in here** + +build/obj: +- all .o's and .d's (from make depend) go in here + +
+ +## Getting Started + +1. _pull_ the class repo. +2. cp the project to your personal repo +3. If you are working from your local machine, modify the `CS3081DIR` variable in `src/Makefile` so that it points to your local installation of libSimpleGraphics. Ignore this step if working on a CSE labs machine. +4. At the top level from the OS command prompt: `make` +5. Navigate to `build/bin` +6. At command prompt: `./arenaviewer` + +If you have trouble compiling, it might be that your account is behind on the gcc version. At the prompt, type `gcc --version` and if it says 4.x, you need to load gcc: `module load /soft/gcc/7.1`. Check your ~/.bashrc file. +If you see a load for gcc, remove this. Then when you login, gcc 5.x will be the default. + +There are more directions in libSimpleGraphics about getting things configured for +your personal platform. Systems is working on a potential solution for remote access through vole. + +
+ +## Code explanation + +### class GraphicsApp + +The GraphicsApp makes use of the nanogui, nanovg, and openGL libraries to create a window with 2D graphics and a GUI. As with most graphics applications, there is a main loop in which keyboard and mouse events are handled, then all objects in the graphics window are drawn. The loop is executed at the frame rate until the application is closed. The actual loop is hidden inside the library code, but it behaves something like this: + +```C++ +while (Active(window)) { // ie user has not clicked 'x' to close the window + handleKeyMouseEvents(); + clearScreen() // start with a fresh screen/buffer + drawContents(); + swapBuffers(window); // double-buffered system +} +``` + +This loop is initiated by a call to nanogui::mainloop(), which you can find in GraphicsApp::Run(). + +```C++ +void GraphicsApp::Run() { + glfwSetTime(0.0); + nanogui::mainloop(); +} +``` + +Once that mainloop() is initiated, at the frame rate, GraphicsApp::draw() will be called. Notice the **two very important function calls `UpdateSimulation()` and `DrawUsingNanoVG()`**. + +```C++ +void GraphicsApp::draw(NVGcontext *ctx) { + + double now = glfwGetTime(); + UpdateSimulation(now - last_draw_time_); + last_draw_time_ = now; + + // Draw the user interface using the nanogui library, which is built upon nanovg + Screen::draw(ctx); + + // Draw other vector graphics using the nanovg library + DrawUsingNanoVG(ctx); +} +``` + +### class GraphicsArenaViewer in graphics_arena_viewer.h / .cc + +RobotViewer is a subclass of GraphicsApp. In this application, it is instantiated in main() with the name _app_. Notice in the constructor of RobotViewer, you see the instantiation of robot land. The simulator gets started with a call to app->Run(), which calls nanogui::mainloop(): + +```C++ +int main(int argc, char **argv) { + + csci3081::InitGraphics(); + + csci3081::GraphicsArenaViewer \*app = new csci3081::GraphicsArenaViewer(); + app->Run(); + + csci3081::ShutdownGraphics(); + return 0; +} +``` + +Recall that in the draw() function of the GraphicsApp, there is a call to UpdateSimulation() and DrawUsingNanoVG(). **Look at these function definitions in _graphics\_arena\_viewer.cc._** All that is happening in Update is a call to the arena to update based on how much time has passed. In the draw function, each entity is being drawn. Notice that the graphics commands for drawing each of the objects begins with `nvg` for nanovg. The header file `https://github.com/memononen/nanovg/blob/master/src/nanovg.h` is pretty well documented. Take a moment to look at this header file and read about some of the function calls that you see in drawing the various graphics objects (e.g. DrawRobot, DrawObstacle). + +(The keyboard and mouse event processing is discussed below.) + +### The Classes + +Arena has no awareness of the viewer, despite the fact that it was instantiated inside of the viewer. You can think about robots in the arena constantly moving around (even though they move in discrete steps), but periodically the viewer takes a snapshot of the situation and displays it on the screen. The viewer interacts with the arena through getters, which get information from each robot and each obstacle that is relevant to drawing them in the graphics window. It also sends commands from the user to control the robot. + +Currently, the arena has a stationary obstacle, home base, recharging station, and a mobile robot. +All of these entities are subclasses of either ArenaMobileEntity or ArenaImmobileEntity, which in turn is +a subclass of ArenaEntity. The robot contains a battery, touch sensor, a motion handler for setting velocity, and motion behavior for calculating a new position at each update. These are all instantiations of distinct +classes. There are parameter structures for the various entities for initialization. Finally, there +are different types of event classes, including collision, keypress, recharge, and command. While not +a true implementation of the Visitor pattern, these events are accepted by the arena or entities +in the arena. All event classes are subclasses of the EventBaseClass. + + +### Keyboard and Mouse Event Handling + +Events are handled inside the viewer, because this manages all aspects of the GUI. Look in graphics\_area\_viewer.cc to see the various events to be handled (e.g. OnKeyDown() **OnSpecialKeyDown()**, which handles the down arrow key). The name gives an indication of which event it is responding to. When that event occurs, this is the function that is called. You never see the call to this function, but know that it does happen. Currently, for the most part, events are handled by printing messages to a terminal, but they really come in handy when you want the user to interact with the graphics window. For example, you might want the functionality that wherever the user clicks the mouse, a robot is created at that position. + +The menu you see in the application with a reset and pause button was a custom menu made for this application. If you downloaded the nanogui examples, you saw how complex these menus can be. It is quite easy to add buttons with various functionality to the menu. Let's trace that process through the code ... + +It starts in the viewer constructor: + +```C++ +nanogui::FormHelper *gui = new nanogui::FormHelper(this); + +nanogui::ref window = + gui->addWindow(Eigen::Vector2i(10, 10), "Simulation Controls"); +pause_btn_ = + gui->addButton("Pause", std::bind(&RobotViewer::OnPauseBtnPressed, this)); +gui->addButton("Restart", std::bind(&RobotViewer::OnRestartBtnPressed, this)); +``` + +You start with the instantiation of FormHelper, then add the various components. +The parameters being passed to the `addButton` are the name that appears on the +button and the _callback_ function, meaning the one that is called when the button +is pressed (which is registered via a mouse click). Again, all of this is going +on behind the scenes and you will not see an explicit call to the callback function. + +> Notice the special form of the comments. They are following doxygen formatting, +which when compiled for doxygen, generates the documentation in the form of a +pdf or web pages. diff --git a/class-repo-public/project/iteration1/docs/Doxyfile b/class-repo-public/project/iteration1/docs/Doxyfile new file mode 100755 index 0000000..a813da7 --- /dev/null +++ b/class-repo-public/project/iteration1/docs/Doxyfile @@ -0,0 +1,2363 @@ +# Doxyfile 1.8.10 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "3081W RobotSim Project" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = 1.0 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = docs + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = src + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, +# *.vhdl, *.ucf, *.qsf, *.as and *.js. + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /