diff --git a/PPP3/Makefile b/PPP3/Makefile new file mode 100644 index 0000000..828a03e --- /dev/null +++ b/PPP3/Makefile @@ -0,0 +1,6 @@ + +all: + meson compile -C . + +test: + @echo "../ex08.cpp:7:10: error: use of undeclared identifier \'oops\'" | ../builddir/goc diff --git a/PPP3/README.md b/PPP3/README.md index 73fb00d..5a7a8f6 100644 --- a/PPP3/README.md +++ b/PPP3/README.md @@ -38,3 +38,4 @@ This is dumb as hell but it's working for now. If you have a fancy way to tell V 5. **Disorganized presentation of the core concepts.** The book is all over the place. First you cover objects, types, and values, but this doesn't cover objects it covers base types like `int`, then it goes into computation but nothing about classes and struct, then he gets into error reporting with a reporting method that doesn't work without the `PPP.h` file you can't use, but then gets into writing a calculator that uses `class` but no explanation of that until Chapters 7 and 8 which finally explains all the things you needed to work on the calculator. It's understandable to not cover things until you can explain them later, since programming is so complicated you do have to gloss over concepts until later. But, Chapters 7 and 8 should have probably replaced the majority of the content in chapters 1-4 before he gets into making code with the concepts. That's my critique for far. Basically, the book is your classic code book where the author has absolutely no idea who he's writing the book for, and just assumes that everyone who reads it has his brain and his computer. He most likely has a whole directory full of the code in the book but you and I will never see it. It's also _definitely_, _DEFINITELY_ not a beginner book. I couldn't imagine someone who's never written code trying to setup MinGW with Meson on a Windows machine or even getting Visual C++ to work, let alone figure out his mishmash of `PPP_*.h` files. +6. **Constantly using things that don't exist.** The book is riddled with things that are claimed to be supported or standard, but that isn't available in many compilers. A great example is `std::format`, which would be great since I planned on having people use the excellent `fmt` library, but none of the compilers I have had this. He does this all the time where he just assumes something exists but it doesn't, and what's even more irritating about this is it's not hard at all to test. There's only like 4 compilers to verify. A single test suite could do it, but nope, I'm forced to research it myself. diff --git a/PPP3/ex07.cpp b/PPP3/ex07.cpp new file mode 100644 index 0000000..e0cafea --- /dev/null +++ b/PPP3/ex07.cpp @@ -0,0 +1,238 @@ +/* + * Same code as ex06.cpp but I use auto everywhere I could to + * see how it works. + */ + +#include +#include +using namespace std; + +class Error { + public: + string message; + + Error(string m) : message{m} {}; +}; + +enum class TokenKind { + SEMI_COLON, + QUIT, + LPAREN, + RPAREN, + PLUS, + MINUS, + MULT, + DIV, + NUMBER, + NAME, + EQ, + ERROR +}; + + +class Token { + public: + TokenKind kind = TokenKind::ERROR; + double value = 0.0; + string name = ""; + + Token(TokenKind k) : kind(k) {} + + Token(TokenKind k, double v) : kind{k}, value{v} {} + + Token(TokenKind k, string n) : kind{k}, name{n}{} +}; + +class TokenStream { + public: + auto get() { + if(!buffer.empty()) { + auto first = buffer.front(); + buffer.pop(); + return first; + } + + char ch = 0; + if(!(cin >> ch)) throw Error{"no input"}; + + switch(ch) { + case ';': return Token{TokenKind::SEMI_COLON}; + case 'q': return Token{TokenKind::QUIT}; + case '(': return Token{TokenKind::LPAREN}; + case ')': return Token{TokenKind::RPAREN}; + case '+': return Token{TokenKind::PLUS}; + case '-': return Token{TokenKind::MINUS}; + case '*': return Token{TokenKind::MULT}; + case '/': return Token{TokenKind::DIV}; + case '.': + case '0': // fallthrough + case '1': // fallthrough + case '2': // fallthrough + case '3': // fallthrough + case '4': + case '5': // fallthrough + case '6': // fallthrough + case '7': // fallthrough + case '8': // fallthrough + case '9': // falltrhough + { + cin.putback(ch); + auto val = 0.0; + cin >> val; + return Token{TokenKind::NUMBER, val}; + } + case '=': + return Token{TokenKind::EQ}; + default: + if(isalpha(ch)) { + cin.putback(ch); + string s; + cin >> s; + return Token{TokenKind::NAME, s}; + } else { + throw Error{"bad token"}; + } + } + } + + void putback(Token t) { + buffer.push(t); + } + + private: + queue buffer; +}; + +class Variable { + public: + string name; + double value; +}; + +vector var_table; + +double get_value(string s) { + for(const Variable& v : var_table) { + if(v.name == s) { + return v.value; + } + } + throw Error{"variable not found"}; +} + +void set_value(string s, double d) +{ + for(Variable& v : var_table) { + if(v.name == s) { + v.value = d; + return; + } + } + + // variable not found make it + var_table.push_back(Variable{s, d}); +} + +Token parse(TokenStream &stream); + +Token expression(TokenStream &stream) { + auto lval = stream.get(); + auto op = stream.get(); + auto rval = stream.get(); + double result = 0; + + // parens start a sub-expression + if(rval.kind == TokenKind::LPAREN) { + rval = expression(stream); + // eat the RPAREN + auto rparen = stream.get(); + if(rparen.kind != TokenKind::RPAREN) { + throw Error{"expected RPAREN"}; + } + } else if(rval.kind == TokenKind::NAME) { + auto var_val = get_value(rval.name); + rval = Token{TokenKind::NUMBER, var_val}; + } + + switch(op.kind) { + case TokenKind::PLUS: + result = lval.value + rval.value; + break; + case TokenKind::MINUS: + result = lval.value - rval.value; + break; + case TokenKind::DIV: + result = lval.value / rval.value; + break; + case TokenKind::MULT: + result = lval.value * rval.value; + default: + throw Error{"invalid syntax in expresion"}; + } + + return Token{TokenKind::NUMBER, result}; +} + + +Token parse(TokenStream &stream) { + auto tok = stream.get(); + + switch(tok.kind) { + case TokenKind::NUMBER: + stream.putback(tok); + return expression(stream); + case TokenKind::NAME: { + auto eq = stream.get(); + + if(eq.kind == TokenKind::EQ) { + auto val = stream.get(); + if(val.kind != TokenKind::NUMBER) throw Error{"invalid value in variable assign"}; + + set_value(tok.name, val.value); + return val; + } else { + auto var_val = get_value(tok.name); + stream.putback(Token{TokenKind::NUMBER, var_val}); + stream.putback(eq); // push the next token back on + return expression(stream); + } + } + case TokenKind::LPAREN: { + auto val = expression(stream); + tok = stream.get(); + if(tok.kind != TokenKind::RPAREN) { + throw Error{"missing rparen"}; + } + return val; + } + case TokenKind::QUIT: + return tok; + default: + cout << "Got token " << ":" << tok.value << " expected NUMBER or LPAREN\n"; + throw Error{"invalid syntax in parse"}; + } + + return Token{TokenKind::ERROR}; +} + +int main() { + TokenStream stream; + + while(cin) { + try { + auto val = parse(stream); + + if(val.kind == TokenKind::ERROR) { + cout << "Parse returned an error token.\n"; + } else if(val.kind == TokenKind::QUIT) { + cout << "Goodbye!\n"; + return 0; + } else { + cout << "=" << val.value << "\n"; + } + } catch (Error &err) { + cout << "ERROR " << err.message << "\n"; + } + } + return 0; +} diff --git a/PPP3/ex08.cpp b/PPP3/ex08.cpp new file mode 100644 index 0000000..e467e6d --- /dev/null +++ b/PPP3/ex08.cpp @@ -0,0 +1,8 @@ +#include + +using namespace std; + +int main() +{ + return 0; +} diff --git a/PPP3/goc.cpp b/PPP3/goc.cpp new file mode 100644 index 0000000..c6f5d81 --- /dev/null +++ b/PPP3/goc.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include + +using namespace std; +using namespace fmt; + +int main() +{ + regex err_re("(.*?):([0-9]+):([0-9]+):\\s*(.*?):\\s*(.*)"); + string line; + smatch err_match; + + while(getline(cin, line)) { + print("{}\n", line); + if(regex_match(line, err_match, err_re)) { + string file_name = err_match[1].str(); + string line = err_match[2].str(); + string col = err_match[3].str(); + string type = err_match[4].str(); + string message = err_match[5].str(); + + print("{},{},{},{},{}\n", + file_name, line, col, type, message); + } + } + + return 0; +} diff --git a/PPP3/meson.build b/PPP3/meson.build index 4333a0a..e7b19ec 100644 --- a/PPP3/meson.build +++ b/PPP3/meson.build @@ -1,9 +1,18 @@ project('lcppthw', 'cpp', default_options: ['cpp_std=c++20']) +fmt_dep = dependency('fmt') + +executable('goc', 'goc.cpp', + win_subsystem: 'windows', + cpp_args: '-DFMT_HEADER_ONLY', + dependencies: fmt_dep) + executable('ex01', 'ex01.cpp') executable('ex02', 'ex02.cpp') executable('ex03', 'ex03.cpp') executable('ex04', 'ex04.cpp') executable('ex05', 'ex05.cpp') executable('ex06', 'ex06.cpp') +executable('ex07', 'ex07.cpp') +executable('ex08', 'ex08.cpp') diff --git a/PPP3/stats.py b/PPP3/stats.py new file mode 100644 index 0000000..5d4fa5f --- /dev/null +++ b/PPP3/stats.py @@ -0,0 +1,21 @@ +import sys +import re +import json +from datetime import datetime + +err_re = re.compile("(?P.*?):(?P[0-9]+):(?P[0-9]+): (?P.*?): (?P.*)\n") + +stats = []; + +for line in sys.stdin: + found = err_re.fullmatch(line) + print(line, end="") + + if found: + stats.append(found.groupdict()) + print("FOUND", found.groupdict()) + +with open("stats.json", "a+") as out: + out.write(json.dumps({"date": datetime.now().isoformat(), + "messages": stats})); + out.write("\n")