├── .gitignore ├── Chapter 00 - Foreword └── main.cpp ├── Chapter 01 - Hello World └── main.cpp ├── Chapter 02 - Types and Variables └── main.cpp ├── Chapter 03 - If └── main.cpp ├── Chapter 04 - While └── main.cpp ├── Chapter 05 - Vectors └── main.cpp ├── Chapter 06 - For └── main.cpp ├── Chapter 07 - Functions └── main.cpp ├── Chapter 08 - Using Multiple Files ├── main.cpp ├── vector_algos.cpp └── vector_algos.hpp ├── Chapter 09 - Iterators ├── main.cpp ├── vector_algos.cpp └── vector_algos.hpp ├── Chapter 10 - References ├── main.cpp ├── vector_algos.cpp └── vector_algos.hpp ├── Chapter 11 - Standard Algorithms ├── main.cpp ├── vector_algos.cpp └── vector_algos.hpp ├── Chapter 12 - Function Templates ├── main.cpp └── vector_algos.hpp ├── Chapter 13 - Exceptions ├── io.hpp ├── main.cpp ├── math.cpp └── math.hpp ├── Chapter 14 - Project Overview └── main.cpp ├── Chapter 15 - Basic Structs ├── lex.cpp ├── lex.hpp ├── main.cpp └── token.hpp ├── Chapter 16 - Basic Operator Overloading ├── lex.cpp ├── lex.hpp ├── main.cpp ├── token.cpp └── token.hpp ├── Chapter 17 - Member Functions ├── lexer.cpp ├── lexer.hpp ├── main.cpp ├── token.cpp └── token.hpp ├── Chapter 18 - Recursion ├── lexer.cpp ├── lexer.hpp ├── main.cpp ├── parser.cpp ├── parser.hpp ├── token.cpp └── token.hpp ├── Chapter 19 - Inheritance ├── expression.hpp ├── lexer.cpp ├── lexer.hpp ├── list_expr.cpp ├── list_expr.hpp ├── main.cpp ├── number_expr.cpp ├── number_expr.hpp ├── parser.cpp ├── parser.hpp ├── token.cpp ├── token.hpp ├── variable_expr.cpp └── variable_expr.hpp ├── Chapter 20 - Function Objects ├── builtin_operations.cpp ├── builtin_operations.hpp ├── expression.hpp ├── lexer.cpp ├── lexer.hpp ├── list_expr.cpp ├── list_expr.hpp ├── main.cpp ├── number_expr.cpp ├── number_expr.hpp ├── parser.cpp ├── parser.hpp ├── symbol_table.cpp ├── symbol_table.hpp ├── token.cpp ├── token.hpp ├── variable_expr.cpp └── variable_expr.hpp ├── README.md └── cpp_2_markdown.py /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | *.o 3 | *.swp 4 | build.sh 5 | 6 | -------------------------------------------------------------------------------- /Chapter 00 - Foreword/main.cpp: -------------------------------------------------------------------------------- 1 | /* Hi! This is a multi-line comment. I'm terrible at doing any kind of web 2 | * work, but I really wanted to make a less bad resource for learning C++ 3 | * available online, so I decided to do what I usually do with C++: make a 4 | * Github repository and write C++ programs. Just reading the programs would 5 | * probably be too confusing to learn the language, so I added lots of comments 6 | * to explain what's going on. 7 | * 8 | * Like this one. 9 | */ 10 | 11 | /* I decided to call this "book" Linear C++, because most people believe you 12 | * can't learn C++ in a linear fashion, and I disagree. I think existing 13 | * resources, particularly online ones, just teach things in entirely the wrong 14 | * order. They focus a lot on the low-level bits and the syntax and never show 15 | * you how the whole thing fits together as one language, and how you can write 16 | * useful programs in it with little pain. Relatively little pain, anyway. 17 | */ 18 | 19 | /* I will do my best to introduce all the concepts I use properly, without 20 | * having to resort to claims about magic. However, there are a few things 21 | * which you really do have to know first. I expect you to have already worked 22 | * in another programming language and to know how arithmetic operators such as 23 | * +, -, *, / and comparison operators such as ==, !=, <, <=, >, >= work. I 24 | * also expect you to have a vague idea of what functions, types and variables 25 | * are. Finally, I'll be using fairly new features wherever they are useful, so 26 | * make sure your compiler can handle that. Clang is probably the most friendly 27 | * compiler, but GCC or MSVC should be fine, too. 28 | * 29 | * There's a number of tutorials online about setting up your compiler. I like 30 | * the ones at http://latedev.wordpress.com , but there are others if you'd like 31 | * to use an IDE not covered there. Dev-C++ is not recommended due to being 32 | * outdated. If you want to read more about the C++ language or standard 33 | * library, http://en.cppreference.com/w/ has articles on almost everything. 34 | * 35 | * If you have any questions, feel free to submit an issue on 36 | * https://github.com/jesyspa/linear-cpp/issues 37 | * or ask me by email to jesyspa@gmail.com 38 | */ 39 | 40 | /* A note to those with experience in C: while C++ does allow a significant 41 | * portion of C, good solutions in C often aren't particularly good solutions in 42 | * C++. I suggest working through the material without resorting to any tricks 43 | * you learned in C. In particular when it comes to exceptions, we'll see that 44 | * the problems and solutions that result interact poorly with C-style code. 45 | */ 46 | 47 | /* Every chapter of the book is a correct program. This is only the foreword, 48 | * but it would be a pity to make it an exception, so we'll make it a program 49 | * that does nothing. 50 | */ 51 | 52 | // This is another kind of comment. They will be used when talking directly 53 | // about the code present, as opposed to general remarks. 54 | 55 | // A function called main, taking no parameters and returning an int. 56 | int main() { 57 | return 0; 58 | } 59 | 60 | /* main is a very special function. It is the starting point of your program, 61 | * and its return value can be used to report if any errors happened. A return 62 | * value of 0 traditionally indicates success, and is what we will return in 63 | * most cases. 64 | */ 65 | -------------------------------------------------------------------------------- /Chapter 01 - Hello World/main.cpp: -------------------------------------------------------------------------------- 1 | /* Welcome back. In the following few chapters we'll look at how variables work 2 | * in C++, and how we can perform input and output. 3 | * 4 | * In this example, we'll meet the preprocessor and look at some very basic 5 | * output. 6 | */ 7 | 8 | // This, like any line starting with a #, is a preprocessor directive. Such 9 | // lines are instructions for manipulating the program text. The #include 10 | // directive, which is what we will use most often, states that the contents of 11 | // a certain file should be inserted where the directive is. In this case, we 12 | // ask for the file iostream. The io stands for input output, and that's what 13 | // we'll be using it for. 14 | #include 15 | 16 | /* Now that we have the things we need to do input and output available to us, 17 | * we can declare and define the main function. Let's take a look at those two 18 | * terms first, though: 19 | * 20 | * A declaration introduces a name into the program, and specifies the type of 21 | * what that name refers to. In the last program, we declared main as an int(), 22 | * which you can read as "a function taking no parameters and returning int". 23 | * We can declare a name as many times as we want, as long as we declare it to 24 | * be the same type every time. Using a name before it has been declared is 25 | * usually not allowed; I'll mention exceptions as we come to them. 26 | * 27 | * When talking about functions, a definition does everything a declaration does 28 | * but also provides the body of the function. In the example in the foreword, 29 | * we define main: if we didn't, we'd get an error when trying to compile the 30 | * program. Every definition is automatically also a declaration. However, 31 | * unlike with declarations, it is generally not allowed to define something 32 | * multiple times. The rules become more nuanced when we start writing programs 33 | * that span multiple files, but you can safely assume it will not work for now. 34 | * 35 | * With these terms explained, we can now properly state why we wanted to 36 | * include iostream before doing anything else: we want to refer to names 37 | * declared in there. 38 | */ 39 | 40 | // Now we define (and thus also declare) the main function: 41 | int main() { 42 | /* Output in C++ is stream-based. We have a stream, and some value we want 43 | * to put into it, and then we use the << operator to insert the value into 44 | * the stream. 45 | */ 46 | 47 | // More concretely, this will insert the string literal "Hello world!\n" 48 | // into the stream std::cout. 49 | // 50 | // A string literal represents the text contained in it, except that some 51 | // character sequences (such as \n) are converted into characters you can't 52 | // easily write; for example, \n ends the line and goes to the next one. 53 | // 54 | // std::cout is the output stream we will use most. Anything piped into it 55 | // simply gets printed by the program. You can see std::cout as a single 56 | // name for now. 57 | // 58 | // Putting all the above together, this will print Hello World! and then 59 | // end the line, so anything we print later will end up on the next line. 60 | std::cout << "Hello world!\n"; 61 | 62 | // String literals aren't the only the only things we can print. You can 63 | // print numbers, too, in exactly the same way. 64 | // 65 | // Note that this only prints 5 -- if we want to print a newline, we need to 66 | // do that separately. 67 | std::cout << 5; 68 | 69 | // We often want to print multiple things, and repeating std::cout over and 70 | // over again would get tedious. Fortunately, we can chain insertions: 71 | ((((std::cout << " == ") << 3) << " + ") << 2) << "\n"; 72 | 73 | // This works because the result of std::cout << " == " is again std::cout. 74 | // The above is equivalent to writing five statements where std::cout is 75 | // named every time. Even better, though, the way the << operator is 76 | // defined lets us skip all the parentheses and write things like: 77 | std::cout << 3 << " plus " << 2 << " equals " << 5 << "\n"; 78 | 79 | // This last form is what we will use most. 80 | 81 | return 0; 82 | } 83 | 84 | /* The explanation that std::cout should be treated as a single name may be 85 | * unsatisfying to those with more programming experience. If you really wish 86 | * to see it as a compound entity, you can read it as "the thing named cout from 87 | * std". There's a variety of reasons we'd like to be able to have different 88 | * places provide things with the same name, and we'll eventually cover them. 89 | * However, giving that whole explanation now would be entirely unlinear, and 90 | * wouldn't help with understanding the coming few chapters. 91 | * 92 | * Here's what the practical part boils down: several names separated by :: are 93 | * a compound name for something, and we currently don't need to distinguish 94 | * compound names from non-compound ones. We will see many such names, for 95 | * types, functions, and variables, and they shouldn't come as a surprise. When 96 | * talking about things we will sometimes drop the std::, but we'll always write 97 | * it out in code until we get far enough for the proper rules to be introduced. 98 | */ 99 | -------------------------------------------------------------------------------- /Chapter 02 - Types and Variables/main.cpp: -------------------------------------------------------------------------------- 1 | /* Now that we know how to print things, it would be nice to be able to accept 2 | * input from the user. However, at the moment, we don't have anywhere to put 3 | * such input. Before we can start writing interactive programs, we need to 4 | * look at how to define variables, and for that we need to know a little about 5 | * types. 6 | * 7 | * The type of a variable specifies the possible values, what operations we can 8 | * perform on that variable, and how such operations will behave. Let's start 9 | * with a few examples: 10 | * 11 | * int is the type used for whole numbers. We can add, subtract, multiply 12 | * and divide ints, though division only gives the whole part: be careful, as 13 | * this means 2/3 == 0. There is a maximum and minimum value an int can 14 | * store; if your program adds two ints that are too large, the results can 15 | * be unpredictable and unpleasant. Dividing by zero is not allowed. 16 | * 17 | * double is the type used for numbers which aren't necessarily whole; they 18 | * are sometimes also called floating point numbers. We can add, subtract, 19 | * multiply and divide doubles, but the results are not precise. However, if 20 | * you can tolerate some error, they can still be useful. An example of a 21 | * double literal is 5.3. Dividing by zero is still not allowed. 22 | * 23 | * bool is a type which has only two values: true and false. It is used to 24 | * represent things which can be aptly described with one of those two words. 25 | * Amongst other things, comparison operators return bool. 26 | * 27 | * char is a type for representing characters. We haven't seen any 28 | * characters by themselves yet, but the string literals we used in the last 29 | * chapter were built up from them. For example, 'a' is of type char and is 30 | * the character a. Character literals like 'a' are surrounded by single 31 | * quotes, while string literals use double quotes. 32 | * 33 | * std::string is a type used for strings; that is, fragments of text. The 34 | * types above were all primitive ones, but std::string is not, though it is 35 | * standard. In order to use an std::string, we will need to include string. 36 | * It supports a large number of operations, such as getting the string 37 | * length or getting the character at some index. Note that string literals 38 | * are not std::strings -- however, we can create std::strings from them. 39 | * 40 | * TODO: Add more about primitive vs nonprimitive types. 41 | * 42 | * Now that we know what types are, let's include what we need and get into main 43 | * to experiment a little. 44 | */ 45 | 46 | #include 47 | #include 48 | 49 | int main() { 50 | // When our types are as simple as those above, we can define a variable 51 | // just by putting the type name and then the variable name we want to use. 52 | int x; 53 | 54 | // We can now store values in x. The stream we will be extracting out of is 55 | // called std::cin, and the extraction operator is >>. 56 | std::cout << "Enter a number: "; 57 | std::cin >> x; 58 | // Now we've extracted a number and put it in x. We can now use this value 59 | // however we want; for example, let's print it back out to the user: 60 | std::cout << "You entered: " << x << "\n"; 61 | 62 | /* What if the user didn't enter a number, though? If the user doesn't 63 | * enter anything, the program will sit there and wait until there's some 64 | * input. If the user does input something but it isn't a number, the 65 | * situation is worse: the program will continue, but the stream will be set 66 | * into a "fail" state and the value of x won't be anything meaningful. 67 | * We'll learn how to check for these errors very soon. 68 | */ 69 | 70 | // Let's make some more variables. 71 | int y = x; 72 | std::string s; 73 | 74 | // Now may be a good time to talk a little about what values variables get 75 | // when you define them. When we're dealing with a primitive type like int, 76 | // double, bool or char, the value will be garbage, and you shouldn't read 77 | // from it until you've given it some value. With types like std::string, 78 | // we don't have to worry about that: the string will be empty. 79 | // 80 | // The way we defined y here, its initial value will be the current value of 81 | // x. If we want to, we can change x and y later: 82 | y = 2*x; 83 | x = y + 5; 84 | // Now y will be equal to two times the value of x, and x will equal the new 85 | // value of y plus five. See: 86 | std::cout << "x == " << x << "\n"; 87 | std::cout << "y == " << y << "\n"; 88 | 89 | // Let's try reading into our std::string. 90 | std::cout << "Enter a string: "; 91 | std::cin >> s; 92 | std::cout << "You entered: " << s << "\n"; 93 | // If you've compiled and run this program, providing various inputs, you 94 | // may have noticed that only the first word you enter is read. This is 95 | // because the >> for std::string works by ignoring any whitespace 96 | // characters, and then reading until it again sees a whitespace character. 97 | 98 | /* We'll look at how to read lines later; due to the way the >> operator 99 | * works, mixing input with it and line-by-line input can give some 100 | * surprising results. 101 | */ 102 | 103 | return 0; 104 | } 105 | 106 | /* You now know enough to get input from the user, do some basic operations on 107 | * it, and then write it back out. You may want to put the book aside for a 108 | * little and write a few of your own programs to internalize the concepts. 109 | * I won't repeat this notice at the bottom of every chapter, but I hope to 110 | * cover sufficient concepts for it to be worth it after most, anyway. 111 | */ 112 | -------------------------------------------------------------------------------- /Chapter 03 - If/main.cpp: -------------------------------------------------------------------------------- 1 | /* Last time we looked at how to accept user input, but we couldn't detect 2 | * invalid input. We will introduce a language construct that lets us handle 3 | * that, but first let's take a quick refresher. 4 | * 5 | * So far, we have seen three kinds of statements. The first we encountered was 6 | * the return statement. This let us return a value out of a function. So far, 7 | * the only function we've written was main, and we always returned 0; needless 8 | * to say, we'll see much more of this later on. 9 | * 10 | * The second was an expression statement. This is simply a statement that 11 | * consists entirely of an expression. For example, 12 | * std::cout << "Hello world!\n"; 13 | * is an expression statement. Here, std::cout << "Hello world!\n" is an 14 | * expression, and the semicolon turns the whole thing into a statement. 15 | * 16 | * Finally, we had variable definitions like 17 | * int x; 18 | * 19 | * 20 | * Now we're going to introduce a fourth kind of statement: the if statement. 21 | * The syntax is as follows: 22 | * 23 | * if (condition) { 24 | * statements... 25 | * } 26 | * 27 | * The condition is something that results in type bool, and you can have as 28 | * many statements in the body as you'd like. If the condition evaluates to 29 | * true, the statements will be executed; otherwise, we skip to after the if 30 | * statement. 31 | * 32 | * Without further ado, let's get to examples. 33 | */ 34 | 35 | #include 36 | #include 37 | 38 | int main() { 39 | int x; 40 | std::cout << "Enter a number: "; 41 | std::cin >> x; 42 | 43 | // Once we've reach this point, we've made an attempt to read an int. We 44 | // can now check whether we were successful. Streams were made in such a 45 | // way that we can evaluate them as bools. If the input succeeded, std::cin 46 | // will be in a good state and converting it to a bool will give true. If 47 | // the input failed, however, std::cin will be set to "fail" and converting 48 | // it to a bool will give false. We'd like to report an error if it failed, 49 | // so we have to invert true and false; this is done with the ! operator. 50 | // !true == false and !false == true. 51 | if (!std::cin) { // if std::cin is false 52 | std::cout << "That's not a number!\n"; 53 | // Once a value has been returned, the function stops executing. This 54 | // means that a return in main works as an "early exit" from the whole 55 | // program. 56 | // 57 | // We return 1 to indicate that the program did not complete 58 | // successfully. As we will be using this program interactively it does 59 | // not matter much; however, this is still neater than returning 0 as if 60 | // everything is fine. 61 | return 1; 62 | } 63 | 64 | int y; 65 | 66 | // Recall that std::cout << "Hello" evaluates to std::cout. Similarly, 67 | // std::cin >> y will evaluate to std::cin. We can use this to take input 68 | // and check whether it succeeded in one step: 69 | std::cout << "Enter another number: "; 70 | if (!(std::cin >> y)) { // if reading y fails... 71 | std::cout << "That's not a number!\n"; 72 | return 1; 73 | } 74 | 75 | /* There's another neat feature if statements have, and that's that you can 76 | * specify some code to be executed if the condition is not true. The 77 | * syntax is: 78 | * 79 | * if (condition) { 80 | * statements... 81 | * } else { 82 | * other_statements... 83 | * } 84 | * 85 | * If condition is true, the first statements will be executed; if not, the 86 | * other ones will be. Once we're done with whichever statements we are 87 | * executing we continue with the code after the else clause. 88 | */ 89 | 90 | // We can use this to compare the two values the user entered: 91 | if (x < y) { 92 | std::cout << "The first number you entered is less than the second.\n"; 93 | } else { 94 | if (x == y) { 95 | std::cout << "The two numbers you entered are equal.\n"; 96 | } else { 97 | std::cout << "The first number you entered is greater than the second.\n"; 98 | } 99 | } 100 | 101 | // However, as you can see, we're using a lot of space to say fairly little. 102 | // When we only have one statement in the if or else block, we can skip the 103 | // curly braces and just write that one statement instead: 104 | 105 | if (x < y) 106 | std::cout << "The second number you entered is greater than the first.\n"; 107 | else if (x == y) 108 | std::cout << "The two numbers you entered are equal.\n"; 109 | else 110 | std::cout << "The second number you entered is less than the first.\n"; 111 | 112 | // Finally, you can combine multiple reads in one condition. 113 | std::cout << "Enter two numbers: "; 114 | if (std::cin >> x >> y) { 115 | std::cout << "You did it right!\n"; 116 | } else { 117 | std::cout << "Something you entered wasn't a number.\n"; 118 | } 119 | 120 | // Whether we use curly braces here or not is a matter of personal 121 | // preference. 122 | 123 | return 0; 124 | } 125 | 126 | -------------------------------------------------------------------------------- /Chapter 04 - While/main.cpp: -------------------------------------------------------------------------------- 1 | /* We now know how to check whether the input was valid, but sometimes we'd like 2 | * to let the user provide an arbitrary number of inputs and then do something 3 | * with them. For this we introduce the while statement: 4 | * 5 | * while (condition) { 6 | * statements... 7 | * } 8 | * 9 | * It is much like the if statement, but when all the statements in the body are 10 | * executed, we continue right above the statement, not right below; the 11 | * condition is again checked, and if it is true we do the same thing again. 12 | * 13 | * A problem here is knowing when to stop. We're going to take the simplest 14 | * solution and assume that any failed read indicates that there is no input 15 | * left over. This is suboptimal, as it means we can't easily have multiple 16 | * input loops, but the extra checks necessary otherwise would detract from the 17 | * main point of looping. 18 | */ 19 | 20 | #include 21 | #include 22 | 23 | int main() { 24 | // When we want to define multiple variables of a simple type, we can write 25 | // the type once and then write the variables separated by commas. 26 | int total = 0, x, count = 0; 27 | 28 | std::cout << "Enter as many numbers as you want:\n"; 29 | 30 | // We're going to read as much input as the user gives us, and then take the 31 | // average. 32 | while (std::cin >> x) { 33 | // count += 1 is the same thing as count = count + 1. We need to count 34 | // how many numbers the user gave us, or we won't know what to divide by 35 | // to get the average. 36 | count += 1; 37 | total += x; 38 | } 39 | // If the loop has ended, it means we failed to read a number. Now, by our 40 | // assumption, it is useless to ask the user to enter anything. 41 | 42 | // Remember: division by zero is bad. 43 | if (count == 0) { 44 | std::cout << "No numbers entered!\n"; 45 | } else { 46 | // Here we can calculate the average. Let's illustrate the difference 47 | // between integer and floating point maths while we're at it: 48 | std::cout << "Integer average: " << total/count << "\n"; 49 | // You can assign an int to a double; the value is converted as you'd 50 | // expect it to be, though you may lose some precision if the value 51 | // can't be stored exactly. 52 | double double_total = total; 53 | // Dividing a double by an int works like dividing a double by a double. 54 | // All other operations work similarly; if either operand is a double, 55 | // the whole thing becomes a double. 56 | std::cout << "Floating point average: " << double_total/count << "\n"; 57 | } 58 | 59 | return 0; 60 | } 61 | 62 | -------------------------------------------------------------------------------- /Chapter 05 - Vectors/main.cpp: -------------------------------------------------------------------------------- 1 | /* We can now take as much input from the user as we want, but we can still only 2 | * store a limited number of numbers at any one time. In this chapter we will 3 | * introduce vectors, which allow for storing an arbitrary number of values. 4 | * They are, in a way, similar to strings, which allow you to store as many 5 | * characters as you want. 6 | * 7 | * A vector can store any of the types we named before, but any particular 8 | * vector must store only objects of one type. We can specify what type this is 9 | * when we declare the vector. For example, if we'd like v to be a vector that 10 | * stores ints, we say 11 | * 12 | * std::vector v; 13 | * 14 | * Once a vector is declared to store things of a certain type, we can not 15 | * change it to store something else. On the bright side, this means that given 16 | * a vector we always know what type of things it contains. We also have 17 | * 18 | * v.size() // the number of elements in the vector 19 | * v.empty() // whether the vector is empty 20 | * v[0] // the first element 21 | * v[1] // the second element 22 | * ... // etc. 23 | * 24 | * As an example, we'll write a program that reads as many numbers as the user 25 | * provides, and then finds the index of the greatest and least elements. 26 | */ 27 | 28 | #include 29 | // We don't use strings in this example, but we do need to include vector to use 30 | // vectors. 31 | #include 32 | 33 | int main() { 34 | int x; 35 | // Just like with std::string, if we don't specify the starting value for 36 | // the vector it will be empty. 37 | std::vector v; 38 | 39 | std::cout << "Enter as many numbers as you want:\n"; 40 | while (std::cin >> x) { 41 | // push_back adds an element at the end of the vector. This means that 42 | // all existing values stay where they were, and the new value has the 43 | // highest index. push_front, on the other hand, puts the new element 44 | // at index 0 and moves the rest up; it can be useful, but is much 45 | // slower. 46 | v.push_back(x); 47 | } 48 | 49 | // The user has entered as many numbers as he wanted to. Now we need to 50 | // loop over the vector to find the greatest and least elements. For this 51 | // we introduce another type. Technically, the correct type to use is 52 | // std::vector::size_type. However, that's rather a mouthful and we 53 | // will instead use std::size_t, which is the same thing on many systems. 54 | // For small vectors and normal platforms this'll work. 55 | std::size_t i = 0; 56 | std::size_t greatest_index = 0; 57 | std::size_t least_index = 0; 58 | while (i < v.size()) { 59 | if (v[i] > v[greatest_index]) 60 | greatest_index = i; 61 | if (v[i] < v[least_index]) 62 | least_index = i; 63 | i += 1; 64 | } 65 | 66 | // By the way, if the user entered no numbers, we don't want to print 67 | // v[greatest_index] or v[least_index]; that would be accessing an element 68 | // which doesn't exist. C++ doesn't specify what happens if we try to do 69 | // that, but it may very well be unpleasant. 70 | // 71 | // Why is it okay that we're only performing this check here, and not before 72 | // we find the greatest and least element? 73 | if (v.empty()) { 74 | std::cout << "An empty sequence has no greatest or least element.\n"; 75 | } else { 76 | std::cout << "Greatest element " << v[greatest_index] << " is at index " << greatest_index << ".\n"; 77 | std::cout << "Least element " << v[least_index] << " is at index " << least_index << ".\n"; 78 | } 79 | 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /Chapter 06 - For/main.cpp: -------------------------------------------------------------------------------- 1 | /* So far, we've done all looping with while. However, if you wrote some of 2 | * your own code after reading the last chapter, you probably noticed that a lot 3 | * of it looked like 4 | * 5 | * std::size_t i = 0; 6 | * while (i < v.size()) { 7 | * statements... 8 | * i += 1; 9 | * } 10 | * 11 | * In fact, code like this is so common that there's a special syntactic 12 | * construct when we want to do something "for every X". Rephrased to use it, 13 | * the above becomes 14 | * 15 | * // Initialisation ; Condition ; Step 16 | * for (std::size_t i = 0; i < v.size(); i += 1) { 17 | * statements... 18 | * } 19 | * 20 | * The initialisation is a definition or expression. You can define multiple 21 | * variables of the same type, but you cannot define multiple variables of 22 | * different types -- there's simply no room. The next part is the condition, 23 | * which is exactly like in a while loop. The last is the step; it behaves much 24 | * as if it were the last statement in the body of the for, but is limited to 25 | * being an expression. 26 | * 27 | * Any of the three parts may be omitted. If you omit the initialisation or 28 | * step, that part will do nothing; if you omit the condition, it will always 29 | * evaluate to true. You will sometimes see for (;;) being used instead of 30 | * while (true) -- the two are equivalent. 31 | * 32 | * A little note about how variables behave: when we define a variable in a 33 | * block of code, it will not be visible outside that block. We will have a 34 | * much more rigorous explanation of this in the next chapter; for now it 35 | * suffices to say that if you define a variable in an if, while, or for loop, 36 | * including in the initialisation of the for loop, you will not be able to use 37 | * it outside that conditional or loop. 38 | * 39 | * Let's rewrite yesterday's program to use a for loop. 40 | */ 41 | 42 | #include 43 | #include 44 | 45 | int main() { 46 | int x; 47 | std::vector v; 48 | 49 | std::cout << "Enter as many numbers as you want:\n"; 50 | while (std::cin >> x) 51 | v.push_back(x); 52 | 53 | if (v.empty()) { 54 | std::cout << "An empty sequence has no greatest or least element.\n"; 55 | return 0; 56 | } 57 | 58 | // We want greatest_index and least_index to be visible outside the loop, so 59 | // we keep them here. 60 | std::size_t greatest_index = 0; 61 | std::size_t least_index = 0; 62 | 63 | // i += 1 is such a common operation that there's a shortcut: ++i does 64 | // exactly the same thing, while --i is i -= 1. There's also i++, which 65 | // increments i, but evaluates to the old value. If you can use either one 66 | // prefer ++i; it's a good habit to get into for when we're dealing with 67 | // more complex types than just integers. 68 | for (std::size_t i = 0; i < v.size(); ++i) { 69 | if (v[i] > v[greatest_index]) 70 | greatest_index = i; 71 | if (v[i] < v[least_index]) 72 | least_index = i; 73 | } 74 | 75 | std::cout << "Greatest element " << v[greatest_index] << " is at index " << greatest_index << ".\n"; 76 | std::cout << "Least element " << v[least_index] << " is at index " << least_index << ".\n"; 77 | 78 | /* It turned out that what we did with the vector is so common that even the 79 | * for syntax wasn't quite enough. C++11 adds range-based for loops, which 80 | * allow us to not have to deal with indices. Instead, we can write 81 | * 82 | * for (int e : v) { 83 | * statements... 84 | * } 85 | * 86 | * This will run statements once for every element of v, and during that run 87 | * e will be set to that element. We can't easily get at the index any 88 | * more, but we usually don't need it; for example, if we just want to take 89 | * the sum, we only care about the elements and not about where in the 90 | * vector they are. 91 | * 92 | * At the moment, we also can't modify the elements while looping over the 93 | * vector this way; however, we'll see a way to do that fairly soon. 94 | */ 95 | 96 | int total = 0; 97 | for (int e : v) 98 | total += e; 99 | 100 | std::cout << "Sum: " << total << "\n"; 101 | std::cout << "Integer average: " << total/v.size() << "\n"; 102 | 103 | /* If we changed the type of v from vector to vector, much of 104 | * the program would work. However, we'd have to change both int total and 105 | * int e above. When our programs get bigger, changing things like this 106 | * will be rather a pain. For that reason, we can use auto in order to have 107 | * the compiler figure out the correct type for us. This isn't a magic word 108 | * that will solve all your problems, but it can help significantly. 109 | */ 110 | 111 | // For example, let's output all the elements of v: 112 | for (auto e : v) 113 | std::cout << "v contains: " << e << '\n'; 114 | 115 | // Try changing the types in this program and see what works and what 116 | // breaks. 117 | 118 | return 0; 119 | } 120 | -------------------------------------------------------------------------------- /Chapter 07 - Functions/main.cpp: -------------------------------------------------------------------------------- 1 | /* So far I've introduced you to a few tools that you can use to write programs 2 | * of your own. You can read input, store as much of it as you want, and then 3 | * perform calculations using loops. 4 | * 5 | * In this chapter, we'll introduce you to your first meta-tool: a tool for 6 | * making tools. While you face relatively easy problems such things may seem 7 | * to cost more than they saves you, but the slightest increase in complexity 8 | * will make them irreplacable. 9 | * 10 | * I am talking about functions. 11 | * 12 | * We've already seen the main function. It had type int() and was where we put 13 | * by far most of our code. Other functions are going to have much the same 14 | * structure: 15 | * 16 | * return_type function_name() { 17 | * statements... 18 | * } 19 | * 20 | * This should look fairly familiar now. The function must return a value of 21 | * its return type; if we wish to not return anything we can use the void type, 22 | * of which no value can be made. 23 | * 24 | * Before moving on to more advanced possibilities, let's look at some examples. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | // We often write code which reads as many numbers as the user is willing to 32 | // give and stores them in a vector. Let's write a function that does that. 33 | std::vector read_int_vector() { 34 | std::vector result; 35 | int x; 36 | 37 | std::cout << "Enter as many numbers as you want:\n"; 38 | 39 | // We could use much the same implementation as we used in the last 40 | // examples, which assumes that if the input failed, there is nothing more 41 | // to read. However, let's be a little more thorough and insist that the 42 | // user keep entering data until they close the stream. If a file is being 43 | // redirected this will happen when the end of the file is reached; when 44 | // entering data directly, Control-D will do the job on Linux, while 45 | // Control-Z will work on Windows. 46 | // 47 | // Seeing as the exit condition of this loop is rather complicated, we'll 48 | // make it and endless loop and break out of it differently. 49 | while (true) { 50 | // Read all the input we can. 51 | while (std::cin >> x) 52 | result.push_back(x); 53 | 54 | // Now we know something went wrong. If we reached end of file, we can 55 | // break out of the loop. Otherwise, we want to fix the stream and 56 | // continue. 57 | if (std::cin.eof()) { 58 | // The break statement does exactly what it says on the tin: it 59 | // breaks out of the innermost loop we happen to be in. It has a 60 | // sibling: the continue statement, which skips to the end of this 61 | // loop iteration, not executing all the statements left over. 62 | break; 63 | } 64 | 65 | // We know there's still data in the stream, and we want to access it. 66 | // However, now that the stream is in a "fail" state, we won't be able 67 | // to read anything. We can get it back to a good state by clearing the 68 | // error flags: 69 | std::cin.clear(); 70 | 71 | // We'd like to tell the user what data caused us to fail, and what we 72 | // ignored. We're going to do this by reading until the end of the line 73 | // and stating that anything in there didn't get into our vector. 74 | std::string s; 75 | std::getline(std::cin, s); // read a line from std::cin into s 76 | 77 | // Rember how I said line-by-line input and the >> operator don't always 78 | // mix? That's because of how the >> operator deals with whitespace. 79 | // If it read something from the stream, it will leave all the 80 | // whitespace after it still in there. That means that when you enter 5 81 | // and press enter, >> will read the five and leave the newline. When 82 | // we then do a getline, that newline will be the "end of the line", so 83 | // we won't get a chance to enter any new data. 84 | // 85 | // Here, we can mix the two because we're reading after a >> attempt 86 | // failed. That means that it already got through all the whitespace 87 | // and what's next is data we may be interested in. 88 | 89 | std::cout << "Warning, ignoring: " << s << "\n"; 90 | } 91 | 92 | // We've read all the input we possibly could. Let's thank the user and 93 | // return the values to the rest of the program. 94 | std::cout << "End of file encountered, stopping input.\n"; 95 | return result; 96 | } 97 | 98 | // Well, that was a mouthful! However, in all the programs we write from this 99 | // point on, we won't have to repeat this code. The details will come in the 100 | // next chapter; for now, let's take a look at a few more functions. 101 | 102 | /* The function we wrote above allows us to execute a block of code from 103 | * wherever we want and have it give us back a value. What we'll do now is 104 | * parametrise that block of code by a value. That is, we'll make the function 105 | * accept values from the caller. Those values are copied, so setting them only 106 | * changes what the function sees, not what the caller sees. However, this also 107 | * means the function has full freedom to do what it wants with them, without 108 | * anyone ever finding out. 109 | * 110 | * For a function to accept parameters we have to expand our syntax a little: 111 | * 112 | * return_type function_name(param_type1 param1, param_type2 param2 ...) { 113 | * statements... 114 | * } 115 | * 116 | * Parameters are like local variables, but their value is specified from 117 | * outside the function. Suppose we define 118 | * 119 | * int square(int x) { 120 | * return x*x; 121 | * } 122 | * 123 | * we have a function square that we can call with an int; for example square(5) 124 | * would be a valid call. square would be executed with x initialised to 5, and 125 | * after it has returned x*x, the original square(5) would evaluate to 25. 126 | * 127 | * Recall that we can declare a function by omitting the body (statements and 128 | * braces) and putting a semicolon after the list of parameters. Let's first 129 | * declare some functions and define them later. 130 | */ 131 | 132 | // Function that, given a vector, returns the average of the elements. 133 | int average(std::vector v); 134 | // Function that, given a vector, returns the sum of the elements. 135 | int sum(std::vector v); 136 | // Function that, given a vector and int, returns a vector of all elements 137 | // greater than the given int. 138 | std::vector filter_greater_than(std::vector v, int x); 139 | 140 | int main() { 141 | // We already know read_int_vector will give us an std::vector, and the 142 | // compiler knows that, too. Writing it out explicitly wouldn't tell anyone 143 | // anything new, so we use auto and the compiler will assume that v is the 144 | // type of whatever read_int_vector gave us. 145 | auto v = read_int_vector(); 146 | 147 | // We've declared average, sum, and filter_greater_than so we can use them, 148 | // even though they haven't yet been defined. This is one of the powerful 149 | // things about declarations; we can worry about the definitions much, much 150 | // later. 151 | std::cout << "Average: " << average(v) << "\n"; 152 | std::cout << "Sum: " << sum(v) << "\n"; 153 | 154 | // We can use the return value of filter_greater_than in a range-based for. 155 | // Note that there's no easy way of rewriting this to be a normal for loop. 156 | std::cout << "Elements greater than 5:"; 157 | for (auto e : filter_greater_than(v, 5)) 158 | std::cout << " " << e; 159 | std::cout << "\n"; 160 | 161 | return 0; 162 | } 163 | 164 | // While we can put off writing the definitions, we do have to do it eventually. 165 | int average(std::vector v) { 166 | // The average of a number is the sum divided by the number of elements. 167 | // Let's define the average of an empty vector to be 0; this makes some 168 | // sense, and means we don't have to check for empty vectors so often. 169 | if (v.empty()) 170 | return 0; 171 | 172 | // Notice that we can write this function without writing any loops. 173 | // They're still there in the compiled program, but we as the programmer 174 | // don't need to explain how to get the sum of a vector every time we want 175 | // it. 176 | // 177 | // As we've seen before, the type of v.size() is std::size_t. We want the 178 | // result to be an int, so we create an int out of v.size() before using it. 179 | int size = v.size(); 180 | return sum(v)/size; 181 | } 182 | 183 | int sum(std::vector v) { 184 | // We've done this before, and it's not hard. It's also the last time we'll 185 | // have to do it explicitly again! 186 | int total = 0; 187 | for (auto e : v) 188 | total += e; 189 | return total; 190 | } 191 | 192 | /* Now that we've gotten to parameter passing and looked at how it works in 193 | * practice, let's take a look at how it works and what is and isn't allowed. 194 | * 195 | * First of all, there's the question of scope. If you define a variable inside 196 | * a set of curly braces, you will not be able to use it outside it. The same 197 | * thing works with functions: a variable defined in a function stays within 198 | * that function. If you want a value from one function to end up in another 199 | * function, then the only ways we know of so far are by passing it as a 200 | * parameter or by returning it. Notice that the functions are so isolated it 201 | * may even appear like we're breaking the one-definition-only rule: all three 202 | * of main, average and sum define a variable v! 203 | * 204 | * The reason this is allowed is that those three variables, even though they 205 | * all have the same name, reside in different functions and so are entirely 206 | * distinct. They don't know about each-others' existence, can't change each 207 | * others' data, and we could rename any of the three with no effect on the 208 | * rest. In this particular program they all have the same value, but that is 209 | * coincidental. 210 | * 211 | * By the way, saying that there are three of them is not entirely correct. 212 | * Every time you call a function, all of the variables used inside it are 213 | * created, and when the function is complete they are destroyed. When main 214 | * calls sum and passes it v, the v and total in sum are created, total is 215 | * changed, its value is returned, and both v and total are again destroyed. 216 | * 217 | * When average calls sum and passes it its v, the process happens again. This 218 | * isn't particularly efficient, as we end up calculating the sum twice. Even 219 | * worse, we end up copying the vector every time we pass it to a function! We 220 | * will see how to make this less slow later; for now, understanding functions 221 | * is much more important than writing blazing-fast code. 222 | * 223 | * The way independent variables are created and destroyed every time a function 224 | * is called is best illustrated with recursive functions: functions that call 225 | * themselves. Some problems naturally have significantly more elegant 226 | * recursive solutions than iterative ones. 227 | * 228 | * TODO: Add a fitting example. 229 | */ 230 | 231 | std::vector filter_greater_than(std::vector v, int x) { 232 | std::vector result; 233 | for (auto e : v) 234 | if (e > x) 235 | result.push_back(e); 236 | return result; 237 | } 238 | 239 | /* The next few chapters will be about some practical aspects of using 240 | * functions and some nuances in the rules regarding them. 241 | * 242 | * Functions are important. So far, these chapters have been fairly low-level 243 | * and focused on getting you acquainted with the basic syntax. Make sure you 244 | * are comfortable with all of these: we're now going to spend some time looking 245 | * at what we can do with the current tools, what the standard library has to 246 | * offer us, and what new features we'd like to have. 247 | */ 248 | 249 | -------------------------------------------------------------------------------- /Chapter 08 - Using Multiple Files/main.cpp: -------------------------------------------------------------------------------- 1 | /* Suggested reading order: main.cpp, vector_algos.hpp, vector_algos.cpp 2 | * 3 | * This chapter is unlike all the previous ones, because instead of having a 4 | * single main.cpp file the directory should contain main.cpp, vector_algos.hpp 5 | * and vector_algos.cpp. We're going to leave the previous program almost 6 | * unchanged, but instead of lumping everything together into one file we'll 7 | * neatly split it out. You can see our main function below: it is entirely 8 | * unchanged. 9 | * 10 | * The new files added are another source file and a header file. The source 11 | * file, vector_algos.cpp, behaves just like main.cpp; it will be compiled the same way, as 12 | * described further. We are going to define all our algorithms for working on 13 | * vectors in there. 14 | * 15 | * The header file, vector_algos.hpp, is somewhat different. We will not give 16 | * it to the compiler directly; instead, we will include it in our source files. 17 | * This is only marginally useful in this example, as only main.cpp really needs 18 | * to include it. However, as our projects grow large we won't want to 19 | * duplicate all our function declarations by hand. Instead, we will write them 20 | * in header files and use include directives to copy their contents into our 21 | * source files. 22 | * 23 | * This is exactly what the include directive does: it takes everything in that 24 | * file and literally copy-pastes it where our include directive was. Do not 25 | * expect any kind of special treatment: if you include a file twice, it will be 26 | * pasted in twice. If file A includes file B, and file B includes file C, file 27 | * A will get a copy of file C inside it. If file A includes file B, and file B 28 | * includes file A, your program will not compile. 29 | * 30 | * What happens when a program is compiled, anyway? The process can be split 31 | * into three stages: 32 | * 33 | * - First of all, the preprocessor is run over all the files. This removes 34 | * comments and handles all preprocessor directives. After this, every source 35 | * file has become a single compilable entity called a Translation Unit. 36 | * Header files are no longer necessary. 37 | * - Secondly, the compiler runs on each source file and outputs an object 38 | * file. This is the compiled code for that one file; it is not yet an 39 | * executable, as not all functions are necessarily defined. It is here that 40 | * most of the translation is done; after this step, finding the C++ code back 41 | * is not possible. 42 | * - Thirdly, the linker takes all our object files and then links them into an 43 | * executable, which we can run. 44 | * 45 | * Why not glue all the source files together before compiling? C++ is a 46 | * complex language, and compiling it takes a long time. By doing the 47 | * preprocessing and compiling separately for every file, we don't need to 48 | * recompile translation units that haven't changed since we last compiled. 49 | * Only linking needs to look at all of the files, but that is relatively fast. 50 | * Even for small projects, this can make the difference between being able to 51 | * test your changes directly and having to wait a minute or two for it to 52 | * build. 53 | * 54 | * Because of this, it is a good idea to minimize the number of things you 55 | * include: the more headers a file depends on, the more likely it is it will 56 | * need to be recompiled. The advice boils down to "include everything you 57 | * need, and no more", which admittedly is not all that specific. 58 | * 59 | * You might have noticed that so far, we've not seen the preprocessed files or 60 | * object files. This is because with a project this small, we can ask the 61 | * compiler to keep the results in memory between the steps. Even with bigger 62 | * projects, the preprocessing output is usually not stored; object files, 63 | * however, are usually written to disk for reusability. 64 | * 65 | * Enough chatter, let's look at the program. 66 | */ 67 | 68 | // What to include and in what order is an interesting question. Due to the 69 | // copy-paste nature of includes, if vector_algos.hpp includes iostream and 70 | // vector, we don't need to include those two. Can we be sure it will, though? 71 | // We know that the only work we do on vectors is with the functions from it, so 72 | // that's fine. We don't have any reason to be sure it will include iostream, 73 | // though, so we include that ourselves. 74 | // 75 | // As to the order, you should do your best to stop includees relying on the 76 | // prior includes of their includers. A mouthful? Let's say A includes B and 77 | // then C. If C needs B, C should include B itself, not rely on A including B 78 | // first. Thus, include your own headers first of all, then the headers of the 79 | // third-party libraries you use, and finally the standard library headers. 80 | #include "vector_algos.hpp" 81 | #include 82 | 83 | 84 | // Look at how clean and simple this is! 85 | // Once you're done admiring it, read vector_algos.hpp 86 | int main() { 87 | auto v = read_int_vector(); 88 | 89 | std::cout << "Average: " << average(v) << "\n"; 90 | std::cout << "Sum: " << sum(v) << "\n"; 91 | 92 | std::cout << "Elements greater than 5:"; 93 | for (auto e : filter_greater_than(v, 5)) 94 | std::cout << " " << e; 95 | std::cout << "\n"; 96 | 97 | // By the way, we've been writing return 0; at the end of every main 98 | // function. As I said before, the main function is weird: for example, you 99 | // can't call it from inside your program. Another (nice) quirk it has is 100 | // that you are allowed to omit the return statement at the end, and the 101 | // compiler will assume you returned 0. This means we can stop writing it 102 | // out now and let the compiler do the work. 103 | // 104 | // As you may have noticed, I'm extremely lazy and love letting software do 105 | // work for me. 106 | } 107 | -------------------------------------------------------------------------------- /Chapter 08 - Using Multiple Files/vector_algos.cpp: -------------------------------------------------------------------------------- 1 | /* This is a fairly simple file. We copy over the functions in the last 2 | * chapter. Nothing special has to be done here. 3 | * 4 | * Perhaps important to note is that while we need to declare things in main.cpp 5 | * (or a header it includes) for them to be visible during the compilation 6 | * stage, everything we define in this file will by default be visible in the 7 | * linker stage. That means that functions we define can still cause double 8 | * definition errors. We will see how to fix that later. 9 | */ 10 | #include "vector_algos.hpp" 11 | #include 12 | #include 13 | 14 | std::vector read_int_vector() { 15 | std::vector result; 16 | int x; 17 | 18 | std::cout << "Enter as many numbers as you want:\n"; 19 | 20 | while (true) { 21 | while (std::cin >> x) 22 | result.push_back(x); 23 | 24 | if (std::cin.eof()) 25 | break; 26 | 27 | std::cin.clear(); 28 | 29 | std::string s; 30 | std::getline(std::cin, s); 31 | 32 | std::cout << "Warning, ignoring: " << s << "\n"; 33 | } 34 | 35 | std::cout << "End of file encountered, stopping input.\n"; 36 | return result; 37 | } 38 | 39 | int sum(std::vector v) { 40 | int total = 0; 41 | for (auto e : v) 42 | total += e; 43 | return total; 44 | } 45 | 46 | std::vector filter_greater_than(std::vector v, int x) { 47 | std::vector result; 48 | for (auto e : v) 49 | if (e > x) 50 | result.push_back(e); 51 | return result; 52 | } 53 | 54 | -------------------------------------------------------------------------------- /Chapter 08 - Using Multiple Files/vector_algos.hpp: -------------------------------------------------------------------------------- 1 | /* As we said in main.cpp, everything in this file will be copy-pasted into all 2 | * files that include it. In this project, this means main.cpp and 3 | * vector_algos.cpp. 4 | * 5 | * Remember how we weren't allowed to define things twice? This is going to 6 | * change a little now that we are using multiple files. We are still only ever 7 | * allowed to define things once per file, but there are things which we may 8 | * define in multiple files, as long as we define all of them the same way. 9 | * 10 | * Functions usually aren't one of these things, but we can make an exception 11 | * for a specific function by marking it inline. We'll do that here with 12 | * average as an example. There are a lot of trade-offs to consider when 13 | * choosing to do this; I won't go into the details here, but you may want to 14 | * consult further sources when doing this in a real project. 15 | * 16 | * Even with inline, we still have a problem. Recall that if a file is included 17 | * twice, its contents will appear twice. A definition might then be present 18 | * in a translation unit twice, which is not allowed. To avoid these problems 19 | * we use the preprocessor. It allows us to define macros: 20 | * 21 | * #define MACRO_NAME arbitrary text 22 | * 23 | * When MACRO_NAME (which is traditionally written in uppercase) is encountered, 24 | * the arbitrary text is inserted where it was. This can be a powerful tool, 25 | * but often of the "blow up in your face" variety. We won't use the text 26 | * portion for now; instead, we'll use the ifndef directive, which stands for 27 | * "if not defined": 28 | * 29 | * #ifndef MACRO_NAME 30 | * code... 31 | * #endif 32 | * 33 | * The preprocessor knows what macros are defined. If MACRO_NAME is not defined in 34 | * the example above, the code is left as-is. If it is defined, however, the 35 | * whole block is erased and all that ends up in the preprocessed translation 36 | * unit are empty lines, if that. We will surround all our code with an ifndef, 37 | * and then define the macro it checks for inside it; that way, the code will be 38 | * kept the first time, but erased the second, preventing duplicate definitions. 39 | */ 40 | 41 | // Check whether we've already included this file... 42 | #ifndef CHAPTER_08_VECTOR_ALGOS_HPP 43 | // Mark that we've included it... 44 | #define CHAPTER_08_VECTOR_ALGOS_HPP 45 | 46 | /* By the way... Modern compilers have noticed that these kind of guards are 47 | * used by everyone and their preprocessor supports #pragma once, which does 48 | * something much like the above. This saves some pain; for example, imagine if 49 | * two files used CHAPTER_08_VECTOR_ALGOS_HPP to guard themselves? 50 | * 51 | * This isn't a standard feature yet, so before using #pragma once, make sure 52 | * you'll only be using compilers that do support it. 53 | * 54 | * Also, some people will use names like __CHAPTER_08_VECTOR_ALGOS_HPP__. While 55 | * they will often work, any names with two consecutive underscores (or starting 56 | * with an underscore followed by a capital letter) are reserved. If you use 57 | * them, it's your own fault if your program breaks. 58 | */ 59 | 60 | #include 61 | 62 | // Now let's put our declarations. Note that I changed the comments a little; 63 | // they were more explicit than they had to be; the types are already mentioned 64 | // in the signature, so we don't have to repeat them in the description. 65 | 66 | // Very persistently get a vector from std::cin. 67 | std::vector read_int_vector(); 68 | // Calculate sum of all vector element values. 69 | int sum(std::vector v); 70 | // Copy the vector, leaving only elements greater than x. 71 | std::vector filter_greater_than(std::vector v, int x); 72 | 73 | // inline is not part of the return type; rather, it documents that average may 74 | // be defined multiple times in our program, though only once per .cpp file, and 75 | // all definitions must be the same. 76 | inline int average(std::vector v) { 77 | if (v.empty()) 78 | return 0; 79 | int size = v.size(); 80 | return sum(v)/size; 81 | } 82 | 83 | // We only want to "guard" the portion above. 84 | #endif 85 | // Take a look at vector_algos.cpp now. 86 | -------------------------------------------------------------------------------- /Chapter 09 - Iterators/main.cpp: -------------------------------------------------------------------------------- 1 | /* Suggested reading: main.cpp, vector_algos.hpp, vector_algos.cpp 2 | * 3 | * We're going to write some more functions now. We'll start by implementing a 4 | * sorting algorithm and then implement a binary search function. Binary search 5 | * lets us find a value in a vector faster, but it requires that the vector be 6 | * sorted. 7 | * 8 | * Let's take a look at the main function first, which gives a high-level 9 | * overview of what's going on: 10 | */ 11 | 12 | #include "vector_algos.hpp" 13 | #include 14 | 15 | int main() { 16 | // Looking at this code, we can deduce that sort probably has type 17 | // std::vector(std::vector) 18 | // and binary_search has type 19 | // bool(std::vector, int) 20 | auto v = read_int_vector(); 21 | auto sorted_v = sort(v); 22 | 23 | std::cout << "You entered:"; 24 | for (auto e : sorted_v) 25 | std::cout << " " << e; 26 | std::cout << "\n"; 27 | 28 | for (int i = 0; i < 10; ++i) { 29 | if (binary_search(sorted_v, i)) 30 | std::cout << i << " was amongst the numbers you entered.\n"; 31 | else 32 | std::cout << i << " was not amongst the numbers you entered.\n"; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Chapter 09 - Iterators/vector_algos.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_09_VECTOR_ALGOS_HPP 2 | #define CHAPTER_09_VECTOR_ALGOS_HPP 3 | 4 | #include 5 | 6 | std::vector read_int_vector(); 7 | 8 | // Well, what do you know, we were right about both! 9 | std::vector sort(std::vector v); 10 | bool binary_search(std::vector v, int val); 11 | 12 | // I suppose it isn't entirely fair, as I was the one to choose them... 13 | 14 | // There's nothing surprising or new in this header; let's go on to 15 | // vector_algos.cpp 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Chapter 10 - References/vector_algos.cpp: -------------------------------------------------------------------------------- 1 | #include "vector_algos.hpp" 2 | #include 3 | #include 4 | 5 | // Note that we shouldn't repeat the default value here; we already know it from 6 | // the header! 7 | // 8 | // Seeing as we can't rely on the user being present any more, let's drop most 9 | // of the output statements. The ones we keep shouldn't go to std::cout any 10 | // more; C++ provides a stream std::cerr that we can use the same way we used 11 | // std::cout, but that is intended for errors. We'll use that for reporting 12 | // what characters we ignored. 13 | std::vector read_int_vector(std::istream& stream) { 14 | std::vector result; 15 | int x; 16 | 17 | while (true) { 18 | while (stream >> x) 19 | result.push_back(x); 20 | 21 | if (stream.eof()) 22 | break; 23 | 24 | stream.clear(); 25 | 26 | std::string s; 27 | std::getline(stream, s); 28 | 29 | std::cerr << "Warning, ignoring: " << s << "\n"; 30 | } 31 | 32 | return result; 33 | } 34 | 35 | int sum(std::vector const& v) { 36 | int total = 0; 37 | for (auto e : v) 38 | total += e; 39 | return total; 40 | } 41 | 42 | std::vector filter_greater_than(std::vector const& v, int x) { 43 | std::vector result; 44 | for (auto e : v) 45 | if (e > x) 46 | result.push_back(e); 47 | return result; 48 | } 49 | 50 | // We move the declarations out now that they're no longer a surprise. 51 | using iterator = std::vector::iterator; 52 | iterator partition(iterator begin, iterator end); 53 | void sort_impl(iterator begin, iterator end); 54 | 55 | std::vector sort(std::vector v) { 56 | 57 | sort_impl(v.begin(), v.end()); 58 | return v; 59 | } 60 | 61 | void sort_impl(iterator begin, iterator end) { 62 | if (end - begin <= 1) 63 | return; 64 | 65 | auto pivot = partition(begin, end); 66 | sort_impl(begin, pivot); 67 | sort_impl(pivot+1, end); 68 | } 69 | 70 | iterator partition(iterator begin, iterator end) { 71 | auto pivot = begin++; 72 | 73 | for (; begin != end; ++begin) { 74 | if (*pivot > *begin) { 75 | std::swap(*pivot, *begin); 76 | ++pivot; 77 | std::swap(*pivot, *begin); 78 | } 79 | } 80 | 81 | return pivot; 82 | } 83 | 84 | std::vector::const_iterator binary_search(std::vector const& v, int val) { 85 | auto bottom = v.begin(), top = v.end(); 86 | 87 | while (top != bottom) { 88 | auto mid = bottom + (top - bottom)/2; 89 | if (*mid < val) 90 | bottom = mid+1; 91 | else if (*mid > val) 92 | top = mid; 93 | else 94 | return mid; 95 | } 96 | 97 | return v.end(); 98 | } 99 | 100 | 101 | -------------------------------------------------------------------------------- /Chapter 10 - References/vector_algos.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_10_VECTOR_ALGOS_HPP 2 | #define CHAPTER_10_VECTOR_ALGOS_HPP 3 | 4 | #include 5 | #include 6 | 7 | // All we have to do here is replace std::vector with std::vector 8 | // const& in the parameter lists. 9 | 10 | // Now that we can pass references, we can tell read_int_vector where to read 11 | // the data from. Instead of always reading from std::cin, let's make std::cin 12 | // the default and allow the caller to pass an alternative if they want. 13 | // 14 | // The type of std::cin is std::istream. We want a reference to that; we'll be 15 | // extracting things from the stream, so it can't be a reference to const. That 16 | // means the type should be std::istream&. In order to give it a default value 17 | // we say type param_name = default_value. 18 | std::vector read_int_vector(std::istream& stream = std::cin); 19 | int sum(std::vector const& v); 20 | 21 | // Note that we don't change the return value to std::vector const&. Why 22 | // not? Do not continue to the next chapter until you are comfortable with the 23 | // answer; this is fundamental understanding that cannot be skipped over. 24 | std::vector filter_greater_than(std::vector const& v, int x); 25 | 26 | inline int average(std::vector const& v) { 27 | if (v.empty()) 28 | return 0; 29 | // Now that we've seen how to create a variable of a certain type on the 30 | // fly, we could use int{v.size()} to turn v.size() into an int. However, 31 | // this would be a narrowing conversion, as we're losing part of the data. 32 | // We know that if a collection has more elements than the maximum value of 33 | // an int, we won't be getting any kind of meaningful average anyway, so we 34 | // don't mind that. 35 | // 36 | // To tell the compiler that this really is what we want, we use the older 37 | // and less strict int(v.size()), which does allow such unsafe conversions. 38 | // 39 | // Be careful with int()! There are things it can do that you definitely 40 | // don't want. Don't use it unless you know that int{} won't do, and you've 41 | // checked that it really does what you expect it to. 42 | // 43 | // We'll see some safer ways to do even what we're doing now, but I'd like 44 | // to leave that bag of tricks for another day. 45 | return sum(v)/int(v.size()); 46 | } 47 | 48 | // We're not going to change the type of sort. We need to perform a copy of the 49 | // vector anyway; we may as well do that when the vector is passed in -- there 50 | // are cases where this is faster than doing it later. 51 | std::vector sort(std::vector v); 52 | 53 | // On the other hand, now that we are taking a reference to a vector here, we 54 | // can return an iterator. The vector we are searching is the one that is in 55 | // the function calling us, so the iterator can still be valid after we return 56 | // it. 57 | // 58 | // Instead of returning an std::vector::iterator, we return an 59 | // std::vector::const_iterator. This is also an iterator, but it doesn't 60 | // allow us to modify the values it refers to. 61 | std::vector::const_iterator binary_search(std::vector const& v, int val); 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /Chapter 11 - Standard Algorithms/main.cpp: -------------------------------------------------------------------------------- 1 | /* We've already used functions in a number of different ways. We were able to 2 | * get rid of some code duplication by using a function in several different 3 | * places; we were also able to make our code clearer by moving parts out into 4 | * functions of their own, and only having to mention their name. 5 | * 6 | * Now we will look at another benefit functions can have: they make it easy for 7 | * us to use code others have written. As an example, we're going to rewrite 8 | * our sum function to use a standard functions. We're also going to write a 9 | * few new functions. 10 | * 11 | * Before we get to that, though, there's a number of standard algorithms that 12 | * we can use directly from main. 13 | */ 14 | 15 | #include "vector_algos.hpp" 16 | #include 17 | #include 18 | 19 | // Let's define an alternative rule for comparing ints. Instead of comparing 20 | // them directly, we'll compare their values squared. custom_comparison will 21 | // return true for all x which are strictly less than y according to these 22 | // rules. 23 | bool custom_comparison(int x, int y) { 24 | return x*x < y*y; 25 | } 26 | 27 | int main() { 28 | std::cout << "Enter at least one number:\n"; 29 | auto v = read_int_vector(); 30 | 31 | // We don't want to have to write special cases for empty everywhere, so 32 | // let's terminate the program if that happens. 33 | if (v.empty()) { 34 | std::cout << "I said, at least one number.\n"; 35 | return -1; 36 | } 37 | 38 | // std::max_element takes an iterator range and returns an iterator to the 39 | // greatest element. Note that we can dereference the iterator safely here; 40 | // why? How long will what it points to exist for? 41 | std::cout << "Maximum element: " << *std::max_element(v.begin(), v.end()) << "\n"; 42 | 43 | // Minimum element is similar. However, let's use our custom comparison 44 | // function. Both max_element and min_element allow us to pass it as a 45 | // third argument. If we don't, they assume we want comparison using the < 46 | // operator. 47 | std::cout << "Minimum element (normal): " 48 | << *std::min_element(v.begin(), v.end()) << "\n"; 49 | std::cout << "Minimum element (custom comparison): " 50 | << *std::min_element(v.begin(), v.end(), custom_comparison) << "\n"; 51 | 52 | std::cout << "Sum: " << sum(v) << "\n"; 53 | std::cout << "Average: " << average(v) << "\n"; 54 | 55 | if (all_positive(v)) 56 | std::cout << "All numbers you entered were positive.\n"; 57 | else 58 | std::cout << "You entered at least one negative number or zero.\n"; 59 | 60 | // We don't have to write our own sort function: the standard provides one 61 | // for us. 62 | std::sort(v.begin(), v.end()); 63 | 64 | // The sort is in-place, though, so we lost the original order of v. 65 | 66 | // Let's add a function that lets us display a range. 67 | std::cout << "Your input, sorted: "; 68 | display_range(v.begin(), v.end()); 69 | std::cout << "\n"; 70 | 71 | // std::sort also takes a custom comparison function. Let's see how our 72 | // input looks sorted that way: 73 | std::sort(v.begin(), v.end(), custom_comparison); 74 | 75 | std::cout << "Your input, sorted using a custom comparison function: "; 76 | display_range(v.begin(), v.end()); 77 | std::cout << "\n"; 78 | } 79 | -------------------------------------------------------------------------------- /Chapter 11 - Standard Algorithms/vector_algos.cpp: -------------------------------------------------------------------------------- 1 | #include "vector_algos.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | std::vector read_int_vector(std::istream& stream) { 9 | std::vector result; 10 | 11 | while (true) { 12 | // Instead of using a loop here, we can use std::copy. 13 | // 14 | // This may be a little surprising: after all, std::istream doesn't look 15 | // like a collection, how could it have begin and end iterators? What 16 | // would it mean to add a number to the iterator; would we be skipping 17 | // some inputs? Would subtracting a number mean we go back to earlier 18 | // inputs? 19 | // 20 | // The answer is that unlike vector iterators, std::istream iterators aren't 21 | // random access. You can still do a few of the things we could do with 22 | // vector iterators, but nowhere near as many. In fact, std::istream 23 | // iterators are input iterators: that's the most limited family of 24 | // iterators available. You can only increment them, read from them, 25 | // and compare them for equality. For copying, we don't need any more. 26 | // 27 | // We will be using the type std::istream_iterator. If we want to 28 | // create a temporary object of a type, we can use the type followed by 29 | // a pair of curly braces. Within these curly braces we can give 30 | // arguments for how the temporary should be constructed. For example, 31 | // std::vector{1, 2, 3} would create a vector containing the ints 32 | // 1, 2, and 3. We can do a similar thing for std::istream iterators, 33 | // where we pass a stream, and get the current iterator for that stream. 34 | // If we don't pass anything, we get an end iterator that works for any 35 | // stream. 36 | // 37 | // Thus, our begin iterator is std::istream_iterator{stream}, while 38 | // our end iterator is std::istream_iterator{}. 39 | // 40 | // Where do we copy to? We can't copy to result.begin(), because there 41 | // won't be enough room there. Instead, the standard library provides 42 | // us with std::back_insert_iterator>. That's a 43 | // mouthful, but we can make one with std::back_inserter(result). 44 | // 45 | // All in all, we get: 46 | std::copy(std::istream_iterator{stream}, std::istream_iterator{}, 47 | std::back_inserter(result)); 48 | 49 | // This may seem like a mouthful, but it's a good idea to understand it; 50 | // iterators provide a powerful abstraction over much more than simple, 51 | // linear containers like vector. 52 | 53 | if (stream.eof()) 54 | break; 55 | 56 | stream.clear(); 57 | 58 | std::string s; 59 | std::getline(stream, s); 60 | 61 | std::cerr << "Warning, ignoring: " << s << "\n"; 62 | } 63 | 64 | return result; 65 | } 66 | 67 | int sum(std::vector const& v) { 68 | // We use std::accumulate. It takes a range and an initial value, and then 69 | // uses the + operator to add all the values together. There's another 70 | // version which also takes a function to use instead of the + operator, but 71 | // we don't need that here. 72 | // 73 | // begin, end, and initial value 0. Tada! And don't worry - chances are 74 | // that this is as efficient as writing out the loop. 75 | return std::accumulate(v.begin(), v.end(), 0); 76 | } 77 | 78 | std::vector filter_greater_than(std::vector const& v, int x) { 79 | // While there is an std::copy_if that we could use to implement this 80 | // function, we currently don't have the tools to turn x into a function 81 | // that returns true for values greater than x, and false for all other 82 | // values. We'll see how to do it soon enough, though! 83 | std::vector result; 84 | for (auto e : v) 85 | if (e > x) 86 | result.push_back(e); 87 | return result; 88 | } 89 | 90 | bool is_positive(int x) { 91 | return x > 0; 92 | } 93 | 94 | bool all_positive(std::vector const& v) { 95 | // This is a very straightforward use of std::all_of. It takes a range and 96 | // a predicate -- a function taking some values and returning bool -- and 97 | // then returns true if the predicate returns true for all values in the 98 | // range. If the range is empty, it also returns true; this is a nicer 99 | // generalisation than if the empty range resulted in false. (For instance, 100 | // it means that every subrange of a range that returns true will also 101 | // return true.) 102 | return std::all_of(v.begin(), v.end(), is_positive); 103 | } 104 | 105 | void display_range(std::vector::const_iterator begin, std::vector::const_iterator end) { 106 | // Let's surround the output with nice curly braces. 107 | std::cout << "{ "; 108 | 109 | // The way to print things with iterators is much the same to how one reads 110 | // things with them. We use an std::ostream_iterator, which we give a 111 | // stream to write to and a bit of text to print after each value. 112 | // 113 | // std::ostream_iterators are output iterators. They support all the 114 | // operations of input iterators, except that you cannot read from them; you 115 | // can, however, write to them, and whatever you write will be printed. You 116 | // should increment them between each write. 117 | std::copy(begin, end, std::ostream_iterator{std::cout, " "}); 118 | 119 | std::cout << "}"; 120 | 121 | /* By the way, have you noticed that even though copy works on two ranges, 122 | * we only need to pass the end iterator of the one we're copying from? 123 | * That's because the standard library assumes that the other will be long 124 | * enough. If that's not the case, unpredictable and almost certainly 125 | * unpleasant things will happen. (Unpredictable and unpleasant things 126 | * happening is a common thing in C++ if you're not careful; there'll be a 127 | * chapter on it later.) 128 | */ 129 | } 130 | 131 | /* Notice how we're mentioning the fact that we're operating on vectors of ints 132 | * everywhere. However, if we wanted to write these algorithms for a vector of 133 | * doubles, the code would likely be much the same. In the next chapter we'll 134 | * take a look at how we can make these functions work on a vector of any type 135 | * that supports the right operations. Our sum function will even be able to 136 | * take the sum of a vector of strings! 137 | */ 138 | -------------------------------------------------------------------------------- /Chapter 11 - Standard Algorithms/vector_algos.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_11_VECTOR_ALGOS 2 | #define CHAPTER_11_VECTOR_ALGOS 3 | 4 | #include 5 | #include 6 | 7 | std::vector read_int_vector(std::istream& stream = std::cin); 8 | 9 | 10 | int sum(std::vector const& v); 11 | std::vector filter_greater_than(std::vector const& v, int x); 12 | 13 | inline int average(std::vector const& v) { 14 | if (v.empty()) 15 | return 0; 16 | return sum(v)/int(v.size()); 17 | } 18 | 19 | bool all_positive(std::vector const& v); 20 | void display_range(std::vector::const_iterator begin, std::vector::const_iterator end); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Chapter 12 - Function Templates/main.cpp: -------------------------------------------------------------------------------- 1 | /* Suggested order: main.cpp (beginning), vector_algos.hpp, main.cpp (end) 2 | * 3 | * In this chapter we'll take a look at templates. We won't cover them entirely 4 | * right away; templates have some very advanced features that we don't 5 | * currently have a use for. However, the basic uses are already applicable, 6 | * and so we'll take a brief look at those. 7 | * 8 | * Functions allow us to generalise code over values; templates allow us to 9 | * generalise code over types. 10 | * 11 | * In the last few chapters we wrote many functions that worked on vectors of 12 | * ints. We'll now use templates to allow us to work over many types. 13 | * 14 | * By the way, even though we are only now writing our own templates, we've 15 | * already used many that the standard library has provided us with. The 16 | * only explicit use was std::vector, where we could provide the type of the 17 | * elements we'd like to store. We could use std::vector, or 18 | * std::vector, or even std::vector>, while the 19 | * programmers of the standard library only had to write std::vector once. 20 | * 21 | * (In practice, they may have written specialised options to improve 22 | * performance in some cases. There is also std::vector, which the 23 | * standard insists be a little different.) 24 | * 25 | * Apart from that, all of the standard algorithms we used in the last chapter 26 | * were actually templates. We could pretend they were just functions because 27 | * the compiler could figure out what types we wanted to use them on from the 28 | * arguments we passed. We'll keep using that feature in this chapter, though 29 | * there'll be a few places we have to explicitly specify the types anyway. 30 | * 31 | * Take a look at vector_algos.hpp now, and come back to look at main once 32 | * you've seen how templates are implemented. 33 | */ 34 | #include "vector_algos.hpp" 35 | #include 36 | #include 37 | 38 | int main() { 39 | // Welcome back. As you can see, our main function is almost undisturbed, 40 | // except that we have read_vector instead of read_int_vector. Does 41 | // the program still work if you change int to double? What about to 42 | // std::string? 43 | std::cout << "Enter at least one number:\n"; 44 | auto v = read_vector(); 45 | 46 | if (v.empty()) { 47 | std::cout << "I said, at least one number.\n"; 48 | return -1; 49 | } 50 | 51 | std::cout << "Sum: " << sum(v) << "\n"; 52 | std::cout << "Average: " << average(v) << "\n"; 53 | 54 | auto sorted_v = sort(v); 55 | 56 | // Let's add a function that lets us display a range. 57 | std::cout << "Your input, sorted: "; 58 | display_range(sorted_v.begin(), sorted_v.end()); 59 | std::cout << "\n"; 60 | } 61 | 62 | /* That's all we'll cover about templates for now. There is much, much more to 63 | * see, but it isn't relevant yet. The next chapter is going to be about 64 | * handling errors. 65 | * 66 | * The next big topic is going to be classes, and that's going to take quite a 67 | * few chapters. I find that covering classes in isolation makes it hard to 68 | * grasp why you'd use them. Starting from the lesson after next, we'll be 69 | * working on building a larger program, where we'll be applying a mix of 70 | * everything we've learned so far. 71 | */ 72 | -------------------------------------------------------------------------------- /Chapter 12 - Function Templates/vector_algos.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_12_VECTOR_ALGOS_HPP 2 | #define CHAPTER_12_VECTOR_ALGOS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /* A template of a function allows us to write a function and then specify some 12 | * of the types later. When we specify the types, a specific instantiation of 13 | * the template will be created, with the blanks for the types filled in. Note 14 | * that a template of a function is not itself a function: it is a recipe for creating 15 | * a function. 16 | * 17 | * Because of this, you can't simply declare the template in a header and then 18 | * define it in a source file. If we tried, the compiler wouldn't know how to 19 | * create an instantiation for a specific type, and would assume that it has 20 | * been explicitly created elsewhere. This can work sometimes, but the "normal" 21 | * way is to define templates in headers. 22 | */ 23 | 24 | /* How do we define a function template, anyway? We start out be specifying 25 | * how many types we're generalising over; listing the parameters of the 26 | * template, just like we'd normally list the parameters of the function: 27 | * 28 | * template 29 | * 30 | * After we've written that, we write a function as normal. However, we can use 31 | * T1 and T2 from above as types. As a very simple example, we may write 32 | * 33 | * template 34 | * NUM add(NUM x, NUM y, NUM z) { 35 | * return x + y + z; 36 | * } 37 | * 38 | * When we want to call our function template, we provide the type arguments to the 39 | * template between < and > angle braces, just like we did with std::vector. If 40 | * we want to use add with NUM being int, we say: 41 | * 42 | * add(5, 6, 7) 43 | * 44 | * The resulting code will probably be (almost) the same as if we wrote 45 | * 46 | * int add_int(int x, int y, int z) { 47 | * return x + y + z; 48 | * } 49 | * 50 | * and then called add_int(5, 6, 7). However, the template version lets us say 51 | * 52 | * add(2.71, 3.14, 9.9) 53 | * 54 | * and even (adding two std::strings concatenates them) 55 | * 56 | * add("Hello ", "World", "!") 57 | * 58 | * Notice that in add(5, 6, 7) all parameters have type int. This lets the 59 | * compiler guess that we want to use the int version, and so we can simply call 60 | * 61 | * add(5, 6, 7) 62 | * 63 | * and the template arguments will be deduced. The same works for the double 64 | * example. Unfortunately, it breaks down in the last example: string literals 65 | * aren't of type std::string, so the deduction would not be correct. 66 | * 67 | * Let's take a look at how our functions look when rewritten to function 68 | * templates. 69 | */ 70 | 71 | // T is a very common name to use for template parameters. Here, we still want 72 | // to read an std::vector, but we want to allow vectors of types other than int. 73 | // 74 | // We use a single parameter to take the place of int, and then replace all 75 | // int-specific code with code that works with T. When we use read_vector, 76 | // the int will be filled back in and we'll get our old function back. 77 | template 78 | std::vector read_vector(std::istream& stream = std::cin) { 79 | // The function's return type and the type of result need slight changes. 80 | std::vector result; 81 | 82 | while (true) { 83 | // We want to copy whatever T is, now, not specifically int. 84 | std::copy(std::istream_iterator{stream}, std::istream_iterator{}, 85 | std::back_inserter(result)); 86 | 87 | // The rest of the function's code doesn't care whether we're working 88 | // with int or not, so we leave it as it was. 89 | 90 | if (stream.eof()) 91 | break; 92 | 93 | stream.clear(); 94 | 95 | std::string s; 96 | std::getline(stream, s); 97 | 98 | std::cerr << "Warning, ignoring: " << s << "\n"; 99 | } 100 | 101 | return result; 102 | } 103 | 104 | /* Above, we said we could pass the template parameters "later". How much 105 | * later? Templates are a compile-time feature of C++. All types still have to 106 | * be known when you compile the code, so "later" here means "later in the 107 | * program text". 108 | * 109 | * By the time the program has been compiled, most of the types you could see in 110 | * the source code are gone. We will see some ways of working with types at 111 | * run-time, but those are the exception, not the rule. Templates are the same 112 | * way; once your program is compiled, they're gone, and only their 113 | * instantiations remain. 114 | */ 115 | 116 | // We do a similar thing here. 117 | template 118 | T sum(std::vector const& v) { 119 | // We could use 0 as the starting value and assume that 0 can be converted 120 | // to a T. However, a better solution is to use T{}, which constructs a T 121 | // with no parameters. For int, that's a 0; for double, it's 0.0; for bool, 122 | // it's false; for std::string, it's the empty string. 123 | // 124 | // Note that because of this, sum will work on std::strings! 125 | return std::accumulate(v.begin(), v.end(), T{}); 126 | 127 | // By the way, std::accumulate is itself a template. We could explicitly 128 | // specify the template arguments: 129 | // 130 | // std::accumulate::const_iterator, T>( 131 | // v.begin(), v.end(), T{}); 132 | // 133 | // Sometimes the compiler is not smart enough to figure it out, so we have 134 | // to explicitly do this; however, as you can see, it doesn't look 135 | // particularly good. 136 | } 137 | 138 | /* By the way, what if we pass the template a type that doesn't make any sense? 139 | * For example, what if we try to call sum? Seeing as templates are done 140 | * compile-time, the compiler can detect errors like this and give you an error. 141 | * In this case, it may complain that we're passing void{} to a function, or 142 | * that std::vector isn't allowed. These errors are sometimes long and 143 | * unclear; learning to read them is part of learning C++. 144 | */ 145 | 146 | // In the original function, we took int x by value. However, now that we may 147 | // be dealing with arbitrary T, the copy may be expensive, so we take it by 148 | // const reference. 149 | template 150 | std::vector filter_greater_than(std::vector const& v, T const& x) { 151 | std::vector result; 152 | // Notice how auto here means we have one less thing to change. 153 | for (auto e : v) 154 | if (e > x) 155 | result.push_back(e); 156 | return result; 157 | } 158 | 159 | template 160 | T average(std::vector const& v) { 161 | if (v.empty()) 162 | return T{}; 163 | // At the moment, we don't enough about the language to be sure T(v.size()) 164 | // is safe. On the other hand, we also don't know any examples where it's 165 | // dangerous. I know this'll be okay, but don't fall into the trap of this 166 | // being "obviously right"! 167 | return sum(v)/T(v.size()); 168 | } 169 | 170 | template 171 | bool is_positive(T const& x) { 172 | return x > T{}; 173 | } 174 | 175 | template 176 | bool all_positive(std::vector const& v) { 177 | // We can specify template arguments even if we don't immediately call the 178 | // function and just pass it along. If we were to not specify it, we'd get 179 | // an error about the compiler not knowing which one to use. 180 | return std::all_of(v.begin(), v.end(), is_positive); 181 | } 182 | 183 | // Instead of generalising int and taking std::vector::const_iterator, let's 184 | // allow any kind of iterator. std::copy requires an input iterator to read 185 | // from, so we'll call the type parameter InputIt; this is fairly common. This 186 | // is purely for people reading our code; the compiler doesn't care what we call 187 | // it. 188 | template 189 | void display_range(InputIt begin, InputIt end) { 190 | std::cout << "{ "; 191 | 192 | // However, we're now faced with a bit of a problem: how do we specify the 193 | // type that std::ostream_iterator should output? Had we generalised only 194 | // over int, that would have been T, but now we need to extract the type of 195 | // the iterator's value. 196 | // 197 | // Fortunately, the designers of the standard library had anticipated this, 198 | // and so there's a type 199 | // 200 | // std::vector::const_iterator::value_type 201 | // 202 | // which is another name for int. (More generally, if we switch int to T in 203 | // the above, it'll be another name for T.) This is not very useful by 204 | // itself, but if InputIt is another name for 205 | // std::vector::const_iterator, then we can use InputIt::value_type to 206 | // get at that type! 207 | // 208 | // The above is almost true. We say that a type is a dependent type when it 209 | // depends on a template parameter. InputIt is a template parameter itself, 210 | // and so is trivially a dependent type. That means that 211 | // InputIt::value_type could be either a value or a type. C++ compilers are 212 | // only human and have a hard time figuring this out themselves; because of 213 | // that, we have to write typename before InputIt::value_type to indicate 214 | // that it is, in fact, a type. 215 | // 216 | // For clarity, let's do it in two steps, first making a local name for the 217 | // type: 218 | using T = typename InputIt::value_type; 219 | 220 | // Now that we have extracted the value type, we can use it normally as a 221 | // type. 222 | std::copy(begin, end, std::ostream_iterator{std::cout, " "}); 223 | 224 | std::cout << "}"; 225 | } 226 | 227 | // We can generalise all the sort-related functions now. They all use random 228 | // access iterators, so let's call them RandomIt. 229 | template 230 | RandomIt partition(RandomIt begin, RandomIt end); 231 | 232 | template 233 | void sort_impl(RandomIt begin, RandomIt end); 234 | 235 | /* Didn't we say that declaring templates didn't work? Not quite. We can 236 | * still declare them, but we need to have their definition in the same 237 | * translation unit. We just want to write the definitions below the definition 238 | * of sort. 239 | */ 240 | 241 | // Why not make sort work on any container that has begin, end, and random 242 | // access iterators? 243 | template 244 | Container sort(Container v) { 245 | 246 | sort_impl(v.begin(), v.end()); 247 | return v; 248 | } 249 | 250 | template 251 | void sort_impl(RandomIt begin, RandomIt end) { 252 | if (end - begin <= 1) 253 | return; 254 | 255 | // Notice how usage of auto means we can leave more code unchanged. 256 | auto pivot = partition(begin, end); 257 | sort_impl(begin, pivot); 258 | sort_impl(pivot+1, end); 259 | } 260 | 261 | template 262 | RandomIt partition(RandomIt begin, RandomIt end) { 263 | auto pivot = begin++; 264 | 265 | for (; begin != end; ++begin) { 266 | if (*pivot > *begin) { 267 | std::swap(*pivot, *begin); 268 | ++pivot; 269 | std::swap(*pivot, *begin); 270 | } 271 | } 272 | 273 | return pivot; 274 | } 275 | 276 | // I'll leave converting our binary search function into a function template as 277 | // an exercise to the reader. 278 | inline std::vector::const_iterator binary_search(std::vector const& v, int val) { 279 | auto bottom = v.begin(), top = v.end(); 280 | 281 | while (top != bottom) { 282 | auto mid = bottom + (top - bottom)/2; 283 | if (*mid < val) 284 | bottom = mid+1; 285 | else if (*mid > val) 286 | top = mid; 287 | else 288 | return mid; 289 | } 290 | 291 | return v.end(); 292 | } 293 | 294 | /* Now that you've seen how templates are implemented, let's take a look at our 295 | * main function. 296 | */ 297 | 298 | #endif 299 | -------------------------------------------------------------------------------- /Chapter 13 - Exceptions/io.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_13_IO_HPP 2 | #define CHAPTER_13_IO_HPP 3 | 4 | #include 5 | #include 6 | 7 | /* We're going to define a function template that reads a value of the type we 8 | * specify. If it can't read the value, it'll throw an instance of 9 | * std::runtime_error. 10 | * 11 | * C++ allows us to throw values of most types, but there are types that are 12 | * specifically designed to be thrown, and we should generally limit ourselves 13 | * to those. We will later see how to create our own, which means this will not 14 | * be a severe limitation. 15 | */ 16 | 17 | template 18 | T read(std::istream& stream = std::cin) { 19 | T result; 20 | 21 | // If everything went fine, we return. 22 | if (stream >> result) 23 | return result; 24 | 25 | // If we hit the end of file, report that 26 | if (stream.eof()) 27 | throw std::runtime_error{"read failed; EOF encountered."}; 28 | 29 | // otherwise, the input wasn't in the format we expected. 30 | throw std::runtime_error{"read failed; malformed input."}; 31 | } 32 | 33 | /* When we call read to take some input, we can't accidentally ignore an error. 34 | * We can write a catcher that does nothing to explicitly silence it, but if we 35 | * simply forget to write a catcher, the exception will fly right through our 36 | * code and out of main. If that happens, our program will terminate. 37 | * 38 | * Let's take a look at math.hpp now, where we'll also use exceptions. 39 | */ 40 | 41 | #endif 42 | 43 | -------------------------------------------------------------------------------- /Chapter 13 - Exceptions/main.cpp: -------------------------------------------------------------------------------- 1 | /* Recommended read order: main.cpp (beginning), io.hpp, math.hpp, math.cpp, 2 | * main.cpp (end) 3 | * 4 | * In this chapter, we're going to look at an approach to dealing with errors: 5 | * namely, exception handling. 6 | * 7 | * Let's first recap the way we've already seen though: error flags. We've seen 8 | * that when we use an istream and a read fails, it is set to a fail state, 9 | * which we can then check for. We can query it for whether the failure was 10 | * caused by invalid input or by there not being any more input to read, and 11 | * then clear the errors once we're ready to continue. 12 | * 13 | * Notice that nobody forces us to check the error flags. In chapter 2, we 14 | * skipped that step, because we didn't know about if statements yet. We have 15 | * been doing it systematically since then, but only for std::cin: we've never 16 | * checked whether our output to std::cout would work, just assuming that 17 | * everything would be fine. If anything ever did go wrong, our program would 18 | * never notice. 19 | * 20 | * Exceptions take a different approach to the problem. They split the work 21 | * between two pieces of code: the thrower and the catcher. 22 | * 23 | * The thrower is the code that reports an error. It looks almost like a return 24 | * statement: 25 | * 26 | * throw expression; 27 | * 28 | * The expression is evaluated, and its value becomes the exception. Just like 29 | * when you return a value the function doesn't continue as normal, throwing a 30 | * value breaks the normal flow of control. Instead of continuing with the next 31 | * statement, the program looks for a catcher. If none is found in the current 32 | * function, the function that called this one is looked at; and so on, until 33 | * we've found a catcher or checked main and found that nobody is willing to 34 | * handle our error. 35 | * 36 | * The catcher is the code that claims to be able to deal with the error. It 37 | * looks like this: 38 | * 39 | * try { 40 | * throwing statements... 41 | * } 42 | * catch (type x) { 43 | * handling statements... 44 | * } 45 | * catch (other_type x) { 46 | * other handling statements... 47 | * } 48 | * 49 | * The catcher specifies some potentially throwing statements by putting them in 50 | * a try clause. It then lists at least one catch clause: the word catch, 51 | * followed by what looks like a parameter list, but it's limited to one 52 | * parameter. The type of that parameter is used to know what exceptions we 53 | * want to catch. 54 | * 55 | * When an exception is thrown, its type is remembered. A catcher is found, and 56 | * then its catch clauses are inspected from top to bottom. If any of them have 57 | * a suitable type, that catch clause is run and the statements in there should 58 | * handle the error. If we've checked all catchers up to main and nobody is 59 | * willing to handle this type of exception, the program terminates. 60 | * 61 | * We can see this system as one of passing around responsibility. When you 62 | * call a function, you make it responsible for computing a certain value. If a 63 | * function cannot do that, it can throw an exception. It loses its 64 | * responsibility (and thus also its right to keep executing), and we look at 65 | * each caller in turn to see who is willing to accept this responsibility. 66 | * Whoever does has his handler code run. If nobody does, no code is run, and 67 | * your program grinds to a halt. 68 | * 69 | * Go ahead and look at io.hpp for an example involving streams. 70 | */ 71 | 72 | #include "io.hpp" 73 | #include "math.hpp" 74 | 75 | // When an entire function is a catcher, we can use a so-called function try 76 | // block. In this case, it's a matter of personal preference whether you do it 77 | // this way or just by having a normal try block. We'll see some cases where 78 | // you can't avoid using it later. 79 | int main() try { 80 | std::cout << "Enter a: "; 81 | auto a = read(); 82 | std::cout << "Enter b: "; 83 | auto b = read(); 84 | std::cout << "Enter c: "; 85 | auto c = read(); 86 | 87 | auto result = find_roots(a, b, c); 88 | std::cout << "First root: " << result.first << "\n"; 89 | std::cout << "Second root: " << result.second << "\n"; 90 | 91 | // Notice how we don't have to do any error-checking here. It's all done by 92 | // read and find_roots; if anything goes wrong, an exception will be thrown 93 | // and we'll catch it below. We've separated the code that does the work 94 | // from the code that handles the errors. 95 | } 96 | // Now that we're at our error-handling part, what do we catch? The two error 97 | // types we threw were std::runtime_error and std::domain_error. We could write 98 | // individual catch blocks for each, but there's a better solution. 99 | // 100 | // C++ types don't have to be entirely distinct. A type A can be a subtype of a 101 | // type B; that means that every value of type A also has type B. We say B is a 102 | // supertype of A. In this case, std::runtime_error and std::domain_error both 103 | // share a supertype std::exception, so catching std::exception will catch both 104 | // of them. 105 | // 106 | // However, a subtype may say more about the value than the supertype does. If 107 | // we catch std::exception by value, we will be copying the std::runtime_error 108 | // and std::domain_error, which may lose us data. Instead, we'll catch it by 109 | // reference: seeing as our value has type std::exception that is allowed, and 110 | // seeing as our reference refers to the original object, no data loss can 111 | // occur. 112 | catch (std::exception& e) { 113 | // e.what() returns the message we attached to the std::runtime_error or 114 | // std::domain_error 115 | std::cerr << "Error: " << e.what() << "\n"; 116 | return -1; 117 | } 118 | // As I said before, we really don't want exceptions escaping main. We can use 119 | // a catch (...) clause to catch errors of any type. However, as the value can 120 | // be of any type, it doesn't make sense for us to be able to inspect it; we 121 | // wouldn't know what to do with it! 122 | catch (...) { 123 | std::cerr << "Unknown error.\n"; 124 | return -1; 125 | } 126 | 127 | /* So far, I've been talking about exceptions as an altogether positive 128 | * solution. This is not an opinion held by everyone -- you'll find that quite 129 | * a few people aren't particularly fond of exceptions. 130 | * 131 | * Put simply: exceptions are the Hard Mode of C++. If you're coding with 132 | * exceptions, a large number of your expressions can throw, meaning that only 133 | * part of your code will be executed, while the rest will be skipped. You have 134 | * to make sure that this isn't a problem. Again: you have to make sure, for 135 | * every piece of code, that either it does not (and never will!) throw an 136 | * exception, or that only part of the code being executed does not corrupt your 137 | * program state in any way. You can use catch clauses to fix some things, but 138 | * we will need more powerful tools to handle all the problems that this brings 139 | * us. 140 | * 141 | * Some examples: 142 | * 143 | * foo(); 144 | * bar(); 145 | * 146 | * Up to now, we knew foo would be called, then bar would be called. In hard 147 | * mode, there's a chance foo will throw an exception and bar won't be called. 148 | * 149 | * bar(foo()); 150 | * 151 | * Same as above. We cannot assume bar will be called, unless we proved some 152 | * very strong claims about foo. 153 | * 154 | * bar(foo(), faa()) 155 | * 156 | * Earlier, we knew foo and faa would be called in some unspecified order. 157 | * After that, bar would be called with the results. In hard mode, all of these 158 | * are possible: 159 | * 160 | * - foo called but throws, others never called 161 | * - faa called but throws, others never called 162 | * - foo called successfully, faa called but throws, bar never called 163 | * - faa called successfully, foo called but throws, bar never called 164 | * - foo called successfully, faa called successfully, bar called but throws 165 | * - faa called successfully, foo called successfully, bar called but throws 166 | * 167 | * Let's add another one: 168 | * 169 | * bar(foo(), faa(), fee()) 170 | * 171 | * We couldn't be sure about what order these would be called in, but we'd knew 172 | * all three of foo, faa, and fee would be called, and then bar would be. Now 173 | * the situation is as above, but with even more options. Perhaps faa will be 174 | * called and complete successfully, and then foo will throw, so that fee is 175 | * never called. Perhaps things will happen in some other order. In general, 176 | * you cannot know, and you'll have a lot of code on your hands if you write a 177 | * catch clause for every function you call. 178 | * 179 | * If exceptions make C++ harder, why use them? It turns out that making our 180 | * code exception-safe is often a good idea anyway. When you have a group of 181 | * functions that must always be executed together, it generally pays to put a 182 | * little extra effort into guaranteeing that; even if exceptions don't break 183 | * it, a careless code change or a change in design might. 184 | * 185 | * Besides, it isn't all doom and gloom. Solutions to many of these problems 186 | * have been found, and once generalised, they can save effort. In the end, 187 | * once the pitfalls have been studied and bridges have been built, the results 188 | * are better. 189 | * 190 | * In my opinion, anyway. 191 | */ 192 | -------------------------------------------------------------------------------- /Chapter 13 - Exceptions/math.cpp: -------------------------------------------------------------------------------- 1 | #include "math.hpp" 2 | #include 3 | #include 4 | 5 | // This is a straightforward implementation of the quadratic formula: 6 | // http://en.wikipedia.org/wiki/Quadratic_formula 7 | std::pair find_roots(double a, double b, double c) { 8 | // Nobody forces us to use const when dealing with locals, but it is a 9 | // sensible thing to do if you know the value will not change. 10 | auto const determinant = b*b - 4*a*c; 11 | 12 | // Instead of throwing std::runtime_error, we throw std::domain_error. This 13 | // is a very similar type, varying mostly in name. It is intended to be 14 | // used when "a mathematical function is not defined for the value of the 15 | // argument", which is what we have here. 16 | if (determinant < 0) 17 | throw std::domain_error{"no real roots."}; 18 | 19 | // std::sqrt returns the square root of its argument. 20 | // We make these to save ourselves from computing some values twice. 21 | auto const two_a = 2*a; 22 | auto const d_part = std::sqrt(determinant); 23 | 24 | // std::make_pair makes a pair. Duh. 25 | return std::make_pair((-b+d_part)/two_a, (-b-d_part)/two_a); 26 | } 27 | 28 | /* We've now written all our throwers. They throw std::runtime_error and 29 | * std::domain_error, so we'll want to catch those two in main. Let's go there 30 | * and make that work. 31 | */ 32 | -------------------------------------------------------------------------------- /Chapter 13 - Exceptions/math.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_13_MATH_HPP 2 | #define CHAPTER_13_MATH_HPP 3 | 4 | #include 5 | 6 | // The equation will likely have two roots, so we return an std::pair. This is 7 | // just a type that lets us store two values; if we have a pair p then the first 8 | // value can be accessed as p.first, and the second as p.second. Nothing 9 | // spectacular, but it means we can return two values. 10 | std::pair find_roots(double a, double b, double c); 11 | 12 | #endif 13 | 14 | -------------------------------------------------------------------------------- /Chapter 14 - Project Overview/main.cpp: -------------------------------------------------------------------------------- 1 | /* Over the next few chapters, we'll be writing a simple lexer, parser, and 2 | * interpreter for a LISP-like language. 3 | * 4 | * Many of the features of C++ are intended for larger programs. While they 5 | * could all be demonstrated in smaller examples, I expect this to do nothing 6 | * but obscure the purpose of the thing. 7 | * 8 | * Let's take a high-level overview of the project: 9 | * - First we'll want to write some code that we can use to split the input 10 | * into logical chunks, skipping anything we're not interested in (like 11 | * whitespace). We don't need anything new for that, so this chapter will 12 | * cover it fully. 13 | * - We'll then want to be able to store these chunks, which we'll call tokens. 14 | * A number of chapters will be spent on improving the representation. 15 | * - Working directly with the sequence of tokens would be inconvenient, so we 16 | * write a helper to automate the common tasks. 17 | * - Once we can work with the tokens easily, we'll write a function that can 18 | * analyze the structure in order to make a tree. We'll see the details later. 19 | * - We'll spend some time looking at how we can represent homogenous and 20 | * heterogenous trees, and how we can perform computations over them. 21 | * - Some implementation details like symbol tables may need more attention. 22 | * - Finally, we'll put everything together to make both an interpreter for 23 | * files and a REPL. 24 | * 25 | * If you've never seen Lisp before, it looks roughly like this: 26 | * 27 | * (+ (* 2 2) 3) 28 | * 29 | * In C++, this expression would look like 30 | * 31 | * (2*2) + 3 32 | * 33 | * We can also define variables: 34 | * 35 | * (define x 5) 36 | * 37 | * and have so-called lambda-functions 38 | * 39 | * (lambda (x) (* x x)) 40 | * 41 | * the above is a function that takes a single parameter x and returns x*x. 42 | * 43 | * We'll also support the -, /, ==, !=, <, <=, >, >= operators and an if 44 | * function. This means the factorial function can be written as: 45 | * 46 | * (define factorial 47 | * (lambda (x) 48 | * (if (<= x 1) 49 | * 0 50 | * (* x (factorial (- x 1)))))) 51 | * 52 | * Translated to C++, the above is almost exactly the same as 53 | * 54 | * int factorial(int x) { 55 | * if (x <= 1) 56 | * return 0; 57 | * else 58 | * return x * factorial(x-1); 59 | * } 60 | * 61 | * Leaving aside the question of which one looks better, we can guess that the 62 | * Lisp version is going to be much easier to write a program to evaluate. 63 | * 64 | * We have five kinds of tokens: 65 | * - opening parentheses 66 | * - closing parentheses 67 | * - operators (sequences of +*-/!=<>) 68 | * - names (sequences of letters) 69 | * - numbers (sequences of digits) 70 | * 71 | * However, notice that we can use operators and names interchangably. This 72 | * means that we don't have to make a separate category for them. We end up 73 | * with four categories: open_paren, close_paren, name and number. 74 | * 75 | * It also helps to have a special token type for the end of the file. This 76 | * serves a double purpose: it means we know when to stop asking for more 77 | * tokens, and it lets us check that all input has indeed been handled. Seeing 78 | * as we're just printing the tokens for now it may not seem like that big a 79 | * difference, but by the next chapter we'll be glad we did it. 80 | * 81 | * Apart from knowing that a name or number was seen, we'd also like to know 82 | * what name or number it was. This means that every token should have an 83 | * associated string. For opening and closing parentheses this could be 84 | * omitted, as they are always the same, but doing so at this point would be 85 | * more trouble than it's worth. 86 | * 87 | * All in all, we want a program that can take 88 | * 89 | * (+ foo 24) 90 | * 91 | * And print 92 | * 93 | * { open_paren: "(" } 94 | * { name: "+" } 95 | * { name: "foo" } 96 | * { number: "24" } 97 | * { close_paren: ")" } 98 | * { end_of_file: "" } 99 | * 100 | */ 101 | 102 | #include 103 | #include 104 | #include 105 | #include 106 | 107 | // As usual, we'll split our program into a few functions. 108 | 109 | // Does exactly what it says on the tin and is our main "driving" function. 110 | // All the other parsing functions are called from here. 111 | void print_next_token(std::istream& stream); 112 | 113 | // Let's get the implementation of main out of the way before we look at more 114 | // specific things. 115 | int main() try { 116 | while (std::cin) { 117 | print_next_token(std::cin); 118 | std::cout << "\n"; 119 | } 120 | } 121 | catch (std::exception& e) { 122 | std::cerr << "Error: " << e.what() << "\n"; 123 | return -1; 124 | } 125 | catch (...) { 126 | std::cerr << "Unknown error.\n"; 127 | return -1; 128 | } 129 | 130 | // Well, that was quick and painless. Now on to the less painless parts: 131 | 132 | // We'll use helper functions for lexing the non-trivial symbols. The simpler 133 | // ones can go right in print_next_token. 134 | void lex_name(std::istream& stream); 135 | void lex_number(std::istream& stream); 136 | void lex_operator(std::istream& stream); 137 | 138 | // Let's write a function to check for tokens that are allowed in operators. 139 | bool isoperator(char c) { 140 | // std::string's find returns the position of the character, or 141 | // std::string::npos if the character isn't found. 142 | std::string const valid_chars = "+*-/!=<>"; 143 | return valid_chars.find(c) != std::string::npos; 144 | } 145 | 146 | void print_next_token(std::istream& stream) { 147 | // We want to parse the input character-by-character. We'll store the 148 | // character we're currently parsing in c 149 | char c; 150 | // Here's a loop somewhat unlike what we've seen before -- instead of a body 151 | // it's just got a semicolon after it. That's allowed: the semicolon is an 152 | // empty statement, which is a valid body. You can read this as "while the 153 | // condition is true, do nothing". 154 | // 155 | // Why is this useful? Our condition is what's doing the work. The && 156 | // operator is defined to first evaluate what's on the left of it, and only 157 | // if that is true will the right-hand side ever be evaluated. 158 | // stream.get(c) is true if and only if it managed to extract a character 159 | // into c. After that, we use std::isspace to check whether that character 160 | // is a space. This means that characters will keep getting extracted as 161 | // long as they're whitespace, and once the loop is done, either we've read 162 | // all the input we had or c is a non-whitespace character. 163 | while (stream.get(c) && std::isspace(c)); 164 | 165 | // Now that we've ignored the spaces, we can check whether there's anything 166 | // more to parse: 167 | if (!stream) { 168 | std::cout << "{ end_of_file: \"\" }"; 169 | return; 170 | } 171 | 172 | // We can check if the character is a parenthesis... 173 | if (c == '(') { 174 | std::cout << "{ open_paren: \"(\" }"; 175 | return; 176 | } 177 | if (c == ')') { 178 | std::cout << "{ close_paren: \")\" }"; 179 | return; 180 | } 181 | 182 | // If it isn't any of the above, it must be a name or number. We've 183 | // declared functions for parsing those three things already, so we just 184 | // need to select the right function. However, those functions don't take a 185 | // char for the first symbol, which we've already extracted, so if we just 186 | // called one of them with the rest of the stream, we would end up skipping 187 | // over the first character of whatever we were parsing. 188 | // 189 | // To fix this we "unget" the character we extracted. We can still use c, 190 | // as its value won't be changed by this, but now our lexing functions will 191 | // see the whole token. 192 | stream.unget(); 193 | 194 | if (std::isalpha(c)) 195 | lex_name(stream); 196 | else if (std::isdigit(c)) 197 | lex_number(stream); 198 | else if (isoperator(c)) 199 | lex_operator(stream); 200 | else // we couldn't recognise the character 201 | throw std::runtime_error{"unrecognised character"}; 202 | } 203 | 204 | // Now let's write our lexing functions 205 | void lex_name(std::istream& stream) { 206 | char c; 207 | std::string name; 208 | // This is similar to the whitespace-clearing loop above, but now we store 209 | // the character in a string. Pushing-backing into a string works just like 210 | // it does with a vector. 211 | while (stream.get(c) && std::isalpha(c)) 212 | name.push_back(c); 213 | // We don't want the last character we extracted to disappear. 214 | if (stream) 215 | stream.unget(); 216 | 217 | std::cout << "{ name: \"" << name << "\" }"; 218 | } 219 | 220 | void lex_number(std::istream& stream) { 221 | char c; 222 | std::string number; 223 | while (stream.get(c) && std::isdigit(c)) 224 | number.push_back(c); 225 | if (stream) 226 | stream.unget(); 227 | 228 | std::cout << "{ number: \"" << number << "\" }"; 229 | } 230 | 231 | void lex_operator(std::istream& stream) { 232 | char c; 233 | std::string op; 234 | while (stream.get(c) && isoperator(c)) 235 | op.push_back(c); 236 | if (stream) 237 | stream.unget(); 238 | 239 | std::cout << "{ name: \"" << op << "\" }"; 240 | } 241 | 242 | // Tada! Notice how similar lex_name, lex_number and lex_operator are. 243 | // Eventually we'll be able to make these things less similar. 244 | 245 | /* We can now recognise different tokens. Next we'll look at how we can 246 | * represent these tokens inside the program so that we can pass them to and 247 | * from functions and make vectors of them. You can probably guess that 248 | * std::pair would do the job, but C++ offers a 249 | * neater solution. 250 | */ 251 | -------------------------------------------------------------------------------- /Chapter 15 - Basic Structs/lex.cpp: -------------------------------------------------------------------------------- 1 | #include "lex.hpp" 2 | #include 3 | #include 4 | 5 | // We want these to return a token. 6 | Token lex_name(std::istream& stream); 7 | Token lex_number(std::istream& stream); 8 | Token lex_operator(std::istream& stream); 9 | 10 | bool isoperator(char c) { 11 | std::string const valid_chars = "+*-/!=<>"; 12 | return valid_chars.find(c) != std::string::npos; 13 | } 14 | 15 | // Most of this function is unchaged. 16 | Token extract_next_token(std::istream& stream) { 17 | char c; 18 | while (stream.get(c) && std::isspace(c)); 19 | 20 | if (!stream) { 21 | // Here's the first difference. We don't print anything; instead, we 22 | // create a Token and return it. We could use the Token{...} syntax 23 | // described earlier, but the compiler already knows we want a Token, so 24 | // we can just use {...}. In particular, as type and value were defined 25 | // in that order, when we say Token{x, y}, type will be initialised with 26 | // x and value will be initialised with y. 27 | return {end_of_file_token, ""}; 28 | } 29 | 30 | if (c == '(') 31 | return {open_paren_token, "("}; 32 | if (c == ')') 33 | return {close_paren_token, ")"}; 34 | 35 | stream.unget(); 36 | 37 | if (std::isalpha(c)) 38 | return lex_name(stream); 39 | if (std::isdigit(c)) 40 | return lex_number(stream); 41 | if (isoperator(c)) 42 | return lex_operator(stream); 43 | 44 | // If we haven't returned by now, something is wrong. 45 | throw std::runtime_error{"unrecognised character"}; 46 | } 47 | 48 | Token lex_name(std::istream& stream) { 49 | char c; 50 | std::string name; 51 | while (stream.get(c) && std::isalpha(c)) 52 | name.push_back(c); 53 | 54 | if (stream) 55 | stream.unget(); 56 | 57 | return {name_token, name}; 58 | } 59 | 60 | Token lex_number(std::istream& stream) { 61 | char c; 62 | std::string number; 63 | while (stream.get(c) && std::isdigit(c)) 64 | number.push_back(c); 65 | 66 | if (stream) 67 | stream.unget(); 68 | 69 | return {number_token, number}; 70 | } 71 | 72 | Token lex_operator(std::istream& stream) { 73 | char c; 74 | std::string op; 75 | while (stream.get(c) && isoperator(c)) 76 | op.push_back(c); 77 | 78 | if (stream) 79 | stream.unget(); 80 | 81 | return {name_token, op}; 82 | } 83 | 84 | /* Now that we can do this, let's go back to main and show that we can make a 85 | * vector of Tokens using all this. 86 | */ 87 | -------------------------------------------------------------------------------- /Chapter 15 - Basic Structs/lex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_15_LEX_HPP 2 | #define CHAPTER_15_LEX_HPP 3 | 4 | #include "token.hpp" 5 | #include 6 | 7 | /* Now that we've defined the Token type, we can use it like just about any 8 | * other kind of type. It doesn't have all that many operations defined on it 9 | * yet -- we can copy and assign it, but not compare it, for instance -- but we 10 | * have enough to create them and return them from functions. 11 | */ 12 | 13 | // Instead of print_next_token we have an extract_next_token which reads a token 14 | // and returns it. 15 | // 16 | // All other lexing functions are implementation details, so we don't declare 17 | // them in the header. 18 | Token extract_next_token(std::istream& stream); 19 | 20 | #endif 21 | 22 | -------------------------------------------------------------------------------- /Chapter 15 - Basic Structs/main.cpp: -------------------------------------------------------------------------------- 1 | /* Suggested reading order: main.cpp (beginning), token.hpp, lex.hpp, lex.cpp, 2 | * main.cpp (rest) 3 | * 4 | * We've seen how we can print the tokens present in a stream. Now, instead of 5 | * simply outputting the tokens we'd like to return them. 6 | * 7 | * First of all, we'd like to have a name for each kind of token: name_token, 8 | * open_paren_token, close_paren_token, number_token, and end_of_file_token. We 9 | * don't really care what the type of these is, as long as we can assign them 10 | * and compare them for equality. 11 | * 12 | * Once we have that, we'd like Token to be a type. If tok is an instance of 13 | * token, we want tok.type to be the type of token tok is, and tok.value to be 14 | * the string associated with tok. 15 | * 16 | * Proceed to token.hpp and we'll take a look at how we can create such a type. 17 | */ 18 | 19 | #include "lex.hpp" 20 | #include 21 | #include 22 | #include 23 | 24 | void print_token(Token const& tok) { 25 | std::cout << "{ "; 26 | 27 | // This chain of ifs and elses doesn't look particularly good. We'll see 28 | // how we can make it better later on. 29 | if (tok.type == open_paren_token) 30 | std::cout << "open_paren_token"; 31 | else if (tok.type == close_paren_token) 32 | std::cout << "close_paren_token"; 33 | else if (tok.type == name_token) 34 | std::cout << "name_token"; 35 | else if (tok.type == number_token) 36 | std::cout << "number_token"; 37 | else if (tok.type == end_of_file_token) 38 | std::cout << "end_of_file_token"; 39 | 40 | std::cout << ", \"" << tok.value << "\" }\n"; 41 | } 42 | 43 | int main() try { 44 | std::vector tokens; 45 | while (std::cin) 46 | tokens.push_back(extract_next_token(std::cin)); 47 | 48 | std::for_each(tokens.begin(), tokens.end(), print_token); 49 | } 50 | catch (std::exception& e) { 51 | std::cerr << "Error: " << e.what() << "\n"; 52 | return -1; 53 | } 54 | catch (...) { 55 | std::cerr << "Unknown error.\n"; 56 | return -1; 57 | } 58 | 59 | /* We can now operate on tokens from inside the program. It is tempting to 60 | * immediately continue to parsing and tree-construction, but there's no need to 61 | * rush. There are a number of places where the current implementation is 62 | * suboptimal and we should take a look at those before we move on to more 63 | * advanced matters. 64 | * 65 | * In particular, we can make it possible to compare tokens using == and != and 66 | * input and output them using >> and <<. We can also make print_token neater 67 | * and finally get a proper introduction to the name::other_name syntax that 68 | * we've been using without a proper explanation for far too long. 69 | */ 70 | -------------------------------------------------------------------------------- /Chapter 15 - Basic Structs/token.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_15_TOKEN_HPP 2 | #define CHAPTER_15_TOKEN_HPP 3 | 4 | #include 5 | 6 | /* Let's start by defining the token types. We'll make them int consts; we will 7 | * see a better solution soon, but this will do until we've had a chance to 8 | * introduce that. 9 | * 10 | * There's two reasons to make them const here: the first and most important one 11 | * is that it makes no sense for anyone to modify them: after all, they're just 12 | * tags and their values, as long as they're all different, don't really matter. 13 | * 14 | * The other reason is that marking them const means we can define them in the 15 | * header. Had they been normal ints this would lead to double definition 16 | * errors, but because the type is const they are excluded from that rule and 17 | * may be defined multiple times in different translation units, as long as the 18 | * definitions are all the same. 19 | */ 20 | 21 | /* By the way, yes, you are allowed to define arbitrary variables outside 22 | * functions. However, doing so is a sure way to make your project a tangled 23 | * mess. Chances are that if you wanted to do this, the features of structs 24 | * we'll be looking at shortly will provide a better solution. 25 | */ 26 | 27 | int const open_paren_token = 0; 28 | int const close_paren_token = 1; 29 | int const name_token = 2; 30 | int const number_token = 3; 31 | int const end_of_file_token = 4; 32 | 33 | /* With that out of the way, we can look at creating the Token type. The syntax 34 | * for that is, very roughly: 35 | * 36 | * struct name { 37 | * members... 38 | * } instances...; 39 | * 40 | * For now, members are just variable declarations; they'll become much more as 41 | * we progress, but there's no need to worry about that for now. instances are 42 | * an interesting historical curiousity. They allow us to immediately define 43 | * variables of the type we just defined. You won't see them used all that 44 | * often, but knowing that they're allowed explains why you need a semicolon at 45 | * the end of your struct definition. 46 | * 47 | * Now, our Token type: 48 | */ 49 | 50 | // We wanted the name to be Token, and we wanted tok.type and tok.value to be 51 | // available. 52 | // 53 | // Note that we use int for type, even though open_paren_token and friends are 54 | // all int const. Note that when we say tok_a = tok_b, we overwrite tok_a's 55 | // type and value members. If we were to use int const here, that overwrite 56 | // would be illegal and we wouldn't be able to assign Tokens to each other. 57 | struct Token { 58 | int type; 59 | std::string value; 60 | }; 61 | 62 | /* Note that I did say struct *definition*. struct declarations exist, too: if 63 | * we wanted to only declare Token and not define it, we could have said 64 | * 65 | * struct Token; 66 | * 67 | * However, Token would be a so-called incomplete type, meaning we couldn't make 68 | * instances of it or use any members; after all, we don't know which ones it 69 | * has. 70 | * 71 | * Why are we allowed to put a struct definition in a header? This is yet 72 | * another case of multiple definitions being allowed in different translation 73 | * units if they are identical. The definition of Token is something that most 74 | * code that uses Token needs, so it would be very impractical if we could only 75 | * define it in one translation unit. 76 | * 77 | * Continue to lex.hpp. 78 | */ 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /Chapter 16 - Basic Operator Overloading/lex.cpp: -------------------------------------------------------------------------------- 1 | #include "lex.hpp" 2 | #include 3 | #include 4 | 5 | Token lex_name(std::istream& stream); 6 | Token lex_number(std::istream& stream); 7 | Token lex_operator(std::istream& stream); 8 | 9 | bool isoperator(char c) { 10 | std::string const valid_chars = "+*-/!=<>"; 11 | return valid_chars.find(c) != std::string::npos; 12 | } 13 | 14 | Token extract_next_token(std::istream& stream) { 15 | char c; 16 | while (stream.get(c) && std::isspace(c)); 17 | 18 | if (!stream) 19 | return {end_of_file_token, ""}; 20 | 21 | if (c == '(') 22 | return {open_paren_token, "("}; 23 | if (c == ')') 24 | return {close_paren_token, ")"}; 25 | 26 | stream.unget(); 27 | 28 | if (std::isalpha(c)) 29 | return lex_name(stream); 30 | if (std::isdigit(c)) 31 | return lex_number(stream); 32 | if (isoperator(c)) 33 | return lex_operator(stream); 34 | 35 | throw std::runtime_error{"unrecognised character"}; 36 | } 37 | 38 | Token lex_name(std::istream& stream) { 39 | char c; 40 | std::string name; 41 | while (stream.get(c) && std::isalpha(c)) 42 | name.push_back(c); 43 | 44 | if (stream) 45 | stream.unget(); 46 | 47 | return {name_token, name}; 48 | } 49 | 50 | Token lex_number(std::istream& stream) { 51 | char c; 52 | std::string number; 53 | while (stream.get(c) && std::isdigit(c)) 54 | number.push_back(c); 55 | 56 | if (stream) 57 | stream.unget(); 58 | 59 | return {number_token, number}; 60 | } 61 | 62 | Token lex_operator(std::istream& stream) { 63 | char c; 64 | std::string op; 65 | while (stream.get(c) && isoperator(c)) 66 | op.push_back(c); 67 | 68 | if (stream) 69 | stream.unget(); 70 | 71 | return {name_token, op}; 72 | } 73 | 74 | -------------------------------------------------------------------------------- /Chapter 16 - Basic Operator Overloading/lex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_16_LEX_HPP 2 | #define CHAPTER_16_LEX_HPP 3 | 4 | #include "token.hpp" 5 | #include 6 | 7 | Token extract_next_token(std::istream& stream); 8 | 9 | #endif 10 | 11 | -------------------------------------------------------------------------------- /Chapter 16 - Basic Operator Overloading/main.cpp: -------------------------------------------------------------------------------- 1 | /* Suggested reading order: main.cpp (beginning), token.hpp, token.cpp, main.cpp 2 | * (rest) 3 | * lex.hpp and lex.cpp have not changed. 4 | * 5 | * We now have a Token type that we can use to represent tokens in our program. 6 | * At the moment, we can create Tokens, pass them to and from functions, and 7 | * access the type and value members. However, given two tokens x and y, we 8 | * cannot do x == y or x != y, and we cannot output a token with std::cout << x, 9 | * nor read one with std::cin >> x. In this chapter we'll fix those issues. 10 | * 11 | * In order to make those operations legal, we need to define how those 12 | * operators work when given Tokens. Just like functions, operators take 13 | * parameters and give a result, so it comes as no surprise that overloaded 14 | * operators are actually functions written with nicer syntax. The name of the 15 | * function is operator@@, where @@ is the operator in question. 16 | * 17 | * (BUG: At the moment, there is no mention of overloading up to this point. 18 | * This should probably be handled at least briefly before operator overloading 19 | * comes to play. I can't think of where to put it, though.) 20 | * 21 | * Go to token.hpp for some examples. 22 | */ 23 | 24 | #include "lex.hpp" 25 | #include 26 | #include 27 | #include 28 | 29 | int main() try { 30 | // There are no implicit conversions to string happening here: the >> and << 31 | // operators really are taking Tokens. 32 | Token tok; 33 | // Notice that this way of reading means we never see the end_of_file_token. 34 | // This is just for demonstration purposes; soon we'll have a better way to 35 | // do this. 36 | while (std::cin >> tok) 37 | std::cout << tok << "\n"; 38 | } 39 | catch (std::exception& e) { 40 | std::cerr << "Error: " << e.what() << "\n"; 41 | return -1; 42 | } 43 | catch (...) { 44 | std::cerr << "Unknown error.\n"; 45 | return -1; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Chapter 16 - Basic Operator Overloading/token.cpp: -------------------------------------------------------------------------------- 1 | // A minor detail: here we use the fact we split token and lex into header and 2 | // source files. This means we can have both source files include both header 3 | // files, and everything will work fine. Had we not split them this way, we 4 | // would have to first have to include everything declared and defined in token, 5 | // and then everything defined in lex, or the other way around. Try rewriting 6 | // the program like that, and see where you run into issues. 7 | #include "token.hpp" 8 | #include "lex.hpp" 9 | 10 | /* The definitions are just as close to function definitions. The syntax is 11 | * new, but conceptually these are all things we could have written normal 12 | * functions for before. 13 | */ 14 | 15 | bool operator==(Token const& lhs, Token const& rhs) { 16 | return lhs.type == rhs.type && lhs.value == rhs.value; 17 | } 18 | 19 | bool operator!=(Token const& lhs, Token const& rhs) { 20 | // We could repeat the logic here, but defining it in terms of operator== is 21 | // simpler. 22 | return !(lhs == rhs); 23 | } 24 | 25 | std::istream& operator>>(std::istream& is, Token& tok) { 26 | // We already have extract_next_token, so there's no need to reimplement all 27 | // that functionality. 28 | // 29 | // If the stream is already in a failed state we don't want to modify tok. 30 | if (is) 31 | tok = extract_next_token(is); 32 | return is; 33 | } 34 | 35 | /* We could use the print_token code here, and the compiler would have no issue 36 | * with it. However, we are used to the >> and << operators being symmetrical: 37 | * if we have defined each, it would be nice if reading using >> and then 38 | * writing using << would return similar results. 39 | * 40 | * In the case of tokens, this isn't particularly convenient: printing them is 41 | * mostly intended for debugging, and then we want to have full information 42 | * about each token. In the next lesson we'll look at a more elaborate lexer 43 | * which will replace both extract_next_token and operator>>, and then we'll 44 | * define operator<< as we like it. 45 | */ 46 | std::ostream& operator<<(std::ostream& os, Token const& tok) { 47 | os << tok.value; 48 | return os; 49 | } 50 | -------------------------------------------------------------------------------- /Chapter 16 - Basic Operator Overloading/token.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_16_TOKEN_HPP 2 | #define CHAPTER_16_TOKEN_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | int const open_paren_token = 0; 9 | int const close_paren_token = 1; 10 | int const name_token = 2; 11 | int const number_token = 3; 12 | int const end_of_file_token = 4; 13 | 14 | struct Token { 15 | int type; 16 | std::string value; 17 | }; 18 | 19 | /* As token.hpp is a header file, we don't define the operator overloads yet. 20 | * However, we do want the other files to be aware of them, so we do need to 21 | * declare them. 22 | * 23 | * Treat the operator declarations just as function declarations with a funny 24 | * name. You have less freedom when defining operators: for example, you can't 25 | * change the number of parameters willy-nilly, but the body may do anything a 26 | * function may do. 27 | */ 28 | 29 | bool operator==(Token const& lhs, Token const& rhs); 30 | bool operator!=(Token const& lhs, Token const& rhs); 31 | std::istream& operator>>(std::istream& is, Token& tok); 32 | std::ostream& operator<<(std::ostream& os, Token const& tok); 33 | 34 | /* Now go to token.cpp to see how things are implemented. 35 | */ 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /Chapter 17 - Member Functions/lexer.cpp: -------------------------------------------------------------------------------- 1 | #include "lexer.hpp" 2 | #include 3 | #include 4 | 5 | // We keep this helper function around: it doesn't need any class data so it 6 | // doesn't make sense to make it a member function. 7 | bool isoperator(char c) { 8 | std::string const valid_chars = "+*-/!=<>"; 9 | return valid_chars.find(c) != std::string::npos; 10 | } 11 | 12 | /* We need to define all those functions we declared Lexer to have. We again 13 | * use the scope resolution operator, now to specify that we are indeed defining 14 | * a member function of Lexer, not a normal function of the same name. 15 | */ 16 | 17 | // We've already initialized current_position, so we don't have to do anything 18 | // about that in our constructor. On the other hand, we get the value for 19 | // input_stream from a parameter, and we'd like to initialize it. Furthermore, 20 | // being a reference, input_stream cannot be default-constructed, and so we use 21 | // the constructor-initializer. (Even if we could have used an assignment, a 22 | // constructor-initializer is neater, and may be faster.) 23 | Lexer::Lexer(std::istream& is) : input_stream(is) {} 24 | // The : starts the constructor-initializer, then input_stream(is) binds 25 | // input_stream to is, and finally {} is the empty constructor body. 26 | 27 | // This function looks much like extract_next_token. However, we don't have to 28 | // pass the stream around explicitly; instead, we can use input_stream from any 29 | // of our member functions. When inside a member function, calling another 30 | // member function without specifying what instance we are calling it on calls 31 | // it on the current instance. 32 | Token Lexer::extract() { 33 | ignore_whitespace(); 34 | 35 | char c; 36 | if (!peek(c)) 37 | return {end_of_file_token, ""}; 38 | 39 | if (std::isalpha(c)) 40 | return lex_name(); 41 | if (std::isdigit(c)) 42 | return lex_number(); 43 | if (isoperator(c)) 44 | return lex_operator(); 45 | 46 | ignore(); 47 | 48 | if (c == '(') 49 | return {open_paren_token, "("}; 50 | if (c == ')') 51 | return {close_paren_token, ")"}; 52 | 53 | throw std::runtime_error{"unrecognised character"}; 54 | } 55 | 56 | Lexer::Position Lexer::get_position() const { 57 | return current_position; 58 | } 59 | 60 | Lexer::operator bool() const { 61 | // Here we again use the function-like form of conversion. This is because 62 | // operator bool() in std::istream is also explicit, and so simply returning 63 | // would give us an error. 64 | return bool(input_stream); 65 | } 66 | 67 | bool Lexer::peek(char& c) const { 68 | // std::istream doesn't allow us to peek(c) like it allowed us to get(c), so 69 | // we have to work around this. 70 | 71 | // peek returns an int, so that we can check for end-of-file. 72 | int x = input_stream.peek(); 73 | // std::char_traits::eof returns the value that indicates an end of file. 74 | if (x == std::char_traits::eof()) 75 | return false; 76 | // Otherwise, the implicit conversion is guaranteed to be safe. 77 | c = x; 78 | return true; 79 | } 80 | 81 | // ignore is where the work regarding position tracking happens. This isn't the 82 | // most efficient way of implementing a Lexer; we almost always call peek double 83 | // the number of times necessary. However, at this point the gain in safety is 84 | // worth the drop in performance. 85 | void Lexer::ignore() { 86 | char c; 87 | if (!peek(c)) 88 | throw std::logic_error{"ignoring past end of file"}; 89 | input_stream.ignore(); 90 | if (c == '\n') { 91 | current_position.line += 1; 92 | current_position.column = 1; 93 | } else { 94 | current_position.column += 1; 95 | } 96 | } 97 | 98 | bool Lexer::get(char& c) { 99 | if (!peek(c)) 100 | return false; 101 | ignore(); 102 | return true; 103 | } 104 | 105 | void Lexer::ignore_whitespace() { 106 | char c; 107 | while (peek(c) && std::isspace(c)) 108 | ignore(); 109 | } 110 | 111 | Token Lexer::lex_name() { 112 | char c; 113 | std::string name; 114 | while (peek(c) && std::isalpha(c)) { 115 | name.push_back(c); 116 | ignore(); 117 | } 118 | 119 | return {name_token, name}; 120 | } 121 | 122 | Token Lexer::lex_number() { 123 | char c; 124 | std::string number; 125 | while (peek(c) && std::isdigit(c)) { 126 | number.push_back(c); 127 | ignore(); 128 | } 129 | 130 | return {number_token, number}; 131 | } 132 | 133 | Token Lexer::lex_operator() { 134 | char c; 135 | std::string op; 136 | while (peek(c) && isoperator(c)) { 137 | op.push_back(c); 138 | ignore(); 139 | } 140 | 141 | return {name_token, op}; 142 | } 143 | 144 | bool operator!(Lexer const& lex) { 145 | return !bool(lex); 146 | } 147 | 148 | bool operator==(Lexer::Position lhs, Lexer::Position rhs) { 149 | return lhs.line == rhs.line && lhs.column == rhs.column; 150 | } 151 | 152 | bool operator!=(Lexer::Position lhs, Lexer::Position rhs) { 153 | return !(lhs == rhs); 154 | } 155 | 156 | std::ostream& operator<<(std::ostream& os, Lexer::Position pos) { 157 | os << "(line: " << pos.line << ", column:" << pos.column << ")"; 158 | return os; 159 | } 160 | 161 | /* With the lexer implemented, let's look at the resulting main function. 162 | */ 163 | -------------------------------------------------------------------------------- /Chapter 17 - Member Functions/lexer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_17_LEXER_HPP 2 | #define CHAPTER_17_LEXER_HPP 3 | 4 | #include "token.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | /* The definition of Lexer is going to have many similarities with the 10 | * definition of Token; members are declared much the same way, for instance. 11 | * We'll comment only on the things that change. 12 | */ 13 | 14 | struct Lexer { 15 | /* The first change is that apart from member variables, our lexer will have 16 | * member functions. We've seen member functions before, though we never 17 | * called them that: when we have an std::vector v and call v.size(), 18 | * we are calling a member function; same when we have an std::string str 19 | * and call str.find('x'). Member functions take parameters just like 20 | * normal functions do, but they have an extra implicit parameter: when we 21 | * call v.size(), size can implicitly access everything in v. 22 | * 23 | * Let's decide what member functions we want: we'd like to be able to 24 | * create a Lexer from an std::istream&, get the next token, check if 25 | * there are any more tokens, and get the current position in the input. 26 | */ 27 | 28 | // So far, we have been using the Token{x, y} syntax to specify the members 29 | // one-by-one; with our Lexer, this won't work because we don't want people 30 | // to know about most of our members. We will define a constructor: this is 31 | // a special member function which is called when we create an instance. It 32 | // has no return type and has the same name as the type it is a constructor 33 | // of. 34 | // 35 | // We can also choose whether to require the constructor to be called 36 | // explicitly or implicitly. The default is implicit; this means that if we 37 | // have a function taking a Lexer, we can give it an std::istream& and a 38 | // temporary Lexer will be automatically created. We probably don't want 39 | // this, so let's disable it here. 40 | explicit Lexer(std::istream& is); 41 | // We can now use Lexer lex{std::cin}; to create a lexer called lex. 42 | // However, we cannot use Lexer lex; anymore. Usually, a constructor taking 43 | // no parameters (also called a default-constructor) is auto-generated by 44 | // the compiler, but defining your own constructor disables that. We could 45 | // define a custom default-constructor by defining a constructor that can be 46 | // called with no arguments, but we choose not to do so here. 47 | 48 | // We don't want our Lexer to be copyable: in fact, if it were easy to do, 49 | // we'd like the lexer to be the only thing that could do anything with the 50 | // input stream we gave it. Unfortunately, there's no easy way of achieving 51 | // that, but making sure we don't accidentally copy our lexer is a good 52 | // begin. 53 | // 54 | // In order to prohibit copying, we delete the copy-constructor. This is a 55 | // constructor that takes a reference to a const instance of the type it is 56 | // constructing. A deletion looks much like a declaration: 57 | Lexer(Lexer const&) = delete; 58 | // Of course, there will be no accompanying definition. 59 | 60 | // Extract and return the next token. 61 | Token extract(); 62 | 63 | /* We'd like to return position information, but how do we best do this? We 64 | * could define separate get_line and get_column member functions, but the 65 | * two are likely to be used together, so we can create another type to 66 | * store a line and a column. 67 | */ 68 | 69 | struct Position { 70 | int line, column; 71 | }; 72 | 73 | // Getting the position shouldn't change the state of our lexer. This means 74 | // that even if we have a Lexer const, we should still be able to call 75 | // get_position on it. We can allow this by adding const to the end of the 76 | // member function declaration; this is part of the function's type. The 77 | // compiler will check that we don't accidentally modify the Lexer. 78 | // 79 | // Whether you add const or not is a question much like whether to add it 80 | // when you pass something by reference. Adding it gives you more freedom 81 | // to use this function, but means the function can do less. As more 82 | // features are introduced the choice becomes harder, but for now we can 83 | // make fairly good judgements about what does and doesn't modify the lexer. 84 | Position get_position() const; 85 | 86 | // This is a conversion operator. We will use it much like we used 87 | // std::cin's conversion to bool: if it returns true, it means we can 88 | // extract at least one more token. 89 | // 90 | // The explicit here means that it will only trigger when we really want a 91 | // bool. Normally, bools can convert to ints, and so we could accidentally 92 | // end up converting our Lexer to an int. If we ever did that it would 93 | // probably be a mistake, so we don't allow it. 94 | explicit operator bool() const; 95 | 96 | /* We've now defined our external interface. However, we've not yet defined 97 | * any of the data our Lexer contains. We don't want people to rely on how 98 | * we represent our state, so we mark it as private. This means that most 99 | * functions can't access it: at the moment, only Lexer's member functions 100 | * are given access. We can expand this access by making a function a 101 | * friend, but we don't need to yet. 102 | */ 103 | 104 | // This is how we specify that all members past this point are private. If 105 | // we want to give everyone access again, we can instead say "public:". 106 | // public is the default access level for types declared as structs. We 107 | // could have used "class" instead of "struct" when defining Lexer, which 108 | // would make the default access level private. 109 | private: 110 | /* So far we've been using fairly short and non-descriptive names for our 111 | * variables. For things local to a short function, that may be acceptable: 112 | * seeing how it is defined is often enough to judge. However, once a 113 | * variable is used in many places, it pays off to give it a clearer name. 114 | * Notice that we've already been doing this with function names. 115 | */ 116 | 117 | // Remember how once a reference was bound to something, we couldn't rebind 118 | // it? A consequence of this is that we cannot assign a Lexer to another 119 | // Lexer. We can still copy it, though. 120 | std::istream& input_stream; 121 | 122 | // We can specify initial values for our members. Here it matters little, 123 | // as we could have just written them in the constructor, but this way is 124 | // easier if there are multiple constructors. 125 | Position current_position{1, 1}; 126 | 127 | /* Sometimes we want a member function to be private. In this case, we'd 128 | * like to have a few helper functions to work with the input stream for us. 129 | * All further operations will be in terms of these functions. 130 | */ 131 | 132 | // Try to get the next character in the stream: if that succeeds, assign it 133 | // to c, otherwise leave c unchanged. Return whether it succeeded. 134 | bool peek(char& c) const; 135 | 136 | // Ignore a single character. 137 | // 138 | // This is an interesting case when it comes to const. Strictly speaking, 139 | // as we have a reference to an std::istream, we could make this function 140 | // const. Intuitively, we can see that it does modify the lexer state, so 141 | // we don't mark it const. 142 | // 143 | // More formally, if two functions don't modify an instance's state, the 144 | // order in which we call them should not matter. Were ignore to be const, 145 | // we would expect that given a Lexer lex, lex.ignore(); lex.peek(c); would 146 | // give the same result as lex.peek(c); lex.ignore();. However, the two are 147 | // obviously different, so marking ignore as const would be misleading. 148 | void ignore(); 149 | 150 | // Same as peek_raw, except that the character is also dropped. 151 | bool get(char& c); 152 | 153 | // Ignore characters until a non-whitespace character is encountered. 154 | void ignore_whitespace(); 155 | 156 | /* Our old lex_* functions will be here, too, and do the same thing as 157 | * before. 158 | */ 159 | 160 | Token lex_name(); 161 | Token lex_number(); 162 | Token lex_operator(); 163 | }; 164 | 165 | // This goes well together with operator bool; it lets us do !lex. 166 | bool operator!(Lexer const& lex); 167 | 168 | /* A little more about the :: operator. It is called the scope resolution 169 | * operator, and we use it when we want to specify a name that is defined within 170 | * some other entity. We've seen that types can contain a variety of members. 171 | * When we want to say "member X of type Y", we write "Y::X". This doesn't 172 | * always make sense: for example, you can't access a member variable without an 173 | * instance. However, it does make sense for types. 174 | * 175 | * Thus, when we wish to use the Position type, which we defined inside Lexer, 176 | * we simply say Lexer::Position. 177 | */ 178 | 179 | bool operator==(Lexer::Position lhs, Lexer::Position rhs); 180 | bool operator!=(Lexer::Position lhs, Lexer::Position rhs); 181 | std::ostream& operator<<(std::ostream& os, Lexer::Position pos); 182 | 183 | /* Continue to lexer.cpp to see how we implement all this. 184 | */ 185 | 186 | #endif 187 | -------------------------------------------------------------------------------- /Chapter 17 - Member Functions/main.cpp: -------------------------------------------------------------------------------- 1 | /* Suggested reading order: main.cpp (beginning), lexer.hpp, lexer.cpp, main.cpp 2 | * (rest) 3 | * token.hpp and token.cpp have undergone minor changes: operator>> is gone, and 4 | * operator<< works as print_token did in chapter 15. 5 | * 6 | * Now let's take a closer look at how we read tokens. In practice, just being 7 | * able to extract tokens one by one often isn't good enough: we want to track 8 | * position information, look a few tokens ahead or behind, and all that without 9 | * caring about how it is implemented. 10 | * 11 | * The solution that comes to mind with the tools we have so far is to make a 12 | * type Lexer that contains all the necessary data, and then write a bunch of 13 | * functions that take it by reference and update the state. This would work, 14 | * but there's two big issues with it: 15 | * 16 | * - Having to always explicitly pass around our Lexer would get tedious. We 17 | * already have this problem with passing the std::istream& around to our lex 18 | * functions; this would make it worse. 19 | * - There's no easy way to specify what is part of the public interface and 20 | * what is an implementation detail. We may want to keep a vector of parsed 21 | * tokens, but we don't want people to write code relying on that vector; 22 | * what if we later decide to use a different data structure for it? 23 | * 24 | * We will still create a Lexer type, but we will avoid both of the above issues 25 | * using member functions. Take a look at lexer.hpp. 26 | */ 27 | 28 | #include "lexer.hpp" 29 | #include 30 | 31 | 32 | int main() try { 33 | Lexer lex{std::cin}; 34 | while (lex) { 35 | // We report the positions one past the end of tokens, not the starting 36 | // ones. There's a number of ways to change this, the simplest being to 37 | // make the position part of the token data, but it doesn't matter much 38 | // for the example. 39 | auto tok = lex.extract(); 40 | auto pos = lex.get_position(); 41 | std::cout << pos << " " << tok << "\n"; 42 | } 43 | } 44 | catch (std::exception& e) { 45 | std::cerr << "Error: " << e.what() << "\n"; 46 | return -1; 47 | } 48 | catch (...) { 49 | std::cerr << "Unknown error.\n"; 50 | return -1; 51 | } 52 | 53 | /* The main thing to take away from this chapter is that when we have a type, 54 | * there's a nice syntax for defining functions that operate on this type. We 55 | * will use member functions extensively from now on: for now they are easily 56 | * rewritten as normal functions that take an extra reference parameter, but 57 | * we will see usages where the conversion isn't quite as simple. 58 | * 59 | * Spend some time writing your own types. There's quite a few improvements 60 | * that can be made to the lexer class: the position information could be made 61 | * more precise, and we could make things more efficient. These things are not 62 | * hard to fix, but add more complexity, which is why I did not include them. 63 | * 64 | * A bigger issue is the fact that Lexer is doing a little too much: it handles 65 | * position information and token retrieval all at once. It would have been 66 | * better if the Lexer only had to implement the extraction functions, and a 67 | * separate StreamWithPosition type would deal with the rest. See if you can 68 | * come up with a neat way of splitting the two. 69 | * 70 | * By the way, as mentioned in lexer.hpp, both the struct and class keywords can 71 | * be used to define a type. I'll use "struct" and "class" interchangably when 72 | * referring to types we define; just "type" is a more generic term, as int is 73 | * also a type 74 | */ 75 | -------------------------------------------------------------------------------- /Chapter 17 - Member Functions/token.cpp: -------------------------------------------------------------------------------- 1 | #include "token.hpp" 2 | 3 | bool operator==(Token const& lhs, Token const& rhs) { 4 | return lhs.type == rhs.type && lhs.value == rhs.value; 5 | } 6 | 7 | bool operator!=(Token const& lhs, Token const& rhs) { 8 | return !(lhs == rhs); 9 | } 10 | 11 | std::ostream& operator<<(std::ostream& os, Token const& tok) { 12 | os << "{ "; 13 | 14 | if (tok.type == open_paren_token) 15 | os << "open_paren_token"; 16 | else if (tok.type == close_paren_token) 17 | os << "close_paren_token"; 18 | else if (tok.type == name_token) 19 | os << "name_token"; 20 | else if (tok.type == number_token) 21 | os << "number_token"; 22 | else if (tok.type == end_of_file_token) 23 | os << "end_of_file_token"; 24 | 25 | os << ", \"" << tok.value << "\" }"; 26 | return os; 27 | } 28 | -------------------------------------------------------------------------------- /Chapter 17 - Member Functions/token.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_17_TOKEN_HPP 2 | #define CHAPTER_17_TOKEN_HPP 3 | 4 | #include 5 | #include 6 | 7 | int const open_paren_token = 0; 8 | int const close_paren_token = 1; 9 | int const name_token = 2; 10 | int const number_token = 3; 11 | int const end_of_file_token = 4; 12 | 13 | struct Token { 14 | int type; 15 | std::string value; 16 | }; 17 | 18 | bool operator==(Token const& lhs, Token const& rhs); 19 | bool operator!=(Token const& lhs, Token const& rhs); 20 | std::ostream& operator<<(std::ostream& os, Token const& tok); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Chapter 18 - Recursion/lexer.cpp: -------------------------------------------------------------------------------- 1 | #include "lexer.hpp" 2 | #include 3 | #include 4 | 5 | bool isoperator(char c) { 6 | std::string const valid_chars = "+*-/!=<>"; 7 | return valid_chars.find(c) != std::string::npos; 8 | } 9 | 10 | Lexer::Lexer(std::istream& is) : input_stream(is) {} 11 | 12 | Token Lexer::extract() { 13 | ignore_whitespace(); 14 | 15 | char c; 16 | if (!peek(c)) 17 | return {end_of_file_token, ""}; 18 | 19 | if (std::isalpha(c)) 20 | return lex_name(); 21 | if (std::isdigit(c)) 22 | return lex_number(); 23 | if (isoperator(c)) 24 | return lex_operator(); 25 | 26 | ignore(); 27 | 28 | if (c == '(') 29 | return {open_paren_token, "("}; 30 | if (c == ')') 31 | return {close_paren_token, ")"}; 32 | 33 | throw std::runtime_error{"unrecognised character"}; 34 | } 35 | 36 | Lexer::Position Lexer::get_position() const { 37 | return current_position; 38 | } 39 | 40 | Lexer::operator bool() const { 41 | return bool(input_stream); 42 | } 43 | 44 | bool Lexer::peek(char& c) const { 45 | int x = input_stream.peek(); 46 | if (x == std::char_traits::eof()) 47 | return false; 48 | c = x; 49 | return true; 50 | } 51 | 52 | void Lexer::ignore() { 53 | char c; 54 | if (!peek(c)) 55 | throw std::logic_error{"ignoring past end of file"}; 56 | input_stream.ignore(); 57 | if (c == '\n') { 58 | current_position.line += 1; 59 | current_position.column = 1; 60 | } else { 61 | current_position.column += 1; 62 | } 63 | } 64 | 65 | bool Lexer::get(char& c) { 66 | if (!peek(c)) 67 | return false; 68 | ignore(); 69 | return true; 70 | } 71 | 72 | void Lexer::ignore_whitespace() { 73 | char c; 74 | while (peek(c) && std::isspace(c)) 75 | ignore(); 76 | } 77 | 78 | Token Lexer::lex_name() { 79 | char c; 80 | std::string name; 81 | while (peek(c) && std::isalpha(c)) { 82 | name.push_back(c); 83 | ignore(); 84 | } 85 | 86 | return {name_token, name}; 87 | } 88 | 89 | Token Lexer::lex_number() { 90 | char c; 91 | std::string number; 92 | while (peek(c) && std::isdigit(c)) { 93 | number.push_back(c); 94 | ignore(); 95 | } 96 | 97 | return {number_token, number}; 98 | } 99 | 100 | Token Lexer::lex_operator() { 101 | char c; 102 | std::string op; 103 | while (peek(c) && isoperator(c)) { 104 | op.push_back(c); 105 | ignore(); 106 | } 107 | 108 | return {name_token, op}; 109 | } 110 | 111 | bool operator!(Lexer const& lex) { 112 | return !bool(lex); 113 | } 114 | 115 | bool operator==(Lexer::Position lhs, Lexer::Position rhs) { 116 | return lhs.line == rhs.line && lhs.column == rhs.column; 117 | } 118 | 119 | bool operator!=(Lexer::Position lhs, Lexer::Position rhs) { 120 | return !(lhs == rhs); 121 | } 122 | 123 | std::ostream& operator<<(std::ostream& os, Lexer::Position pos) { 124 | os << "(line: " << pos.line << ", column:" << pos.column << ")"; 125 | return os; 126 | } 127 | -------------------------------------------------------------------------------- /Chapter 18 - Recursion/lexer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_18_LEXER_HPP 2 | #define CHAPTER_18_LEXER_HPP 3 | 4 | #include "token.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | struct Lexer { 10 | explicit Lexer(std::istream& is); 11 | Lexer(Lexer const&) = delete; 12 | 13 | Token extract(); 14 | 15 | struct Position { 16 | int line, column; 17 | }; 18 | 19 | Position get_position() const; 20 | 21 | explicit operator bool() const; 22 | 23 | private: 24 | std::istream& input_stream; 25 | 26 | Position current_position{1, 1}; 27 | 28 | bool peek(char& c) const; 29 | 30 | void ignore(); 31 | 32 | bool get(char& c); 33 | 34 | void ignore_whitespace(); 35 | 36 | Token lex_name(); 37 | Token lex_number(); 38 | Token lex_operator(); 39 | }; 40 | 41 | bool operator!(Lexer const& lex); 42 | 43 | bool operator==(Lexer::Position lhs, Lexer::Position rhs); 44 | bool operator!=(Lexer::Position lhs, Lexer::Position rhs); 45 | std::ostream& operator<<(std::ostream& os, Lexer::Position pos); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /Chapter 18 - Recursion/main.cpp: -------------------------------------------------------------------------------- 1 | /* Suggested reading order: main.cpp (beginning), parser.hpp, parser.cpp, main.cpp 2 | * (rest) 3 | * 4 | * The tokens and lexer have not changed. 5 | * 6 | * We now have a lexer, and it gives us some (though not perfect) position 7 | * information, so let's implement a simple parser on top of it. At the moment, 8 | * we don't have any tools to store the tree, so we'll start by simply 9 | * recognizing the structure and pretty-printing it back with some annotations. 10 | * To be specific, we'll take 11 | * 12 | * (+ (* 5 x) 3) 13 | * 14 | * and print 15 | * 16 | * function call: 17 | * function: 18 | * name: + 19 | * arguments: 20 | * function call: 21 | * function: 22 | * name: * 23 | * arguments: 24 | * number: 5 25 | * name: x 26 | * number: 3 27 | * 28 | * Take a look at parser.hpp to see the interface. 29 | */ 30 | 31 | #include "parser.hpp" 32 | #include 33 | #include 34 | 35 | 36 | int main() try { 37 | /* Up to now we've been reading all standard input and then writing 38 | * everything to standard output. We could do this again, but we'd much 39 | * rather be able to enter an expression on a single line and then 40 | * immediately get the pretty-printed version. 41 | */ 42 | 43 | // We need a variable to store the current line in. 44 | std::string line; 45 | 46 | // This extracts a line from std::cin into line, and then returns std::cin 47 | // so that we can check whether we've hit the end of file yet or not. 48 | while (std::getline(std::cin, line)) { 49 | // We now have a string that we would like to parse, but our parser 50 | // expects an std::istream&. Fortunately, the standard library has an 51 | // std::istringstream class that can be treated as an istream, and 52 | // allows us to store an arbitrary string in it. 53 | std::istringstream line_stream(line); 54 | 55 | // We could just call parse_and_reprint_expression, but that would 56 | // terminate the program if the expression was invalid. 57 | // 58 | // Recall the semantics of an exception. A throw indicates that the 59 | // program has entered an invalid state, and a catch sets it back into a 60 | // valid state it can continue from. If it wasn't possible to parse a 61 | // line of input, we can just notify the user of this and then continue 62 | // with the next line, so we add a catch and do exactly that. 63 | try { 64 | parse_and_reprint_expression(line_stream); 65 | } 66 | catch (std::exception& e) { 67 | // Notice that we're catching more than strictly necessary here. 68 | // We're almost at the point where this can be fixed. 69 | std::cerr << e.what() << "\n"; 70 | } 71 | } 72 | } 73 | catch (std::exception& e) { 74 | std::cerr << "Error: " << e.what() << "\n"; 75 | return -1; 76 | } 77 | catch (...) { 78 | std::cerr << "Unknown error.\n"; 79 | return -1; 80 | } 81 | 82 | /* The next chapter will introduce two important C++ features: inheritance and 83 | * pointers. We'll look at how we can represent a tree with nodes of different 84 | * types, and how we can then use virtual functions to perform operations on the 85 | * tree. 86 | * 87 | * After that, we'll go back and refactor the resulting code. I've already 88 | * mentioned many of the issues as we came across them: the longer we keep them 89 | * in, the more of a pain they will be as we continue, so we'll invest some time 90 | * into weeding them out. 91 | */ 92 | -------------------------------------------------------------------------------- /Chapter 18 - Recursion/parser.cpp: -------------------------------------------------------------------------------- 1 | #include "parser.hpp" 2 | #include "lexer.hpp" 3 | #include "token.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | // As we saw in the examples, an expression can contain another expression. 9 | // We already have a function for parsing expressions, so we may as well use 10 | // that for the subexpression, too. We can't use parse_and_reprint_expression 11 | // directly for this purpose, as we can't give it a lexer and indentation level, 12 | // so we introduce a helper function that does track that data. 13 | // 14 | // We return bool to indicate whether parsing an expression succeded. We don't 15 | // throw an exception for that, as it isn't an error situation: when we're 16 | // parsing the arguments to a function, at some point we'll run into the closing 17 | // brace, which means we have all the arguments and continue. 18 | bool p_expression(Lexer& lexer, int indent); 19 | 20 | // Print the specified number of spaces. 21 | void indent_to(int indent); 22 | 23 | // Helper functions for printing. 24 | void pretty_print_name(std::string const& name, int indent); 25 | void pretty_print_number(std::string const& number, int indent); 26 | 27 | void parse_and_reprint_expression(std::istream& input) { 28 | // Why bother with this function, if we could just let people call p_call? 29 | // Encapsulation. This function specifies only that which is really 30 | // necessary: we need an istream to read input from. Details like 31 | // indentation and the lexer are exactly that: details, and whoever is using 32 | // the parser likely won't care about them. Splitting things this way makes 33 | // the calling code easier to understand. 34 | Lexer lexer(input); 35 | 36 | if (!lexer) 37 | throw std::runtime_error{"Invalid input: stream not in good state."}; 38 | 39 | auto success = p_expression(lexer, 0); 40 | if (!success) 41 | throw std::runtime_error{"Invalid input: no expression found."}; 42 | } 43 | 44 | /* When the lexer reaches the end of file, it will emit an eof token before 45 | * going into a bad state. It would be convenient to check only for this token, 46 | * instead of also having to check the lexer state all the time. 47 | * 48 | * Unfortunately, there may be a bug in the code leading to one of these 49 | * functions being called when the eof has already been extracted. If we don't 50 | * check the state, such a bug would lead to failures when we try to use the 51 | * values, which could happen significantly later than the read itself, and 52 | * which would likely be hard to fix. 53 | * 54 | * To detect such programmer errors, we can use the standard assert facility. 55 | * The syntax is like a function call: 56 | * 57 | * assert(expression); 58 | * 59 | * If the expression evaluates to true, nothing happens. However, if the 60 | * expression is false, the program will exit and an error message will be 61 | * displayed. 62 | * 63 | * assert may look like a function, but it isn't one. There is, for example, a 64 | * way to disabling asserts, so that the statement is ignored entirely and the 65 | * expression is not evaluated at all. 66 | */ 67 | 68 | void indent_to(int indent) { 69 | for (int i = 0; i < indent; ++i) 70 | std::cout.put(' '); 71 | } 72 | 73 | void pretty_print_name(std::string const& name, int indent) { 74 | indent_to(indent); 75 | std::cout << "name: " << name << '\n'; 76 | } 77 | 78 | void pretty_print_number(std::string const& number, int indent) { 79 | indent_to(indent); 80 | std::cout << "number: " << number << '\n'; 81 | } 82 | 83 | // Parsing a function call involves quite a bit more work than other 84 | // expressions, so we make a separate function for that. 85 | void p_function_call(Lexer& lexer, int indent) { 86 | assert(lexer); 87 | 88 | indent_to(indent); 89 | std::cout << "function call:\n"; 90 | 91 | indent_to(indent+4); 92 | std::cout << "function:\n"; 93 | 94 | auto success = p_expression(lexer, indent+8); 95 | if (!success) 96 | throw std::runtime_error{"Invalid input: expected function."}; 97 | 98 | indent_to(indent+4); 99 | std::cout << "arguments: \n"; 100 | // The parsing already does everything we want, so we simply need to loop 101 | // until we have no more expressions. 102 | while (p_expression(lexer, indent+8)); 103 | } 104 | 105 | bool p_expression(Lexer& lexer, int indent) { 106 | assert(lexer); 107 | 108 | auto token = lexer.extract(); 109 | 110 | // First check for cases where there is no expression. 111 | if (token.type == end_of_file_token) 112 | throw std::runtime_error{"Invalid input: expected an expression."}; 113 | 114 | if (token.type == close_paren_token) 115 | return false; 116 | 117 | // From here on, we know that if the token type is valid, the return value 118 | // will be true. 119 | if (token.type == name_token) 120 | pretty_print_name(token.value, indent); 121 | else if (token.type == number_token) 122 | pretty_print_number(token.value, indent); 123 | else if (token.type == open_paren_token) 124 | p_function_call(lexer, indent); 125 | else 126 | throw std::logic_error{"Unrecognised token type."}; 127 | 128 | return true; 129 | } 130 | 131 | /* We've now implemented a very simple parser! Note that the way we're doing 132 | * things is hardly optimal. For one, we're mixing code does that parsing with 133 | * code that does output. This is ugly, but means we can get the parsing logic 134 | * written before we look at the features necessary to store the program that we 135 | * parsed. Take a look at main.cpp, where we use the parser. 136 | */ 137 | 138 | -------------------------------------------------------------------------------- /Chapter 18 - Recursion/parser.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_18_PARSER_HPP 2 | #define CHAPTER_18_PARSER_HPP 3 | 4 | #include 5 | 6 | /* This header can be extremely simple. Unlike in the case of a Lexer, we don't 7 | * need to give the parser observable state. Thanks to this, a simple function 8 | * will do, and so we only expose that, leaving the details to the 9 | * implementation. 10 | */ 11 | 12 | // Read an expression and re-print it in a pretty way. 13 | void parse_and_reprint_expression(std::istream& input); 14 | 15 | /* Continue to parser.cpp to see how we implement this. 16 | */ 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /Chapter 18 - Recursion/token.cpp: -------------------------------------------------------------------------------- 1 | #include "token.hpp" 2 | 3 | bool operator==(Token const& lhs, Token const& rhs) { 4 | return lhs.type == rhs.type && lhs.value == rhs.value; 5 | } 6 | 7 | bool operator!=(Token const& lhs, Token const& rhs) { 8 | return !(lhs == rhs); 9 | } 10 | 11 | std::ostream& operator<<(std::ostream& os, Token const& tok) { 12 | os << "{ "; 13 | 14 | if (tok.type == open_paren_token) 15 | os << "open_paren_token"; 16 | else if (tok.type == close_paren_token) 17 | os << "close_paren_token"; 18 | else if (tok.type == name_token) 19 | os << "name_token"; 20 | else if (tok.type == number_token) 21 | os << "number_token"; 22 | else if (tok.type == end_of_file_token) 23 | os << "end_of_file_token"; 24 | 25 | os << ", \"" << tok.value << "\" }"; 26 | return os; 27 | } 28 | -------------------------------------------------------------------------------- /Chapter 18 - Recursion/token.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_18_TOKEN_HPP 2 | #define CHAPTER_18_TOKEN_HPP 3 | 4 | #include 5 | #include 6 | 7 | int const open_paren_token = 0; 8 | int const close_paren_token = 1; 9 | int const name_token = 2; 10 | int const number_token = 3; 11 | int const end_of_file_token = 4; 12 | 13 | struct Token { 14 | int type; 15 | std::string value; 16 | }; 17 | 18 | bool operator==(Token const& lhs, Token const& rhs); 19 | bool operator!=(Token const& lhs, Token const& rhs); 20 | std::ostream& operator<<(std::ostream& os, Token const& tok); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/expression.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_19_EXPRESSION_HPP 2 | #define CHAPTER_19_EXPRESSION_HPP 3 | 4 | #include 5 | 6 | // The basic syntax for defining the expression class is the same. 7 | struct Expression { 8 | // There's two things here that we haven't seen before. 9 | // 10 | // First of all, the function is prefixed with virtual. This means that the 11 | // function may be overriden by a derived class. If we have a NumberExpr 12 | // class that derives from Expression, and a reference to Expression that 13 | // actually refers to a NumberExpr object, a lookup will be done at runtime, 14 | // and the override defined in NumberExpr will be called instead. Had the 15 | // function not been virtual, the print defined in Expression would be 16 | // called. 17 | // 18 | // Secondly, we have the = 0 at the end. This means that print is a pure 19 | // virtual function: it does not have to be defined in Expression. On the 20 | // other hand, it also means that we cannot create an instance of 21 | // Expression. If we want to create an Expression, we have to make a 22 | // derived type, define an override for print, and then create an instance 23 | // of that. 24 | // 25 | // How is Expression still useful? We can still make references to it. 26 | // Take a look at the operator<< defined below for an example. 27 | virtual void print(std::ostream& out) const = 0; 28 | 29 | // One of the things we'd like to be able to do with Expressions is to 30 | // destroy them; if we kept them around forever, we'd be using much more 31 | // memory than we really need. Sometimes, we'll want to destroy an 32 | // Expression without knowing what kind of Expression it is. 33 | // 34 | // Whenever an object is destroyed, the destructor is run. If we want to be 35 | // able to destroy objects as above, we must make the destructor virtual. 36 | // In this case, that's the only thing we need to change, so we can give it 37 | // the default implementation. 38 | virtual ~Expression() = default; 39 | }; 40 | 41 | // Let's take a look at an example use of Expression. We know that expr is 42 | // going to be a reference to an object of some derived type. We also know that 43 | // that type must implement print for there to be an instance of it. We can 44 | // thus conclude that this is safe, but we can't know which version will be 45 | // called until runtime -- and that's by design. 46 | inline std::ostream& operator<<(std::ostream& os, Expression const& expr) { 47 | expr.print(os); 48 | return os; 49 | } 50 | 51 | /* Go on to number_expr.hpp. 52 | */ 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/lexer.cpp: -------------------------------------------------------------------------------- 1 | #include "lexer.hpp" 2 | #include 3 | #include 4 | 5 | bool isoperator(char c) { 6 | std::string const valid_chars = "+*-/!=<>"; 7 | return valid_chars.find(c) != std::string::npos; 8 | } 9 | 10 | Lexer::Lexer(std::istream& is) : input_stream(is) {} 11 | 12 | Token Lexer::extract() { 13 | ignore_whitespace(); 14 | 15 | char c; 16 | if (!peek(c)) 17 | return {end_of_file_token, ""}; 18 | 19 | if (std::isalpha(c)) 20 | return lex_name(); 21 | if (std::isdigit(c)) 22 | return lex_number(); 23 | if (isoperator(c)) 24 | return lex_operator(); 25 | 26 | ignore(); 27 | 28 | if (c == '(') 29 | return {open_paren_token, "("}; 30 | if (c == ')') 31 | return {close_paren_token, ")"}; 32 | 33 | throw std::runtime_error{"unrecognised character"}; 34 | } 35 | 36 | Lexer::Position Lexer::get_position() const { 37 | return current_position; 38 | } 39 | 40 | Lexer::operator bool() const { 41 | return bool(input_stream); 42 | } 43 | 44 | bool Lexer::peek(char& c) const { 45 | int x = input_stream.peek(); 46 | if (x == std::char_traits::eof()) 47 | return false; 48 | c = x; 49 | return true; 50 | } 51 | 52 | void Lexer::ignore() { 53 | char c; 54 | if (!peek(c)) 55 | throw std::logic_error{"ignoring past end of file"}; 56 | input_stream.ignore(); 57 | if (c == '\n') { 58 | current_position.line += 1; 59 | current_position.column = 1; 60 | } else { 61 | current_position.column += 1; 62 | } 63 | } 64 | 65 | bool Lexer::get(char& c) { 66 | if (!peek(c)) 67 | return false; 68 | ignore(); 69 | return true; 70 | } 71 | 72 | void Lexer::ignore_whitespace() { 73 | char c; 74 | while (peek(c) && std::isspace(c)) 75 | ignore(); 76 | } 77 | 78 | Token Lexer::lex_name() { 79 | char c; 80 | std::string name; 81 | while (peek(c) && std::isalpha(c)) { 82 | name.push_back(c); 83 | ignore(); 84 | } 85 | 86 | return {name_token, name}; 87 | } 88 | 89 | Token Lexer::lex_number() { 90 | char c; 91 | std::string number; 92 | while (peek(c) && std::isdigit(c)) { 93 | number.push_back(c); 94 | ignore(); 95 | } 96 | 97 | return {number_token, number}; 98 | } 99 | 100 | Token Lexer::lex_operator() { 101 | char c; 102 | std::string op; 103 | while (peek(c) && isoperator(c)) { 104 | op.push_back(c); 105 | ignore(); 106 | } 107 | 108 | return {name_token, op}; 109 | } 110 | 111 | bool operator!(Lexer const& lex) { 112 | return !bool(lex); 113 | } 114 | 115 | bool operator==(Lexer::Position lhs, Lexer::Position rhs) { 116 | return lhs.line == rhs.line && lhs.column == rhs.column; 117 | } 118 | 119 | bool operator!=(Lexer::Position lhs, Lexer::Position rhs) { 120 | return !(lhs == rhs); 121 | } 122 | 123 | std::ostream& operator<<(std::ostream& os, Lexer::Position pos) { 124 | os << "(line: " << pos.line << ", column:" << pos.column << ")"; 125 | return os; 126 | } 127 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/lexer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_19_LEXER_HPP 2 | #define CHAPTER_19_LEXER_HPP 3 | 4 | #include "token.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | struct Lexer { 10 | explicit Lexer(std::istream& is); 11 | Lexer(Lexer const&) = delete; 12 | 13 | Token extract(); 14 | 15 | struct Position { 16 | int line, column; 17 | }; 18 | 19 | Position get_position() const; 20 | 21 | explicit operator bool() const; 22 | 23 | private: 24 | std::istream& input_stream; 25 | 26 | Position current_position{1, 1}; 27 | 28 | bool peek(char& c) const; 29 | 30 | void ignore(); 31 | 32 | bool get(char& c); 33 | 34 | void ignore_whitespace(); 35 | 36 | Token lex_name(); 37 | Token lex_number(); 38 | Token lex_operator(); 39 | }; 40 | 41 | bool operator!(Lexer const& lex); 42 | 43 | bool operator==(Lexer::Position lhs, Lexer::Position rhs); 44 | bool operator!=(Lexer::Position lhs, Lexer::Position rhs); 45 | std::ostream& operator<<(std::ostream& os, Lexer::Position pos); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/list_expr.cpp: -------------------------------------------------------------------------------- 1 | #include "list_expr.hpp" 2 | 3 | void ListExpr::add(std::shared_ptr expr) { 4 | elements.push_back(expr); 5 | } 6 | 7 | void ListExpr::print(std::ostream& out) const { 8 | // Ignoring the extra trouble done to not print the trailing space, this is 9 | // surprisingly straightforward code. We don't need to check the types of 10 | // anything; we can simply use the virtual print function and it'll do the 11 | // right thing depending on the type of the element. 12 | out << '('; 13 | bool first = true; 14 | for (auto const& e : elements) { 15 | if (first) 16 | first = false; 17 | else 18 | out << ' '; 19 | e->print(out); 20 | } 21 | out << ')'; 22 | } 23 | 24 | /* Now let's take a look at how it all fits together in main.cpp. 25 | */ 26 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/list_expr.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_19_LIST_EXPR_HPP 2 | #define CHAPTER_19_LIST_EXPR_HPP 3 | 4 | #include "expression.hpp" 5 | #include 6 | #include 7 | 8 | /* We'd like our ListExpr to store a vector of expressions. However, a vector 9 | * stores instances of the type it contains, and we want can't make an instance 10 | * of Expression -- nor do we want to, for that matter. We'd like to be able to 11 | * put NumberExprs, VariableExprs, and ListExprs into whatever data structure we 12 | * use. 13 | * 14 | * In order to do this we introduce pointers. We won't store the object itself: 15 | * instead we'll create an object somewhere and then store a pointer to it in 16 | * the vector. You could see such a pointer as an iterator to a collection that 17 | * only has one element: it isn't the object itself, but we can access the 18 | * object through it. Alternatively, you can see the pointer as a reference 19 | * which we can reassign at will. 20 | * 21 | * An important question that comes up is how we'll make sure the object we 22 | * create exists as long as we want it to, and no longer. If we make a local 23 | * and then store a pointer to it, it will be destroyed once we exit the 24 | * function, and the pointer will be invalid. On the other hand, we somehow 25 | * have to make sure that once we no longer have a use for the object, it gets 26 | * cleaned up. 27 | * 28 | * First, though, let's take a look at how to create an object. For this 29 | * purpose we will use the std::make_shared function template. For example, 30 | * 31 | * std::make_shared(x) 32 | * 33 | * This will construct a NumberExpr with value x, and then return a pointer to 34 | * the object. The pointer will have type std::shared_ptr, which 35 | * will be convertible to std::shared_ptr. 36 | * 37 | * Apart from creating our object, make_shared will also create a counter for 38 | * the number of shared_ptrs that point to it. Every time we copy a pointer 39 | * that counter will be incremented, while every time we destroy one it will be 40 | * decremented. Once the counter reaches zero, our object will be destroyed; 41 | * this is what we want, as we don't want an object we can't access lying 42 | * around. 43 | * 44 | * Note that there are cases when objects become unreachable, but are kept 45 | * alive: if A has a shared_ptr to B, and B has a shared_ptr to A, neither will 46 | * be destroyed, even if the rest of our program cannot access them. 47 | * 48 | * Also note that seeing as shared_ptr handles the destruction of the object, 49 | * the object has to have been created in a way that is compatible with how it 50 | * destroys things. This won't be a problem for us yet, as make_shared is the 51 | * only way we know to make pointers, but it's worth keeping in mind if you 52 | * experiment. 53 | */ 54 | 55 | class ListExpr : public Expression { 56 | // This may look strange, but is simply a vector of pointers to Expressions. 57 | std::vector> elements; 58 | 59 | public: 60 | // We'll want to add expressions to the vector one by one. 61 | void add(std::shared_ptr expr); 62 | 63 | void print(std::ostream& out) const override; 64 | }; 65 | 66 | /* Now that we can represent the tree, let's make the parser create it. Go on 67 | * to parser.hpp. 68 | */ 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/main.cpp: -------------------------------------------------------------------------------- 1 | /* Suggested reading order: main.cpp (beginning), expression.hpp, 2 | * number_expr.hpp, variable_expr.hpp, list_expr.hpp, parser.hpp, parser.cpp, 3 | * number.cpp, variable.cpp, call.cpp, main.cpp (rest) 4 | * 5 | * We've already seen that every object in C++ must have a type. We'd like to 6 | * make an Expression type that can represent any kind of expression we can 7 | * parse. However, there's no single representation of an Expression: it might 8 | * be a number, or a variable name, or a list of other expressions. We could 9 | * go the same route as we went with Token, adding an int to keep track of what 10 | * kind of expression we're working with; while this would work, it would 11 | * involve reserving space for all three possibilities, and we'd have to be 12 | * extra careful about which possibility we are currently in. 13 | * 14 | * Instead, we'll have the Expression type which will only define what must be 15 | * possible to do with an expression; it won't define how that expression is 16 | * represented, or how the operations are implemented. Each of the three kinds 17 | * of expressions we saw will then get their own, distinct type, but will 18 | * inherit from the common Expression type. The compiler will enforce that they 19 | * define the operations we left undefined in Expression, and we will be able to 20 | * treat them as if they were Expressions. In particular, we will be able to 21 | * make a reference to an Expression that actually refers to an instance of one 22 | * of the derived types. 23 | * 24 | * Take a look at expression.hpp to see how it is implemented. 25 | */ 26 | 27 | #include "parser.hpp" 28 | #include 29 | #include 30 | 31 | 32 | int main() try { 33 | std::string line; 34 | while (std::getline(std::cin, line)) { 35 | std::istringstream line_stream(line); 36 | 37 | try { 38 | auto ptr = parse_expression(line_stream); 39 | // Recall the operator<< overload in expression.hpp; thanks to that, 40 | // we can put this here. 41 | std::cout << *ptr << '\n'; 42 | } 43 | catch (std::exception& e) { 44 | std::cerr << e.what() << "\n"; 45 | } 46 | } 47 | } 48 | catch (std::exception& e) { 49 | std::cerr << "Error: " << e.what() << "\n"; 50 | return -1; 51 | } 52 | catch (...) { 53 | std::cerr << "Unknown error.\n"; 54 | return -1; 55 | } 56 | 57 | /* And that's our parser! Note that the representation we chose for our 58 | * expressions is neither the only possible nor necessarily the best one; it is 59 | * simple and illustrates some important concepts, which is why I chose it for 60 | * the moment. 61 | * 62 | * Even given this representation, there's often better ways of doing things. 63 | * For one example, take a look at 64 | * http://channel9.msdn.com/Events/GoingNative/2013/Inheritance-Is-The-Base-Class-of-Evil 65 | * 66 | */ 67 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/number_expr.cpp: -------------------------------------------------------------------------------- 1 | #include "number_expr.hpp" 2 | 3 | NumberExpr::NumberExpr(int value) : value(value) {} 4 | 5 | // Implementing an override looks just like implementing a normal member function. 6 | void NumberExpr::print(std::ostream& out) const { 7 | out << value; 8 | } 9 | 10 | /* variable_expr.cpp looks exactly the same, except for names, so feel free to 11 | * skip to list_expr.cpp. 12 | */ 13 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/number_expr.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_19_NUMBER_EXPR_HPP 2 | #define CHAPTER_19_NUMBER_EXPR_HPP 3 | 4 | #include "expression.hpp" 5 | 6 | // This is the syntax for defining NumberExpr as a derived class of Expression. 7 | // The public means that everyone can see this relationship: had we put private, 8 | // then only NumberExpr would know that it derives from Expression. 9 | class NumberExpr : public Expression { 10 | // The representation of a NumberExpr is simple: we just store the number. 11 | int value; 12 | 13 | public: 14 | // We require that a NumberExpr be created with a value. Adding this 15 | // constructor means that a default one is not auto-generated, but this is 16 | // okay: we want every NumberExpr to have a value, anyway. 17 | NumberExpr(int value); 18 | 19 | // We want to make instances of NumberExpr, so we have to override print. 20 | // By using the override keyword we tell the compiler that we really do want 21 | // an override, not an overload; if there was no function in Expression that 22 | // our print overrides, we would get a compiler error. 23 | // 24 | // Notice that we don't specify virtual here. We could if we wanted, and it 25 | // would not change anything: as the base class made it virtual, it will 26 | // automatically be virtual in all derived classes. 27 | void print(std::ostream& out) const override; 28 | }; 29 | 30 | /* variable_expr.hpp is next, and almost the same as this file. 31 | */ 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/parser.cpp: -------------------------------------------------------------------------------- 1 | #include "parser.hpp" 2 | #include "lexer.hpp" 3 | #include "token.hpp" 4 | #include "expression.hpp" 5 | #include "number_expr.hpp" 6 | #include "variable_expr.hpp" 7 | #include "list_expr.hpp" 8 | #include 9 | #include 10 | #include 11 | 12 | // Here too we'll return the parsed expression. Note that we aren't returning a 13 | // bool. Instead, we'll use the fact that we can have a pointer pointing to 14 | // nothing. p_expression will return a pointer to the parsed expression if 15 | // there is one, or a so-called null pointer if there isn't. 16 | std::shared_ptr p_expression(Lexer& lexer); 17 | 18 | std::shared_ptr parse_expression(std::istream& input) { 19 | Lexer lexer(input); 20 | 21 | if (!lexer) 22 | throw std::runtime_error{"Invalid input: stream not in good state."}; 23 | 24 | auto expr = p_expression(lexer); 25 | if (!expr) 26 | throw std::runtime_error{"Invalid input: no expression found."}; 27 | 28 | return expr; 29 | } 30 | 31 | std::shared_ptr p_function_call(Lexer& lexer) { 32 | assert(lexer); 33 | 34 | auto list = std::make_shared(); 35 | 36 | while (auto ptr = p_expression(lexer)) 37 | list->add(ptr); 38 | 39 | return list; 40 | } 41 | 42 | std::shared_ptr p_expression(Lexer& lexer) { 43 | assert(lexer); 44 | 45 | auto token = lexer.extract(); 46 | 47 | if (token.type == end_of_file_token) 48 | throw std::runtime_error{"Invalid input: expected an expression."}; 49 | 50 | if (token.type == close_paren_token) 51 | return {}; 52 | 53 | if (token.type == name_token) 54 | return std::make_shared(token.value); 55 | if (token.type == number_token) 56 | return std::make_shared(std::stoi(token.value)); 57 | if (token.type == open_paren_token) 58 | return p_function_call(lexer); 59 | 60 | throw std::logic_error{"Unrecognised token type."}; 61 | } 62 | 63 | /* Notice how much simpler the parsing code became now that we don't have to 64 | * worry about printing at the same time. 65 | * 66 | * With this out of the way, let's implement the printing. We'll go for a 67 | * simpler format than in the previous chapter; feel free to expand it as an 68 | * exercises. 69 | * 70 | * Next is number_expr.cpp. 71 | */ 72 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/parser.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_19_PARSER_HPP 2 | #define CHAPTER_19_PARSER_HPP 3 | 4 | #include "expression.hpp" 5 | #include 6 | #include 7 | 8 | // Our function no longer does any printing, but it does need to return a 9 | // pointer to the expression we parsed. 10 | std::shared_ptr parse_expression(std::istream& input); 11 | 12 | /* Go on to parser.cpp. 13 | */ 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/token.cpp: -------------------------------------------------------------------------------- 1 | #include "token.hpp" 2 | 3 | bool operator==(Token const& lhs, Token const& rhs) { 4 | return lhs.type == rhs.type && lhs.value == rhs.value; 5 | } 6 | 7 | bool operator!=(Token const& lhs, Token const& rhs) { 8 | return !(lhs == rhs); 9 | } 10 | 11 | std::ostream& operator<<(std::ostream& os, Token const& tok) { 12 | os << "{ "; 13 | 14 | if (tok.type == open_paren_token) 15 | os << "open_paren_token"; 16 | else if (tok.type == close_paren_token) 17 | os << "close_paren_token"; 18 | else if (tok.type == name_token) 19 | os << "name_token"; 20 | else if (tok.type == number_token) 21 | os << "number_token"; 22 | else if (tok.type == end_of_file_token) 23 | os << "end_of_file_token"; 24 | 25 | os << ", \"" << tok.value << "\" }"; 26 | return os; 27 | } 28 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/token.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_19_TOKEN_HPP 2 | #define CHAPTER_19_TOKEN_HPP 3 | 4 | #include 5 | #include 6 | 7 | int const open_paren_token = 0; 8 | int const close_paren_token = 1; 9 | int const name_token = 2; 10 | int const number_token = 3; 11 | int const end_of_file_token = 4; 12 | 13 | struct Token { 14 | int type; 15 | std::string value; 16 | }; 17 | 18 | bool operator==(Token const& lhs, Token const& rhs); 19 | bool operator!=(Token const& lhs, Token const& rhs); 20 | std::ostream& operator<<(std::ostream& os, Token const& tok); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/variable_expr.cpp: -------------------------------------------------------------------------------- 1 | #include "variable_expr.hpp" 2 | 3 | VariableExpr::VariableExpr(std::string const& name) : name(name) {} 4 | 5 | void VariableExpr::print(std::ostream& out) const { 6 | out << name; 7 | } 8 | -------------------------------------------------------------------------------- /Chapter 19 - Inheritance/variable_expr.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_19_VARIABLE_EXPR_HPP 2 | #define CHAPTER_19_VARIABLE_EXPR_HPP 3 | 4 | #include "expression.hpp" 5 | #include 6 | 7 | class VariableExpr : public Expression { 8 | std::string name; 9 | 10 | public: 11 | VariableExpr(std::string const& name); 12 | 13 | void print(std::ostream& out) const override; 14 | }; 15 | 16 | /* Now take a look at list_expr.hpp, which is significantly more interesting. 17 | */ 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/builtin_operations.cpp: -------------------------------------------------------------------------------- 1 | #include "builtin_operations.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* We can implement the built-in functions in "normal" C++; we don't have to 8 | * think about the language we're implementing for this purpose. We'll just 9 | * 10 | */ 11 | 12 | int builtin_add(std::vector const& args) { 13 | return std::accumulate(std::begin(args), std::end(args), 0); 14 | } 15 | 16 | int builtin_subtract(std::vector const& args) { 17 | if (args.size() == 1) 18 | return -args[0]; 19 | if (args.size() == 2) 20 | return args[0] - args[1]; 21 | throw std::runtime_error{"incorrect number of args to builtin_subtract"}; 22 | } 23 | 24 | int builtin_multiply(std::vector const& args) { 25 | return std::accumulate(std::begin(args), std::end(args), 1, std::multiplies{}); 26 | } 27 | 28 | int builtin_divide(std::vector const& args) { 29 | if (args.size() != 2) 30 | throw std::runtime_error{"incorrect number of args to builtin_divide"}; 31 | if (args[1] == 0) 32 | throw std::runtime_error{"division by zero"}; 33 | return args[0] / args[1]; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/builtin_operations.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_20_BUILTIN_OPERATIONS_HPP 2 | #define CHAPTER_20_BUILTIN_OPERATIONS_HPP 3 | 4 | #include 5 | 6 | int builtin_add(std::vector const& args); 7 | int builtin_subtract(std::vector const& args); 8 | int builtin_multiply(std::vector const& args); 9 | int builtin_divide(std::vector const& args); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/expression.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_20_EXPRESSION_HPP 2 | #define CHAPTER_20_EXPRESSION_HPP 3 | 4 | #include "symbol_table.hpp" 5 | #include 6 | 7 | struct Expression { 8 | virtual void print(std::ostream& out) const = 0; 9 | 10 | virtual int evaluate(SymbolTable const& symbol_table) const = 0; 11 | 12 | virtual ~Expression() = default; 13 | }; 14 | 15 | inline std::ostream& operator<<(std::ostream& os, Expression const& expr) { 16 | expr.print(os); 17 | return os; 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/lexer.cpp: -------------------------------------------------------------------------------- 1 | #include "lexer.hpp" 2 | #include 3 | #include 4 | 5 | bool isoperator(char c) { 6 | std::string const valid_chars = "+*-/!=<>"; 7 | return valid_chars.find(c) != std::string::npos; 8 | } 9 | 10 | Lexer::Lexer(std::istream& is) : input_stream(is) {} 11 | 12 | Token Lexer::extract() { 13 | ignore_whitespace(); 14 | 15 | char c; 16 | if (!peek(c)) 17 | return {end_of_file_token, ""}; 18 | 19 | if (std::isalpha(c)) 20 | return lex_name(); 21 | if (std::isdigit(c)) 22 | return lex_number(); 23 | if (isoperator(c)) 24 | return lex_operator(); 25 | 26 | ignore(); 27 | 28 | if (c == '(') 29 | return {open_paren_token, "("}; 30 | if (c == ')') 31 | return {close_paren_token, ")"}; 32 | 33 | throw std::runtime_error{"unrecognised character"}; 34 | } 35 | 36 | Lexer::Position Lexer::get_position() const { 37 | return current_position; 38 | } 39 | 40 | Lexer::operator bool() const { 41 | return bool(input_stream); 42 | } 43 | 44 | bool Lexer::peek(char& c) const { 45 | int x = input_stream.peek(); 46 | if (x == std::char_traits::eof()) 47 | return false; 48 | c = x; 49 | return true; 50 | } 51 | 52 | void Lexer::ignore() { 53 | char c; 54 | if (!peek(c)) 55 | throw std::logic_error{"ignoring past end of file"}; 56 | input_stream.ignore(); 57 | if (c == '\n') { 58 | current_position.line += 1; 59 | current_position.column = 1; 60 | } else { 61 | current_position.column += 1; 62 | } 63 | } 64 | 65 | bool Lexer::get(char& c) { 66 | if (!peek(c)) 67 | return false; 68 | ignore(); 69 | return true; 70 | } 71 | 72 | void Lexer::ignore_whitespace() { 73 | char c; 74 | while (peek(c) && std::isspace(c)) 75 | ignore(); 76 | } 77 | 78 | Token Lexer::lex_name() { 79 | char c; 80 | std::string name; 81 | while (peek(c) && std::isalpha(c)) { 82 | name.push_back(c); 83 | ignore(); 84 | } 85 | 86 | return {name_token, name}; 87 | } 88 | 89 | Token Lexer::lex_number() { 90 | char c; 91 | std::string number; 92 | while (peek(c) && std::isdigit(c)) { 93 | number.push_back(c); 94 | ignore(); 95 | } 96 | 97 | return {number_token, number}; 98 | } 99 | 100 | Token Lexer::lex_operator() { 101 | char c; 102 | std::string op; 103 | while (peek(c) && isoperator(c)) { 104 | op.push_back(c); 105 | ignore(); 106 | } 107 | 108 | return {name_token, op}; 109 | } 110 | 111 | bool operator!(Lexer const& lex) { 112 | return !bool(lex); 113 | } 114 | 115 | bool operator==(Lexer::Position lhs, Lexer::Position rhs) { 116 | return lhs.line == rhs.line && lhs.column == rhs.column; 117 | } 118 | 119 | bool operator!=(Lexer::Position lhs, Lexer::Position rhs) { 120 | return !(lhs == rhs); 121 | } 122 | 123 | std::ostream& operator<<(std::ostream& os, Lexer::Position pos) { 124 | os << "(line: " << pos.line << ", column:" << pos.column << ")"; 125 | return os; 126 | } 127 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/lexer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_20_LEXER_HPP 2 | #define CHAPTER_20_LEXER_HPP 3 | 4 | #include "token.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | struct Lexer { 10 | explicit Lexer(std::istream& is); 11 | Lexer(Lexer const&) = delete; 12 | 13 | Token extract(); 14 | 15 | struct Position { 16 | int line, column; 17 | }; 18 | 19 | Position get_position() const; 20 | 21 | explicit operator bool() const; 22 | 23 | private: 24 | std::istream& input_stream; 25 | 26 | Position current_position{1, 1}; 27 | 28 | bool peek(char& c) const; 29 | 30 | void ignore(); 31 | 32 | bool get(char& c); 33 | 34 | void ignore_whitespace(); 35 | 36 | Token lex_name(); 37 | Token lex_number(); 38 | Token lex_operator(); 39 | }; 40 | 41 | bool operator!(Lexer const& lex); 42 | 43 | bool operator==(Lexer::Position lhs, Lexer::Position rhs); 44 | bool operator!=(Lexer::Position lhs, Lexer::Position rhs); 45 | std::ostream& operator<<(std::ostream& os, Lexer::Position pos); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/list_expr.cpp: -------------------------------------------------------------------------------- 1 | #include "list_expr.hpp" 2 | #include "variable_expr.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | void ListExpr::add(std::shared_ptr expr) { 8 | elements.push_back(expr); 9 | } 10 | 11 | void ListExpr::print(std::ostream& out) const { 12 | out << '('; 13 | bool first = true; 14 | for (auto const& e : elements) { 15 | if (first) 16 | first = false; 17 | else 18 | out << ' '; 19 | e->print(out); 20 | } 21 | out << ')'; 22 | } 23 | 24 | int ListExpr::evaluate(SymbolTable const& symbol_table) const { 25 | if (elements.empty()) 26 | throw std::runtime_error{"evaluating empty list"}; 27 | std::vector args; 28 | // TODO: Comment on how this works. 29 | std::transform(std::begin(elements) + 1, std::end(elements), std::back_inserter(args), [&](std::shared_ptr expr) { 30 | return expr->evaluate(symbol_table); 31 | }); 32 | 33 | auto& function_name = dynamic_cast(*elements[0]); 34 | auto& function = symbol_table.at(function_name.get_name()); 35 | return function(args); 36 | } 37 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/list_expr.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_20_LIST_EXPR_HPP 2 | #define CHAPTER_20_LIST_EXPR_HPP 3 | 4 | #include "expression.hpp" 5 | #include 6 | #include 7 | 8 | class ListExpr : public Expression { 9 | std::vector> elements; 10 | 11 | public: 12 | void add(std::shared_ptr expr); 13 | 14 | void print(std::ostream& out) const override; 15 | 16 | int evaluate(SymbolTable const& symbol_table) const override; 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/main.cpp: -------------------------------------------------------------------------------- 1 | /* Suggested reading order: ... 2 | * 3 | * Now that we know how to print expressions, it's not a big step to evaluate 4 | * arithmetic expressions and print their results. We'll do this by adding a 5 | * second virtual member function to Expression which will perform whatever 6 | * operation we want it to and then return the result as an int. 7 | * 8 | * What work has to be done to evaluate an expression depends on its form. If 9 | * we have a number, we can simply return it as-is; it's already as evaluated as 10 | * it can be. If we have a list, we want to treat it as function application. 11 | * Suppose we wanted to evaluate 12 | * 13 | * (+ 1 (* 2 3)) 14 | * 15 | * We would like this to evaluate as 1 + (2 * 3); that is, to 7. We do this by 16 | * evaluating all but the first element of the list, and then using the first 17 | * element to decide what operation should be performed. In pseudocode: 18 | * 19 | * eval (f arg1 arg2 arg3...) == f (eval arg1) (eval arg2) (eval arg3)... 20 | * 21 | * What do we do when we encounter a variable name? At the moment, our system 22 | * is too simple to handle this case, so we'll throw an exception. 23 | * 24 | * Note that the first element of each list is special, in that it does not get 25 | * evaluated. Instead, we will assume that it is a variable and use the value 26 | * of that to choose what operation to perform. We'll need a way of mapping 27 | * strings to functions. 28 | * 29 | * [Note: std::maps were not yet covered, but this is an oversight. A chapter 30 | * will be added to the beginning to introduce them properly. The five-minute 31 | * explanation is that a map is a container which associates keys to values. 32 | * That means that an object of type std::map will let you look up 33 | * what Value corresponds to some Key easily. For example, we can have a map 34 | * from strings to ints: 35 | * 36 | * std::map map; 37 | * map["hello"] = 5; // automatically creates a "hello" element 38 | * std::cout << map["hello"] << '\n'; // prints 5 39 | * 40 | * If the element is not yet in the map, it will be constructed and initialized. 41 | * That means that for class types the constructor will be run, and for built-in 42 | * types they will get their corresponding zero value.] 43 | * 44 | * We don't want to restrict the number of elements in the list, so our 45 | * functions should take a vector of arguments. We're only working on ints at 46 | * the moment, so the function will look as follows: 47 | * 48 | * int builtin_add(std::vector const& args); 49 | * 50 | * The type of the above is int(std::vector const&). The C++ standard 51 | * library provides the std::function template which we can use to store 52 | * anything that can be used like a function with a certain type. This means 53 | * we can represent each of our operations as an object of type 54 | * 55 | * std::function const&)> 56 | * 57 | * We can then represent the whole symbol table as an object of type 58 | * 59 | * std::map const&)>> 60 | * 61 | * A bit of a mouthful, but this structure will come in useful often in the 62 | * future. 63 | * 64 | */ 65 | 66 | #include "parser.hpp" 67 | #include "builtin_operations.hpp" 68 | #include "symbol_table.hpp" 69 | #include 70 | #include 71 | #include 72 | 73 | 74 | int main() try { 75 | auto symbol_table = get_default_symbol_table(); 76 | std::string line; 77 | while (std::getline(std::cin, line)) { 78 | std::istringstream line_stream(line); 79 | 80 | try { 81 | auto ptr = parse_expression(line_stream); 82 | std::cout << ptr->evaluate(symbol_table) << '\n'; 83 | } 84 | catch (std::exception& e) { 85 | std::cerr << e.what() << "\n"; 86 | } 87 | } 88 | } 89 | catch (std::exception& e) { 90 | std::cerr << "Error: " << e.what() << "\n"; 91 | return -1; 92 | } 93 | catch (...) { 94 | std::cerr << "Unknown error.\n"; 95 | return -1; 96 | } 97 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/number_expr.cpp: -------------------------------------------------------------------------------- 1 | #include "number_expr.hpp" 2 | 3 | NumberExpr::NumberExpr(int value) : value(value) {} 4 | 5 | void NumberExpr::print(std::ostream& out) const { 6 | out << value; 7 | } 8 | 9 | int NumberExpr::evaluate(SymbolTable const&) const { 10 | return value; 11 | } 12 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/number_expr.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_20_NUMBER_EXPR_HPP 2 | #define CHAPTER_20_NUMBER_EXPR_HPP 3 | 4 | #include "expression.hpp" 5 | 6 | class NumberExpr : public Expression { 7 | int value; 8 | 9 | public: 10 | NumberExpr(int value); 11 | 12 | void print(std::ostream& out) const override; 13 | 14 | int evaluate(SymbolTable const& symbol_table) const override; 15 | }; 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/parser.cpp: -------------------------------------------------------------------------------- 1 | #include "parser.hpp" 2 | #include "lexer.hpp" 3 | #include "token.hpp" 4 | #include "expression.hpp" 5 | #include "number_expr.hpp" 6 | #include "variable_expr.hpp" 7 | #include "list_expr.hpp" 8 | #include 9 | #include 10 | #include 11 | 12 | std::shared_ptr p_expression(Lexer& lexer); 13 | 14 | std::shared_ptr parse_expression(std::istream& input) { 15 | Lexer lexer(input); 16 | 17 | if (!lexer) 18 | throw std::runtime_error{"Invalid input: stream not in good state."}; 19 | 20 | auto expr = p_expression(lexer); 21 | if (!expr) 22 | throw std::runtime_error{"Invalid input: no expression found."}; 23 | 24 | return expr; 25 | } 26 | 27 | std::shared_ptr p_function_call(Lexer& lexer) { 28 | assert(lexer); 29 | 30 | auto list = std::make_shared(); 31 | 32 | while (auto ptr = p_expression(lexer)) 33 | list->add(ptr); 34 | 35 | return list; 36 | } 37 | 38 | std::shared_ptr p_expression(Lexer& lexer) { 39 | assert(lexer); 40 | 41 | auto token = lexer.extract(); 42 | 43 | if (token.type == end_of_file_token) 44 | throw std::runtime_error{"Invalid input: expected an expression."}; 45 | 46 | if (token.type == close_paren_token) 47 | return {}; 48 | 49 | if (token.type == name_token) 50 | return std::make_shared(token.value); 51 | if (token.type == number_token) 52 | return std::make_shared(std::stoi(token.value)); 53 | if (token.type == open_paren_token) 54 | return p_function_call(lexer); 55 | 56 | throw std::logic_error{"Unrecognised token type."}; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/parser.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_20_PARSER_HPP 2 | #define CHAPTER_20_PARSER_HPP 3 | 4 | #include "expression.hpp" 5 | #include 6 | #include 7 | 8 | std::shared_ptr parse_expression(std::istream& input); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/symbol_table.cpp: -------------------------------------------------------------------------------- 1 | #include "symbol_table.hpp" 2 | #include "builtin_operations.hpp" 3 | 4 | SymbolTable get_default_symbol_table() { 5 | return { 6 | {"+", builtin_add}, 7 | {"-", builtin_subtract}, 8 | {"*", builtin_multiply}, 9 | {"/", builtin_divide} 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/symbol_table.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_20_SYMBOL_TABLE_HPP 2 | #define CHAPTER_20_SYMBOL_TABLE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using Operation = std::function const&)>; 10 | using SymbolTable = std::map; 11 | 12 | SymbolTable get_default_symbol_table(); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/token.cpp: -------------------------------------------------------------------------------- 1 | #include "token.hpp" 2 | 3 | bool operator==(Token const& lhs, Token const& rhs) { 4 | return lhs.type == rhs.type && lhs.value == rhs.value; 5 | } 6 | 7 | bool operator!=(Token const& lhs, Token const& rhs) { 8 | return !(lhs == rhs); 9 | } 10 | 11 | std::ostream& operator<<(std::ostream& os, Token const& tok) { 12 | os << "{ "; 13 | 14 | if (tok.type == open_paren_token) 15 | os << "open_paren_token"; 16 | else if (tok.type == close_paren_token) 17 | os << "close_paren_token"; 18 | else if (tok.type == name_token) 19 | os << "name_token"; 20 | else if (tok.type == number_token) 21 | os << "number_token"; 22 | else if (tok.type == end_of_file_token) 23 | os << "end_of_file_token"; 24 | 25 | os << ", \"" << tok.value << "\" }"; 26 | return os; 27 | } 28 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/token.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_20_TOKEN_HPP 2 | #define CHAPTER_20_TOKEN_HPP 3 | 4 | #include 5 | #include 6 | 7 | int const open_paren_token = 0; 8 | int const close_paren_token = 1; 9 | int const name_token = 2; 10 | int const number_token = 3; 11 | int const end_of_file_token = 4; 12 | 13 | struct Token { 14 | int type; 15 | std::string value; 16 | }; 17 | 18 | bool operator==(Token const& lhs, Token const& rhs); 19 | bool operator!=(Token const& lhs, Token const& rhs); 20 | std::ostream& operator<<(std::ostream& os, Token const& tok); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/variable_expr.cpp: -------------------------------------------------------------------------------- 1 | #include "variable_expr.hpp" 2 | #include 3 | 4 | VariableExpr::VariableExpr(std::string const& name) : name(name) {} 5 | 6 | void VariableExpr::print(std::ostream& out) const { 7 | out << name; 8 | } 9 | 10 | int VariableExpr::evaluate(SymbolTable const&) const { 11 | throw std::runtime_error{"variables are not yet supported"}; 12 | } 13 | 14 | std::string VariableExpr::get_name() const { 15 | return name; 16 | } 17 | -------------------------------------------------------------------------------- /Chapter 20 - Function Objects/variable_expr.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHAPTER_20_VARIABLE_EXPR_HPP 2 | #define CHAPTER_20_VARIABLE_EXPR_HPP 3 | 4 | #include "expression.hpp" 5 | #include 6 | 7 | class VariableExpr : public Expression { 8 | std::string name; 9 | 10 | public: 11 | VariableExpr(std::string const& name); 12 | 13 | void print(std::ostream& out) const override; 14 | 15 | int evaluate(SymbolTable const& symbol_table) const override; 16 | 17 | std::string get_name() const; 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Linear C++ 2 | 3 | A C++ tutorial that can (hopefully?) be followed without jumping from chapter to chapter at every step. 4 | 5 | ## Downloads: 6 | 7 | - [No project files](https://github.com/jesyspa/linear-cpp/archive/master.zip) 8 | - [Code::Blocks project files](https://github.com/jesyspa/linear-cpp/archive/with_codeblocks_project_files.zip) 9 | 10 | ## Building 11 | 12 | Via a bash shell, compiling all chapters: 13 | 14 | ```sh 15 | for a in Chapter*; do g++ -std=c++11 -o "$a/out" "$a"/*.cpp; done 16 | ``` 17 | 18 | On OSX: 19 | - You must install XCode and the Command Line Tools package. As of 9/04/2013, Mac OS X doesn't ship with a C++11 compiler. 20 | - Then use clang to build: 21 | 22 | ```sh 23 | for a in Chapter*; do clang++ --std=c++11 --stdlib=libc++ "$a"/*.cpp -o "$a"/out; done 24 | ``` 25 | 26 | Also, when compiling your own files on either of those two platforms, I recommend you add `-Wall` and `-Wextra` to your flags. Clang users may also want to add `-fsanitize=undefined`. 27 | 28 | ## Markdown and EPUB 29 | 30 | Thanks to [@Gullumluvl](https://github.com/Gullumluvl) it's 31 | possible to export these chapters as Markdown or epub files! 32 | 33 | To do so, just run 34 | 35 | ```sh 36 | ./cpp_2_markdown.py Chapter\ NN* 37 | ``` 38 | 39 | where `NN` is the number of the chapter. 40 | 41 | You can use 42 | 43 | ```sh 44 | ./cpp_2_markdown.py . 45 | ``` 46 | 47 | to build the whole book. 48 | 49 | For building an epub, make sure you have `pandoc` and run 50 | 51 | ```sh 52 | pandoc -M author=jesyspa \ 53 | --standalone \ 54 | -V 'header-includes=""' \ 55 | --toc --toc-depth=1 \ 56 | -o linear-cpp.epub \ 57 | ebook.md 58 | ``` 59 | 60 | The `header-includes` argument is needed to wrap long code lines. 61 | 62 | ## Future plans 63 | 64 | This project is discontinued. I don't plan to update it for 65 | new C++ standards or add any new language features. 66 | 67 | Outline of what's ~~coming~~ missing: 68 | - Practical Examples 69 | - Variants, Optional 70 | - Most of inheritance 71 | - Scope 72 | - Storage Duration 73 | - Undefined Behaviour 74 | - File IO 75 | - Libraries: Boost, Abseil... 76 | - Metaprogramming 77 | - C++14 and up 78 | - The preprocessor 79 | - Most C features 80 | 81 | So while I'm glad people have found it useful, please *don't* rely on this by itself to learn C++! 82 | It really only scratches the surface, and you'll do yourself 83 | a service if you get a [good book](https://stackoverflow.com/q/388242/559931). 84 | -------------------------------------------------------------------------------- /cpp_2_markdown.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Convert a C++ project directory into a markdown book. 5 | 6 | Tested on linear-cpp tutorial. 7 | 8 | USAGE: 9 | 10 | ./cpp_2_markdown.py 11 | 12 | Will create a new file _ebook.md 13 | 14 | 15 | DETAILS: 16 | 17 | 1. organize folders and files into chapters; if a README is found, it is placed first. 18 | 2. put code and inline comments inside fenced code blocks. 19 | 3. turn multiline comments into paragraphs; 20 | * symbols like # _ < > and \\n or \\t are escaped 21 | * preserve indentation for comments formatted with a leading asterisk: 22 | - indent > 5 spaces after the asterisk is preserved, thus becoming 23 | valid markdown indented code block, if preceeded by a blank line; 24 | - if not preceeded by a blank line, it is turned into inline code; 25 | - indent > 3 spaces is interpreted as list item; same if line startswith 26 | an hyphen or asterisk (excluding the comment format asterisk) 27 | 28 | Convert to epub with pandoc: 29 | 30 | pandoc -M author=jesyspa \ 31 | --standalone \ 32 | -V 'header-includes=""' \ 33 | --toc --toc-depth=1 \ 34 | -o linear-cpp.epub \ 35 | ebook.md 36 | # The header-includes argument is needed to wrap long code lines 37 | """ 38 | 39 | 40 | import glob 41 | import sys 42 | import os 43 | import os.path as op 44 | import re 45 | 46 | 47 | START_REGEX = re.compile(r'/\*') 48 | END_REGEX = re.compile(r'\*/') 49 | MKD_ESCAPE = re.compile(r'([#_<>]|(? 3 and in_comment_env != 'item': 67 | line = '*' + line 68 | in_comment_env = 'item' 69 | elif in_comment_env != 'codeblock': 70 | line = '\n`' + line.strip().replace('`', '\\`') + '`\n' 71 | else: 72 | in_comment_env = 'codeblock' 73 | 74 | return line, in_comment_env 75 | 76 | 77 | def mkd_increment(lines, level=1): 78 | """Increment titles""" 79 | for line in lines: 80 | if line.startswith('#'): 81 | line = '#'*level + line 82 | if line.rstrip().endswith('#'): 83 | line = line.rstrip() + '#'*level + '\n' 84 | yield line 85 | 86 | 87 | def code2mkd(lines): 88 | mkd = "" 89 | 90 | in_comment = 0 # nesting depth of being inside /* ... */ comments. 91 | star_indent = None # save the index of the asterisk used to format comments. 92 | in_comment_env = None # Inside comments, there can be indented code blocks, and lists 93 | 94 | code = "" # current code block content. Not written if empty 95 | 96 | for lineno, line in enumerate(lines): 97 | line = line.rstrip() 98 | column = 0 99 | debug(lineno, end='. ') 100 | while True: 101 | begin = 0 102 | end = None 103 | if in_comment: 104 | debug('in+=', end='') 105 | match = END_REGEX.search(line) 106 | indent = 0 107 | if column == 0 and line.startswith(star_indent): 108 | begin = len(star_indent) 109 | else: 110 | # Unindent 111 | begin = re.search(r'\S', line).start() 112 | if match: 113 | end = match.start() 114 | in_comment -= 1 115 | debug('%s-%s' % (begin, end), end=' ') 116 | next_line, in_comment_env = prepare_mkd_line(line[begin:end], in_comment_env) 117 | mkd += next_line + '\n' # necessary newline 118 | else: 119 | in_comment_env = None 120 | debug('code', end=' ') 121 | match = START_REGEX.search(line) 122 | if match: 123 | end = match.start() 124 | in_comment += 1 125 | star_indent = ' '*end + ' *' 126 | if line[:end].rstrip(): 127 | code += line[:end].rstrip() 128 | debug('+=-%s' % (end), end=' ') 129 | if code.strip(): 130 | if mkd: 131 | mkd += '\n' 132 | mkd += '```cpp\n' + code.lstrip('\n').rstrip() + '\n```\n\n' 133 | debug(' >blk.', end=' ') 134 | code = "" 135 | else: 136 | code += line.rstrip() + '\n' 137 | debug('+=-', end=' ') 138 | break 139 | if match is None: 140 | break 141 | line = line[match.end():] 142 | column += match.end() 143 | debug('') 144 | if code.strip(): 145 | debug('Post.', end=' ') 146 | if mkd: 147 | mkd += '\n' 148 | mkd += '```cpp\n' + code.rstrip() + '\n```\n' 149 | 150 | return mkd 151 | 152 | 153 | def codefile2mkd(codefile): 154 | """Return the markdown text""" 155 | if codefile == '-': 156 | return code2mkd(sys.stdin) 157 | else: 158 | with open(codefile) as f: 159 | return code2mkd(f) 160 | 161 | def ls(directory): 162 | content = [name for name in sorted(os.listdir(directory)) if not name.startswith('.')] 163 | if 'README.md' in content: 164 | content.remove('README.md') 165 | content.insert(0, 'README.md') 166 | return content 167 | 168 | 169 | def project2mkd(directory): 170 | """Convert the hierarchy of directories into a hierarchy of chapters""" 171 | level = 1 172 | parents = [directory] 173 | dircontent = {1: ls(directory)} # at level one 174 | if directory == '.': 175 | out_name = 'ebook.md' 176 | else: 177 | out_name = directory + '_ebook.md' 178 | with open(out_name, 'w') as out: 179 | out.write('% ' + directory + '\n%\n%\n\n') 180 | 181 | while level: 182 | try: 183 | element = dircontent[level].pop(0) 184 | except IndexError: 185 | level -= 1 186 | parents.pop() 187 | continue 188 | 189 | path = op.join(*parents, element) 190 | if op.isfile(path): 191 | out.write('\n' + '#' * level + ' ' + element + '\n\n') 192 | ext = op.splitext(element)[1] 193 | if ext in TXT_EXTENSIONS: 194 | with open(path) as f: 195 | for line in mkd_increment(f): 196 | out.write(line) 197 | elif ext in CODE_EXTENSIONS: 198 | out.write(codefile2mkd(path)) 199 | elif op.isdir(path): 200 | out.write('\n' + '#'*level + ' ' + element + '\n\n') 201 | level += 1 202 | parents.append(element) 203 | if level > 6: 204 | print('WARNING: directories deeper than 7 levels are not explored.', file=sys.stderr) 205 | out.write('\n- '.join('`%s`' % e for e in ls(path)) + '\n') 206 | dircontent[level] = ls(path) 207 | else: 208 | print('WARNING: Ignored: not a regular file or dir:', element, file=sys.stderr) 209 | 210 | 211 | 212 | def main(): 213 | try: 214 | if sys.argv[1] in ('-h', '--help'): 215 | print(__doc__) 216 | sys.exit() 217 | 218 | if sys.argv[1] in ('-d', '--debug'): 219 | global debug 220 | def debug(*args, end=None): print(*args, end=end, file=sys.stderr) 221 | directory = sys.argv[2] 222 | else: 223 | directory = sys.argv[1] 224 | 225 | except IndexError: 226 | print(__doc__) 227 | sys.exit(2) 228 | 229 | project2mkd(directory) 230 | 231 | 232 | if __name__ == '__main__': 233 | main() 234 | --------------------------------------------------------------------------------