Overview
Comment: | clean up rels mechanism, get parvan mostly working again |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
bc37f027013083243953b8bff99ce8e2 |
User & Date: | lexi on 2022-04-28 22:52:49 |
Other Links: | manifest | tags |
Context
2022-10-27
| ||
20:04 | add soda check-in: 0d4aa1c43a user: lexi tags: trunk | |
2022-04-28
| ||
22:52 | clean up rels mechanism, get parvan mostly working again check-in: bc37f02701 user: lexi tags: trunk | |
21:01 | commit to preserve old code im about to axe, parvan is broken currently check-in: f996abb5e5 user: lexi tags: trunk | |
Changes
Modified parvan.lua from [30400bd2f4] to [af37224306].
50 50 if src == nil then return end 51 51 local sc = #src 52 52 for j=1,sc do dest[i+j] = src[j] end 53 53 i = i + sc 54 54 iter(...) 55 55 end 56 56 iter(...) 57 +end 58 +local function fastDelete(table,idx) 59 +-- delete without preserving table order 60 + local l = #table 61 + table[idx] = table[l] 62 + table[l] = nil 63 + return table 57 64 end 58 65 local function tcat(...) 59 66 local new = {} 60 67 tcatD(new, ...) 61 68 return new 62 69 end 63 70 local ansi = { ................................................................................ 329 336 decode = function(s) 330 337 local n = parse(ty,s) 331 338 if (n+1) > #vals then error(string.format('enum "%s" does not have %u members', table.concat(vals,'","'),n),3) end 332 339 return vals[n+1] 333 340 end; 334 341 } 335 342 end 343 + 344 +fmt.uid = fmt.u32 345 + 346 +fmt.relatable = function(ty) 347 + return tcat(ty,{ 348 + {'rels',fmt.list(fmt.uid,fmt.u16)}; 349 + }) 350 +end 336 351 337 352 fmt.note = { 338 353 {'kind', fmt.tag}; 339 354 {'paras', fmt.list(fmt.string)}; 340 355 } 341 356 342 357 fmt.example = { 343 358 {'quote',fmt.string}; 344 359 {'src',fmt.label}; 345 360 } 346 -fmt.meaning = { 361 + 362 +fmt.meaning = fmt.relatable { 347 363 {'lit', fmt.string}; 348 364 {'examples', fmt.list(fmt.example,fmt.u8)}; 349 365 {'notes', fmt.list(fmt.note,fmt.u8)}; 350 366 } 351 367 352 -fmt.phrase = { 368 +fmt.phrase = fmt.relatable { 353 369 {'str',fmt.label}; 354 370 {'means',fmt.list(fmt.meaning,fmt.u8)}; 355 - {'xref',fmt.list(fmt.path,fmt.u16)}; 356 371 } 357 372 358 -fmt.def = { 373 +fmt.def = fmt.relatable { 359 374 {'part', fmt.u8}; 360 375 {'branch', fmt.list(fmt.label,fmt.u8)}; 361 376 {'means', fmt.list(fmt.meaning,fmt.u8)}; 362 377 {'forms', fmt.map(fmt.u16,fmt.label,fmt.u16)}; 363 378 {'phrases', fmt.list(fmt.phrase,fmt.u16)}; 364 379 } 365 380 366 -fmt.word = { 381 +fmt.word = fmt.relatable { 367 382 {'defs', fmt.list(fmt.def,fmt.u8)}; 368 383 } 369 384 370 385 fmt.dictHeader = { 371 386 {'lang', fmt.tag}; 372 387 {'meta', fmt.string}; 373 388 {'partsOfSpeech', fmt.list(fmt.tag,fmt.u16)}; ................................................................................ 378 393 {'parts', fmt.list(fmt.tag,fmt.u8)}; 379 394 -- which parts of speech does this form apply to? 380 395 -- leave empty if not relevant 381 396 },fmt.u16)}; 382 397 } 383 398 384 399 fmt.relSet = { 385 - {'uid', fmt.u32}; 400 + {'uid', fmt.uid}; 386 401 -- IDs are persistent random values so they can be used 387 402 -- as reliable identifiers even when merging exports in 388 403 -- a parvan-unaware VCS 389 - {'members', fmt.list(fmt.path,fmt.u16)}; 390 404 {'kind', fmt.enum('syn','ant','met')}; 405 + -- membership is stored in individual objects, using a field 406 + -- attached by the 'relatable' template 391 407 } 392 408 393 409 fmt.dict = { 394 410 {'header', fmt.dictHeader}; 395 411 {'words', fmt.map(fmt.string,fmt.word)}; 396 412 {'relsets', fmt.list(fmt.relSet)}; 397 413 } ................................................................................ 441 457 end 442 458 443 459 local function rebuildRelationCache(d) 444 460 -- (re)build a dictionary's relation cache; needed 445 461 -- at load time and whenever any changes to relsets 446 462 -- are made (unless they're simple enough to update 447 463 -- the cache directly by hand, but that's very eeeh) 448 - local sc = {} 449 - for i,s in ipairs(d.relsets) do 450 - for j,m in ipairs(s.members) do 451 - sc[m.w] = sc[m.w] or {} 452 - table.insert(sc[m.w], s) 464 + local setMems = {} -- indexed by set id 465 + local function scan(obj,path) 466 + for _,v in pairs(obj.rels) do 467 + setMems[v] = setMems[v] or {mems={}} 468 + table.insert(setMems[v].mems, {path=path, obj=obj}) 453 469 end 454 470 end 455 - d._relCache = sc 471 + for wk,wv in pairs(d.words) do 472 + scan(wv, {w=wk}) 473 + for dk,dv in pairs(wv.defs) do 474 + scan(dv, {w=wk, dn=dk}) 475 + for mk,mv in pairs(dv.means) do 476 + scan(mv, {w=wk, dn=dk, mn=mk}) 477 + end 478 + for pk,pv in pairs(dv.phrases) do 479 + scan(pv, {w=wk, dn=dk, pn=pk}) 480 + for mk,mv in pairs(pv.means) do 481 + scan(mv, {w=wk, dn=dk, pn=pk, mn=mk}) 482 + end 483 + end 484 + end 485 + end 486 + for sk,sv in pairs(d.relsets) do 487 + setMems[sv.uid] = setMems[sv.uid] or {} 488 + setMems[sv.uid].set = sv 489 + end 490 + d._relCache = setMems 456 491 end 457 492 458 493 local function 459 494 writeDict(d) 460 495 local atomizePoS, posMap = atomizer() 461 496 for lit,w in pairs(d.words) do 462 497 for j,def in ipairs(w.defs) do ................................................................................ 749 784 if not fd then error(userError("cannot open file " .. file),2) end 750 785 return fd 751 786 else 752 787 return file 753 788 end 754 789 end 755 790 756 -local function 757 -safeNavWord(ctx, word, dn, mn, nn) 758 - local w = ctx.dict.words[word] 759 - if not w then id10t 'bad word' end 760 - if dn == nil then return w end 761 - 762 - local d = w.defs[tonumber(dn)] 763 - if not d then id10t('no definition #%u',dn) end 764 - if mn == nil then return w,d end 765 - 766 - local m = d.means[tonumber(mn)] 767 - if not m then id10t('no meaning #%u',mn) end 768 - if nn == nil then return w,d,m end 769 - 770 - local n = m.notes[tonumber(nn)] 771 - if not n then id10t('no note #%u',nn) end 772 - return w,d,m,n 773 -end 774 - 775 791 local function copy(tab) 776 792 local new = {} 777 793 for k,v in pairs(tab) do new[k] = v end 778 794 return new 779 795 end 780 796 781 797 local function pathParse(p) ................................................................................ 840 856 and a.dn == b.dn 841 857 and a.mn == b.mn 842 858 and a.pn == b.pn 843 859 and a.nn == b.nn 844 860 and a.xn == b.xn 845 861 end 846 862 local function pathResolve(ctx, a) 847 - if not a.w then return end -- empty paths are valid! 863 + local res = {} 864 + 865 + if not a.w then return res end -- empty paths are valid! 848 866 local function lookup(seg, tbl,val) 849 867 if not tbl then error('bad table',2) end 850 868 local v = tbl[val] 851 869 if v then return v end 852 870 id10t('bad %s in path: %s', seg, val) 853 871 end 854 872 855 - local res = {} 856 873 res.word = lookup('word', ctx.dict.words, a.w) 857 874 if not a.dn then return res end 858 875 859 - res.def = lookup('definition', w.defs, a.dn) 876 + res.def = lookup('definition', res.word.defs, a.dn) 860 877 if (not a.pn) and (not a.mn) then return res end 861 878 862 879 local m if a.pn then 863 - res.phrase = lookup('phrase', d.phrases, a.pn) 864 - res.meaning = lookup('meaning', p.means, a.mn) 880 + res.phrase = lookup('phrase', res.def.phrases, a.pn) 881 + res.meaning = lookup('meaning', res.phrase.means, a.mn) 865 882 else 866 - res.meaning = lookup('meaning', d.means, a.mn) 883 + res.meaning = lookup('meaning', res.def.means, a.mn) 867 884 end 868 885 869 886 if a.xn then 870 - res.ex = lookup('example',m.examples,a.xn) 887 + res.ex = lookup('example',res.meaning.examples,a.xn) 871 888 elseif a.nn then 872 - res.note = lookup('note',m.notes,a.nn) 889 + res.note = lookup('note',res.meaning.notes,a.nn) 873 890 end 874 891 875 892 return res 876 893 end 877 894 878 895 local function pathNav(...) 879 896 local t = pathResolve(...) ................................................................................ 936 953 help = "add a new word"; 937 954 syntax = "<word>"; 938 955 write = true; 939 956 exec = function(ctx,word) 940 957 if ctx.dict.words[word] then 941 958 id10t "word already coined" 942 959 end 943 - ctx.dict.words[word] = {defs={}} 960 + ctx.dict.words[word] = {defs={},rels={}} 944 961 end; 945 962 }; 946 963 def = { 947 964 help = "define a word"; 948 965 syntax = "<word> <part-of-speech> [<meaning> [<root>…]]"; 949 966 write = true; 950 967 exec = function(ctx,word,part,means,...) 951 968 local etym = {...} 952 969 if (not word) or not part then 953 970 id10t 'bad definition' 954 971 end 955 972 if not ctx.dict.words[word] then 956 - ctx.dict.words[word] = {defs={}} 973 + ctx.dict.words[word] = {defs={},rels={}} 957 974 end 958 975 local n = #(ctx.dict.words[word].defs)+1 959 976 ctx.dict.words[word].defs[n] = { 960 977 part = part; 961 978 branch = etym; 962 979 means = {means and { 963 980 lit=means; 964 981 examples={}; 965 982 notes={}; 983 + rels={}; 966 984 } or nil}; 967 985 forms = {}; 986 + phrases = {}; 987 + rels={}; 968 988 } 969 989 ctx.log('info', string.format('added definition #%u to “%s”', n, word)) 970 990 end; 971 991 }; 972 992 mean = { 973 993 help = "add a meaning to a definition"; 974 994 syntax = "<word> <def#> <meaning>"; ................................................................................ 1002 1022 end 1003 1023 local links={} 1004 1024 for i,l in ipairs{select(2,...)} do 1005 1025 links[i] = pathParse(l) 1006 1026 end 1007 1027 local newstruct = { 1008 1028 uid=math.random(1,0xffffFFFF); 1009 - members=links; 1010 1029 kind = rel; 1011 1030 } 1012 1031 table.insert(ctx.dict.relsets, newstruct) 1032 + for _, m in pairs(links) do 1033 + local obj = pathRef(ctx,m) 1034 + table.insert(obj.rels,newstruct.uid) 1035 + end 1013 1036 1014 - local rc = ctx.dict._relCache 1015 - for i,l in pairs(links) do 1016 - rc[l.w] = rc[l.w] or {} 1017 - table.insert(rc[l.w], newstruct) 1018 - end 1019 1037 rebuildRelationCache(ctx.dict) 1020 1038 else -- assemble a list of groups 1021 1039 local tgtw = ... 1022 1040 local wp = pathParse(tgtw) 1023 - local w,d,m = pathNav(ctx, wp) 1024 - for i,ss in ipairs(ctx.dict.relsets) do 1025 - for j,s in ipairs(ss.members) do 1026 - if pathSub(s, wp) then 1027 --- if s.word == wp.w and (wp.dn == nil or s.def == wp.dn) then 1028 - table.insert(groups, {set = ss, mem = s, id = i}) 1041 + local o = pathResolve(ctx, wp) 1042 + 1043 + for i,rs in pairs(ctx.dict.relsets) do 1044 + local allMembers = ctx.dict._relCache[rs.uid].mems 1045 + for j,s in ipairs(allMembers) do 1046 + if pathSub(s.path, wp) then 1047 + table.insert(groups, { 1048 + set = { 1049 + uid = rs.uid; 1050 + kind = rs.kind; 1051 + members = allMembers; 1052 + }; 1053 + mem = s; 1054 + id = i; 1055 + }) 1029 1056 break 1030 1057 end 1031 1058 end 1032 1059 end 1033 1060 1034 1061 if op == 'show' then 1035 1062 for i, g in ipairs(groups) do ................................................................................ 1049 1076 end 1050 1077 end 1051 1078 1052 1079 return repr 1053 1080 end 1054 1081 1055 1082 local others = {} 1056 - for j, o in ipairs(g.set.members) do 1083 + for j, oo in ipairs(g.set.members) do 1084 + local o = oo.path 1057 1085 local ow = pathResolve(ctx, {w=o.w}).w 1058 - if (g.set.kind == 'ant' or not pathMatch(o, g.mem)) and 1086 + if (g.set.kind == 'ant' or not pathMatch(o, g.mem.path)) and 1059 1087 --exclude antonym headwords 1060 1088 not (g.set.kind == 'ant' and j==1) then 1061 1089 table.insert(others, ' '..label(o,ow)) 1062 1090 end 1063 1091 end 1064 1092 local llab do 1065 1093 local cdw = ctx.dict.words 1066 1094 if g.set.kind == 'ant' then 1067 - local ap = g.set.members[1] 1095 + local ap = g.set.members[1].path 1068 1096 llab = fo.br(label(ap,cdw[ap.w]) or '') 1069 1097 else 1070 - llab = fo.br(label(g.mem,cdw[g.mem.w]) or '') 1098 + llab = fo.br(label(g.mem.path,cdw[g.mem.w]) or '') 1071 1099 end 1072 1100 end 1073 1101 local kls = { 1074 1102 syn = fo.color('synonyms',2,true)..' of'; 1075 1103 ant = fo.color('antonyms',1,true)..' of'; 1076 1104 met = fo.color('metonyms',4,true)..' of'; 1077 1105 } ................................................................................ 1082 1110 local tgtn, paths = (select(2,...)), { select(3, ...) } 1083 1111 rebuildRelationCache(ctx.dict) 1084 1112 elseif op == 'destroy' then 1085 1113 local tgtw, tgtn = ... 1086 1114 if not tgtn then id10t 'missing group number' end 1087 1115 local delendum = groups[tonumber(tgtn)] 1088 1116 if not delendum then id10t 'bad group number' end 1089 - local rs = ctx.dict.relsets 1090 - local last = #rs 1091 - if delendum.id == last then 1092 - rs[delendum.id] = nil 1093 - else -- since order doesn't matter, we can use a 1094 - -- silly swapping trick to reduce the deletion 1095 - -- worst case from O(n) to O(2) 1096 - rs[delendum.id] = rs[last] 1097 - rs[last] = nil 1117 + 1118 + for k,v in pairs(delendum.set.members) do 1119 + for idx, e in pairs(v.obj.rels) do 1120 + if e == delendum.set.uid then 1121 + fastDelete(v.obj.rels,idx) 1122 + end 1123 + end 1098 1124 end 1125 + fastDelete(ctx.dict.relsets, delendum.id) 1126 + 1099 1127 rebuildRelationCache(ctx.dict) 1100 1128 else 1101 1129 id10t 'invalid operation' 1102 1130 end 1103 1131 end 1104 1132 end; 1105 1133 }; ................................................................................ 1115 1143 help = "add a note to a definition or a paragraph to a note"; 1116 1144 syntax = {"(<m-path> (add|for) <kind> | <m-path>:<note#>) <para>…"; 1117 1145 "m-path ::= <word>@<def#>/<meaning#>"}; 1118 1146 write = true; 1119 1147 exec = function(ctx,path,...) 1120 1148 local paras, mng 1121 1149 local dest = pathParse(path) 1122 - local _,_,m = safeNavWord(ctx,dest.w,dest.dn,dest.mn) 1150 + local t = pathResolve(ctx,path) 1123 1151 if dest.nn then 1124 1152 paras = {...} 1125 1153 else 1126 1154 local op, kind = ... 1127 1155 paras = { select(3, ...) } 1128 1156 if op == 'add' then 1129 - dest.nn = #(m.notes) + 1 1130 - m.notes[dest.nn] = {kind=kind, paras=paras} 1157 + dest.nn = #(t.m.notes) + 1 1158 + t.m.notes[dest.nn] = {kind=kind, paras=paras} 1131 1159 return 1132 1160 elseif op == 'for' then 1133 - for i,nn in ipairs(m.notes) do 1161 + for i,nn in ipairs(t.m.notes) do 1134 1162 if nn.kind == kind then 1135 1163 dest.nn = i break 1136 1164 end 1137 1165 end 1138 1166 if not dest.nn then 1139 1167 id10t('no note of kind %s in %s',kind,path) 1140 1168 end 1141 1169 end 1142 1170 end 1143 - local dpa = m.notes[dest.nn].paras 1171 + local dpa = t.m.notes[dest.nn].paras 1144 1172 local top = #dpa 1145 1173 for i,p in ipairs(paras) do 1146 1174 dpa[top+i] = p 1147 1175 end 1148 1176 end 1149 1177 }; 1150 1178 shell = { ................................................................................ 1216 1244 end 1217 1245 end 1218 1246 table.sort(out, function(a,b) return a.lit < b.lit end) 1219 1247 local fo = ctx.sty[io.stdout] 1220 1248 1221 1249 local function gatherRelSets(path) 1222 1250 local antonymSets, synonymSets, metonymSets = {},{},{} 1223 - if ctx.dict._relCache[path.w] then 1224 - for i, rel in ipairs(ctx.dict._relCache[path.w]) do 1251 + local obj = pathRef(ctx,path) 1252 + if next(obj.rels) then 1253 + for i, relid in ipairs(obj.rels) do 1225 1254 local specuset,tgt,anto = {} 1226 - for j, mbr in ipairs(rel.members) do 1227 - if pathMatch(mbr, path) then 1255 + local rel = ctx.dict._relCache[relid].set 1256 + for j, mbr in ipairs(ctx.dict._relCache[relid].mems) do 1257 + if pathMatch(mbr.path, path) then 1228 1258 if rel.kind == 'syn' then tgt = synonymSets 1229 1259 elseif rel.kind == 'met' then tgt = metonymSets 1230 1260 elseif rel.kind == 'ant' then 1231 1261 if j == 1 -- is this the headword? 1232 1262 then tgt = antonymSets 1233 1263 else tgt = synonymSets 1234 1264 end 1235 1265 end 1236 1266 elseif j == 1 and rel.kind == 'ant' then 1237 - anto = mbr 1267 + anto = mbr.path 1238 1268 else 1239 - table.insert(specuset, mbr) 1269 + table.insert(specuset, mbr.path) 1240 1270 end 1241 1271 end 1242 1272 if tgt then 1243 1273 table.insert(tgt, specuset) 1244 1274 if anto then 1245 1275 table.insert(antonymSets, {anto}) 1246 1276 end ................................................................................ 1327 1357 end 1328 1358 for i, w in ipairs(out) do 1329 1359 local d = fo.ul(fo.br(w.lit)) 1330 1360 local wordrels = autobreak(table.concat( 1331 1361 formatRels(gatherRelSets{w=w.lit}, 2), 1332 1362 '\n' 1333 1363 )) 1334 - local wc = ctx.dict._relCache[w.lit] 1335 1364 if #w.word.defs == 1 then 1336 1365 d=d .. ' ' 1337 1366 .. fo.rgb(fo.em('('..(w.word.defs[1].part)..')'),.8,.5,1) .. '\n' 1338 1367 .. meanings(w,w.word.defs[1],false,1) .. '\n' 1339 1368 .. autobreak(table.concat(formatRels(gatherRelSets{w=w.lit,dn=1}, 4), '\n')) 1340 1369 .. wordrels .. '\n' 1341 1370 else ................................................................................ 1393 1422 if c ~= 'pv0' then 1394 1423 id10t "not a parvan export" 1395 1424 end 1396 1425 new.header.lang = words[2] 1397 1426 new.header.meta = words[3] 1398 1427 state = 1 1399 1428 else 1400 - print(pathString(path, ctx.sty[io.stderr])) 1401 - local W,D,M,N = pathNav({dict=new}, path) 1429 + local T = pathResolve({dict=new}, path) 1430 + local W,D,P,M,N,X = 1431 + T.word, 1432 + T.def, 1433 + T.phrase, 1434 + T.meaning, 1435 + T.note, 1436 + T.ex 1402 1437 if c == 'w' then syn(1) state = 2 1403 1438 path = {w=words[2]} 1404 - new.words[words[2]] = {defs={}} 1439 + new.words[words[2]] = {defs={},rels={}} 1405 1440 elseif c == 'f' then syn(1) 1406 1441 local nf = { 1407 1442 name = words[2]; 1408 1443 abbrev = words[3] or ""; 1409 1444 desc = words[4] or ""; 1410 1445 parts = {}; 1411 1446 } ................................................................................ 1418 1453 table.insert(lastinfl.parts,words[2]) 1419 1454 elseif c == 's' then syn(2) 1420 1455 relsets[words[3]] = relsets[words[3]] or {} 1421 1456 relsets[words[3]].kind = words[2] 1422 1457 relsets[words[3]].uid = tonumber(words[3]) 1423 1458 relsets[words[3]].members = relsets[words[3]].members or {} 1424 1459 elseif state >= 2 and c == 'r' then syn(1) 1460 + local rt 1461 + if state == 2 then 1462 + rt = W.rels 1463 + elseif state == 3 then 1464 + rt = D.rels 1465 + elseif state == 4 then 1466 + rt = D.rels 1467 + elseif state == 14 then 1468 + rt = P.rels 1469 + end 1425 1470 relsets[words[2]] = relsets[words[2]] or { 1426 - uid = tonumber(words[2]); 1471 + uid = tonumber(words[2]) or math.random(0,0xffffFFFF); 1427 1472 members={}; 1428 1473 } 1429 1474 table.insert(relsets[words[2]].members, path) 1430 1475 elseif state >= 2 and c == 'd' then syn(1) state = 3 1431 1476 table.insert(W.defs, { 1432 1477 part = words[2]; 1433 1478 branch = {}; 1434 1479 means = {}; 1435 1480 forms = {}; 1436 1481 phrases = {}; 1482 + rels = {}; 1437 1483 }) 1438 1484 path = {w = path.w, dn = #(W.defs)} 1439 1485 elseif state >= 3 and c == 'dr' then syn(1) 1440 1486 table.insert(D.branch, words[2]) 1441 1487 elseif state >= 3 and c == 'df' then syn(2) 1442 1488 if not inflmap[words[2]] then 1443 1489 id10t('no inflection form %s defined', words[2]) 1444 1490 end 1445 1491 D.forms[inflmap[words[2]]] = words[3] 1492 + elseif state >= 3 and c == 'p' then syn(1) state = 14 1493 + table.insert(D.phrases, { 1494 + str = words[2]; 1495 + means = {}; 1496 + rels = {}; 1497 + }) 1498 + path = {w = path.w, dn = path.dn, pn = #(D.phrases)} 1446 1499 elseif state >= 3 and c == 'm' then syn(1) state = 4 1447 1500 table.insert(D.means, { 1448 1501 lit = words[2]; 1449 1502 notes = {}; 1450 1503 examples = {}; 1504 + rels = {}; 1451 1505 }); 1452 - path = {w = path.w, dn = path.dn, mn = #(D.means)} 1506 + path = {w = path.w, dn = path.dn, pn=path.pn, mn = #(D.means)} 1453 1507 elseif state >= 4 and c == 'n' then syn(1) state = 5 1454 1508 table.insert(M.notes, {kind=words[2], paras={}}) 1455 - path = {w = path.w, dn = path.dn, mn = path.mn, nn = #(M.notes)}; 1509 + path = {w = path.w, dn = path.dn, pn = path.pn, mn = path.mn, nn = #(M.notes)}; 1456 1510 elseif state >= 5 and c == 'np' then syn(1) 1457 1511 table.insert(N.paras, words[2]) 1458 1512 end 1459 1513 -- we ignore invalid ctls, for sake of forward-compat 1460 1514 end 1461 1515 end 1462 1516 end ................................................................................ 1463 1517 1464 1518 for k,v in pairs(relsets) do 1465 1519 if not v.uid then 1466 1520 --handle non-numeric export ids 1467 1521 v.uid = math.random(0,0xffffFFFF) 1468 1522 end 1469 1523 table.insert(new.relsets, v) 1524 + 1525 + for q,m in pairs(v.members) do 1526 + table.insert(pathRef({dict=new},m).rels, v.uid) 1527 + end 1470 1528 end 1471 1529 1472 1530 local ofd = safeopen(ctx.file,"w+b") 1473 1531 local o = writeDict(new); 1474 1532 ofd:write(o) 1475 1533 ofd:close() 1476 1534 end ................................................................................ 1507 1565 if ctx.flags.human and lvl > 0 then 1508 1566 pfx = string.rep('\t', lvl) 1509 1567 end 1510 1568 ofd:write(pfx..string.format(...)..'\n') 1511 1569 end 1512 1570 local d = ctx.dict 1513 1571 o(0,'pv0 %s %s', san(d.header.lang), san(d.header.meta)) 1514 - local function checksyn(obj) 1515 --- for _,s in ipairs(d.synonyms) do 1516 - local lvl = 0 1517 - if obj.nn then lvl = 4 1518 - elseif obj.mn then lvl = 3 1519 - elseif obj.dn then lvl = 2 1520 - elseif obj.w then lvl = 1 end 1521 - if not d._relCache[obj.w] then return end 1522 - for _,s in ipairs(d._relCache[obj.w]) do 1523 - for _,sm in ipairs(s.members) do 1524 - if pathMatch(obj, sm) then 1525 - o(lvl,'r %u',s.uid) 1526 - break 1527 - end 1528 - end 1572 + local function checksyn(obj,lvl) 1573 + for k,v in pairs(obj.rels) do 1574 + o(lvl,'r %u',s.uid) 1529 1575 end 1530 1576 end 1531 1577 for i,f in pairs(d.header.inflectionForms) do 1532 1578 o(0,'f %s %s %s', san(f.name), san(f.abbrev), san(f.desc)) 1533 1579 for j,p in pairs(f.parts) do 1534 1580 o(1,'fp %s', san(p)) 1535 1581 end 1582 + end 1583 + local function scanMeans(tbl,path,lvl) 1584 + for j,m in ipairs(def.means) do 1585 + o(lvl,'m %s', san(m.lit)) 1586 + local lp = copy(path) 1587 + lp.mn = j 1588 + checksyn(m,lp,lvl+1) 1589 + for k,n in ipairs(m.notes) do 1590 + o(lvl+1,'n %s', san(n.kind)) 1591 + for a,p in ipairs(n.paras) do 1592 + o(lvl+2,'np %s', san(p)) 1593 + end 1594 + end 1595 + end 1536 1596 end 1537 1597 for lit, w in pairs(d.words) do 1538 1598 o(0,'w %s',san(lit)) 1539 - checksyn{w=lit} 1599 + checksyn(w,{w=lit},1) 1540 1600 for i,def in ipairs(w.defs) do 1541 1601 o(1,'d %s',san(def.part)) 1542 - checksyn{w=lit,dn=i} 1602 + checksyn(def,{w=lit,dn=i},2) 1543 1603 for j,r in ipairs(def.branch) do 1544 1604 o(2,'dr %s',san(r)) 1545 1605 end 1546 - for j,m in ipairs(def.means) do 1547 - o(2,'m %s', san(m.lit)) 1548 - checksyn{w=lit,dn=i,mn=j} 1549 - for k,n in ipairs(m.notes) do 1550 - o(3,'n %s', san(n.kind)) 1551 - for a,p in ipairs(n.paras) do 1552 - o(4,'np %s', san(p)) 1553 - end 1554 - end 1606 + for j,p in ipairs(def.phrases) do 1607 + o(2,'p %s',san(p.str)) 1608 + scanMeans(p.means, {w=lit,dn=i,pn=j}, 3) 1555 1609 end 1610 + scanMeans(def.means, {w=lit,dn=i}, 2) 1556 1611 end 1557 1612 end 1558 1613 for _,s in ipairs(d.relsets) do o(0,'s %s %u', s.kind, s.uid) end 1559 1614 end 1560 1615 1561 1616 local function filterD(lst, fn) 1562 1617 -- cheap algorithm to destructively filter a list ................................................................................ 1569 1624 top = top - 1 1570 1625 end 1571 1626 end 1572 1627 return lst 1573 1628 end 1574 1629 1575 1630 function cmds.mod.exec(ctx, orig, oper, dest, ...) 1576 - if (not orig) or not oper then 1577 - id10t '`mod` requires at least an origin and an operation' 1578 - end 1579 - local op, dp = pathParse(orig) 1580 - local w,d,m,n = safeNavWord(ctx, op.w,op.dn,op.mn,op.nn) 1581 - -- unfortunately, "pointers" exist elsewhere in the 1582 - -- structure, currently just from relsets, that must 1583 - -- be updated whenever an object moves or changes. 1584 - -- this is miserable and takes a lot of work, using 1585 - -- algorithms provided by the following functions. 1586 - -- note that we don't bother trying to update the 1587 - -- relcache as we go, it's simply not worth the work; 1588 - -- instead we simply rebuild the whole cache when 1589 - -- this command returns 1590 - local function cleanupRels(path, fn) 1591 - local rc = ctx.dict._relCache[path.w] 1592 - if rc then 1593 - for k,s in pairs(rc) do fn(s,k) end 1594 - end 1595 - end 1596 - local function cleanupRelsEach(path, fn) 1597 - cleanupRels(path, function(s,k) 1598 - local top = #s.members 1599 - for i=1,top do local m=s.members[i] 1600 - if pathSub(path, m) then 1601 - local val = fn(m,s,i) 1602 - if val ~= nil then 1603 - s.members[i] = val 1604 - end 1605 - end 1606 - end 1607 - end) 1608 - end 1609 - local function deleteRefsTo(path) 1610 - cleanupRels(path, function(s) 1611 - -- antonyms: delete the headword and transform the group 1612 - -- to a list of synonyms 1613 - if s.kind == 'ant' and pathSub(path,s.members[1]) then 1614 - s.kind = 'syn' 1615 - end 1616 - filterD(s.members, function(m) 1617 - return not pathSub(path, m) 1618 - end) 1619 - end) 1620 - if not path.dn then 1621 - ctx.dict._relCache[path.w] = nil 1622 - end 1623 - end 1624 - local function moveRelTree(op,dp) 1625 - cleanupRelsEach(op, function(old,set,idx) 1626 - local new = {} 1627 - for _,elt in pairs{'w','dn','mn','nn'} do 1628 - if dp[elt] ~= nil then 1629 - new[elt] = dp[elt] 1630 - else 1631 - new[elt] = op[elt] or old[elt] 1632 - end 1633 - end 1634 - return new 1635 - end) 1636 - end 1637 - local function shiftRelTree(dp, fld, nid, amt) 1638 - local cleanupTargetMask = ({ 1639 - dn = {w=dp.w}; 1640 - mn = {w=dp.w,dn=dp.dn}; 1641 - nn = {w=dp.w,dn=dp.dn,mn=dp.mn}; 1642 - })[fld] -- >____< 1643 - cleanupRelsEach(cleanupTargetMask, function(old,set,i) 1644 - if old[fld] >= nid then 1645 - old[fld] = old[fld] + amt 1646 - end 1647 - end) 1648 - end 1649 - local function insertAndMoveRelTree(tbl,n,op,dp,fld) 1650 - local nid = #tbl 1651 - local path = copy(dp) 1652 - path[fld] = nid 1653 - tbl[nid] = n 1654 - shiftRelTree(dp,fld,1) 1655 - moveRelTree(op, path) 1656 - end 1657 - if oper == 'drop' then 1658 - -- clean out the cache and delete relationships 1659 - deleteRefsTo(op) 1660 - if not d then 1661 - ctx.dict.words[op.w] = nil 1662 - elseif not m then 1663 - table.remove(w.defs, op.dn) 1664 - elseif not n then 1665 - table.remove(d.means, op.mn) 1666 - else 1667 - table.remove(m.notes, op.nn) 1668 - end 1669 - elseif oper == 'out' then 1670 - if n or not m then id10t '`mod out` must target a meaning' end 1671 - if not dest then id10t '`mod out` requires at least a part of speech' end 1672 - local newdef = { 1673 - part = dest; 1674 - branch = {...}; 1675 - forms = {}; 1676 - means = {m}; 1677 - } 1678 - shiftRelTree(op, 'dn', op.dn, 1) 1679 - table.insert(w.defs,op.dn+1, newdef) 1680 - moveRelTree(op,{w=op.w, dn=op.dn+1, mn=1}) 1681 - table.remove(d.means,op.mn) 1682 - elseif oper == 'move' or oper == 'merge' or oper == 'clobber' then 1683 - if dest 1684 - then dp = pathParse(dest) 1685 - else id10t('`mod %s` requires a target',oper) 1686 - end 1687 - if n then 1688 - if not dp.mn then 1689 - id10t '`mod` on a note requires a note or meaning destination' 1690 - end 1691 - local _,_,dm = safeNavWord(ctx, dp.w,dp.dn,dp.mn) 1692 - if dp.nn then 1693 - if oper == 'move' then 1694 - shiftRelTree(dp, 'nn', dp.nn, 1) 1695 - table.insert(dm.notes, dp.nn, n) 1696 - elseif oper == 'merge' then 1697 - local top = #(dm.notes[dp.nn].paras) 1698 - for i, v in ipairs(n.paras) do 1699 - dm.notes[dp.nn].paras[i+top] = v 1700 - end 1701 - elseif oper == 'clobber' then 1702 - deleteRefsTo(dp) 1703 - dm.notes[dp.nn] = n 1704 - end 1705 - moveRelTree(op,dp) 1706 - else 1707 - if oper ~= 'move' then 1708 - id10t('`mod note %s` requires a note target', oper) 1709 - end 1710 - insertAndMoveRelTree(dm.notes,n,op,dp,'nn') 1711 - end 1712 - if oper == 'move' and dp.nn and dm == m and op.nn > dp.nn then 1713 - table.remove(m.notes,op.nn+1) 1714 - else 1715 - table.remove(m.notes,op.nn) 1716 - end 1717 - elseif m then 1718 - if not dp.dn then 1719 - local newdef = { 1720 - part = d.part; 1721 - branch = copy(d.branch); 1722 - forms = copy(d.forms); 1723 - means = {m}; 1724 - } 1725 - local didx 1726 - if ctx.dict.words[dp.w] then 1727 - local defst = ctx.dict.words[dp.w].defs 1728 - didx = #defst 1729 - defst[didx] = newdef 1730 - else 1731 - ctx.dict.words[dp.w] = { 1732 - defs = {newdef}; 1733 - } 1734 - didx = 1 1735 - end 1736 - cleanupRelsEach(op, function(oldpath,set,mi) 1737 - return {w=dp.w,dn=didx,mn=1,nn=oldpath.nn} 1738 - end) 1739 - table.remove(d.means,dp.mn) 1740 - else 1741 - local dw, dd = safeNavWord(ctx, dp.w, dp.dn) 1742 - if dp.mn then 1743 - if dd.means[dp.mn] and (oper == 'merge' or oper=='clobber') then 1744 - if oper == 'merge' then 1745 - dd.means[dp.mn] = dd.means[dp.mn] .. '; ' .. m 1746 - elseif oper == 'clobber' then 1747 - deleteRefsTo(dp) 1748 - dd.means[dp.mn] = m 1749 - end 1750 - else 1751 - cleanupRelsEach({w=dp.w,dn=dp.dn}, function(old,set,i) 1752 - if old.mn >= dp.mn then 1753 - old.mn = old.mn + 1 1754 - end 1755 - end) 1756 - table.insert(dd.means, dp.mn, m) 1757 - end 1758 - moveRelTree(op,dp) 1759 - else 1760 - insertAndMoveRelTree(dd.means,m, op,dp,'mn') 1761 --- table.insert(dd.means, m) 1762 - end 1763 - if oper == 'move' and dp.mn and dd.means == d.means and op.mn > dp.mn then 1764 - table.remove(d.means,op.mn+1) 1765 - else 1766 - table.remove(d.means,op.mn) 1767 - end 1768 - end 1769 - elseif d then 1770 - local ddefs = safeNavWord(ctx, dp.w).defs 1771 - if dp.dn then 1772 - if oper == 'merge' then 1773 - local top = #(ddefs[dp.dn].means) 1774 - for i,om in ipairs(d.means) do 1775 - ddefs[dp.dn].means[top+i] = om 1776 - end 1777 - for k,p in pairs(d.forms) do 1778 - deleteRefsTo(dp) 1779 - ddefs[dp.dn].forms[k] = p -- clobbers! 1780 - end 1781 - else 1782 - shiftRelTree(dp,'dn',dp.dn,1) 1783 - table.insert(ddefs, dp.dn, d) 1784 - end 1785 - moveRelTree(op,dp) 1786 - else 1787 - insertAndMoveRelTree(ddefs,d, op,dp,'dn') 1788 --- table.insert(ddefs, d) 1789 - end 1790 - if oper == 'move' and dp.mn and w.defs == ddefs and op.mn > dp.mn then 1791 - table.remove(w.defs,op.dn+1) 1792 - else 1793 - table.remove(w.defs,op.dn) 1794 - end 1795 - else 1796 - if ctx.dict.words[dp.w] then 1797 - if oper ~= 'merge' then 1798 - id10t('the word “%s” already exists; use `merge` if you want to merge the words together', dp.w) 1799 - end 1800 - for i,def in ipairs(w.defs) do 1801 - local odp = copy(op) odp.dn = i 1802 - local ddp = {w=dp.w, dn=dp.dn+i-1} 1803 - if dp.dn then 1804 - shiftRelTree(dp, 'dn', dp.dn+i-1, 1) 1805 - table.insert(ctx.dict.words[dp.w].defs, dp.dn+i-1, def) 1806 - moveRelTree(odp,ddp) 1807 - else 1808 --- table.insert(ctx.dict.words[dp.w].defs, def) 1809 - insertAndMoveRelTree(ctx.dict.words[dp.w].defs, def, 1810 - odp,dp,'dn') 1811 - end 1812 - end 1813 - else 1814 - ctx.dict.words[dp.w] = w 1815 - moveRelTree(op,dp) 1816 --- ctx.dict._relCache[dp.w] = ctx.dict._relCache[op.w] 1817 --- ctx.dict._relCache[op.w] = nil 1818 - end 1819 - ctx.dict.words[op.w] = nil 1820 - end 1821 - end 1822 1631 rebuildRelationCache(ctx.dict) 1823 1632 end 1824 1633 1825 1634 local function fileLegible(file) 1826 1635 -- check if we can access the file 1827 1636 local fd = io.open(file,"rb") 1828 1637 local ret = false