Load a file written against an earlier format version - #43
Conversation
A release that changes a syntax registers one migration under its own version, and reading applies every migration between the version in the header and the running one, oldest first, to the lines in memory. The file on disk is untouched, and writing it back out writes today's version. A release that breaks nothing registers nothing, so the chain carries an entry per breaking change rather than per release, and an older file with nothing behind it loads as it is. A migration rewrites lines one for one and may not add or drop any, which is what keeps a ParseError's line number and every source_map entry naming a line of the file its author opened. The runner refuses one that breaks the count and names it. Both formats read this way. .qp and .wfl carry the same number, each being the library version cut to major.minor, so one running version bounds both chains, but the rewrites stay apart: `"pi" = Square(...)` in a library and `play "drive" Square(...)` in a program are not the same line. `file_format` picks the table, and a change to vocabulary the two share is one rewrite registered twice. Either reader now takes a header version to be exactly major.minor and refuses anything above the running one. A newer file used to load when its major matched; nothing runs backwards, so it is refused. A patch or a bare major in a header is refused too: a patch release changes code, never the format. The grammar's header terminal already said so, and the hand-written parser now agrees with it. `require` lines are untouched. `parse_file_version` joins `parse_major_minor` in `qprogram/_version.py`, the strict reading for a file and the tolerant one for a package.
A `require <vendor>` line names a wire form, so it is read the way a header is: exactly major.minor, anything the installed extension cannot provide refused, anything older accepted. `register_vendor_migration` gives the extension the same mechanism the format has, keyed to its own version, and `_check_vendor_compat` runs that chain over the lines before the body is read. Two things change for a file. A line carrying a patch is refused rather than rounded down, since a patch release of an extension has no wire form of its own. A line naming an earlier major is migrated rather than refused, which is what makes renaming an operation survivable, and it merges `major versions must match` and `minor version too old` into one message that says which version to install. The ceiling for a vendor chain is the installed extension rather than the library, so `migrate_vendor_lines` takes it as an argument, and the apply loop the two runners share moves into `_apply`. What `register_vendor_version` accepts is untouched: that is a package version, patch and all.
AI AnalysisPurpose of the changes:The changes introduce a comprehensive migration system that allows older file formats to be upgraded to the current version seamlessly, ensuring backward compatibility while reading files written against earlier syntax versions.
|
A `pytest.raises` block that calls the helper building its input has two calls that could raise, and only one of them is the point.
AI Analysis
Purpose of the changes:Refactor test cases to enhance readability and maintainability by reducing code duplication.
|
AI Analysis
Purpose of the changes:The changes introduce detailed documentation on handling migrations for waveform constructors and vendor operations, ensuring backward compatibility for older files in the serialization system.
|
|



A file written against an earlier version now loads instead of being refused. A release that changes a syntax registers one rewrite under its own version with
qp.register_migration, and reading applies every rewrite between the version in the file's header and the running one, oldest first, to the lines in memory; the file on disk is untouched. A release that breaks nothing registers nothing, so an older file with no rewrite behind it loads as it is. A rewrite works line for line and may not add or drop one, which is what keeps aParseError's line number and everysource_mapentry naming a line of the file its author opened..wflreads the same way, from its own table, since a program body and a library entry are not the same text. Therequire <vendor>line follows the rule too, against the extension's version instead of the library's, so an extension that renames an operation registers a rewrite withqp.register_vendor_migrationand the files its users already have keep loading.Both readers now take a version to be exactly
major.minorand refuse anything above the running one. A newer file used to load when its major matched; nothing runs backwards, so it is refused. A patch or a bare major in a header is refused as well, since a patch release changes code and never the format — the grammar's header terminal already said so, and the hand-written parser now agrees with it.Sits on top of #42.