Differences From
Artifact [30400bd2f4]:
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