Parsing strategy

Since ipuz is a very loosely defined spec, we have a similarly tolerant approach to parsing ipuz files. We try to map gobject properties to fields in the puzzle whenever possible. However, that’s a really bad fit for the board where we want one data structure for the board, and there can be multiple fields representing them.

Since the Json-glib::serializable interface doesn’t let us map multiple nodes to a single property (such as mapping "puzzle" and "solution" to a board property) we have to do custom parsing.

When we call ipuz_puzzle_new_*, we will build the json dom and then:

  1. Confirm the ipuz file is a version of ipuz that we can handle (.v2)

  2. Read the kind field and create a sub-class appropriately.

  3. Go through all members in the toplevel object in the file, and call :load_node on it:

    1. The default handler should handle setting properties with the same name with SCALAR json nodes (aka booleans, strings, ints, etc).

    2. For more complex types, the :load_node handler should handle them manually

    3. Be sure to chain up to the super type!

  4. We then call :post_load_node to catch any types that need loading after the rest of the cells have been called. As an example, both solutions and puzzle need a lot of previously parsed values (dimensions, block, etc)

    1. There is no need to chain up on post_load_node.

  5. Next, there’s a fixup stage. This is for making everything that’s implicit, explicit. For example, calculating enumerations or cell areas.

  6. Lastly, there’s a validate stage that makes sure the puzzle makes sense. Catches nonsensical puzzles like puzzles without a grid or clues, etc.

Notes:

  • We ignore fields that are misformed or we don’t understand in the interest of compatibility. There is no strict parsing mode right now.

  • GError handling really only catches misformed files. We don’t provide a way to warn right now about unhandled elements

  • Boxed types (style and clue) have a _load_node convenience function

  • Cells have _parse_* functions, becuase they’re allocated statically and we need to fill in their data.

  • It’s possible to have a block in both the “puzzle” and the “solution”. This is confusing and can lead to conflicting puzzles. To make it simpler, we ignore the block in the solution field.