- ludic.ui cv_join (units_pieces.ludic:93): a var() value's pieces were joined by +, a text left behind per piece on every memo miss; it is written at its length at once (units_join.ludic), and the miss is its own declared function (cs_vars_miss), bounded by the memo. - ludic.hints hn_slots (rail.ludic:41): hints_reset made a new rail every time; the rail is emptied in place, made again only for another count (rail_slots.ludic). - value_spare_put (value.ludic:138): a list's spares grow only past the most it has ever cut off; declared as such. - ludic.save: save_read reads, parses and frees the file's text - the tree's strings are the parser's own - and says whether the text was whole (SaveRead.intact) for the words of a refusal. Maroon Lake's trip and home reads kept the whole file on every load and every menu card read. Tests: ludic.save (10), ludic.hints (8); the 36 ui examples; value_list_regrow, json_saves and alloc_fence_declared unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
66 lines
2.5 KiB
Text
66 lines
2.5 KiB
Text
# read.ludic - a file read far enough to know it is one of this kind, then brought up to today's
|
|
# shape; and the two together with the backup standing in for a damaged file
|
|
|
|
# the text checked whole, parsed, and asked whether it is this kind of file at all
|
|
export function save_parse(text: string, f: SaveFormat) -> SaveRead {
|
|
if text == null { return read_new(SAVE_NONE, null) }
|
|
if not save_text_intact(text) { return read_new(SAVE_CORRUPT, null) }
|
|
let v = Json.parse(text)
|
|
if value_kind(v) != 6 or (f.is_one != null and not f.is_one(v)) { # 6 is an object
|
|
if v != null { Json.free(v) } # a refused tree is let go
|
|
return read_new(SAVE_CORRUPT, null)
|
|
}
|
|
return read_new(SAVE_OK, v)
|
|
}
|
|
|
|
# the file read and parsed, and its text given back - the tree's strings are the parser's own, and a
|
|
# load that kept the whole file for good kept it on every load and every menu card read
|
|
export function save_read(path: string, f: SaveFormat) -> SaveRead {
|
|
let text = Fs.read_text(path)
|
|
let r = save_parse(text, f)
|
|
if text != null {
|
|
r.intact = save_text_intact(text)
|
|
free(text)
|
|
}
|
|
return r
|
|
}
|
|
|
|
# The version checks, then the chain. True when the object is now in today's shape; a newer file is
|
|
# SAVE_FUTURE and read-only, an older one than the format carries is SAVE_CORRUPT.
|
|
export function save_upgrade(r: SaveRead, f: SaveFormat) -> bool {
|
|
if r.status != SAVE_OK or r.value == null { return false }
|
|
r.migrated = false
|
|
var ver = 1
|
|
if value_has(r.value, f.version_key) != 0 { ver = value_as_int(value_get(r.value, f.version_key)) }
|
|
r.from = ver
|
|
if ver > f.version {
|
|
r.status = SAVE_FUTURE
|
|
r.readonly = true
|
|
return false
|
|
}
|
|
if ver < f.oldest {
|
|
r.status = SAVE_CORRUPT
|
|
return false
|
|
}
|
|
if ver < f.version {
|
|
save_migrate(r.value, f, ver)
|
|
r.migrated = true
|
|
}
|
|
return true
|
|
}
|
|
|
|
# The file, else its backup when the file is damaged. A newer build's file is never replaced by its
|
|
# backup (that is from the newer build too); both copies damaged is read-only, so nothing this build
|
|
# writes can land over what it could not read. No file at all is SAVE_NONE and writable.
|
|
export function save_open(path: string, backup: string, f: SaveFormat) -> SaveRead {
|
|
let r = save_read(path, f)
|
|
if r.status == SAVE_OK and save_upgrade(r, f) { return r }
|
|
if r.status != SAVE_CORRUPT or len(backup) == 0 or not Fs.exists(backup) { return r }
|
|
let b = save_read(backup, f)
|
|
if b.status == SAVE_OK and save_upgrade(b, f) {
|
|
b.from_backup = true
|
|
return b
|
|
}
|
|
r.readonly = true
|
|
return r
|
|
}
|