Overview
Comment: | add termcolors, update parvan and compose |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
54874eb3ebe45d0f80603bf99a0808e3 |
User & Date: | lexi on 2022-12-26 13:31:53 |
Other Links: | manifest | tags |
Context
2022-12-30
| ||
23:55 | make language more offensive check-in: b4fe00021c user: lexi tags: trunk | |
2022-12-26
| ||
13:31 | add termcolors, update parvan and compose check-in: 54874eb3eb user: lexi tags: trunk | |
2022-11-01
| ||
20:05 | fixes check-in: 987a1aac03 user: lexi tags: trunk | |
Changes
Modified clib/compose.c from [5d94fe752b] to [9e5668944d].
7 7 * compose.c contains some useful string utilities 8 8 * that libc lacks. 9 9 10 10 TODO add macro to use homebrew strlen instead of 11 11 dragging in <string.h> */ 12 12 13 13 #ifdef k_header // only emit the declarations 14 -# define fn(x) 14 +# define k_impl(x) 15 15 #else 16 -# define fn(x) x 16 +# define k_impl(x) x 17 17 #endif 18 18 19 19 #include <string.h> 20 20 21 21 typedef struct pstr { size_t len; union { 22 22 const char* ptr; 23 23 char* mutptr; ................................................................................ 32 32 # ifndef k_static 33 33 bool heap; 34 34 # endif 35 35 size_t len; 36 36 } safestr; 37 37 38 38 #ifndef k_static 39 - void delstr(safestr s) fn ({ 39 + void delstr(safestr s) k_impl ({ 40 40 if (s.heap) { free(s.mutptr); } 41 41 }); 42 42 #endif 43 43 44 -void clrstr(safestr* s) fn ({ 44 +void clrstr(safestr* s) k_impl ({ 45 45 # ifndef k_static 46 46 delstr(*s); 47 47 s->heap = false; 48 48 # endif 49 49 s->ptr = NULL; 50 50 s->len = 0; 51 51 }) 52 52 53 -size_t pstrsum(pstr* lst,size_t ct) fn({ 53 +size_t pstrsum(pstr* lst,size_t ct) k_impl ({ 54 54 size_t len = 0; 55 55 for (size_t i = 0; i < ct; ++i) { 56 56 if (lst[i].len == 0) { 57 57 if (lst[i].ptr == NULL) continue; 58 58 lst[i].len = strlen(lst[i].ptr); 59 59 } 60 60 len += lst[i].len; 61 61 } 62 62 return len; 63 63 }) 64 64 65 -char* pstrcoll(pstr* lst, size_t ct, char* ptr) fn({ 65 +char* pstrcoll(pstr* lst, size_t ct, char* ptr) k_impl ({ 66 66 for (size_t i = 0; i < ct; ++i) { 67 67 if (lst[i].len == 0) continue; 68 68 strncpy(ptr,lst[i].ptr,lst[i].len); 69 69 ptr += lst[i].len; 70 70 } 71 71 return ptr; 72 72 }) 73 73 74 74 #ifndef k_static 75 -char* compose(pstr* lst,size_t ct, size_t* strsz) fn({ 75 +char* compose(pstr* lst,size_t ct, size_t* strsz) k_impl ({ 76 76 size_t len = pstrsum(lst,ct) 77 77 if (strsz != NULL) *strsz = len; 78 78 if (len == 0) return NULL; 79 79 80 80 char* str = malloc(len + 1); 81 81 char* ptr = pstrcoll(lst, ct, ptr); 82 82 *ptr = 0; 83 83 return str; 84 84 }); 85 85 #endif 86 86 87 -char* impose(pstr* lst,size_t ct, size_t* strsz, char* buf) fn({ 87 +char* impose(pstr* lst,size_t ct, size_t* strsz, char* buf) k_impl({ 88 88 size_t len = pstrsum(lst,ct); 89 89 if (strsz != NULL) *strsz = len; 90 90 if (len == 0) return NULL; 91 91 92 92 char* ptr = pstrcoll(lst, ct, buf); 93 93 *ptr = 0; 94 94 return ptr; 95 95 }); 96 96 97 -char* imprint(pstr lst, size_t* strsz, char* buf) fn({ 97 +char* imprint(pstr lst, size_t* strsz, char* buf) k_impl({ 98 98 size_t len = pstrsum(&lst,1); 99 99 if (strsz != NULL) *strsz = len; 100 100 if (len == 0) return NULL; 101 101 102 102 char* ptr = pstrcoll(&lst,1,buf); 103 103 *ptr = 0; 104 104 return ptr; 105 105 }); 106 -#undef fn 106 +#undef k_impl
Modified parvan.lua from [af37224306] to [3480abaab0].
6 6 -- < Commission for Defense Communication > 7 7 -- +WCO Worlds Culture Overdirectorate 8 8 -- SSD Social Sciences Directorate 9 9 -- ELS External Linguistics Subdirectorate 10 10 -- +WSO Worlds Security Overdirectorate 11 11 -- EID External Influence Directorate ] 12 12 13 +--# parvan 14 +-- 15 +--## orthographies 16 +-- parvan supports encoding words using multiple 17 +-- orthographies. every database has a "primary" 18 +-- orthography, which must be Unicode- or ASCII- 19 +-- compatible, and which is used as the basis of the 20 +-- uniform object paths. other orthographies can be 21 +-- managed with the [$script] command, which can set 22 +-- how they are displayed to the user. every word 23 +-- can have zero or more representations mapped to a 24 +-- particular orthography. 25 +-- 26 +--## file format 27 +-- parvan defines two separate file formats, both 28 +-- representations of a dictionary. one, the "working 29 +-- format", is binary; the other, the "exchange format" 30 +-- is comprised of UTF8 codepoint sequences and can be 31 +-- (to some degree) written and read by human beings, 32 +-- tho its primary purpose is as a line-based format 33 +-- that allows parvan dictionaries to be managed with 34 +-- conventional source control solutions like fossil 35 +-- or git 36 +-- 37 +--## magic numbers 38 +-- all parvan files share the same 4-byte header. it 39 +-- is comprised of the sequence 40 +-- [$ 0x50 0x56 $VERS $SUBTYPE ] 41 +-- where [$$VERS] is a byte that is altered whenever a 42 +-- breaking change is made to the format. [$$SUBTYPE] 43 +-- indicates whether the file is binary- or text-based. 44 +-- the byte 0x20 indicates an exchange file, while 45 +-- 0x02 indicates a binary database. 46 +-- 47 +--## extensions 48 +-- parvan recommends the use of the extension [$*.pv] 49 +-- for its binary databases, and [$*.pvx] for the 50 +-- exchange format. 51 +-- 52 +--## styled text 53 +-- text in parvan documents should be written using 54 +-- cortav syntax. at some future time this will 55 +-- hopefully be used to generate styled output where 56 +-- possible 57 + 13 58 local function implies(a,b) return a==b or not(a) end 14 59 15 60 local function map(lst,fn) 16 61 local new = {} 17 62 for k,v in pairs(lst) do 18 63 local nv, nk = fn(v,k) 19 64 new[nk or k] = nv ................................................................................ 50 95 if src == nil then return end 51 96 local sc = #src 52 97 for j=1,sc do dest[i+j] = src[j] end 53 98 i = i + sc 54 99 iter(...) 55 100 end 56 101 iter(...) 102 +end 103 +local function mergeD(dest, tbl, ...) 104 + if tbl == nil then return dest end 105 + for k,v in pairs(tbl) do dest[k] = v end 106 + return mergeD(dest, ...) 107 +end 108 +local function merge(...) 109 + local t = {} 110 + return mergeD(t, ...) 111 +end 112 +local function copy(t,...) 113 + return merge(t), copy(...) 57 114 end 58 115 local function fastDelete(table,idx) 59 116 -- delete without preserving table order 60 117 local l = #table 61 118 table[idx] = table[l] 62 119 table[l] = nil 63 120 return table 64 121 end 65 122 local function tcat(...) 66 123 local new = {} 67 124 tcatD(new, ...) 68 125 return new 69 126 end 127 +local function iota(n,m) 128 + if not m then return iota(1,n) end 129 + if n == m then return n end 130 + return n, iota(n+1,m) 131 +end 132 +local function keys(m) 133 + local i,ks = 1,{} 134 + for k in next, m do 135 + ks[i] = k 136 + i = i + 1 137 + end 138 + return ks 139 +end 140 + 70 141 local ansi = { 71 142 levels = { 72 143 plain = 0; 73 144 ansi = 1; 74 145 color = 2; 75 146 color8b = 3; 76 147 color24b = 4; ................................................................................ 112 183 local id = function(...) return ... end 113 184 local esc = '\27' 114 185 local f = {} 115 186 for k,v in pairs(ansi.seqs) do 116 187 local lvl, on, off = table.unpack(v) 117 188 if lvl <= cl then 118 189 f[k] = function(s) 119 - return esc..on .. s .. esc..off 190 + return (esc..on) .. s .. (esc..off) 120 191 end 121 192 else f[k] = id end 122 193 end 123 194 local function ftoi(r,g,b) 124 195 return math.ceil(r*0xff), 125 196 math.ceil(g*0xff), 126 197 math.ceil(b*0xff) 127 198 end 128 199 local reset = "\27[39m" 129 - function f.color(str, n, br) 130 - return string.format("\27[%s%cm", 131 - (bg and 4 or 3) + 132 - (br and 6 or 0), 0x30+n) 133 - .. str .. reset 200 + function f.color(str, n, bright) 201 + if n<=15 then 202 + return string.format("\27[%s%cm", 203 + (bg and 4 or 3) + 204 + (br and 6 or 0), 0x30+n) 205 + .. str .. reset 206 + else 207 + return string.format("\27[%c8;5;%sm", 208 + (bg and 0x34 or 0x33), n) 209 + .. str .. reset 210 + end 134 211 end 135 212 function f.resetLine() 136 213 return '\27[1K\13' 137 214 end 138 215 if cl == ansi.levels.color24b then 139 216 function f.rgb(str, r,g,b, bg) 140 217 return string.format("\27[%c8;2;%u;%u;%um", bg and 0x34 or 0x33, ................................................................................ 295 372 for i=1,n do 296 373 table.insert(vals, parse(t, s)) 297 374 end 298 375 return vals 299 376 end; 300 377 } 301 378 end 379 + 380 +fmt.any = function(struct) 381 + local map, keylist = {},{} 382 + for i,v in ipairs(struct) do 383 + if type(v) ~= 'string' then 384 + map[v[1]] = v[2] 385 + v = v[1] 386 + else 387 + map[v] = true 388 + end 389 + table.insert(keylist, v) 390 + end 391 + local tdisc = fmt.enum(table.unpack(keylist)) 392 + return { 393 + encode = function(a) 394 + if type(a) == 'string' and map[a] == true then 395 + return marshal(tdisc, a) 396 + else 397 + local tname, obj = table.unpack(a) 398 + assert(map[tname] ~= true, '`any` enumeration '..tostring(tname)..' has no associated struct') 399 + return marshal(tdisc, tname) .. 400 + marshal(map[tname], obj) 401 + end 402 + end; 403 + decode = function(s) 404 + local tname = parse(tdisc, s) 405 + if map[tname] ~= true then 406 + local obj = parse(map[tname], s) 407 + return {tname,obj} 408 + else return tname end 409 + end; 410 + } 411 +end 302 412 303 413 fmt.map = function(from,to,ity) 304 414 local ent = fmt.list({ 305 415 {'key', from}, 306 416 {'val', to} 307 417 }, ity) 308 418 return { ................................................................................ 315 425 end; 316 426 decode = function(s) 317 427 local lst = ent.decode(s) 318 428 local m = {} 319 429 for _,p in pairs(lst) do m[p.key] = p.val end 320 430 return m 321 431 end; 432 + null = function() return {} end; 322 433 } 323 434 end 324 435 325 436 fmt.enum = function(...) 326 437 local vals,rmap = {...},{} 327 438 for k,v in pairs(vals) do rmap[v] = k-1 end 328 439 local ty = fmt.u8 ................................................................................ 338 449 if (n+1) > #vals then error(string.format('enum "%s" does not have %u members', table.concat(vals,'","'),n),3) end 339 450 return vals[n+1] 340 451 end; 341 452 } 342 453 end 343 454 344 455 fmt.uid = fmt.u32 456 +fmt.blob = fmt.string 345 457 346 458 fmt.relatable = function(ty) 347 459 return tcat(ty,{ 348 460 {'rels',fmt.list(fmt.uid,fmt.u16)}; 349 461 }) 350 462 end 351 463 ................................................................................ 365 477 {'notes', fmt.list(fmt.note,fmt.u8)}; 366 478 } 367 479 368 480 fmt.phrase = fmt.relatable { 369 481 {'str',fmt.label}; 370 482 {'means',fmt.list(fmt.meaning,fmt.u8)}; 371 483 } 484 + 485 +fmt.ortho = fmt.map(fmt.uid, fmt.blob, fmt.u8) 486 +-- UID <0> is always the UTF-8 representation of the primary ortho 487 + 488 +fmt.writing = { 489 + {'enc',fmt.ortho}; -- if empty, print the morphs in sequence 490 + {'info',fmt.label}; 491 + {'morphs',fmt.list(fmt.uid,fmt.u16)}; 492 +} 372 493 373 494 fmt.def = fmt.relatable { 495 + {'writings', fmt.list(fmt.writing,fmt.u8)}; 496 + -- for japanese-like languages where words that are 497 + -- pronounced/written the same under the indexing 498 + -- orthography have alternate writings that are 499 + -- definition-specific. e.g. words よう and さま 500 + -- would both have a definition written as 様 501 + -- ordinary languages will have only 1 writing 374 502 {'part', fmt.u8}; 375 503 {'branch', fmt.list(fmt.label,fmt.u8)}; 376 504 {'means', fmt.list(fmt.meaning,fmt.u8)}; 377 505 {'forms', fmt.map(fmt.u16,fmt.label,fmt.u16)}; 378 506 {'phrases', fmt.list(fmt.phrase,fmt.u16)}; 379 507 } 380 508 381 509 fmt.word = fmt.relatable { 382 510 {'defs', fmt.list(fmt.def,fmt.u8)}; 511 + -- store secondary encodings of this word 512 + {'enc', fmt.ortho}; 513 +} 514 + 515 +fmt.orthography = { 516 + {'uid', fmt.uid}; 517 + {'name', fmt.tag}; 518 + {'repr', fmt.any{ 519 + 'utf8'; -- display as utf-8 compatible text 520 + 'opaque'; -- do not display at all; used only by other tools 521 + 'bytes'; -- display as raw hexadecimal bytes 522 + {'int',fmt.u8}; -- display as a series of integers (n=byte len) 523 + {'glyphs',{ -- map to a palette of custom glyphs. 524 + -- treated as 'opaque' in text-only environments 525 + {'glyphs', fmt.list { 526 + {'image',fmt.blob}; 527 + {'name',fmt.tag}; 528 + }}; 529 + {'encoding', fmt.u8}; -- number of bytes per codepoint 530 + {'format',fmt.enum('svg','bmp','png')}; 531 + }}; 532 + }}; 383 533 } 384 534 385 535 fmt.dictHeader = { 386 536 {'lang', fmt.tag}; 387 537 {'meta', fmt.string}; 388 538 {'partsOfSpeech', fmt.list(fmt.tag,fmt.u16)}; 389 539 {'inflectionForms', fmt.list({ ................................................................................ 390 540 {'name', fmt.tag}; 391 541 {'abbrev', fmt.tag}; 392 542 {'desc', fmt.string}; 393 543 {'parts', fmt.list(fmt.tag,fmt.u8)}; 394 544 -- which parts of speech does this form apply to? 395 545 -- leave empty if not relevant 396 546 },fmt.u16)}; 547 + {'orthographies', fmt.list(fmt.orthography,fmt.u8)} 397 548 } 398 549 399 550 fmt.relSet = { 400 551 {'uid', fmt.uid}; 401 552 -- IDs are persistent random values so they can be used 402 553 -- as reliable identifiers even when merging exports in 403 554 -- a parvan-unaware VCS 404 555 {'kind', fmt.enum('syn','ant','met')}; 405 556 -- membership is stored in individual objects, using a field 406 557 -- attached by the 'relatable' template 407 558 } 559 + 560 +fmt.pair = function(k,v) return { 561 + {'key',k or fmt.tag}; 562 + {'val', v or fmt.blob}; 563 +} end 564 + 565 +fmt.morph = { 566 + {'name',fmt.tag}; 567 + {'enc', fmt.ortho}; 568 + {'meta', fmt.list(fmt.pair(nil,fmt.string),fmt.u16)}; 569 + {'rads', fmt.list(fmt.uid,fmt.u16)}; 570 +} 408 571 409 572 fmt.dict = { 410 573 {'header', fmt.dictHeader}; 411 574 {'words', fmt.map(fmt.string,fmt.word)}; 412 575 {'relsets', fmt.list(fmt.relSet)}; 576 + {'morphs', fmt.map(fmt.uid,fmt.morph)}; 413 577 } 414 578 415 -function marshal(ty, val) 579 +function marshal(ty, val, pvers) 580 + pvers = pvers or 0 416 581 if ty.encode then 417 582 return ty.encode(val) 418 583 end 419 584 local ac = {} 420 585 421 586 for idx,fld in ipairs(ty) do 422 - local name, fty = table.unpack(fld) 423 - table.insert(ac, marshal(fty, 424 - assert(val[name], 425 - string.format('marshalling error: missing field %s', name) 426 - ) 427 - )) 587 + local name, fty, vers = table.unpack(fld) 588 + vers = vers or 0 589 + if pvers >= vers then 590 + table.insert(ac, marshal(fty, 591 + assert(val[name], 592 + string.format('marshalling error: missing field %s', name) 593 + ), 594 + pvers)) 595 + end 428 596 end 429 597 430 598 return table.concat(ac) 431 599 end 432 600 433 -function parse(ty, stream) 601 +function parse(ty, stream, pvers) 602 + pvers = pvers or 0 434 603 if ty.decode then 435 604 return ty.decode(stream) 436 605 end 437 606 438 607 local obj = {} 439 608 for idx,fld in ipairs(ty) do 440 - local name, fty = table.unpack(fld) 441 - obj[name] = parse(fty, stream) 609 + local name, fty, vers, dflt = table.unpack(fld) 610 + vers = vers or 0 611 + if pvers >= vers then 612 + obj[name] = parse(fty, stream, pvers) 613 + else 614 + obj[name] = dflt 615 + end 442 616 end 443 617 return obj 444 618 end 445 619 446 620 local function 447 621 atomizer() 448 622 local map = {} ................................................................................ 505 679 return 'PV0\2'..marshal(fmt.dict, d) 506 680 end 507 681 508 682 local function 509 683 readDict(file) 510 684 local s = stream(file) 511 685 local magic = s:next 'c4' 512 - if magic ~= 'PV0\2' then 513 - id10t 'not a parvan file' 686 + if magic == 'PV0 ' then 687 + id10t 'text-based dictionaries must be translated to binary using the `import` command before they can be used' 688 + elseif magic ~= 'PV0\2' then 689 + id10t 'not a parvan0 file' 514 690 end 515 691 local d = parse(fmt.dict, s) 516 692 -- handle atoms 517 693 for lit,w in pairs(d.words) do 518 694 for j,def in ipairs(w.defs) do 519 695 def.part = d.header.partsOfSpeech[def.part] 520 696 end ................................................................................ 524 700 -- enable faster lookup that would otherwise require 525 701 -- expensive scans 526 702 rebuildRelationCache(d) 527 703 return d 528 704 end 529 705 530 706 531 -local function strwords(str) -- here be dragons 707 +local function strwords(str,maxwords) -- here be dragons 532 708 local wds = {} 533 709 local w = {} 534 710 local state, d, quo, dquo = 0,0 535 711 local function flush(n,final) 536 712 if next(w) or state ~= 0 and state < 10 then 537 713 table.insert(wds, utf8.char(table.unpack(w))) 538 714 w = {} ................................................................................ 539 715 elseif final and state > 10 then 540 716 table.insert(wds, '\\') 541 717 end 542 718 state = n 543 719 quo = nil 544 720 dquo = nil 545 721 d = 0 722 + if #wds == maxwords then 723 + state = 100 724 + end 546 725 end 547 726 local function isws(c) 548 727 return c == 0x20 or c == 0x09 or c == 0x0a 549 728 end 550 729 for p,cp in utf8.codes(str) do 551 730 if state == 0 then -- begin 552 731 if not(isws(cp)) then ................................................................................ 594 773 -- 12 = quote escape, 11 = raw escape 595 774 if cp == 0x63 then --n 596 775 table.insert(w,0x0a) 597 776 else 598 777 table.insert(w,cp) 599 778 end 600 779 state = state - 10 780 + elseif state == 100 then -- word limit reached 781 + -- triggered from flush 782 + table.insert(wds, string.sub(str, p)) 783 + return wds 601 784 end 602 785 end 603 786 flush(nil,true) 604 787 return wds 605 788 end 789 + 790 +local function strsan(str) 791 + local d,m = 0,0 792 + local r = {} 793 + local unclosed = {} 794 + local i = 1 795 + for bytepos,cp in utf8.codes(str) do 796 + -- insert backslashes for characters that would 797 + -- disrupt strwords() parsing 798 + if cp == 0x0a then 799 + table.insert(r, 0x5c) 800 + table.insert(r, 0x6e) i=i+2 801 + else 802 + if cp == 0x5b then 803 + d = d + 1 804 + table.insert(unclosed,i) 805 + elseif cp == 0x5d then 806 + if d >= 1 then 807 + d = d - 1 808 + unclosed[rawlen(unclosed)] = nil 809 + else 810 + table.insert(r, 0x5c) i=i+1 811 + end 812 + end 813 + table.insert(r, cp) i=i+1 814 + end 815 + end 816 + for j=#unclosed,1,-1 do 817 + table.insert(r,unclosed[j],0x5c) 818 + end 819 + return '[' .. utf8.char(table.unpack(r)) .. ']' 820 +end 606 821 607 822 local predicates 608 823 local function parsefilter(str) 609 824 local f = strwords(str) 610 825 if #f == 1 then return function(e) return predicates.lit.fn(e,f[1]) end end 611 - if not predicates[f[1]] then 826 + if not next(f) then 827 + -- null predicate matches all 828 + return function() return true end 829 + elseif not predicates[f[1]] then 612 830 id10t('no such predicate %s',f[1]) 613 831 else 614 832 local p = predicates[f[1]].fn 615 833 return function(e) 616 834 return p(e, table.unpack(f,2)) 617 835 end 618 836 end ................................................................................ 633 851 end; 634 852 local function p_none(e,pred,...) 635 853 if pred == nil then return true end 636 854 pred = parsefilter(pred) 637 855 if pred(e) then return false end 638 856 return p_none(e,...) 639 857 end; 640 - local function p_some(e,count,pred,...) 641 - if count == 0 then return true end 642 - if pred == nil then return false end 643 - pred = parsefilter(pred) 644 - if pred(e) then 645 - count = count-1 858 + local function p_some(e,cmp,count,...) 859 + local cfn = { 860 + eq = function(a,b) return a == b end; 861 + ne = function(a,b) return a ~= b end; 862 + lt = function(a,b) return a < b end; 863 + gt = function(a,b) return a > b end; 864 + } 865 + if not cfn[cmp] then 866 + id10t('[some %s]: invalid comparator', cmp) 646 867 end 647 - return p_some(e,count,...) 868 + count = tonumber(count) 869 + local function rec(n,pred,...) 870 + if pred == nil then 871 + return cfn[cmp](n,count) 872 + end 873 + pred = parsefilter(pred) 874 + if pred(e) then 875 + n=n+1 876 + end 877 + return rec(n,...) 878 + end 879 + return rec(0,...) 648 880 end; 649 881 650 882 local function prepScan(...) 651 883 local map = {} 652 884 local tgt = select('#',...) 653 885 for _,v in pairs{...} do map[v] = true end 654 886 return map,tgt 655 887 end 656 888 predicates = { 657 889 all = { 658 890 fn = p_all; 659 891 syntax = '<pred>…'; 660 - help = 'every sub-<pred> matches' 892 + help = 'every sub-<pred> matches'; 661 893 }; 662 894 any = { 663 895 fn = p_any; 664 896 syntax = '<pred>…'; 665 - help = 'any sub-<pred> matches' 897 + help = 'any sub-<pred> matches'; 666 898 }; 667 899 none = { 668 900 fn = p_none; 669 901 syntax = '<pred>…'; 670 - help = 'no sub-<pred> matches' 902 + help = 'no sub-<pred> matches (also useful to force evaluation for side effects without creates matches)'; 671 903 }; 672 904 some = { 673 905 fn = p_some; 674 - syntax = '<count> <pred>…'; 675 - help = '<count> or more sub-<pred>s match' 906 + syntax = '(eq|ne|lt|gt) <count> <pred>…'; 907 + help = '<count> [or more/less] sub-<pred>s match'; 908 + }; 909 + seq = { 910 + syntax = "<wrap> '[' <arg>… ']' <pred>…"; 911 + help = 'reuse the same stack of arguments'; 912 + fn = function(e,wrap,args,...) 913 + local lst = {} 914 + local function eval(pred,...) 915 + if not pred then return end 916 + table.insert(lst, pred .. ' ' .. args) 917 + eval(...) 918 + end 919 + eval(...) 920 + local filter = wrap .. ' ' ..table.concat(map(lst, strsan), ' ') 921 + return parsefilter(filter)(e) 922 + end; 923 + }; 924 + mark = { 925 + syntax = '<mark> [<pred>]'; 926 + help = 'apply <mark> to the words that match <pred>, or all the words that are tested if no <pred> is supplied. use to visually indicate the reason that a given term matched the query'; 927 + fn = function(e, val, pred) 928 + if pred == nil or parsefilter(pred)(e) then 929 + e.mark = e.mark or {} 930 + for k,v in pairs(e.mark) do 931 + if v==val then return true end 932 + end 933 + table.insert(e.mark, val) 934 + return true 935 + end 936 + end; 937 + }; 938 + clear = { 939 + syntax = '<mark> [<pred>]'; 940 + help = 'like [mark] but clears marks instead of setting them'; 941 + fn = function(e, val, pred) 942 + if pred == nil or parsefilter(pred)(e) then 943 + e.mark = e.mark or {} 944 + for k,v in pairs(e.mark) do 945 + if v==val then 946 + table.remove(e.mark,k) 947 + return true 948 + end 949 + end 950 + return true 951 + end 952 + end; 953 + }; 954 + marked = { 955 + syntax = '(by <mark> [pred]|in <pred>)'; 956 + help = 'tests for an existing <mark> on the result'; 957 + fn = function(e, mode, val, pred) 958 + if mode == 'in' then 959 + pred = val val = nil 960 + if pred == nil then 961 + id10t '[marked in <pred>] requires a predicate' 962 + end 963 + elseif mode == 'by' then 964 + if val == nil then 965 + id10t '[marked by <mark>] requires a mark' 966 + end 967 + else id10t('invalid form [marked %s]', mode) end 968 + 969 + if pred == nil or parsefilter(pred)(e) then 970 + if e.mark == nil or not next(e.mark) 971 + then return false end 972 + if val then 973 + for k,v in pairs(e.mark) do 974 + if v==val then return true end 975 + end 976 + else return true end 977 + end 978 + end; 676 979 }; 677 980 def = { 678 981 help = 'word has at least one definition that contains all <keyword>s'; 679 982 syntax = '<keyword>…'; 680 983 fn = function(e,...) 681 984 local kw = {...} 682 985 for i,d in ipairs(e.word.defs) do ................................................................................ 690 993 ::notfound:: 691 994 end 692 995 end 693 996 return false 694 997 end; 695 998 }; 696 999 lit = { 697 - help = 'word is, begins with, or ends with <word>'; 698 - syntax = '<word> [(pfx|sfx)]'; 699 - fn = function(e,val,op) 1000 + help = 'word is, begins with, matches, or ends with <search> in <script> or the primary orthography ("also" enables searching the primary as well as the listed scripts)'; 1001 + syntax = '<search> [(pfx|sfx|match)] [any|(in|also) <script>…]'; 1002 + fn = function(e,val,...) 1003 + local opts,oc = {...},1 1004 + local scripts, op = {0} 1005 + if opts[oc] == 'pfx' or opts[oc] == 'sfx' or opts[oc] == 'match' then 1006 + op = opts[oc] 1007 + oc = oc + 1 1008 + end 1009 + if opts[oc] then 1010 + if opts[oc] == 'any' then 1011 + scripts = nil 1012 + else 1013 + if opts[oc] == 'in' then 1014 + scripts = {} 1015 + elseif opts[oc] ~= 'also' then 1016 + id10t('[lit … %s]: invalid spec', opts[oc]) 1017 + end 1018 + if #opts < oc+1 then 1019 + id10t('[lit … %s]: missing argument', opts[oc]) 1020 + end 1021 + for i=oc+1,#opts do 1022 + table.insert(scripts, opts[oc]) 1023 + end 1024 + end 1025 + end 700 1026 if not op then 701 1027 return e.lit == val 702 1028 elseif op == 'pfx' then 703 1029 return val == string.sub(e.lit,1,#val) 704 1030 elseif op == 'sfx' then 705 1031 return val == string.sub(e.lit,(#e.lit) - #val + 1) 1032 + elseif op == 'match' then 1033 + return string.find(e.lit, val) ~= nil 706 1034 else 707 - id10t('[lit %s %s] is not a valid filter, “%s” should be either “pfx” or “sfx”',val,op,op) 1035 + id10t('[lit %s %s] is not a valid filter, “%s” should be “pfx”, “sfx”, or “match”',val,op,op) 708 1036 end 709 1037 end; 710 1038 }; 1039 + morph = { 1040 + help = 'find words with specific morphs'; 1041 + syntax = "(any|all|only) <script> [seq] ((lit|rec) <repr>|rad '[' <repr>… ']')…"; 1042 + }; 711 1043 form = { 712 1044 help = 'match against word\'s inflected forms'; 713 - syntax = '(<inflect> | <form> (set | is <inflect> | (pfx|sfx|match) <affix>))'; 714 - fn = function(e, k, op, v) 1045 + syntax = '(<inflect> | (of <form>|has) [any] ([un]set | is <inflect> | (pfx|sfx|sub) <affix>)…)'; 1046 + fn = function(e, k, mode, ...) 1047 + if k == nil then -- eq [form has set] 1048 + for _,d in pairs(e.word.defs) do 1049 + if next(d.forms) then return true end 1050 + end 1051 + elseif mode == 'of' or mode == 'has' then 1052 + local match,mc = {...},1 1053 + if not next(match) then 1054 + id10t('[form %s]: missing spec',mode) 1055 + end 1056 + 1057 + local any = match[1]=='any' 1058 + local eval = function()return true end; 1059 + if any then 1060 + nc = 2 1061 + eval = function()return false end; 1062 + end 1063 + 1064 + local ok = false 1065 + local fns = { 1066 + set = function(a) return a~=nil end; 1067 + unset = function(a) return a==nil end; 1068 + is = function(a,b)return a and a==b end; 1069 + pfx = function(a,b)return a and string.sub(a,1,#b) == b end; 1070 + sfx = function(a,b)return a and string.sub(a,0-#b) == b end; 1071 + sub = function(a,b)return a and string.find(a,b) ~= nil end; 1072 + } 1073 + repeat local n, op, arg = 1, table.unpack(match,mc) 1074 + print(op,arg) 1075 + if not op then id10t "missing argument for [form]" end 1076 + if not fns[op] then 1077 + id10t('[form … %s] is not a valid filter', op) 1078 + end 1079 + if op ~= "set" and op ~= 'unset' then 1080 + n = 2 1081 + if not arg then 1082 + id10t('[form … %s]: missing argument', op) 1083 + end 1084 + end 1085 + local oe = eval 1086 + eval = any and function(a,b) 1087 + return fns[op](a,b) or oe(a,b) 1088 + end or function(a,b) 1089 + return fns[op](a,b) and oe(a,b) 1090 + end 1091 + ok = true 1092 + mc = mc + n until mc > #match 1093 + if not ok then 1094 + id10t '[form]: incomplete spec' 1095 + end 1096 + 1097 + for _,d in pairs(e.word.defs) do 1098 + if mode=='has' then 1099 + for cat,infd in pairs(d.forms) do 1100 + if eval(infd) then return true end 1101 + end 1102 + else 1103 + if eval(d.forms[k]) then return true end 1104 + end 1105 + end 1106 + elseif mode ~= nil then 1107 + id10t('[form %s]: invalid mode', mode) 1108 + else 1109 + for _,d in pairs(e.word.defs) do 1110 + for _,v in pairs(d.forms) do 1111 + if v == k then return true end 1112 + end 1113 + end 1114 + end 1115 + return false 715 1116 end; 716 1117 }; 717 1118 part = { 718 1119 help = 'word has definitions for every <part> of speech'; 719 1120 syntax = '<part>…'; 720 1121 fn = function(e,...) 721 1122 local map, tgt = prepScan(...) ................................................................................ 723 1124 for i,d in ipairs(e.word.defs) do 724 1125 if map[d.part] then matches = matches + 1 end 725 1126 end 726 1127 return matches == tgt 727 1128 end 728 1129 }; 729 1130 root = { 730 - help = 'match a word that derives from every <word>'; 1131 + help = 'match an entry that derives from every <word>'; 731 1132 syntax = '<word>…'; 732 1133 fn = function(e,...) 733 1134 local map, tgt = prepScan(...) 734 1135 for i,d in ipairs(e.word.defs) do 735 1136 local matches = 0 736 1137 for j,r in ipairs(d.branch) do 737 1138 if map[r] then matches = matches + 1 end 738 1139 end 739 1140 if matches == tgt then return true end 740 1141 end 741 1142 end 742 1143 }; 1144 + phrase = { 1145 + syntax = '<pred>…'; 1146 + help = 'match only words with phrases'; 1147 + }; 1148 + ex = { 1149 + syntax= '[by <source>] [(any|all) <term>…]'; 1150 + help = 'entry has an example by <source> with any/all of <term>s'; 1151 + fn = function() end; 1152 + }; 743 1153 note = { 744 - help = 'word has a matching note'; 1154 + help = 'entry has a matching note'; 745 1155 syntax = '([kind <kind> [<term>]] | term <term> | (min|max|count) <n>)'; 746 1156 fn = function(e, op, k, t) 747 1157 if op == 'kind' or op == 'term' then 748 1158 if op == 'term' and t then 749 1159 id10t('too many arguments for [note term <term>]') 750 1160 end 751 1161 for _,d in ipairs(e.word.defs) do ................................................................................ 784 1194 if not fd then error(userError("cannot open file " .. file),2) end 785 1195 return fd 786 1196 else 787 1197 return file 788 1198 end 789 1199 end 790 1200 791 -local function copy(tab) 792 - local new = {} 793 - for k,v in pairs(tab) do new[k] = v end 794 - return new 795 -end 796 - 797 1201 local function pathParse(p) 798 1202 -- this is cursed, rewrite without regex pls TODO 799 1203 if p == '.' then return {} end 800 1204 local function comp(pfx) 801 1205 return pfx .. '([0-9]+)' 802 1206 end 803 1207 local function mtry(...) ................................................................................ 824 1228 end 825 1229 if w then break end 826 1230 end 827 1231 end 828 1232 if not w then w=p:match('^(.-)%.?$') end 829 1233 return {w = w, dn = tonumber(dn), mn = tonumber(mn), pn=tonumber(pn); nn = tonumber(nn), xn = tonumber(xn)} 830 1234 end 831 -local function pathString(p,styler) 1235 +local function pathString(p,styler,display) 832 1236 local function s(s, st, ...) 833 1237 if styler then 834 1238 return styler[st](tostring(s),...) 835 1239 else return s end 836 1240 end 837 1241 838 1242 local function comp(c,n,...) ................................................................................ 842 1246 local t = {} 843 1247 if p.w then t[1] = s(p.w,'ul') else return '.' end 844 1248 if p.dn then t[2] = string.format(".%s", s(p.dn,'br')) end 845 1249 if p.pn then t[#t+1] = comp('p',p.pn,4,true) end 846 1250 if p.mn then t[#t+1] = comp('m',p.mn,5,true) end 847 1251 if p.xn then t[#t+1] = comp('x',p.xn,6,true) 848 1252 elseif p.nn then t[#t+1] = comp('n',p.nn,4) end 849 - if t[2] == nil then 1253 + if t[2] == nil and not display then 850 1254 return p.w .. '.' --make sure paths are always valid 851 1255 end 852 1256 return s(table.concat(t),'em') 853 1257 end 854 1258 local function pathMatch(a,b) 855 1259 return a.w == b.w 856 1260 and a.dn == b.dn ................................................................................ 874 1278 if not a.dn then return res end 875 1279 876 1280 res.def = lookup('definition', res.word.defs, a.dn) 877 1281 if (not a.pn) and (not a.mn) then return res end 878 1282 879 1283 local m if a.pn then 880 1284 res.phrase = lookup('phrase', res.def.phrases, a.pn) 881 - res.meaning = lookup('meaning', res.phrase.means, a.mn) 1285 + if a.mn then 1286 + res.meaning = lookup('meaning', res.phrase.means, a.mn) 1287 + else return res end 882 1288 else 883 1289 res.meaning = lookup('meaning', res.def.means, a.mn) 884 1290 end 885 1291 886 1292 if a.xn then 887 1293 res.ex = lookup('example',res.meaning.examples,a.xn) 888 1294 elseif a.nn then ................................................................................ 919 1325 elseif super.nn then 920 1326 if sub.xn then return false end 921 1327 if sub.nn ~= super.nn then return false end 922 1328 end 923 1329 924 1330 return true 925 1331 end 1332 + 1333 +function ansi.formatMarkup(text, sty) 1334 + return (string.gsub(text, '(.?)(%b[])', function(esc,seg) 1335 + if esc == '\\' then return seg end 1336 + local mode, text = seg:match('^%[(.)%s*(.-)%]$') 1337 + local r 1338 + if mode == '\\' then 1339 + r = text 1340 + elseif mode == '*' then 1341 + r = sty.br(ansi.formatMarkup(text,sty)) 1342 + elseif mode == '!' then 1343 + r = sty.em(ansi.formatMarkup(text,sty)) 1344 + elseif mode == '_' then 1345 + r = sty.ul(ansi.formatMarkup(text,sty)) 1346 + elseif mode == '$' then 1347 + r = sty.color(ansi.formatMarkup(text,sty),6,true) 1348 + elseif mode == '>' then 1349 + r = pathString(pathParse(text),sty,true) 1350 + else return seg end 1351 + return esc..r 1352 + end)) 1353 +end 926 1354 927 1355 local cmds = { 928 1356 create = { 929 1357 help = "initialize a new dictionary file"; 930 1358 syntax = "<lang>"; 931 1359 raw = true; 932 1360 exec = function(ctx, lang) ................................................................................ 936 1364 local fd = safeopen(ctx.file,"wb") 937 1365 local new = { 938 1366 header = { 939 1367 lang = lang; 940 1368 meta = ""; 941 1369 partsOfSpeech = {}; 942 1370 inflectionForms = {}; 1371 + orthographies = {}; 943 1372 }; 944 1373 words = {}; 945 1374 relsets = {}; 1375 + morphs = {}; 946 1376 } 947 1377 local o = writeDict(new); 948 1378 fd:write(o) 949 1379 fd:close() 950 1380 end; 951 1381 }; 952 1382 coin = { ................................................................................ 953 1383 help = "add a new word"; 954 1384 syntax = "<word>"; 955 1385 write = true; 956 1386 exec = function(ctx,word) 957 1387 if ctx.dict.words[word] then 958 1388 id10t "word already coined" 959 1389 end 960 - ctx.dict.words[word] = {defs={},rels={}} 1390 + ctx.dict.words[word] = {defs={},rels={},enc={}} 961 1391 end; 962 1392 }; 963 1393 def = { 964 1394 help = "define a word"; 965 1395 syntax = "<word> <part-of-speech> [<meaning> [<root>…]]"; 966 1396 write = true; 967 1397 exec = function(ctx,word,part,means,...) 968 1398 local etym = {...} 969 1399 if (not word) or not part then 970 1400 id10t 'bad definition' 971 1401 end 972 1402 if not ctx.dict.words[word] then 973 - ctx.dict.words[word] = {defs={},rels={}} 1403 + ctx.dict.words[word] = {defs={},rels={},enc={}} 974 1404 end 975 1405 local n = #(ctx.dict.words[word].defs)+1 976 1406 ctx.dict.words[word].defs[n] = { 977 1407 part = part; 1408 + writings = {}; 978 1409 branch = etym; 979 1410 means = {means and { 980 1411 lit=means; 981 1412 examples={}; 982 1413 notes={}; 983 1414 rels={}; 984 1415 } or nil}; ................................................................................ 990 1421 end; 991 1422 }; 992 1423 mean = { 993 1424 help = "add a meaning to a definition"; 994 1425 syntax = "<word> <def#> <meaning>"; 995 1426 write = true; 996 1427 exec = function(ctx,word,dn,m) 997 - local t = pathResolve(ctx,{w=word,dn=dn}) 998 - table.insert(t.d.means, {lit=m,notes={}}) 1428 + local t = pathResolve(ctx,{w=word,dn=tonumber(dn)}) 1429 + table.insert(t.def.means, {lit=m,notes={},examples={},rels={}}) 999 1430 end; 1000 1431 }; 1001 1432 rel = { 1002 1433 help = "manage groups of related words"; 1003 1434 syntax = { 1004 1435 "(show|purge) <path> [<kind>]"; 1005 1436 "(link|drop) <word> <group#> <path>…"; ................................................................................ 1130 1561 end 1131 1562 end 1132 1563 end; 1133 1564 }; 1134 1565 mod = { 1135 1566 help = "move, merge, split, or delete words or definitions"; 1136 1567 syntax = { 1137 - "<path> (drop | [move|merge|clobber] <path> | out [<part> [<root>…]])"; 1138 - "path ::= <word>[(@<def#>[/<meaning#>[:<note#>]]|.)]"; 1568 + "<path> (drop | [(to|move)|merge|clobber] <path>)"; 1569 + "path ::= <word>[.[<def#>[/p<phrase#>][/m<meaning#>[(/n<note#>|/x<example#>)]]]]"; 1139 1570 }; 1140 1571 write = true; 1572 + }; 1573 + morph = { 1574 + help = "manage and attach morphs (morphemes/composable glyphs)"; 1575 + syntax = { 1576 + "(<ls>|<define>|<mod>)"; 1577 + "define ::= def (id <name>|as) [<form>]… [from <morph>…]"; 1578 + "ls ::= ls (<morph>|meta <key> <value>|has <key>)…"; 1579 + "mod ::= <morph> (drop|[un]link <path>|meta <key> [<value>]|inc [<morph>])"; 1580 + "morph ::= (id <name>|enc <form>)"; 1581 + "form ::= [<script>]=<repr>"; 1582 + }; 1141 1583 }; 1142 1584 note = { 1143 1585 help = "add a note to a definition or a paragraph to a note"; 1144 1586 syntax = {"(<m-path> (add|for) <kind> | <m-path>:<note#>) <para>…"; 1145 - "m-path ::= <word>@<def#>/<meaning#>"}; 1587 + "m-path ::= <word>.<def#>[/p<phrase#]/m<meaning#>"}; 1146 1588 write = true; 1147 1589 exec = function(ctx,path,...) 1148 1590 local paras, mng 1149 1591 local dest = pathParse(path) 1150 1592 local t = pathResolve(ctx,path) 1151 1593 if dest.nn then 1152 1594 paras = {...} ................................................................................ 1226 1668 end 1227 1669 end 1228 1670 end 1229 1671 1230 1672 function cmds.ls.exec(ctx,...) 1231 1673 local filter = nil 1232 1674 local out = {} 1233 - for i,f in ipairs{...} do 1675 + local args = {...} 1676 + for i=#args,1,-1 do local f <const> = args[i] 1234 1677 local fn = parsefilter(f) 1235 1678 local of = filter or function() return false end 1236 1679 filter = function(e) 1237 1680 return fn(e) or of(e) 1238 1681 end 1239 1682 end 1240 1683 for lit,w in pairs(ctx.dict.words) do 1241 - local e = {lit=lit, word=w} 1684 + local e = {lit=lit, word=w, dict=ctx.dict} 1242 1685 if filter == nil or filter(e) then 1243 1686 table.insert(out, e) 1244 1687 end 1245 1688 end 1246 1689 table.sort(out, function(a,b) return a.lit < b.lit end) 1247 1690 local fo = ctx.sty[io.stdout] 1248 1691 ................................................................................ 1285 1728 return { 1286 1729 syn = flatten(synonymSets); 1287 1730 ant = flatten(antonymSets); 1288 1731 met = flatten(metonymSets); 1289 1732 } 1290 1733 end 1291 1734 1292 - local function formatRels(rls, padlen) 1735 + local function formatRels(lines, rls, padlen) 1293 1736 -- optimize for the common case 1294 1737 if next(rls.syn) == nil and 1295 1738 next(rls.ant) == nil and 1296 1739 next(rls.met) == nil then return {} end 1297 1740 local pad = string.rep(' ',padlen) 1298 1741 local function format(label, set) 1299 1742 local each = map(set, function(e) ................................................................................ 1301 1744 local str = fo.ul(e.w) 1302 1745 if ed then str = string.format('%s(%s)',str,ed.part) end 1303 1746 if e.mn then str = string.format('%s§%u',str,e.mn) end 1304 1747 return str 1305 1748 end) 1306 1749 return fo.em(string.format("%s%s %s",pad,label,table.concat(each,', '))) 1307 1750 end 1308 - local lines = {} 1309 1751 local function add(l,c,lst) 1310 1752 table.insert(lines, format(fo.color(l,c,true),lst)) 1311 1753 end 1312 1754 if next(rls.syn) then add('synonyms:',2,rls.syn) end 1313 1755 if next(rls.ant) then add('antonyms:',1,rls.ant) end 1314 1756 if next(rls.met) then add('metonyms:',4,rls.met) end 1315 1757 return lines 1316 1758 end 1317 1759 1318 - local function meanings(w,d,md,n) 1319 - local start = md and 2 or 1 1320 - local part = string.format('(%s)', d.part) 1321 - local pad = md and string.rep(' ', #part) or '' 1322 - local function note(n,insert) 1760 + local function formatMeaning(m, obj, path, indent) -- m = dest tbl 1761 + local pad = string.rep(' ', indent) 1762 + local function note(j,n,markup) 1323 1763 if not next(n.paras) then return end 1324 1764 local pad = string.rep(' ',#(n.kind) + 9) 1325 - insert(' ' .. fo.hl(' ' .. n.kind .. ' ') .. ' ' .. n.paras[1]) 1765 + 1766 + local nid = '' 1767 + if ctx.flags.ident then 1768 + nid='‹'..pathString(merge(path,{nn=j}), fo)..'›' 1769 + end 1770 + table.insert(m, nid..' ' .. fo.hl(' ' .. n.kind .. ' ') .. ' ' .. markup(n.paras[1])) 1326 1771 for i=2,#n.paras do 1327 - insert(pad..n.paras[2]) 1328 - end 1329 - end 1330 - local m = { (function() 1331 - if d.means[1] then 1332 - if md then 1333 - local id = '' 1334 - if ctx.flags.ident then 1335 - id=' ['..pathString({w=w.lit,dn=n,mn=1}, fo)..']' 1336 - end 1337 - return string.format(" %s %s 1. %s", id, fo.em(part), d.means[1].lit) 1338 - end 1339 - else return 1340 - fo.em(string.format(' %s [empty definition #%u]', part,n)) 1341 - end 1342 - end)() } 1343 - tcatD(m, formatRels(gatherRelSets{w=w.lit,dn=n,mn=1}, 6)) 1344 - for i=start,#d.means do local v = d.means[i] 1345 - local id = '' 1346 - if ctx.flags.ident then id='['..pathString({w=w.lit,dn=n,mn=n}, fo)..']' end 1347 - table.insert(m, string.format(' %s%s %u. %s', pad, id, i, v.lit)) 1348 - tcatD(m, formatRels(gatherRelSets{w=w.lit,dn=n,mn=i}, 6)) 1349 - for j,n in ipairs(v.notes) do 1350 - note(n, function(v) table.insert(m, v) end) 1351 - end 1352 - end 1353 - return table.concat(m,'\n') 1354 - end 1355 - local function autobreak(str) 1356 - if str ~= '' then return str..'\n' else return str end 1357 - end 1772 + table.insert(m, pad..markup(n.paras[2])) 1773 + end 1774 + end 1775 + local id = '' 1776 + if ctx.flags.ident then id='‹'..pathString(path, fo)..'›' end 1777 + table.insert(m, string.format('%s%s %u. %s', pad, id, path.mn, ansi.formatMarkup(obj.lit,fo))) 1778 + formatRels(m,gatherRelSets(path), 6) 1779 + for j,n in ipairs(obj.notes) do 1780 + note(j,n, function(v) return ansi.formatMarkup(v,fo) end) 1781 + end 1782 + end 1783 + 1784 + local function defnMeanings(m, w,def,path,indent) 1785 + local part = '' 1786 + for i=1,#def.means do local v = def.means[i] 1787 + formatMeaning(m, v, merge(path,{mn=i}),indent) 1788 + end 1789 + end 1790 + local function parthead(def) 1791 + local str = string.format('(%s)', def.part) 1792 + return fo.color(fo.em(str), 199), #str 1793 + end 1794 + local markcolor, markcolors, markmap = 0, { 1795 + 117, 75, 203, 48, 200, 190, 26, 48, 226, 198 1796 + }, {} 1358 1797 for i, w in ipairs(out) do 1359 - local d = fo.ul(fo.br(w.lit)) 1360 - local wordrels = autobreak(table.concat( 1361 - formatRels(gatherRelSets{w=w.lit}, 2), 1362 - '\n' 1363 - )) 1364 - if #w.word.defs == 1 then 1365 - d=d .. ' ' 1366 - .. fo.rgb(fo.em('('..(w.word.defs[1].part)..')'),.8,.5,1) .. '\n' 1367 - .. meanings(w,w.word.defs[1],false,1) .. '\n' 1368 - .. autobreak(table.concat(formatRels(gatherRelSets{w=w.lit,dn=1}, 4), '\n')) 1369 - .. wordrels .. '\n' 1370 - else 1371 - for j, def in ipairs(w.word.defs) do 1372 - local syn if wsc and wsc[j] then syn = wsc[j] end 1373 - d=d .. '\n' 1374 - .. meanings(w,syn,def,true,j) .. '\n' 1375 - .. autobreak(table.concat( 1376 - formatRels(gatherRelSets{w=w.lit,dn=j}, 4), 1377 - '\n' 1378 - )) 1379 - end 1380 - d=d .. wordrels .. '\n' 1381 - end 1382 - io.stdout:write(d) 1798 + local lines = { fo.ul(fo.br(w.lit)) } 1799 + local pad = 4 1800 + local ndefs = #w.word.defs 1801 + if ndefs == 1 then 1802 + local header = parthead(w.word.defs[1]) 1803 + lines[1] = lines[1] .. ' ' .. header 1804 + end 1805 + local mark 1806 + local markline 1807 + if w.mark then 1808 + local marks = {} 1809 + for _,m in pairs(w.mark) do 1810 + if not markmap[m] then 1811 + markmap[m] = markcolors[markcolor+1] 1812 + markcolor = (markcolor+1)%#markcolors 1813 + end 1814 + local c = markmap[m] 1815 + table.insert(marks, fo.hl(fo.color(string.format(' %s ',m),c))) 1816 + end 1817 + mark = table.concat(marks, ' ') 1818 + markline = #lines 1819 + end 1820 + 1821 + for j, d in ipairs(w.word.defs) do 1822 + local top = #lines -- :/ 1823 + local header, hdln = parthead(d) 1824 + defnMeanings(lines, w, d, {w=w.lit, dn=j}, ndefs==1 and 0 or hdln+1) 1825 + if ctx.flags.rels then 1826 + formatRels(lines, gatherRelSets{w=w.lit,dn=j}, 0) 1827 + end 1828 + if ndefs > 1 then 1829 + lines[top+1] = ' ' .. header .. string.sub(lines[top+1],hdln+2) 1830 + end 1831 + end 1832 + if ctx.flags.rels then 1833 + formatRels(lines,gatherRelSets{w=w.lit}, 2) 1834 + end 1835 + if markline then 1836 + lines[markline] = mark .. ' ' .. lines[markline] 1837 + end 1838 + io.stdout:write(table.concat(lines,'\n')..'\n') 1383 1839 end 1384 1840 end 1385 1841 1386 1842 function cmds.import.exec(ctx,file) 1387 1843 local ifd = io.stdin 1388 1844 if file then 1389 1845 ifd = safeopen(file,'r') ................................................................................ 1391 1847 1392 1848 local new = { 1393 1849 header = { 1394 1850 lang = lang; 1395 1851 meta = ""; 1396 1852 partsOfSpeech = {}; 1397 1853 inflectionForms = {}; 1854 + orthographies = {}; 1398 1855 }; 1399 1856 words = {}; 1400 1857 relsets = {}; 1858 + morphs = {}; 1401 1859 } 1402 1860 1403 1861 local state = 0 1404 1862 local relsets = {} 1405 1863 local path = {} 1406 1864 local inflmap, lastinfl = {} 1865 + local orthoIDs, lastOrtho = {} 1866 + local morphIDs, lastMorph = {} 1867 + local lastWriting 1407 1868 for l in ifd:lines() do 1408 1869 local words = strwords(l) 1409 1870 local c = words[1] 1410 1871 local function syn(mn,mx) 1411 1872 local nw = #words - 1 1412 1873 if nw < mn or (mx ~= nil and nw > mx) then 1413 1874 if mx ~= nil then 1414 1875 id10t('command %s needs between %u~%u words',c,mn,mx) 1415 1876 else 1416 1877 id10t('command %s needs at least %u words',c,mn) 1417 1878 end 1418 1879 end 1880 + end 1881 + local function getuid(tbl, uid) 1882 + if tonumber(uid,16) == nil then 1883 + if not tbl[uid] then 1884 + tbl[uid] = math.random(0,0xffffFFFF) 1885 + end 1886 + return tbl[uid] 1887 + end 1888 + return tonumber(uid,16) 1419 1889 end 1420 1890 if c ~= '*' and c~='meta' then -- comments 1421 1891 if state == 0 then 1422 - if c ~= 'pv0' then 1892 + if c ~= 'PV0' then 1423 1893 id10t "not a parvan export" 1424 1894 end 1425 1895 new.header.lang = words[2] 1426 1896 new.header.meta = words[3] 1427 1897 state = 1 1428 1898 else 1429 1899 local T = pathResolve({dict=new}, path) ................................................................................ 1430 1900 local W,D,P,M,N,X = 1431 1901 T.word, 1432 1902 T.def, 1433 1903 T.phrase, 1434 1904 T.meaning, 1435 1905 T.note, 1436 1906 T.ex 1437 - if c == 'w' then syn(1) state = 2 1907 + if c == 'w' then syn(1) state = 10 1438 1908 path = {w=words[2]} 1439 - new.words[words[2]] = {defs={},rels={}} 1909 + new.words[words[2]] = {defs={},rels={},enc={}} 1910 + lastMorph = nil 1440 1911 elseif c == 'f' then syn(1) 1441 1912 local nf = { 1442 1913 name = words[2]; 1443 1914 abbrev = words[3] or ""; 1444 1915 desc = words[4] or ""; 1445 1916 parts = {}; 1446 1917 } ................................................................................ 1450 1921 elseif c == 'fp' then syn(1) 1451 1922 if not lastinfl then 1452 1923 id10t 'fp can only be used after f' end 1453 1924 table.insert(lastinfl.parts,words[2]) 1454 1925 elseif c == 's' then syn(2) 1455 1926 relsets[words[3]] = relsets[words[3]] or {} 1456 1927 relsets[words[3]].kind = words[2] 1457 - relsets[words[3]].uid = tonumber(words[3]) 1928 + relsets[words[3]].uid = tonumber(words[3],16) 1458 1929 relsets[words[3]].members = relsets[words[3]].members or {} 1459 - elseif state >= 2 and c == 'r' then syn(1) 1930 + elseif c == 'mo' then syn(1) 1931 + local uid,name = table.unpack(words,2) 1932 + uid = getuid(morphIDs, uid) 1933 + new.morphs[uid] = { 1934 + name = name or ''; 1935 + enc = {}; 1936 + meta = {}; 1937 + rads = {}; 1938 + } 1939 + lastMorph = new.morphs[uid] 1940 + elseif lastMorph and state < 10 and c == 'M' then syn(2) 1941 + local key, val = table.unpack(words,2) 1942 + table.insert(lastMorph.meta, {key=key,val=val}) 1943 + elseif lastMorph and state < 10 and c == 'r' then syn(1) 1944 + local r = getuid(morphIDs, words[2]) 1945 + table.insert(lastMorph.rads, r) 1946 + elseif c == 'e' then syn(2) 1947 + local scr, blob = table.unpack(words,2) 1948 + scr = getuid(orthoIDs, scr) 1949 + if state <= 10 and lastMorph then 1950 + lastMorph.enc[scr] = blob 1951 + elseif state == 10 then 1952 + W.enc[scr] = blob 1953 + elseif state >= 11 and lastWriting then 1954 + lastWriting.enc[scr] = blob 1955 + else 1956 + id10t('encoding “%s” declared at bad position', blob) 1957 + end 1958 + elseif c == 'o' then syn(3) 1959 + local uid, name, repr = table.unpack(words,2) 1960 + repr = strwords(repr) 1961 + uid = getuid(orthoIDs, uid) 1962 + if #repr > 1 then 1963 + local kind, p1,p2 = table.unpack(repr) 1964 + repr = {kind,{}} 1965 + if kind == 'glyphs' then 1966 + repr[2].format = p1 1967 + repr[2].glyphs = {} 1968 + repr[2].encoding = p2 1969 + elseif kind == 'int' then 1970 + repr[2] = tonumber(p1,16) 1971 + end 1972 + else repr=repr[1] end 1973 + table.insert(new.header.orthographies, { 1974 + uid = uid; 1975 + name = name; 1976 + repr = repr; 1977 + }) 1978 + lastOrtho = new.header.orthographies[#(new.header.orthographies)] 1979 + elseif c == 'og' then syn(2) 1980 + if not lastOrtho then 1981 + id10t '`og` must follow an orthography declaration' 1982 + elseif lastOrtho.repr[1] ~= 'glyphs' then 1983 + id10t('orthography declares %s representation', lastOrtho.repr[1]) 1984 + end 1985 + local name, data = table.unpack(words,2) 1986 + table.insert(lastOrtho.repr[2].glyphs, { 1987 + -- TODO decode base?? data for binary encodings 1988 + name = name, image = data 1989 + }) 1990 + elseif state >= 10 and c == 'r' or c == 'rh' then syn(1) 1460 1991 local rt 1461 - if state == 2 then 1992 + if state == 10 then 1462 1993 rt = W.rels 1463 - elseif state == 3 then 1994 + elseif state == 11 then 1464 1995 rt = D.rels 1465 - elseif state == 4 then 1996 + elseif state == 12 then 1466 1997 rt = D.rels 1467 1998 elseif state == 14 then 1468 1999 rt = P.rels 1469 2000 end 1470 2001 relsets[words[2]] = relsets[words[2]] or { 1471 - uid = tonumber(words[2]) or math.random(0,0xffffFFFF); 2002 + uid = tonumber(words[2],16) or math.random(0,0xffffFFFF); 1472 2003 members={}; 1473 2004 } 1474 - table.insert(relsets[words[2]].members, path) 1475 - elseif state >= 2 and c == 'd' then syn(1) state = 3 2005 + local mems = relsets[words[2]].members 2006 + if c == 'rh' and next(mems) then 2007 + mems[#mems+1] = mems[1] 2008 + mems[1] = path 2009 + else 2010 + table.insert(mems,path) 2011 + end 2012 + elseif state >= 10 and c == 'd' then syn(1) state = 11 1476 2013 table.insert(W.defs, { 1477 2014 part = words[2]; 2015 + writings = {}; 1478 2016 branch = {}; 1479 2017 means = {}; 1480 2018 forms = {}; 1481 2019 phrases = {}; 1482 2020 rels = {}; 1483 2021 }) 1484 2022 path = {w = path.w, dn = #(W.defs)} 1485 - elseif state >= 3 and c == 'dr' then syn(1) 2023 + elseif state >= 11 and c == 'dr' then syn(1) 1486 2024 table.insert(D.branch, words[2]) 1487 - elseif state >= 3 and c == 'df' then syn(2) 2025 + elseif state >= 11 and c == 'df' then syn(2) 1488 2026 if not inflmap[words[2]] then 1489 2027 id10t('no inflection form %s defined', words[2]) 1490 2028 end 1491 2029 D.forms[inflmap[words[2]]] = words[3] 1492 - elseif state >= 3 and c == 'p' then syn(1) state = 14 2030 + elseif state >= 11 and c == 'p' then syn(1) state = 12 1493 2031 table.insert(D.phrases, { 1494 2032 str = words[2]; 1495 2033 means = {}; 1496 2034 rels = {}; 1497 2035 }) 1498 2036 path = {w = path.w, dn = path.dn, pn = #(D.phrases)} 1499 - elseif state >= 3 and c == 'm' then syn(1) state = 4 1500 - table.insert(D.means, { 2037 + elseif state >= 11 and c == 'm' then syn(1) state = 13 2038 + table.insert((P or D).means, { 1501 2039 lit = words[2]; 1502 2040 notes = {}; 1503 2041 examples = {}; 1504 2042 rels = {}; 1505 2043 }); 1506 - path = {w = path.w, dn = path.dn, pn=path.pn, mn = #(D.means)} 1507 - elseif state >= 4 and c == 'n' then syn(1) state = 5 2044 + path = {w = path.w, dn = path.dn, pn=path.pn, mn = #((P or D).means)} 2045 + elseif state >= 11 and c == 'W' then 2046 + table.insert(D.writings, { 2047 + info = words[2] or ''; 2048 + enc = {}; 2049 + morphs = {}; 2050 + }) 2051 + lastWriting = D.writings[#(D.writings)] 2052 + elseif state >= 11 and lastWriting and c == 'Wmo' then syn(1) 2053 + local morph = getuid(morphIDs, words[2]) 2054 + table.insert(lastWriting.morphs, morph) 2055 + elseif state >= 13 and c == 'x' then syn(1) 2056 + table.insert(M.examples, { 2057 + quote = words[2]; 2058 + src = words[3] or ''; 2059 + }) 2060 + elseif state >= 13 and c == 'n' then syn(1) state = 14 1508 2061 table.insert(M.notes, {kind=words[2], paras={}}) 1509 2062 path = {w = path.w, dn = path.dn, pn = path.pn, mn = path.mn, nn = #(M.notes)}; 1510 - elseif state >= 5 and c == 'np' then syn(1) 2063 + elseif state >= 14 and c == 'np' then syn(1) 1511 2064 table.insert(N.paras, words[2]) 1512 2065 end 1513 2066 -- we ignore invalid ctls, for sake of forward-compat 1514 2067 end 1515 2068 end 1516 2069 end 1517 2070 ................................................................................ 1532 2085 ofd:write(o) 1533 2086 ofd:close() 1534 2087 end 1535 2088 1536 2089 function cmds.export.exec(ctx,file) 1537 2090 local ofd = io.stdout 1538 2091 if file then ofd = safeopen(file, 'w+') end 1539 - local function san(str) 1540 - local d = 0 1541 - local r = {} 1542 - for i,cp in utf8.codes(str) do 1543 - -- insert backslashes for characters that would 1544 - -- disrupt strwords() parsing 1545 - if cp == 0x0a then 1546 - table.insert(r, 0x5c) 1547 - table.insert(r, 0x6e) 1548 - else 1549 - if cp == 0x5b then 1550 - d = d + 1 1551 - elseif cp == 0x5d then 1552 - if d >= 1 then 1553 - d = d - 1 1554 - else 1555 - table.insert(r, 0x5c) 1556 - end 1557 - end 1558 - table.insert(r, cp) 1559 - end 1560 - end 1561 - return '[' .. utf8.char(table.unpack(r)) .. ']' 1562 - end 2092 + local san = strsan 1563 2093 local function o(lvl,...) 1564 2094 local pfx = '' 1565 2095 if ctx.flags.human and lvl > 0 then 1566 2096 pfx = string.rep('\t', lvl) 1567 2097 end 1568 2098 ofd:write(pfx..string.format(...)..'\n') 1569 2099 end 1570 2100 local d = ctx.dict 1571 - o(0,'pv0 %s %s', san(d.header.lang), san(d.header.meta)) 2101 + o(0,'PV0 %s %s', san(d.header.lang), san(d.header.meta)) 1572 2102 local function checksyn(obj,lvl) 1573 2103 for k,v in pairs(obj.rels) do 1574 - o(lvl,'r %u',s.uid) 2104 + if d._relCache[v].mems[1].obj == obj 2105 + then o(lvl,'rh %x',v) 2106 + else o(lvl,'r %x',v) 2107 + end 1575 2108 end 1576 2109 end 1577 2110 for i,f in pairs(d.header.inflectionForms) do 1578 2111 o(0,'f %s %s %s', san(f.name), san(f.abbrev), san(f.desc)) 1579 2112 for j,p in pairs(f.parts) do 1580 2113 o(1,'fp %s', san(p)) 1581 2114 end 1582 2115 end 1583 - local function scanMeans(tbl,path,lvl) 1584 - for j,m in ipairs(def.means) do 2116 + for i,s in pairs(d.header.orthographies) do 2117 + local repr 2118 + if type(s.repr) == 'string' 2119 + then repr = s.repr 2120 + else repr = s.repr[1] end 2121 + if repr == 'int' then 2122 + repr = repr .. ' ' .. tostring(s.repr[2]) 2123 + elseif repr == 'glyphs' then 2124 + repr = repr .. ' ' .. s.repr[2].format .. ' ' .. tostring(s.repr[2].encoding) 2125 + end 2126 + o(0, 'o %x %s %s', s.uid, san(s.name), san(repr)) 2127 + if s.repr[1] == 'glyphs' then 2128 + for _,g in ipairs(s.repr[2].glyphs) do 2129 + o(1, 'og %s %s', san(g.name), san(g.image)) 2130 + end 2131 + end 2132 + end 2133 + local function scanMeans(tbl,lvl) 2134 + for j,m in ipairs(tbl) do 1585 2135 o(lvl,'m %s', san(m.lit)) 1586 - local lp = copy(path) 1587 - lp.mn = j 1588 - checksyn(m,lp,lvl+1) 2136 + checksyn(m,lvl+1) 2137 + for k,x in ipairs(m.examples) do 2138 + o(lvl+1,'x %s %s', san(x.quote,x.src)) 2139 + end 1589 2140 for k,n in ipairs(m.notes) do 1590 2141 o(lvl+1,'n %s', san(n.kind)) 1591 2142 for a,p in ipairs(n.paras) do 1592 2143 o(lvl+2,'np %s', san(p)) 1593 2144 end 1594 2145 end 1595 2146 end 2147 + end 2148 + local function scanMeta(n, meta) 2149 + for i,m in ipairs(meta) do 2150 + o(n, 'M %s %s', san(m.key), san(m.val)) end 2151 + end 2152 + local function scanEnc(n, tbl) 2153 + for uid,enc in pairs(tbl) 2154 + do o(n, 'e %x %s',uid,san(enc)) end 2155 + end 2156 + for uid, m in pairs(d.morphs) do 2157 + o(0, 'mo %x %s', uid, san(m.name)) 2158 + scanMeta(1, m.meta) 2159 + scanEnc(1, m.enc) 1596 2160 end 1597 2161 for lit, w in pairs(d.words) do 1598 2162 o(0,'w %s',san(lit)) 1599 - checksyn(w,{w=lit},1) 2163 + checksyn(w,1) 2164 + scanEnc(1, w.enc) 1600 2165 for i,def in ipairs(w.defs) do 1601 2166 o(1,'d %s',san(def.part)) 1602 - checksyn(def,{w=lit,dn=i},2) 2167 + for _, writ in ipairs(def.writings) do 2168 + if writ.info == '' then o(2,'W') else 2169 + o(2,'W %s',san(writ.info)) end 2170 + for mid,uid in pairs(writ.morphs) do 2171 + o(3, 'Wmo %x', uid) end 2172 + for uid,enc in pairs(writ.enc) 2173 + do o(3, 'e %x %s',uid,san(enc)) end 2174 + end 2175 + checksyn(def,2) 1603 2176 for j,r in ipairs(def.branch) do 1604 2177 o(2,'dr %s',san(r)) 1605 2178 end 2179 + scanMeans(def.means, 2) 1606 2180 for j,p in ipairs(def.phrases) do 1607 2181 o(2,'p %s',san(p.str)) 1608 - scanMeans(p.means, {w=lit,dn=i,pn=j}, 3) 2182 + scanMeans(p.means, 3) 1609 2183 end 1610 - scanMeans(def.means, {w=lit,dn=i}, 2) 1611 2184 end 1612 2185 end 1613 - for _,s in ipairs(d.relsets) do o(0,'s %s %u', s.kind, s.uid) end 2186 + for _,s in ipairs(d.relsets) do o(0,'s %s %x', s.kind, s.uid) end 1614 2187 end 1615 2188 1616 2189 local function filterD(lst, fn) 1617 2190 -- cheap algorithm to destructively filter a list 1618 2191 -- DOES NOT preserve order!! 1619 2192 local top = #lst 1620 2193 for i=top,1,-1 do local m = lst[i] ................................................................................ 1624 2197 top = top - 1 1625 2198 end 1626 2199 end 1627 2200 return lst 1628 2201 end 1629 2202 1630 2203 function cmds.mod.exec(ctx, orig, oper, dest, ...) 2204 + local ops = { 2205 + word = { 2206 + mask = { 2207 + word = {move=true,merge=true,clobber=true}; 2208 + }; 2209 + move = function(from,to) end; 2210 + merge = function(from,to) end; 2211 + clobber = function(from,to) end; 2212 + }; 2213 + def = { 2214 + mask = { 2215 + word = {move=true}; 2216 + def = {merge=true,clobber=true}; 2217 + }; 2218 + move = function(from,to) end; 2219 + merge = function(from,to) end; 2220 + clobber = function(from,to) end; 2221 + }; 2222 + phrase = { 2223 + mask = { 2224 + def = {move=true}; 2225 + phrase = {clobber=true}; 2226 + }; 2227 + move = function(from,to) end; 2228 + clobber = function(from,to) end; 2229 + }; 2230 + meaning = { 2231 + mask = { 2232 + def = {move=true}; 2233 + phrase = {move=true}; 2234 + meaning = {merge=true,clobber=true}; 2235 + }; 2236 + move = function(from,to) end; 2237 + merge = function(from,to) end; 2238 + clobber = function(from,to) end; 2239 + }; 2240 + example = { 2241 + mask = { 2242 + meaning={move=true}; 2243 + example={merge=true,clobber=true}; 2244 + }; 2245 + move = function(from,to) end; 2246 + merge = function(from,to) end; 2247 + clobber = function(from,to) end; 2248 + }; 2249 + note = { 2250 + mask = { 2251 + meaning={move=true}; 2252 + note={merge=true,clobber=true}; 2253 + }; 2254 + move = function(from,to) end; 2255 + merge = function(from,to) end; 2256 + clobber = function(from,to) end; 2257 + }; 2258 + } 1631 2259 rebuildRelationCache(ctx.dict) 1632 2260 end 1633 2261 1634 2262 local function fileLegible(file) 1635 2263 -- check if we can access the file 1636 2264 local fd = io.open(file,"rb") 1637 2265 local ret = false ................................................................................ 1759 2387 showHelp(ctx, cmd, c) 1760 2388 end 1761 2389 end 1762 2390 end 1763 2391 1764 2392 local globalFlags <const> = { 1765 2393 human = {'h','human','enable human-readable exports'}; 1766 - ident = {'i','ident','show identifier paths for all items'} 2394 + ident = {'i','ident','show identifier paths for all items'}; 2395 + rels = {'r','rels', 'show relationships between words'}; 1767 2396 } 1768 2397 1769 2398 local function 1770 2399 usage(me,ctx) 1771 2400 local ln = 0 1772 2401 local ct = {} 1773 2402 local fe = ctx.sty[io.stderr] ................................................................................ 1793 2422 showHelp(ctx,k,v) 1794 2423 end 1795 2424 return 64 1796 2425 end 1797 2426 1798 2427 local function 1799 2428 dispatch(argv, ctx) 2429 + local loglevel = 2 1800 2430 local ferr = ctx.sty[io.stderr] 1801 2431 local args = {} 1802 2432 local flags = {} 1803 2433 local i = 1 while i <= #argv do 1804 2434 local a = argv[i] 1805 2435 if a == '--' then i=i+1 break 1806 2436 elseif a:sub(1,2) == '--' then ................................................................................ 1815 2445 if v[1] == c then flags[k] = true break end 1816 2446 end 1817 2447 end 1818 2448 else table.insert(args, a) end 1819 2449 i = i + 1 end 1820 2450 for j=i,#argv do table.insert(args,argv[j]) end 1821 2451 2452 + 2453 + do local ll = os.getenv('parvan_log') 2454 + if ll then loglevel = tonumber(ll) end 2455 + if flags[quiet] then loglevel=0 2456 + elseif flags[debug] then loglevel=4 end 2457 + end 2458 + 1822 2459 local file, cmd = table.unpack(args) 1823 2460 if cmd and cmds[cmd] then 1824 2461 local c,fd,dict = cmds[cmd] 1825 2462 if (not c.raw) and not c.nofile then 1826 2463 fd = safeopen(file, "rb") 1827 2464 dict = readDict(fd:read 'a') 1828 2465 fd:close() 1829 2466 -- lua io has no truncate method, so we must 1830 2467 -- rely on the clobbering behavior of the open() 1831 2468 -- call instead :( 1832 2469 end 1833 2470 2471 + local function log(lvl,...) 2472 + local loglevels = { 2473 + fatal = 1, 2474 + warn = 2, 2475 + info = 3, 2476 + debug = 4 2477 + } 2478 + if loglevels[lvl] <= loglevel then 2479 + ctx.log(lvl,...) 2480 + end 2481 + end 1834 2482 cmds[cmd].exec({ 1835 2483 sty = ctx.sty; 1836 2484 try = ctx.try; 1837 - log = ctx.log; 2485 + log = log; 1838 2486 1839 2487 flags = flags; 1840 2488 file = file; 1841 2489 fd = fd; 1842 2490 dict = dict; 1843 2491 }, table.unpack(args,3)) 1844 2492
Added termcolors.lua version [ccbbe9b5fc].
1 +-- [ʞ] termcolors.lua 2 +-- ~ lexi hale <lexi@hale.su> 3 +-- © AGPLv3 4 +-- ? print grids comparing truecolor output to 256-color output 5 + 6 +function conv(a) 7 + return 16 + math.floor(a.r*5)*36 + math.floor(a.g*5)*6 + math.floor(a.b*5) 8 +end 9 +local S = 32 10 +local M = 33 * 4 11 +local grid = {} 12 + 13 +local X,Y = 0,0 14 +for R=0,S do for G=0,S do 15 + for B=0,S do 16 + local r,g,b = R/S, G/S, B/S 17 + grid[Y*M + X] = {r=r,g=g,b=b} 18 + X = X + 1 19 + if X == M then 20 + X = 0 21 + Y = Y + 1 22 + end 23 + end 24 +end end 25 + 26 +local function show(setcolor) 27 + for y=0,#grid/M - 1,2 do 28 + for x=0,M - 1 do 29 + local top,bot = grid[y*M + x], grid[(y+1)*M + x] 30 + setcolor(top, false) 31 + setcolor(bot, true) 32 + io.write(string.format('▀')) 33 + end print('\27[m') end 34 +end 35 + 36 +print "-- using 256-color escapes" 37 +show(function(clr,bottom) 38 + local tpl 39 + if clr then 40 + if bottom then tpl = '\27[48;5;%um' 41 + else tpl = '\27[38;5;%um' end 42 + io.write(string.format(tpl, conv(clr))) 43 + else 44 + if bottom 45 + then io.write('\27[49m') 46 + else io.write('\27[39m') 47 + end 48 + end 49 +end) 50 + 51 +print "-- using truecolor escapes" 52 +-- if false then 53 +show(function(clr,bottom) 54 + local tpl 55 + if clr then 56 + if bottom then tpl = '\27[48;2;%u;%u;%um' 57 + else tpl = '\27[38;2;%u;%u;%um' end 58 + io.write(string.format(tpl, 59 + math.floor(clr.r*0xff), 60 + math.floor(clr.g*0xff), 61 + math.floor(clr.b*0xff))) 62 + else 63 + if bottom 64 + then io.write('\27[49m') 65 + else io.write('\27[39m') 66 + end 67 + end 68 +end) 69 +-- end 70 + 71 +-- for R=0,S do for G=0,S do for B=0,S do 72 +-- local r,g,b = R/S, G/S, B/S 73 +-- io.write(string.format('\27[48;2;%u;%u;%um ', math.ceil(r*0xff), math.ceil(g*0xff), math.ceil(b*0xff))) 74 +-- end print('\27[m') end end 75 +-- 76 +-- print()