Skip to content

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 ReportError exception 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::stod or streams, with the failure path handled rather than trusted.
  • [ ] A loadFrom(std::istream&) and a loadFromFile(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_error so callers can catch generically if they choose.
  • [ ] Catch by const&, never by value; say why in a comment.
  • [ ] One try block around the whole parse, not one per line.
  • [ ] Builds warning-free with g++ -std=c++17 -Wall -Wextra -Werror.

6. Suggested features

  • A warning channel 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