├── problems ├── problem4 ├── problem1 ├── problem3 ├── problem2 ├── problem6 ├── problem7 ├── problem8 ├── problem5 ├── problem10 └── problem9 ├── Makefile ├── README.md └── regex.cpp /problems/problem4: -------------------------------------------------------------------------------- 1 | --prompt 2 | Repeats: Match all strings that contain lol's 3 | --line 4 | lol 5 | lool 6 | loooooooool 7 | a lolcat 8 | hello 9 | toolbox 10 | long fall 11 | --match 12 | 1 13 | 1 14 | 1 15 | 1 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /problems/problem1: -------------------------------------------------------------------------------- 1 | --prompt 2 | Literal Strings: Match the lines with the string "abc." in them. 3 | --line 4 | abc. 5 | abc.def 6 | cout << "abc." << endl 7 | abc 8 | abcdef 9 | 123 10 | abacus 11 | --match 12 | 1 13 | 1 14 | 1 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /problems/problem3: -------------------------------------------------------------------------------- 1 | --prompt 2 | Start and end of strings: match strings that start or end with quotes 3 | --line 4 | "" 5 | "Hello" 6 | "To be, or not to be" 7 | " <- is a quote 8 | Also a quote -> " 9 | Goodbye 10 | printf(""); 11 | --match 12 | 1 13 | 1 14 | 1 15 | 1 16 | 1 17 | 18 | 19 | -------------------------------------------------------------------------------- /problems/problem2: -------------------------------------------------------------------------------- 1 | --prompt 2 | Bracket expression: Match any line with any CAPTIAL LETTERS. 3 | --line 4 | Hello, World! 5 | Success 6 | ABC 7 | printf("ERROR"); 8 | who am I? 9 | laTeX 10 | hunter22 11 | failure 12 | i am sam 13 | --match 14 | 1 15 | 1 16 | 1 17 | 1 18 | 1 19 | 1 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /problems/problem6: -------------------------------------------------------------------------------- 1 | --prompt 2 | Backrefs: Match words that start and end with the same letter. 3 | --line 4 | starters 5 | roller 6 | growling 7 | mm 8 | noon 9 | yesterday 10 | deterred 11 | endure 12 | apply 13 | mother 14 | retro 15 | geared 16 | added 17 | --match 18 | 4 19 | 5 20 | 6 21 | 7 22 | 8 23 | 9 24 | 0 25 | 1 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /problems/problem7: -------------------------------------------------------------------------------- 1 | --prompt 2 | Replace: replace the first word of each line with "My" 3 | --line 4 | The house is on fire 5 | Her cat is adorable 6 | Jupiter's mass is enormous 7 | Their classes are fun 8 | outer space is outdated 9 | --replace 10 | My house is on fire 11 | My cat is adorable 12 | My mass is enormous 13 | My classes are fun 14 | My space is outdated 15 | -------------------------------------------------------------------------------- /problems/problem8: -------------------------------------------------------------------------------- 1 | --prompt 2 | Real Applications 1: Change each phone number to the form XXX-XXX-XXXX 3 | --line 4 | 123-456-7890 5 | (112)-555-8322 6 | 6035559846 7 | 427-5555338 8 | 214 000 0000 9 | (906) 555-1213 10 | (123)555-1337 11 | --replace 12 | 123-456-7890 13 | 112-555-8322 14 | 603-555-9846 15 | 427-555-5338 16 | 214-000-0000 17 | 906-555-1213 18 | 123-555-1337 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = -std=c++11 2 | LDFLAGS = -lcurses -lboost_regex 3 | LDCONFIG = $(shell which ldconfig 2>/dev/null || echo /sbin/ldconfig) 4 | ifneq (,$(findstring libtinfo,$(shell $(LDCONFIG) -p))) 5 | LDFLAGS += -ltinfo 6 | endif 7 | 8 | BINS = regex 9 | 10 | .PHONY: all 11 | all: $(BINS) 12 | 13 | regex: regex.cpp 14 | 15 | .PHONY: clean 16 | clean: 17 | rm -f $(BINS) 18 | -------------------------------------------------------------------------------- /problems/problem5: -------------------------------------------------------------------------------- 1 | --prompt 2 | Capture groups: Capture the name of all pdf files in capture group 1 3 | --line 4 | test.pdf 5 | hello_world.pdf 6 | passwords.doc.pdf 7 | not_a_virus.pdf.exe 8 | image.png 9 | typo.pfe 10 | --capture 11 | test.pdf 12 | hello_world.pdf 13 | passwords.doc.pdf 14 | 15 | 16 | 17 | --1 18 | test 19 | hello_world 20 | passwords.doc 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /problems/problem10: -------------------------------------------------------------------------------- 1 | --prompt 2 | Real Application 3: replace the function "foo(a, b)" with "bar(b, a) " 3 | --line 4 | int main(){ 5 | foo(a, b); 6 | foo(5, "fizz"); 7 | cout << foo(5 + a, b + "fizz") << endl; 8 | int foo = foo(BUZZ_FLAG, "false"); 9 | foo += foo(0, "") + foo(1, " "); 10 | } 11 | --replace 12 | int main(){ 13 | bar(b, a); 14 | bar("fizz", 5); 15 | cout << bar(b + "fizz", 5 + a) << endl; 16 | int foo = bar("false", BUZZ_FLAG); 17 | foo += bar("", 0) + bar(" ", 1); 18 | } 19 | -------------------------------------------------------------------------------- /problems/problem9: -------------------------------------------------------------------------------- 1 | --prompt 2 | Real Applications 2: Capture simple email addresses. Have user name in \1, domain name in \2. 3 | --line 4 | Contact information: 5 | General Questions - Questions@example.com 6 | Phone - (409)555 9123 7 | Support - Support@company.com 8 | customers@example2.com 9 | spammail@organization.org 10 | CEO email - IamAwesome@CEO.company.com 11 | --capture 12 | 13 | Questions@example.com 14 | 15 | Support@company.com 16 | customers@example2.com 17 | spammail@organization.org 18 | IamAwesome@CEO.company.com 19 | --1 20 | 21 | Questions 22 | 23 | Support 24 | customers 25 | spammail 26 | IamAwesome 27 | --2 28 | 29 | example.com 30 | 31 | company.com 32 | example2.com 33 | organization.org 34 | CEO.company.com 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Practice Regular Expressions 2 | ============ 3 | 4 | This is a simple C++ application which allows you to practice creating [regular expression](http://en.wikipedia.org/wiki/Regular_expression) patterns. We have provided 10 prompts which cover a variety of regular expression pattern usages. With this program, you can modify the prompts we have provided and also create your own prompts to solve. This program will highlight which characters match the regular expression pattern that you type in and will not allow continuing to the next problem until the current problem is solved correct. 5 | 6 | ![alt text](http://i.imgur.com/qZTMalh.gif "gif demo") 7 | 8 | Building and running 9 | --- 10 | Required libraries: **C++11, [Curses Library](http://en.wikipedia.org/wiki/Curses_%28programming_library%29), [Boost Library](http://www.boost.org/)** 11 | 12 | After downloading/cloning this repository, perform the following commands: 13 | ``` 14 | $ make # builds the program 15 | $ ./regex # runs the program 16 | ``` 17 | 18 | Interface 19 | --- 20 | | Input | Description | 21 | |:-----|:-----| 22 | | Arrow keys/mouse | Move between `Regex pattern:` and `Replace with:`| 23 | | Mouse | Toggles `Extended regex` and `Global Tag` checkboxes. | 24 | | `Ctrl+E` | Toggles `Extended regex` checkbox. | 25 | | `Ctrl+G` | Toggles `Global Tag` checkbox. | 26 | | `Ctrl+C` | Exits the program. | 27 | 28 | | Onscreen | Description | 29 | |:------|:-----| 30 | | `TASK` | Tells you what you should try and accomplish for that level. This will change to indicate that the problem is solved correctly.| 31 | | `HINT` | Provides a hint. | 32 | 33 | Making your own problems 34 | --- 35 | Create a blank text file with a number, preferrably starting with one greater than the largest number problem. (For example, adding an 11th question would result in creating a file called "problem11") 36 | 37 | Use the following format: 38 | 39 | | Flag | Description | 40 | |:--------------|:---------------| 41 | |`--prompt` | This is where your prompt goes.| 42 | |`--line`| Each line of these are the possible strings/complete lines/sentences/etc. to capture.| 43 | |`--match`| Indicates a string matching problem (as opposed to a replace or capture problem). Each line indicates whether or not a regex should match that corresponding string/sentence. A blank line indicates not matching, anything else indicates matching.| 44 | |`--capture`| Indicates a string capture problem (as opposed to a replace or matching problem). Each line corresponds to the capture groups that are captured by the regex. These lines indicate what should be captured by `\0` or the entire regex. Following `--n` lines are what should be captured by the corresponding capture groups (backreferencing) i.e. `\1` `\2`| 45 | |`--replace`| Indicates a replace problem (as opposed to a find problem). Each line indicates what the capture and replace regex should return.| 46 | 47 | Refer to the provided prompts if you need assistance. 48 | 49 | TODOs 50 | --- 51 | Create a "main menu" or pass the program parameters to indicate levels/difficulty 52 | Create additional levels 53 | Improve the gui to be more clear and helpful 54 | 55 | Known bugs 56 | --- 57 | -------------------------------------------------------------------------------- /regex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | using namespace boost; 11 | 12 | int x = 0, y = 0; 13 | string regexPattern; 14 | string replacePattern; 15 | string hint; 16 | string prompt; 17 | 18 | vector lines; 19 | vector> coloredLines; 20 | vector replaced; 21 | vector> captures; 22 | vector caplen; 23 | 24 | vector> captureSolutions; 25 | vector replaceSolutions; 26 | 27 | bool extended = false; 28 | bool global = false; 29 | bool replaceMode = false; 30 | bool match = false; 31 | bool finished = false; 32 | 33 | static const int CTRL_E = 5; 34 | static const int CTRL_G = 7; 35 | 36 | void mouseAction(int mx, int my) 37 | { 38 | if (my == 2) 39 | { 40 | if (mx < 27) 41 | extended = !extended; 42 | else if (mx > 31 && mx < 45) 43 | global = !global; 44 | //else if (mx > 38 && mx < 57) 45 | // replaceMode = !replaceMode; 46 | } 47 | else if (my == 0) 48 | { 49 | y = 0; 50 | x = mx-16; 51 | if (mx-16 > regexPattern.length()) 52 | x = regexPattern.length(); 53 | } 54 | else if (my == 1) 55 | { 56 | y = 1; 57 | x = mx-16; 58 | if (mx-16 > replacePattern.length()) 59 | x = replacePattern.length(); 60 | } 61 | } 62 | 63 | void keyAction( int ch ) 64 | { 65 | switch (ch) 66 | { 67 | case KEY_MOUSE: 68 | MEVENT event; 69 | getmouse(&event); 70 | mouseAction(event.x,event.y); 71 | break; 72 | case KEY_BACKSPACE: 73 | case 127: 74 | if (y == 0 && x > 0 && regexPattern.length() >= x) 75 | { 76 | regexPattern.erase(regexPattern.begin()+x-1); 77 | x--; 78 | } 79 | if (y == 1 && x > 0 && replacePattern.length() >= x) 80 | { 81 | replacePattern.erase(replacePattern.begin()+x-1); 82 | x--; 83 | } 84 | break; 85 | case KEY_LEFT: 86 | if (x>0) 87 | x--; 88 | break; 89 | case KEY_RIGHT: 90 | if (y == 0 && x < regexPattern.length()) 91 | x++; 92 | if (y == 1 && x < replacePattern.length()) 93 | x++; 94 | break; 95 | case KEY_UP: 96 | y = 0; 97 | if (x > regexPattern.length()) 98 | x = regexPattern.length(); 99 | break; 100 | case KEY_DOWN: 101 | y = 1; 102 | if (x > replacePattern.length()) 103 | x = replacePattern.length(); 104 | break; 105 | case CTRL_E: 106 | extended = !extended; 107 | break; 108 | case CTRL_G: 109 | global = !global; 110 | break; 111 | default: 112 | if (ch >= 32 && ch < 127) 113 | { 114 | if (y == 0) 115 | regexPattern.insert(regexPattern.begin()+x, ch); 116 | else 117 | replacePattern.insert(replacePattern.begin()+x, ch); 118 | x++; 119 | } 120 | } 121 | } 122 | 123 | bool checkSolution() 124 | { 125 | if (replaceMode) 126 | { 127 | if (replaced.size() != replaceSolutions.size()) 128 | return false; 129 | for (int i = 0; i < replaced.size(); i++) 130 | { 131 | if (replaced[i] != replaceSolutions[i]) 132 | { 133 | hint = "Check number "; 134 | hint += to_string(i+1); 135 | hint += " Expected \"" + replaceSolutions[i] + "\""; 136 | hint += " got \"" + replaced[i] + "\""; 137 | return false; 138 | } 139 | } 140 | return true; 141 | } 142 | else if (!match) 143 | { 144 | if (captureSolutions.size() != captures.size()) 145 | return false; 146 | for (int i = 0; i < captureSolutions.size(); i++) 147 | { 148 | while (captureSolutions[i].size() > captures[i].size()) 149 | { 150 | captures[i].push_back(""); 151 | } 152 | for (int j = 1; j < captureSolutions[i].size(); j++) 153 | { 154 | if (captureSolutions[i][j] != captures[i][j]) 155 | { 156 | hint = "Check line "; 157 | hint += to_string(i+1); 158 | hint += ", Capture group \\"; 159 | hint += to_string(j); 160 | hint += " "; 161 | hint += "Expected \"" +captureSolutions[i][j]+"\" "; 162 | hint += "got \"" + captures[i][j] +"\""; 163 | return false; 164 | } 165 | } 166 | } 167 | return true; 168 | } 169 | else 170 | { 171 | if (captureSolutions.size() != captures.size()) 172 | return false; 173 | for (int i = 0; i < captureSolutions.size(); i++) 174 | { 175 | if (captureSolutions[i][0] == "") 176 | { 177 | if (captures[i].size() != 0) 178 | { 179 | hint = "Line number "; 180 | hint += to_string(i+1); 181 | hint += " should not be captured"; 182 | return false; 183 | } 184 | } 185 | else 186 | { 187 | if (captures[i].size() == 0) 188 | { 189 | hint = "Line number "; 190 | hint += to_string(i+1); 191 | hint += " should be captured"; 192 | return false; 193 | } 194 | } 195 | } 196 | return true; 197 | } 198 | 199 | 200 | } 201 | 202 | void drawScreen() 203 | { 204 | clear(); 205 | move(0,0); 206 | addstr("Regex Pattern: "); 207 | move(0,16); 208 | addstr(regexPattern.c_str()); 209 | move(1,0); 210 | addstr("Replace With: "); 211 | move(1,16); 212 | addstr(replacePattern.c_str()); 213 | move(2,0); 214 | addstr("Extended Regex (Ctrl-E) [ ] Global Tag (Ctrl-G) [ ] "); 215 | if (replaceMode) 216 | addstr("--Replace Mode--"); 217 | else 218 | addstr("--Search Mode--"); 219 | 220 | if (extended) 221 | mvaddch(2,25,'X'); 222 | if (global) 223 | mvaddch(2,52,'X'); 224 | 225 | move(5,0); 226 | addstr("HINT: "); 227 | if (!checkSolution()) 228 | addstr(hint.c_str()); 229 | else 230 | { 231 | attron(COLOR_PAIR(3)); 232 | addstr("CORRECT! Press any key to continue"); 233 | attroff(COLOR_PAIR(3)); 234 | } 235 | move(4,0); 236 | addstr("TASK: "); 237 | addstr(prompt.c_str()); 238 | 239 | for (int i = 0; i < lines.size(); i++) 240 | { 241 | int color = 1; 242 | 243 | move(7,0); 244 | addstr("Text:"); 245 | move(8+i,0); 246 | for (int j = 0; j < coloredLines[i].size(); j++) 247 | { 248 | if (j%2!=0) 249 | { 250 | color = 1 - color; 251 | attron(COLOR_PAIR(color+1)); 252 | } 253 | addstr(coloredLines[i][j].c_str()); 254 | if (j%2!=0) 255 | attroff(COLOR_PAIR(color+1)); 256 | } 257 | if (replaceMode) 258 | { 259 | move(7,50); 260 | addstr("Replaced String:"); 261 | move(8+i, 50); 262 | addstr(replaced[i].c_str()); 263 | } 264 | else 265 | { 266 | int loc = 50; 267 | move(6,loc); 268 | addstr("Captured String:"); 269 | for (int j = 0; j < caplen.size() && j < 9; j++) 270 | { 271 | move (7, loc); 272 | addstr("\\"); 273 | addch(j+'0'); 274 | loc += caplen[j]; 275 | } 276 | 277 | loc = 50; 278 | for (int j = 0; j < captures[i].size(); j++) 279 | { 280 | move(8+i, loc); 281 | addstr(captures[i][j].c_str()); 282 | loc += caplen[j]; 283 | } 284 | } 285 | } 286 | move(y, x+16); 287 | } 288 | 289 | void performRegex() 290 | { 291 | replaced.clear(); 292 | caplen.clear(); 293 | caplen.push_back(5); 294 | hint = ""; 295 | bool valid = true; 296 | regex r; 297 | try 298 | { 299 | if (extended) 300 | { 301 | r.assign(regexPattern.c_str(), regex::extended); 302 | } 303 | else 304 | { 305 | if (regexPattern.back() != '\\') 306 | r.assign(regexPattern.c_str(), regex::basic); 307 | else 308 | valid = false; 309 | } 310 | } 311 | catch (regex_error &e) 312 | { 313 | valid = false; 314 | } 315 | if (!valid) 316 | hint = "Invalid regex!"; 317 | 318 | for (int i = 0; i < lines.size(); i++) 319 | { 320 | coloredLines[i].clear(); 321 | captures[i].clear(); 322 | if (!valid) 323 | { 324 | replaced.push_back(""); 325 | coloredLines[i].push_back(lines[i]); 326 | continue; 327 | } 328 | smatch m; 329 | string s= lines[i]; 330 | auto words_begin = sregex_iterator(s.begin(), s.end(), r); 331 | auto words_end = sregex_iterator(); 332 | 333 | for (sregex_iterator j = words_begin; j != words_end; j++) 334 | { 335 | m = *j; 336 | coloredLines[i].push_back(m.prefix().str()); 337 | coloredLines[i].push_back(m.str()); 338 | if (!global) 339 | break; 340 | } 341 | if (words_begin == words_end) 342 | coloredLines[i].push_back(s); 343 | else 344 | coloredLines[i].push_back(m.suffix().str()); 345 | 346 | if (!global && regex_search (s,m,r)) { 347 | int numcap = 0; 348 | for (auto x:m) 349 | { 350 | captures[i].push_back(x.str()); 351 | if (numcap >= caplen.size()) 352 | caplen.push_back(5); 353 | if (x.str().length() > caplen[numcap]) 354 | caplen[numcap] = x.str().length()+3; 355 | numcap++; 356 | } 357 | } 358 | 359 | if (global) 360 | replaced.push_back(regex_replace(s,r,replacePattern)); 361 | else 362 | replaced.push_back(regex_replace(s,r,replacePattern,regex_constants::format_first_only)); 363 | } 364 | } 365 | 366 | void getFile(string s) 367 | { 368 | ifstream infile(s); 369 | string line; 370 | getline(infile,line); 371 | 372 | lines.clear(); 373 | coloredLines.clear(); 374 | captures.clear(); 375 | replaced.clear(); 376 | caplen.clear(); 377 | 378 | captureSolutions.clear(); 379 | replaceSolutions.clear(); 380 | if (line == "--prompt") 381 | { 382 | string p; 383 | getline(infile, p); 384 | prompt = p; 385 | getline(infile,line); 386 | } 387 | if (line == "--line") 388 | { 389 | for (string s; getline(infile,s);) 390 | { 391 | if (s.length() >= 2 && s[0] == '-' && s[1] == '-') 392 | { 393 | line = s; 394 | break; 395 | } 396 | lines.push_back(s); 397 | coloredLines.push_back(vector()); 398 | captures.push_back(vector()); 399 | captureSolutions.push_back(vector()); 400 | } 401 | } 402 | if (line == "--capture" || line == "--match") 403 | { 404 | match = line == "--match"; 405 | replaceMode = false; 406 | string s; 407 | int i = 0; 408 | while (getline(infile,s)) 409 | { 410 | if (i == lines.size()) 411 | i = 0; 412 | else 413 | { 414 | captureSolutions[i].push_back(s); 415 | i++; 416 | } 417 | } 418 | } 419 | else if (line == "--replace") 420 | { 421 | replaceMode = true; 422 | for (string s; getline(infile,s);) 423 | { 424 | replaceSolutions.push_back(s); 425 | } 426 | } 427 | } 428 | 429 | 430 | void initCurses() 431 | { 432 | initscr(); 433 | start_color(); 434 | use_default_colors(); 435 | cbreak(); 436 | noecho(); 437 | mousemask(BUTTON1_PRESSED, NULL); 438 | keypad(stdscr, TRUE); 439 | init_pair(1, COLOR_BLACK, COLOR_GREEN); 440 | init_pair(2, COLOR_BLACK, COLOR_BLUE); 441 | init_pair(3, COLOR_GREEN, COLOR_BLACK); 442 | } 443 | 444 | 445 | int main () 446 | { 447 | int level = 1; 448 | int ch = 0; 449 | getFile("problems/problem" + to_string(level)); 450 | initCurses(); 451 | while(true) 452 | { 453 | if (checkSolution()) 454 | { 455 | regexPattern = ""; 456 | replacePattern = ""; 457 | x = 0; 458 | y = 0; 459 | if (level == 10) 460 | break; 461 | level++; 462 | getFile("problems/problem" + to_string(level)); 463 | } 464 | keyAction(ch); 465 | performRegex(); 466 | drawScreen(); 467 | ch = getch(); 468 | } 469 | endwin(); 470 | printf("end\n"); 471 | return 0; 472 | } 473 | --------------------------------------------------------------------------------