Mini Project 9 — Report Parser¶
Difficulty: Intermediate · Estimated effort: 4 hours · Prerequisites: Module 13 — Exception Handling, Module 11 — RAII and Smart Pointers · Combines: a custom exception hierarchy, the strong exception guarantee, resource safety on the failure path
No complete solution is provided. The failure design is the project; a parser that only handles good input has not started.
1. Scenario¶
A department exports its stock report as a plain text file, one record per line, fields
separated by |. Nobody controls the export, so the file occasionally contains a blank line, a
missing field, a number that is quoted, or a price with a currency symbol in it. The current
script prints the records it can parse and silently drops the rest — which is how a stock count
came out wrong by a hundred items.
2. Problem statement¶
Build a parser for a documented line format that either reads the whole file correctly or changes nothing and reports exactly what is wrong, with a line number and a reason. Silent skipping is forbidden.
3. User requirements¶
- [ ] Load a report file and get a collection of records.
- [ ] Be told the line number and the problem for the first bad record.
- [ ] Have nothing imported when the file is rejected.
- [ ] See how many records were read on success.
- [ ] Handle a missing file, an empty file and a truncated final line without crashing.
4. Functional requirements¶
- [ ] A documented record format: identifier, name, quantity, unit price.
- [ ] A
ReportErrorexception hierarchy: at least a base plus syntax and value errors. - [ ] Every error message contains the line number and the offending text.
- [ ] Parsing is all-or-nothing: a failure leaves the caller's collection untouched.
- [ ] Numeric fields parsed with
std::stoi/std::stodor streams, with the failure path handled rather than trusted. - [ ] A
loadFrom(std::istream&)and aloadFromFile(path)that opens and delegates.
5. Non-functional requirements¶
- [ ] No resource leaks on any failure path — no raw owning pointers at all.
- [ ] Exceptions derived from
std::runtime_errorso callers can catch generically if they choose. - [ ] Catch by
const&, never by value; say why in a comment. - [ ] One
tryblock around the whole parse, not one per line. - [ ] Builds warning-free with
g++ -std=c++17 -Wall -Wextra -Werror.
6. Suggested features¶
- A
warningchannel for recoverable oddities (extra trailing spaces, a leading+), separate from errors. - A second format behind an interface, so the format is a strategy rather than a branch.
- A dry-run mode that reports every error in the file rather than only the first.
- A round trip: write what you read back out, and compare.
7. OOP concepts involved¶
| Concept | Where it appears |
|---|---|
| Exception hierarchy | A base type for "this report is bad", derived types for why |
| Strong guarantee | All-or-nothing parse, verified by comparing state before and after |
| RAII | The input stream and the records own their resources; nothing leaks on throw |
| Catch by reference | Preserving the exception's dynamic type and avoiding slicing |
| Encapsulation | The parser owns its format rules; callers do not re-validate |
| Interfaces | A RecordFormat strategy, if you do the second format |
8. Suggested design considerations¶
Decide where validation lives. Parsing and validating in one pass is shorter; parsing then validating is easier to test. Either is fine, but the errors should be the same errors either way — that is what the tests check.
Make the message the product. "Syntax error" is not a message. line 12: expected 4 fields,
found 3 is. The whole point of the project is that a human can fix the file from the output
alone.
Mind what a partial container costs. If you push records as you go, a failure halfway leaves a half-filled vector. Convert into a local, then move it into place on success — and verify with the strong-guarantee test.
Do not trust std::stoi. It throws std::invalid_argument and std::out_of_range, both
of which are not your type. Translate them at the boundary so callers see one exception
hierarchy.
9. Minimum viable version¶
Record, the exception hierarchy, a parser for the four-field format, and a main that loads
one good file and one deliberately broken file, printing the error for the second.
10. Intermediate extension¶
Add the dry-run mode that reports every error with a line number, and a summary count. Then prove the strong guarantee: fill the target collection with known contents, load a broken file, and print the collection to show it is unchanged.
11. Advanced extension¶
Move the format behind an interface and add a second, incompatible one (say, comma-separated with quoted fields). Then answer in writing: did the interface reduce the conditional logic, or relocate it? Count the files a third format would touch.
12. Testing checklist¶
- [ ] A correct file parses to the expected number of records with the expected values
- [ ] A missing file produces your file-level error, not a crash
- [ ] An empty file is either zero records or an error — as documented, and tested
- [ ] A truncated final line is rejected with its line number
- [ ] A missing field is rejected with
expected 4 fields, found 3 - [ ] A non-numeric quantity is reported as a value error, not a syntax error
- [ ] After a failed load, the caller's collection is byte-for-byte unchanged
- [ ] A name containing the separator, if you support escaping, round-trips
- [ ] Catching the base class catches both derived types
13. Documentation checklist¶
- [ ] The file format documented as a specification, with an example
- [ ] The exception hierarchy drawn as a small tree with the meaning of each type
- [ ] The guarantee stated: what a failed load leaves behind
- [ ] Why catching by value is wrong, in one sentence
- [ ] A sample of the actual error output, pasted from a real run
14. Self-evaluation rubric¶
| Criterion | Excellent looks like | Weight |
|---|---|---|
| Failure design | Exceptions carry line numbers and reasons; nothing is silently skipped | 30% |
| Guarantee | Strong guarantee demonstrated, not asserted | 25% |
| Resource safety | No leaks on any path; no raw owning pointers | 20% |
| Interface | istream& and file overloads; clean separation of parsing and opening |
10% |
| Test coverage | Bad-input tests outnumber good-input tests, and include the boundaries | 10% |
| Documentation | Format spec plus pasted real error output | 5% |
Related: Mini projects · Project roadmap · All projects · Module 13 — Exception Handling